blob: e9fd1370772131315dfe1222d18c8b5167f785a9 [file] [log] [blame]
Axel Lin362af732019-04-17 22:16:32 +08001// SPDX-License-Identifier: GPL-2.0+
2//
3// Regulator support for WM8400
4//
5// Copyright 2008 Wolfson Microelectronics PLC.
6//
7// Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
Mark Brown42fad572008-09-11 11:12:01 +01008
9#include <linux/bug.h>
10#include <linux/err.h>
11#include <linux/kernel.h>
Paul Gortmaker65602c32011-07-17 16:28:23 -040012#include <linux/module.h>
Mark Brown42fad572008-09-11 11:12:01 +010013#include <linux/regulator/driver.h>
14#include <linux/mfd/wm8400-private.h>
15
Matti Vaittinen60ab7f42020-05-08 18:43:36 +030016static const struct linear_range wm8400_ldo_ranges[] = {
Axel Lin8828bae2013-10-11 09:32:18 +080017 REGULATOR_LINEAR_RANGE(900000, 0, 14, 50000),
18 REGULATOR_LINEAR_RANGE(1700000, 15, 31, 100000),
Mark Brown6692e432013-07-02 23:26:36 +010019};
Mark Brown42fad572008-09-11 11:12:01 +010020
Julia Lawallb0d6dd32015-12-19 16:31:24 +010021static const struct regulator_ops wm8400_ldo_ops = {
Mark Brownc54a1552012-05-10 00:12:09 +010022 .is_enabled = regulator_is_enabled_regmap,
23 .enable = regulator_enable_regmap,
24 .disable = regulator_disable_regmap,
Mark Brown6692e432013-07-02 23:26:36 +010025 .list_voltage = regulator_list_voltage_linear_range,
Mark Brownc54a1552012-05-10 00:12:09 +010026 .get_voltage_sel = regulator_get_voltage_sel_regmap,
27 .set_voltage_sel = regulator_set_voltage_sel_regmap,
Mark Brown6692e432013-07-02 23:26:36 +010028 .map_voltage = regulator_map_voltage_linear_range,
Mark Brownc54a1552012-05-10 00:12:09 +010029};
Mark Brown42fad572008-09-11 11:12:01 +010030
31static unsigned int wm8400_dcdc_get_mode(struct regulator_dev *dev)
32{
Axel Line08abec2019-02-27 09:30:53 +080033 struct regmap *rmap = rdev_get_regmap(dev);
Mark Brown42fad572008-09-11 11:12:01 +010034 int offset = (rdev_get_id(dev) - WM8400_DCDC1) * 2;
35 u16 data[2];
36 int ret;
37
Axel Line08abec2019-02-27 09:30:53 +080038 ret = regmap_bulk_read(rmap, WM8400_DCDC1_CONTROL_1 + offset, data, 2);
Mark Brown42fad572008-09-11 11:12:01 +010039 if (ret != 0)
40 return 0;
41
42 /* Datasheet: hibernate */
43 if (data[0] & WM8400_DC1_SLEEP)
44 return REGULATOR_MODE_STANDBY;
45
46 /* Datasheet: standby */
47 if (!(data[0] & WM8400_DC1_ACTIVE))
48 return REGULATOR_MODE_IDLE;
49
50 /* Datasheet: active with or without force PWM */
51 if (data[1] & WM8400_DC1_FRC_PWM)
52 return REGULATOR_MODE_FAST;
53 else
54 return REGULATOR_MODE_NORMAL;
55}
56
57static int wm8400_dcdc_set_mode(struct regulator_dev *dev, unsigned int mode)
58{
Axel Line08abec2019-02-27 09:30:53 +080059 struct regmap *rmap = rdev_get_regmap(dev);
Mark Brown42fad572008-09-11 11:12:01 +010060 int offset = (rdev_get_id(dev) - WM8400_DCDC1) * 2;
61 int ret;
62
63 switch (mode) {
64 case REGULATOR_MODE_FAST:
65 /* Datasheet: active with force PWM */
Axel Line08abec2019-02-27 09:30:53 +080066 ret = regmap_update_bits(rmap, WM8400_DCDC1_CONTROL_2 + offset,
Mark Brown42fad572008-09-11 11:12:01 +010067 WM8400_DC1_FRC_PWM, WM8400_DC1_FRC_PWM);
68 if (ret != 0)
69 return ret;
70
Axel Line08abec2019-02-27 09:30:53 +080071 return regmap_update_bits(rmap, WM8400_DCDC1_CONTROL_1 + offset,
Mark Brown42fad572008-09-11 11:12:01 +010072 WM8400_DC1_ACTIVE | WM8400_DC1_SLEEP,
73 WM8400_DC1_ACTIVE);
74
75 case REGULATOR_MODE_NORMAL:
76 /* Datasheet: active */
Axel Line08abec2019-02-27 09:30:53 +080077 ret = regmap_update_bits(rmap, WM8400_DCDC1_CONTROL_2 + offset,
Mark Brown42fad572008-09-11 11:12:01 +010078 WM8400_DC1_FRC_PWM, 0);
79 if (ret != 0)
80 return ret;
81
Axel Line08abec2019-02-27 09:30:53 +080082 return regmap_update_bits(rmap, WM8400_DCDC1_CONTROL_1 + offset,
Mark Brown42fad572008-09-11 11:12:01 +010083 WM8400_DC1_ACTIVE | WM8400_DC1_SLEEP,
84 WM8400_DC1_ACTIVE);
85
86 case REGULATOR_MODE_IDLE:
87 /* Datasheet: standby */
Axel Line08abec2019-02-27 09:30:53 +080088 return regmap_update_bits(rmap, WM8400_DCDC1_CONTROL_1 + offset,
Axel Lin40013762012-08-01 09:15:11 +080089 WM8400_DC1_ACTIVE | WM8400_DC1_SLEEP, 0);
Mark Brown42fad572008-09-11 11:12:01 +010090 default:
91 return -EINVAL;
92 }
93}
94
95static unsigned int wm8400_dcdc_get_optimum_mode(struct regulator_dev *dev,
96 int input_uV, int output_uV,
97 int load_uA)
98{
99 return REGULATOR_MODE_NORMAL;
100}
101
Julia Lawallb0d6dd32015-12-19 16:31:24 +0100102static const struct regulator_ops wm8400_dcdc_ops = {
Mark Brownc54a1552012-05-10 00:12:09 +0100103 .is_enabled = regulator_is_enabled_regmap,
104 .enable = regulator_enable_regmap,
105 .disable = regulator_disable_regmap,
106 .list_voltage = regulator_list_voltage_linear,
Axel Lin27eeabb2012-05-31 17:40:22 +0800107 .map_voltage = regulator_map_voltage_linear,
Mark Brownc54a1552012-05-10 00:12:09 +0100108 .get_voltage_sel = regulator_get_voltage_sel_regmap,
109 .set_voltage_sel = regulator_set_voltage_sel_regmap,
Mark Brown42fad572008-09-11 11:12:01 +0100110 .get_mode = wm8400_dcdc_get_mode,
111 .set_mode = wm8400_dcdc_set_mode,
112 .get_optimum_mode = wm8400_dcdc_get_optimum_mode,
113};
114
115static struct regulator_desc regulators[] = {
116 {
117 .name = "LDO1",
118 .id = WM8400_LDO1,
119 .ops = &wm8400_ldo_ops,
Mark Brownc54a1552012-05-10 00:12:09 +0100120 .enable_reg = WM8400_LDO1_CONTROL,
121 .enable_mask = WM8400_LDO1_ENA,
Mark Brown216765d2009-03-10 16:28:35 +0000122 .n_voltages = WM8400_LDO1_VSEL_MASK + 1,
Mark Brown6692e432013-07-02 23:26:36 +0100123 .linear_ranges = wm8400_ldo_ranges,
124 .n_linear_ranges = ARRAY_SIZE(wm8400_ldo_ranges),
Mark Brownc54a1552012-05-10 00:12:09 +0100125 .vsel_reg = WM8400_LDO1_CONTROL,
126 .vsel_mask = WM8400_LDO1_VSEL_MASK,
Mark Brown42fad572008-09-11 11:12:01 +0100127 .type = REGULATOR_VOLTAGE,
128 .owner = THIS_MODULE,
129 },
130 {
131 .name = "LDO2",
132 .id = WM8400_LDO2,
133 .ops = &wm8400_ldo_ops,
Mark Brownc54a1552012-05-10 00:12:09 +0100134 .enable_reg = WM8400_LDO2_CONTROL,
135 .enable_mask = WM8400_LDO2_ENA,
Mark Brown216765d2009-03-10 16:28:35 +0000136 .n_voltages = WM8400_LDO2_VSEL_MASK + 1,
Mark Brown6692e432013-07-02 23:26:36 +0100137 .linear_ranges = wm8400_ldo_ranges,
138 .n_linear_ranges = ARRAY_SIZE(wm8400_ldo_ranges),
Mark Brown42fad572008-09-11 11:12:01 +0100139 .type = REGULATOR_VOLTAGE,
Mark Brownc54a1552012-05-10 00:12:09 +0100140 .vsel_reg = WM8400_LDO2_CONTROL,
141 .vsel_mask = WM8400_LDO2_VSEL_MASK,
Mark Brown42fad572008-09-11 11:12:01 +0100142 .owner = THIS_MODULE,
143 },
144 {
145 .name = "LDO3",
146 .id = WM8400_LDO3,
147 .ops = &wm8400_ldo_ops,
Mark Brownc54a1552012-05-10 00:12:09 +0100148 .enable_reg = WM8400_LDO3_CONTROL,
149 .enable_mask = WM8400_LDO3_ENA,
Mark Brown216765d2009-03-10 16:28:35 +0000150 .n_voltages = WM8400_LDO3_VSEL_MASK + 1,
Mark Brown6692e432013-07-02 23:26:36 +0100151 .linear_ranges = wm8400_ldo_ranges,
152 .n_linear_ranges = ARRAY_SIZE(wm8400_ldo_ranges),
Mark Brownc54a1552012-05-10 00:12:09 +0100153 .vsel_reg = WM8400_LDO3_CONTROL,
154 .vsel_mask = WM8400_LDO3_VSEL_MASK,
Mark Brown42fad572008-09-11 11:12:01 +0100155 .type = REGULATOR_VOLTAGE,
156 .owner = THIS_MODULE,
157 },
158 {
159 .name = "LDO4",
160 .id = WM8400_LDO4,
161 .ops = &wm8400_ldo_ops,
Mark Brownc54a1552012-05-10 00:12:09 +0100162 .enable_reg = WM8400_LDO4_CONTROL,
163 .enable_mask = WM8400_LDO4_ENA,
Mark Brown216765d2009-03-10 16:28:35 +0000164 .n_voltages = WM8400_LDO4_VSEL_MASK + 1,
Mark Brown6692e432013-07-02 23:26:36 +0100165 .linear_ranges = wm8400_ldo_ranges,
166 .n_linear_ranges = ARRAY_SIZE(wm8400_ldo_ranges),
Mark Brownc54a1552012-05-10 00:12:09 +0100167 .vsel_reg = WM8400_LDO4_CONTROL,
168 .vsel_mask = WM8400_LDO4_VSEL_MASK,
Mark Brown42fad572008-09-11 11:12:01 +0100169 .type = REGULATOR_VOLTAGE,
170 .owner = THIS_MODULE,
171 },
172 {
173 .name = "DCDC1",
174 .id = WM8400_DCDC1,
175 .ops = &wm8400_dcdc_ops,
Mark Brownc54a1552012-05-10 00:12:09 +0100176 .enable_reg = WM8400_DCDC1_CONTROL_1,
177 .enable_mask = WM8400_DC1_ENA_MASK,
Mark Brown216765d2009-03-10 16:28:35 +0000178 .n_voltages = WM8400_DC1_VSEL_MASK + 1,
Mark Brownc54a1552012-05-10 00:12:09 +0100179 .vsel_reg = WM8400_DCDC1_CONTROL_1,
180 .vsel_mask = WM8400_DC1_VSEL_MASK,
181 .min_uV = 850000,
182 .uV_step = 25000,
Mark Brown42fad572008-09-11 11:12:01 +0100183 .type = REGULATOR_VOLTAGE,
184 .owner = THIS_MODULE,
185 },
186 {
187 .name = "DCDC2",
188 .id = WM8400_DCDC2,
189 .ops = &wm8400_dcdc_ops,
Mark Brownc54a1552012-05-10 00:12:09 +0100190 .enable_reg = WM8400_DCDC2_CONTROL_1,
Axel Lin178d08e2019-02-27 13:54:21 +0800191 .enable_mask = WM8400_DC2_ENA_MASK,
Mark Brown216765d2009-03-10 16:28:35 +0000192 .n_voltages = WM8400_DC2_VSEL_MASK + 1,
Mark Brownc54a1552012-05-10 00:12:09 +0100193 .vsel_reg = WM8400_DCDC2_CONTROL_1,
194 .vsel_mask = WM8400_DC2_VSEL_MASK,
195 .min_uV = 850000,
196 .uV_step = 25000,
Mark Brown42fad572008-09-11 11:12:01 +0100197 .type = REGULATOR_VOLTAGE,
198 .owner = THIS_MODULE,
199 },
200};
201
Bill Pembertona5023572012-11-19 13:22:22 -0500202static int wm8400_regulator_probe(struct platform_device *pdev)
Mark Brown42fad572008-09-11 11:12:01 +0100203{
Dmitry Torokhov1ad02bb2010-02-25 01:55:37 -0800204 struct wm8400 *wm8400 = container_of(pdev, struct wm8400, regulators[pdev->id]);
Mark Brownc172708d2012-04-04 00:50:22 +0100205 struct regulator_config config = { };
Mark Brown42fad572008-09-11 11:12:01 +0100206 struct regulator_dev *rdev;
207
Mark Brownc172708d2012-04-04 00:50:22 +0100208 config.dev = &pdev->dev;
Jingoo Handff91d02013-07-30 17:20:47 +0900209 config.init_data = dev_get_platdata(&pdev->dev);
Mark Brownc172708d2012-04-04 00:50:22 +0100210 config.driver_data = wm8400;
Mark Brownc54a1552012-05-10 00:12:09 +0100211 config.regmap = wm8400->regmap;
Mark Brown42fad572008-09-11 11:12:01 +0100212
Mark Browneb8b3c82013-08-31 12:00:57 +0100213 rdev = devm_regulator_register(&pdev->dev, &regulators[pdev->id],
214 &config);
Mark Brown42fad572008-09-11 11:12:01 +0100215 if (IS_ERR(rdev))
216 return PTR_ERR(rdev);
217
Dmitry Torokhov1ad02bb2010-02-25 01:55:37 -0800218 platform_set_drvdata(pdev, rdev);
219
Mark Brown42fad572008-09-11 11:12:01 +0100220 return 0;
221}
222
Mark Brown42fad572008-09-11 11:12:01 +0100223static struct platform_driver wm8400_regulator_driver = {
224 .driver = {
225 .name = "wm8400-regulator",
226 },
227 .probe = wm8400_regulator_probe,
Mark Brown42fad572008-09-11 11:12:01 +0100228};
229
230/**
231 * wm8400_register_regulator - enable software control of a WM8400 regulator
232 *
233 * This function enables software control of a WM8400 regulator via
234 * the regulator API. It is intended to be called from the
235 * platform_init() callback of the WM8400 MFD driver.
236 *
Lee Jones0c526162020-06-26 07:57:37 +0100237 * @dev: The WM8400 device to operate on.
238 * @reg: The regulator to control.
239 * @initdata: Regulator initdata for the regulator.
Mark Brown42fad572008-09-11 11:12:01 +0100240 */
241int wm8400_register_regulator(struct device *dev, int reg,
242 struct regulator_init_data *initdata)
243{
Greg Kroah-Hartman1909e2f2009-04-30 15:21:37 -0700244 struct wm8400 *wm8400 = dev_get_drvdata(dev);
Mark Brown42fad572008-09-11 11:12:01 +0100245
246 if (wm8400->regulators[reg].name)
247 return -EBUSY;
248
249 initdata->driver_data = wm8400;
250
251 wm8400->regulators[reg].name = "wm8400-regulator";
252 wm8400->regulators[reg].id = reg;
253 wm8400->regulators[reg].dev.parent = dev;
Mark Brown42fad572008-09-11 11:12:01 +0100254 wm8400->regulators[reg].dev.platform_data = initdata;
255
256 return platform_device_register(&wm8400->regulators[reg]);
257}
258EXPORT_SYMBOL_GPL(wm8400_register_regulator);
259
260static int __init wm8400_regulator_init(void)
261{
262 return platform_driver_register(&wm8400_regulator_driver);
263}
Mark Brown5a1b22b2009-04-27 18:21:18 +0100264subsys_initcall(wm8400_regulator_init);
Mark Brown42fad572008-09-11 11:12:01 +0100265
266static void __exit wm8400_regulator_exit(void)
267{
268 platform_driver_unregister(&wm8400_regulator_driver);
269}
270module_exit(wm8400_regulator_exit);
271
272MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
273MODULE_DESCRIPTION("WM8400 regulator driver");
274MODULE_LICENSE("GPL");
275MODULE_ALIAS("platform:wm8400-regulator");