blob: 801148036ff6cf1adeaeffc0e61092f293d72b94 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2020-2022 Linaro Limited
*/
#define LOG_CATEGORY UCLASS_REGULATOR
#include <common.h>
#include <dm.h>
#include <errno.h>
#include <scmi_agent.h>
#include <scmi_protocols.h>
#include <asm/types.h>
#include <dm/device.h>
#include <dm/device_compat.h>
#include <dm/device-internal.h>
#include <linux/kernel.h>
#include <power/regulator.h>
/**
* struct scmi_regulator_platdata - Platform data for a scmi voltage domain regulator
* @domain_id: ID representing the regulator for the related SCMI agent
*/
struct scmi_regulator_platdata {
u32 domain_id;
};
/**
* struct scmi_regulator_priv - Private data for SCMI voltage regulator
* @channel: Reference to the SCMI channel to use
*/
struct scmi_regulator_priv {
struct scmi_channel *channel;
};
static int scmi_voltd_set_enable(struct udevice *dev, bool enable)
{
struct scmi_regulator_platdata *pdata = dev_get_plat(dev);
struct scmi_regulator_priv *priv = dev_get_priv(dev);
struct scmi_voltd_config_set_in in = {
.domain_id = pdata->domain_id,
.config = enable ? SCMI_VOLTD_CONFIG_ON : SCMI_VOLTD_CONFIG_OFF,
};
struct scmi_voltd_config_set_out out;
struct scmi_msg msg = SCMI_MSG_IN(SCMI_PROTOCOL_ID_VOLTAGE_DOMAIN,
SCMI_VOLTAGE_DOMAIN_CONFIG_SET,
in, out);
int ret;
ret = devm_scmi_process_msg(dev, priv->channel, &msg);
if (ret)
return ret;
return scmi_to_linux_errno(out.status);
}
static int scmi_voltd_get_enable(struct udevice *dev)
{
struct scmi_regulator_platdata *pdata = dev_get_plat(dev);
struct scmi_regulator_priv *priv = dev_get_priv(dev);
struct scmi_voltd_config_get_in in = {
.domain_id = pdata->domain_id,
};
struct scmi_voltd_config_get_out out;
struct scmi_msg msg = SCMI_MSG_IN(SCMI_PROTOCOL_ID_VOLTAGE_DOMAIN,
SCMI_VOLTAGE_DOMAIN_CONFIG_GET,
in, out);
int ret;
ret = devm_scmi_process_msg(dev, priv->channel, &msg);
if (ret < 0)
return ret;
ret = scmi_to_linux_errno(out.status);
if (ret < 0)
return ret;
return out.config == SCMI_VOLTD_CONFIG_ON;
}
static int scmi_voltd_set_voltage_level(struct udevice *dev, int uV)
{
struct scmi_regulator_priv *priv = dev_get_priv(dev);
struct scmi_regulator_platdata *pdata = dev_get_plat(dev);
struct scmi_voltd_level_set_in in = {
.domain_id = pdata->domain_id,
.voltage_level = uV,
};
struct scmi_voltd_level_set_out out;
struct scmi_msg msg = SCMI_MSG_IN(SCMI_PROTOCOL_ID_VOLTAGE_DOMAIN,
SCMI_VOLTAGE_DOMAIN_LEVEL_SET,
in, out);
int ret;
ret = devm_scmi_process_msg(dev, priv->channel, &msg);
if (ret < 0)
return ret;
return scmi_to_linux_errno(out.status);
}
static int scmi_voltd_get_voltage_level(struct udevice *dev)
{
struct scmi_regulator_priv *priv = dev_get_priv(dev);
struct scmi_regulator_platdata *pdata = dev_get_plat(dev);
struct scmi_voltd_level_get_in in = {
.domain_id = pdata->domain_id,
};
struct scmi_voltd_level_get_out out;
struct scmi_msg msg = SCMI_MSG_IN(SCMI_PROTOCOL_ID_VOLTAGE_DOMAIN,
SCMI_VOLTAGE_DOMAIN_LEVEL_GET,
in, out);
int ret;
ret = devm_scmi_process_msg(dev, priv->channel, &msg);
if (ret < 0)
return ret;
ret = scmi_to_linux_errno(out.status);
if (ret < 0)
return ret;
return out.voltage_level;
}
static int scmi_regulator_of_to_plat(struct udevice *dev)
{
struct scmi_regulator_platdata *pdata = dev_get_plat(dev);
fdt_addr_t reg;
reg = dev_read_addr(dev);
if (reg == FDT_ADDR_T_NONE)
return -EINVAL;
pdata->domain_id = (u32)reg;
return 0;
}
static int scmi_regulator_probe(struct udevice *dev)
{
struct scmi_regulator_platdata *pdata = dev_get_plat(dev);
struct scmi_regulator_priv *priv = dev_get_priv(dev);
struct scmi_voltd_attr_in in = { 0 };
struct scmi_voltd_attr_out out = { 0 };
struct scmi_msg scmi_msg = {
.protocol_id = SCMI_PROTOCOL_ID_VOLTAGE_DOMAIN,
.message_id = SCMI_VOLTAGE_DOMAIN_ATTRIBUTES,
.in_msg = (u8 *)&in,
.in_msg_sz = sizeof(in),
.out_msg = (u8 *)&out,
.out_msg_sz = sizeof(out),
};
int ret;
ret = devm_scmi_of_get_channel(dev->parent, &priv->channel);
if (ret)
return ret;
/* Check voltage domain is known from SCMI server */
in.domain_id = pdata->domain_id;
ret = devm_scmi_process_msg(dev, priv->channel, &scmi_msg);
if (ret) {
dev_err(dev, "Failed to query voltage domain %u: %d\n",
pdata->domain_id, ret);
return -ENXIO;
}
return 0;
}
static const struct dm_regulator_ops scmi_voltd_ops = {
.get_value = scmi_voltd_get_voltage_level,
.set_value = scmi_voltd_set_voltage_level,
.get_enable = scmi_voltd_get_enable,
.set_enable = scmi_voltd_set_enable,
};
U_BOOT_DRIVER(scmi_regulator) = {
.name = "scmi_regulator",
.id = UCLASS_REGULATOR,
.ops = &scmi_voltd_ops,
.probe = scmi_regulator_probe,
.of_to_plat = scmi_regulator_of_to_plat,
.plat_auto = sizeof(struct scmi_regulator_platdata),
.priv_auto = sizeof(struct scmi_regulator_priv *),
};
static int scmi_regulator_bind(struct udevice *dev)
{
struct driver *drv;
ofnode node;
int ret;
drv = DM_DRIVER_GET(scmi_regulator);
ofnode_for_each_subnode(node, dev_ofnode(dev)) {
ret = device_bind(dev, drv, ofnode_get_name(node),
NULL, node, NULL);
if (ret)
return ret;
}
return 0;
}
U_BOOT_DRIVER(scmi_voltage_domain) = {
.name = "scmi_voltage_domain",
.id = UCLASS_NOP,
.bind = scmi_regulator_bind,
};