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