18d9f8d57SPi-Hsun Shih // SPDX-License-Identifier: GPL-2.0-only
28d9f8d57SPi-Hsun Shih //
38d9f8d57SPi-Hsun Shih // Copyright 2020 Google LLC.
48d9f8d57SPi-Hsun Shih
58d9f8d57SPi-Hsun Shih #include <linux/module.h>
68d9f8d57SPi-Hsun Shih #include <linux/of.h>
78d9f8d57SPi-Hsun Shih #include <linux/platform_data/cros_ec_proto.h>
88d9f8d57SPi-Hsun Shih #include <linux/platform_device.h>
98d9f8d57SPi-Hsun Shih #include <linux/regulator/driver.h>
108d9f8d57SPi-Hsun Shih #include <linux/regulator/machine.h>
118d9f8d57SPi-Hsun Shih #include <linux/regulator/of_regulator.h>
128d9f8d57SPi-Hsun Shih #include <linux/slab.h>
138d9f8d57SPi-Hsun Shih
148d9f8d57SPi-Hsun Shih struct cros_ec_regulator_data {
158d9f8d57SPi-Hsun Shih struct regulator_desc desc;
168d9f8d57SPi-Hsun Shih struct regulator_dev *dev;
178d9f8d57SPi-Hsun Shih struct cros_ec_device *ec_dev;
188d9f8d57SPi-Hsun Shih
198d9f8d57SPi-Hsun Shih u32 index;
208d9f8d57SPi-Hsun Shih
218d9f8d57SPi-Hsun Shih u16 *voltages_mV;
228d9f8d57SPi-Hsun Shih u16 num_voltages;
238d9f8d57SPi-Hsun Shih };
248d9f8d57SPi-Hsun Shih
cros_ec_regulator_enable(struct regulator_dev * dev)258d9f8d57SPi-Hsun Shih static int cros_ec_regulator_enable(struct regulator_dev *dev)
268d9f8d57SPi-Hsun Shih {
278d9f8d57SPi-Hsun Shih struct cros_ec_regulator_data *data = rdev_get_drvdata(dev);
288d9f8d57SPi-Hsun Shih struct ec_params_regulator_enable cmd = {
298d9f8d57SPi-Hsun Shih .index = data->index,
308d9f8d57SPi-Hsun Shih .enable = 1,
318d9f8d57SPi-Hsun Shih };
328d9f8d57SPi-Hsun Shih
33b1d288d9SPrashant Malani return cros_ec_cmd(data->ec_dev, 0, EC_CMD_REGULATOR_ENABLE, &cmd,
348d9f8d57SPi-Hsun Shih sizeof(cmd), NULL, 0);
358d9f8d57SPi-Hsun Shih }
368d9f8d57SPi-Hsun Shih
cros_ec_regulator_disable(struct regulator_dev * dev)378d9f8d57SPi-Hsun Shih static int cros_ec_regulator_disable(struct regulator_dev *dev)
388d9f8d57SPi-Hsun Shih {
398d9f8d57SPi-Hsun Shih struct cros_ec_regulator_data *data = rdev_get_drvdata(dev);
408d9f8d57SPi-Hsun Shih struct ec_params_regulator_enable cmd = {
418d9f8d57SPi-Hsun Shih .index = data->index,
428d9f8d57SPi-Hsun Shih .enable = 0,
438d9f8d57SPi-Hsun Shih };
448d9f8d57SPi-Hsun Shih
45b1d288d9SPrashant Malani return cros_ec_cmd(data->ec_dev, 0, EC_CMD_REGULATOR_ENABLE, &cmd,
468d9f8d57SPi-Hsun Shih sizeof(cmd), NULL, 0);
478d9f8d57SPi-Hsun Shih }
488d9f8d57SPi-Hsun Shih
cros_ec_regulator_is_enabled(struct regulator_dev * dev)498d9f8d57SPi-Hsun Shih static int cros_ec_regulator_is_enabled(struct regulator_dev *dev)
508d9f8d57SPi-Hsun Shih {
518d9f8d57SPi-Hsun Shih struct cros_ec_regulator_data *data = rdev_get_drvdata(dev);
528d9f8d57SPi-Hsun Shih struct ec_params_regulator_is_enabled cmd = {
538d9f8d57SPi-Hsun Shih .index = data->index,
548d9f8d57SPi-Hsun Shih };
558d9f8d57SPi-Hsun Shih struct ec_response_regulator_is_enabled resp;
568d9f8d57SPi-Hsun Shih int ret;
578d9f8d57SPi-Hsun Shih
58b1d288d9SPrashant Malani ret = cros_ec_cmd(data->ec_dev, 0, EC_CMD_REGULATOR_IS_ENABLED, &cmd,
598d9f8d57SPi-Hsun Shih sizeof(cmd), &resp, sizeof(resp));
608d9f8d57SPi-Hsun Shih if (ret < 0)
618d9f8d57SPi-Hsun Shih return ret;
628d9f8d57SPi-Hsun Shih return resp.enabled;
638d9f8d57SPi-Hsun Shih }
648d9f8d57SPi-Hsun Shih
cros_ec_regulator_list_voltage(struct regulator_dev * dev,unsigned int selector)658d9f8d57SPi-Hsun Shih static int cros_ec_regulator_list_voltage(struct regulator_dev *dev,
668d9f8d57SPi-Hsun Shih unsigned int selector)
678d9f8d57SPi-Hsun Shih {
688d9f8d57SPi-Hsun Shih struct cros_ec_regulator_data *data = rdev_get_drvdata(dev);
698d9f8d57SPi-Hsun Shih
708d9f8d57SPi-Hsun Shih if (selector >= data->num_voltages)
718d9f8d57SPi-Hsun Shih return -EINVAL;
728d9f8d57SPi-Hsun Shih
738d9f8d57SPi-Hsun Shih return data->voltages_mV[selector] * 1000;
748d9f8d57SPi-Hsun Shih }
758d9f8d57SPi-Hsun Shih
cros_ec_regulator_get_voltage(struct regulator_dev * dev)768d9f8d57SPi-Hsun Shih static int cros_ec_regulator_get_voltage(struct regulator_dev *dev)
778d9f8d57SPi-Hsun Shih {
788d9f8d57SPi-Hsun Shih struct cros_ec_regulator_data *data = rdev_get_drvdata(dev);
798d9f8d57SPi-Hsun Shih struct ec_params_regulator_get_voltage cmd = {
808d9f8d57SPi-Hsun Shih .index = data->index,
818d9f8d57SPi-Hsun Shih };
828d9f8d57SPi-Hsun Shih struct ec_response_regulator_get_voltage resp;
838d9f8d57SPi-Hsun Shih int ret;
848d9f8d57SPi-Hsun Shih
85b1d288d9SPrashant Malani ret = cros_ec_cmd(data->ec_dev, 0, EC_CMD_REGULATOR_GET_VOLTAGE, &cmd,
868d9f8d57SPi-Hsun Shih sizeof(cmd), &resp, sizeof(resp));
878d9f8d57SPi-Hsun Shih if (ret < 0)
888d9f8d57SPi-Hsun Shih return ret;
898d9f8d57SPi-Hsun Shih return resp.voltage_mv * 1000;
908d9f8d57SPi-Hsun Shih }
918d9f8d57SPi-Hsun Shih
cros_ec_regulator_set_voltage(struct regulator_dev * dev,int min_uV,int max_uV,unsigned int * selector)928d9f8d57SPi-Hsun Shih static int cros_ec_regulator_set_voltage(struct regulator_dev *dev, int min_uV,
938d9f8d57SPi-Hsun Shih int max_uV, unsigned int *selector)
948d9f8d57SPi-Hsun Shih {
958d9f8d57SPi-Hsun Shih struct cros_ec_regulator_data *data = rdev_get_drvdata(dev);
968d9f8d57SPi-Hsun Shih int min_mV = DIV_ROUND_UP(min_uV, 1000);
978d9f8d57SPi-Hsun Shih int max_mV = max_uV / 1000;
988d9f8d57SPi-Hsun Shih struct ec_params_regulator_set_voltage cmd = {
998d9f8d57SPi-Hsun Shih .index = data->index,
1008d9f8d57SPi-Hsun Shih .min_mv = min_mV,
1018d9f8d57SPi-Hsun Shih .max_mv = max_mV,
1028d9f8d57SPi-Hsun Shih };
1038d9f8d57SPi-Hsun Shih
1048d9f8d57SPi-Hsun Shih /*
1058d9f8d57SPi-Hsun Shih * This can happen when the given range [min_uV, max_uV] doesn't
1068d9f8d57SPi-Hsun Shih * contain any voltage that can be represented exactly in mV.
1078d9f8d57SPi-Hsun Shih */
1088d9f8d57SPi-Hsun Shih if (min_mV > max_mV)
1098d9f8d57SPi-Hsun Shih return -EINVAL;
1108d9f8d57SPi-Hsun Shih
111b1d288d9SPrashant Malani return cros_ec_cmd(data->ec_dev, 0, EC_CMD_REGULATOR_SET_VOLTAGE, &cmd,
1128d9f8d57SPi-Hsun Shih sizeof(cmd), NULL, 0);
1138d9f8d57SPi-Hsun Shih }
1148d9f8d57SPi-Hsun Shih
115308e65adSRikard Falkeborn static const struct regulator_ops cros_ec_regulator_voltage_ops = {
1168d9f8d57SPi-Hsun Shih .enable = cros_ec_regulator_enable,
1178d9f8d57SPi-Hsun Shih .disable = cros_ec_regulator_disable,
1188d9f8d57SPi-Hsun Shih .is_enabled = cros_ec_regulator_is_enabled,
1198d9f8d57SPi-Hsun Shih .list_voltage = cros_ec_regulator_list_voltage,
1208d9f8d57SPi-Hsun Shih .get_voltage = cros_ec_regulator_get_voltage,
1218d9f8d57SPi-Hsun Shih .set_voltage = cros_ec_regulator_set_voltage,
1228d9f8d57SPi-Hsun Shih };
1238d9f8d57SPi-Hsun Shih
cros_ec_regulator_init_info(struct device * dev,struct cros_ec_regulator_data * data)1248d9f8d57SPi-Hsun Shih static int cros_ec_regulator_init_info(struct device *dev,
1258d9f8d57SPi-Hsun Shih struct cros_ec_regulator_data *data)
1268d9f8d57SPi-Hsun Shih {
1278d9f8d57SPi-Hsun Shih struct ec_params_regulator_get_info cmd = {
1288d9f8d57SPi-Hsun Shih .index = data->index,
1298d9f8d57SPi-Hsun Shih };
1308d9f8d57SPi-Hsun Shih struct ec_response_regulator_get_info resp;
1318d9f8d57SPi-Hsun Shih int ret;
1328d9f8d57SPi-Hsun Shih
133b1d288d9SPrashant Malani ret = cros_ec_cmd(data->ec_dev, 0, EC_CMD_REGULATOR_GET_INFO, &cmd,
1348d9f8d57SPi-Hsun Shih sizeof(cmd), &resp, sizeof(resp));
1358d9f8d57SPi-Hsun Shih if (ret < 0)
1368d9f8d57SPi-Hsun Shih return ret;
1378d9f8d57SPi-Hsun Shih
1388d9f8d57SPi-Hsun Shih data->num_voltages =
1398d9f8d57SPi-Hsun Shih min_t(u16, ARRAY_SIZE(resp.voltages_mv), resp.num_voltages);
1408d9f8d57SPi-Hsun Shih data->voltages_mV =
1418d9f8d57SPi-Hsun Shih devm_kmemdup(dev, resp.voltages_mv,
1428d9f8d57SPi-Hsun Shih sizeof(u16) * data->num_voltages, GFP_KERNEL);
143ce410900SAxel Lin if (!data->voltages_mV)
144ce410900SAxel Lin return -ENOMEM;
145ce410900SAxel Lin
1468d9f8d57SPi-Hsun Shih data->desc.n_voltages = data->num_voltages;
1478d9f8d57SPi-Hsun Shih
1488d9f8d57SPi-Hsun Shih /* Make sure the returned name is always a valid string */
1498d9f8d57SPi-Hsun Shih resp.name[ARRAY_SIZE(resp.name) - 1] = '\0';
1508d9f8d57SPi-Hsun Shih data->desc.name = devm_kstrdup(dev, resp.name, GFP_KERNEL);
1518d9f8d57SPi-Hsun Shih if (!data->desc.name)
1528d9f8d57SPi-Hsun Shih return -ENOMEM;
1538d9f8d57SPi-Hsun Shih
1548d9f8d57SPi-Hsun Shih return 0;
1558d9f8d57SPi-Hsun Shih }
1568d9f8d57SPi-Hsun Shih
cros_ec_regulator_probe(struct platform_device * pdev)1578d9f8d57SPi-Hsun Shih static int cros_ec_regulator_probe(struct platform_device *pdev)
1588d9f8d57SPi-Hsun Shih {
1598d9f8d57SPi-Hsun Shih struct device *dev = &pdev->dev;
1608d9f8d57SPi-Hsun Shih struct device_node *np = dev->of_node;
1618d9f8d57SPi-Hsun Shih struct cros_ec_regulator_data *drvdata;
1628d9f8d57SPi-Hsun Shih struct regulator_init_data *init_data;
1638d9f8d57SPi-Hsun Shih struct regulator_config cfg = {};
1648d9f8d57SPi-Hsun Shih struct regulator_desc *desc;
1658d9f8d57SPi-Hsun Shih int ret;
1668d9f8d57SPi-Hsun Shih
1678d9f8d57SPi-Hsun Shih drvdata = devm_kzalloc(
1688d9f8d57SPi-Hsun Shih &pdev->dev, sizeof(struct cros_ec_regulator_data), GFP_KERNEL);
1698d9f8d57SPi-Hsun Shih if (!drvdata)
1708d9f8d57SPi-Hsun Shih return -ENOMEM;
1718d9f8d57SPi-Hsun Shih
1728d9f8d57SPi-Hsun Shih drvdata->ec_dev = dev_get_drvdata(dev->parent);
1738d9f8d57SPi-Hsun Shih desc = &drvdata->desc;
1748d9f8d57SPi-Hsun Shih
1758d9f8d57SPi-Hsun Shih init_data = of_get_regulator_init_data(dev, np, desc);
1768d9f8d57SPi-Hsun Shih if (!init_data)
1778d9f8d57SPi-Hsun Shih return -EINVAL;
1788d9f8d57SPi-Hsun Shih
1798d9f8d57SPi-Hsun Shih ret = of_property_read_u32(np, "reg", &drvdata->index);
1808d9f8d57SPi-Hsun Shih if (ret < 0)
1818d9f8d57SPi-Hsun Shih return ret;
1828d9f8d57SPi-Hsun Shih
1838d9f8d57SPi-Hsun Shih desc->owner = THIS_MODULE;
1848d9f8d57SPi-Hsun Shih desc->type = REGULATOR_VOLTAGE;
1858d9f8d57SPi-Hsun Shih desc->ops = &cros_ec_regulator_voltage_ops;
1868d9f8d57SPi-Hsun Shih
1878d9f8d57SPi-Hsun Shih ret = cros_ec_regulator_init_info(dev, drvdata);
1888d9f8d57SPi-Hsun Shih if (ret < 0)
1898d9f8d57SPi-Hsun Shih return ret;
1908d9f8d57SPi-Hsun Shih
1918d9f8d57SPi-Hsun Shih cfg.dev = &pdev->dev;
1928d9f8d57SPi-Hsun Shih cfg.init_data = init_data;
1938d9f8d57SPi-Hsun Shih cfg.driver_data = drvdata;
1948d9f8d57SPi-Hsun Shih cfg.of_node = np;
1958d9f8d57SPi-Hsun Shih
1968d9f8d57SPi-Hsun Shih drvdata->dev = devm_regulator_register(dev, &drvdata->desc, &cfg);
1978d9f8d57SPi-Hsun Shih if (IS_ERR(drvdata->dev)) {
1983d681804SAxel Lin ret = PTR_ERR(drvdata->dev);
1998d9f8d57SPi-Hsun Shih dev_err(&pdev->dev, "Failed to register regulator: %d\n", ret);
2003d681804SAxel Lin return ret;
2018d9f8d57SPi-Hsun Shih }
2028d9f8d57SPi-Hsun Shih
2038d9f8d57SPi-Hsun Shih platform_set_drvdata(pdev, drvdata);
2048d9f8d57SPi-Hsun Shih
2058d9f8d57SPi-Hsun Shih return 0;
2068d9f8d57SPi-Hsun Shih }
2078d9f8d57SPi-Hsun Shih
2088d9f8d57SPi-Hsun Shih static const struct of_device_id regulator_cros_ec_of_match[] = {
2098d9f8d57SPi-Hsun Shih { .compatible = "google,cros-ec-regulator", },
2108d9f8d57SPi-Hsun Shih {}
2118d9f8d57SPi-Hsun Shih };
2128d9f8d57SPi-Hsun Shih MODULE_DEVICE_TABLE(of, regulator_cros_ec_of_match);
2138d9f8d57SPi-Hsun Shih
2148d9f8d57SPi-Hsun Shih static struct platform_driver cros_ec_regulator_driver = {
2158d9f8d57SPi-Hsun Shih .probe = cros_ec_regulator_probe,
2168d9f8d57SPi-Hsun Shih .driver = {
2178d9f8d57SPi-Hsun Shih .name = "cros-ec-regulator",
218*67dc71c6SDouglas Anderson .probe_type = PROBE_PREFER_ASYNCHRONOUS,
2198d9f8d57SPi-Hsun Shih .of_match_table = regulator_cros_ec_of_match,
2208d9f8d57SPi-Hsun Shih },
2218d9f8d57SPi-Hsun Shih };
2228d9f8d57SPi-Hsun Shih
2238d9f8d57SPi-Hsun Shih module_platform_driver(cros_ec_regulator_driver);
2248d9f8d57SPi-Hsun Shih
2258d9f8d57SPi-Hsun Shih MODULE_LICENSE("GPL v2");
2268d9f8d57SPi-Hsun Shih MODULE_DESCRIPTION("ChromeOS EC controlled regulator");
2278d9f8d57SPi-Hsun Shih MODULE_AUTHOR("Pi-Hsun Shih <pihsun@chromium.org>");
228