1 // SPDX-License-Identifier: GPL-2.0
2 //
3 // Lochnagar regulator driver
4 //
5 // Copyright (c) 2017-2018 Cirrus Logic, Inc. and
6 //                         Cirrus Logic International Semiconductor Ltd.
7 //
8 // Author: Charles Keepax <ckeepax@opensource.cirrus.com>
9 
10 #include <linux/bitops.h>
11 #include <linux/device.h>
12 #include <linux/err.h>
13 #include <linux/module.h>
14 #include <linux/mutex.h>
15 #include <linux/of.h>
16 #include <linux/platform_device.h>
17 #include <linux/regmap.h>
18 #include <linux/regulator/driver.h>
19 #include <linux/regulator/machine.h>
20 #include <linux/regulator/of_regulator.h>
21 
22 #include <linux/mfd/lochnagar.h>
23 
24 static const struct regulator_ops lochnagar_micvdd_ops = {
25 	.enable = regulator_enable_regmap,
26 	.disable = regulator_disable_regmap,
27 	.is_enabled = regulator_is_enabled_regmap,
28 
29 	.list_voltage = regulator_list_voltage_linear_range,
30 	.map_voltage = regulator_map_voltage_linear_range,
31 
32 	.get_voltage_sel = regulator_get_voltage_sel_regmap,
33 	.set_voltage_sel = regulator_set_voltage_sel_regmap,
34 };
35 
36 static const struct regulator_linear_range lochnagar_micvdd_ranges[] = {
37 	REGULATOR_LINEAR_RANGE(1000000, 0,    0xC, 50000),
38 	REGULATOR_LINEAR_RANGE(1700000, 0xD, 0x1F, 100000),
39 };
40 
41 static int lochnagar_micbias_enable(struct regulator_dev *rdev)
42 {
43 	struct lochnagar *lochnagar = rdev_get_drvdata(rdev);
44 	int ret;
45 
46 	mutex_lock(&lochnagar->analogue_config_lock);
47 
48 	ret = regulator_enable_regmap(rdev);
49 	if (ret < 0)
50 		goto err;
51 
52 	ret = lochnagar_update_config(lochnagar);
53 
54 err:
55 	mutex_unlock(&lochnagar->analogue_config_lock);
56 
57 	return ret;
58 }
59 
60 static int lochnagar_micbias_disable(struct regulator_dev *rdev)
61 {
62 	struct lochnagar *lochnagar = rdev_get_drvdata(rdev);
63 	int ret;
64 
65 	mutex_lock(&lochnagar->analogue_config_lock);
66 
67 	ret = regulator_disable_regmap(rdev);
68 	if (ret < 0)
69 		goto err;
70 
71 	ret = lochnagar_update_config(lochnagar);
72 
73 err:
74 	mutex_unlock(&lochnagar->analogue_config_lock);
75 
76 	return ret;
77 }
78 
79 static const struct regulator_ops lochnagar_micbias_ops = {
80 	.enable = lochnagar_micbias_enable,
81 	.disable = lochnagar_micbias_disable,
82 	.is_enabled = regulator_is_enabled_regmap,
83 };
84 
85 static const struct regulator_ops lochnagar_vddcore_ops = {
86 	.enable = regulator_enable_regmap,
87 	.disable = regulator_disable_regmap,
88 	.is_enabled = regulator_is_enabled_regmap,
89 
90 	.list_voltage = regulator_list_voltage_linear_range,
91 	.map_voltage = regulator_map_voltage_linear_range,
92 
93 	.get_voltage_sel = regulator_get_voltage_sel_regmap,
94 	.set_voltage_sel = regulator_set_voltage_sel_regmap,
95 };
96 
97 static const struct regulator_linear_range lochnagar_vddcore_ranges[] = {
98 	REGULATOR_LINEAR_RANGE(600000, 0x8, 0x41, 12500),
99 };
100 
101 enum lochnagar_regulators {
102 	LOCHNAGAR_MICVDD,
103 	LOCHNAGAR_MIC1VDD,
104 	LOCHNAGAR_MIC2VDD,
105 	LOCHNAGAR_VDDCORE,
106 };
107 
108 static int lochnagar_micbias_of_parse(struct device_node *np,
109 				      const struct regulator_desc *desc,
110 				      struct regulator_config *config)
111 {
112 	struct lochnagar *lochnagar = config->driver_data;
113 	int shift = (desc->id - LOCHNAGAR_MIC1VDD) *
114 		    LOCHNAGAR2_P2_MICBIAS_SRC_SHIFT;
115 	int mask = LOCHNAGAR2_P1_MICBIAS_SRC_MASK << shift;
116 	unsigned int val;
117 	int ret;
118 
119 	ret = of_property_read_u32(np, "cirrus,micbias-input", &val);
120 	if (ret >= 0) {
121 		mutex_lock(&lochnagar->analogue_config_lock);
122 		ret = regmap_update_bits(lochnagar->regmap,
123 					 LOCHNAGAR2_ANALOGUE_PATH_CTRL2,
124 					 mask, val << shift);
125 		mutex_unlock(&lochnagar->analogue_config_lock);
126 		if (ret < 0) {
127 			dev_err(lochnagar->dev,
128 				"Failed to update micbias source: %d\n", ret);
129 			return ret;
130 		}
131 	}
132 
133 	return 0;
134 }
135 
136 static const struct regulator_desc lochnagar_regulators[] = {
137 	[LOCHNAGAR_MICVDD] = {
138 		.name = "MICVDD",
139 		.supply_name = "SYSVDD",
140 		.type = REGULATOR_VOLTAGE,
141 		.n_voltages = 32,
142 		.ops = &lochnagar_micvdd_ops,
143 
144 		.id = LOCHNAGAR_MICVDD,
145 		.of_match = of_match_ptr("MICVDD"),
146 
147 		.enable_reg = LOCHNAGAR2_MICVDD_CTRL1,
148 		.enable_mask = LOCHNAGAR2_MICVDD_REG_ENA_MASK,
149 		.vsel_reg = LOCHNAGAR2_MICVDD_CTRL2,
150 		.vsel_mask = LOCHNAGAR2_MICVDD_VSEL_MASK,
151 
152 		.linear_ranges = lochnagar_micvdd_ranges,
153 		.n_linear_ranges = ARRAY_SIZE(lochnagar_micvdd_ranges),
154 
155 		.enable_time = 3000,
156 		.ramp_delay = 1000,
157 
158 		.owner = THIS_MODULE,
159 	},
160 	[LOCHNAGAR_MIC1VDD] = {
161 		.name = "MIC1VDD",
162 		.supply_name = "MICBIAS1",
163 		.type = REGULATOR_VOLTAGE,
164 		.ops = &lochnagar_micbias_ops,
165 
166 		.id = LOCHNAGAR_MIC1VDD,
167 		.of_match = of_match_ptr("MIC1VDD"),
168 		.of_parse_cb = lochnagar_micbias_of_parse,
169 
170 		.enable_reg = LOCHNAGAR2_ANALOGUE_PATH_CTRL2,
171 		.enable_mask = LOCHNAGAR2_P1_INPUT_BIAS_ENA_MASK,
172 
173 		.owner = THIS_MODULE,
174 	},
175 	[LOCHNAGAR_MIC2VDD] = {
176 		.name = "MIC2VDD",
177 		.supply_name = "MICBIAS2",
178 		.type = REGULATOR_VOLTAGE,
179 		.ops = &lochnagar_micbias_ops,
180 
181 		.id = LOCHNAGAR_MIC2VDD,
182 		.of_match = of_match_ptr("MIC2VDD"),
183 		.of_parse_cb = lochnagar_micbias_of_parse,
184 
185 		.enable_reg = LOCHNAGAR2_ANALOGUE_PATH_CTRL2,
186 		.enable_mask = LOCHNAGAR2_P2_INPUT_BIAS_ENA_MASK,
187 
188 		.owner = THIS_MODULE,
189 	},
190 	[LOCHNAGAR_VDDCORE] = {
191 		.name = "VDDCORE",
192 		.supply_name = "SYSVDD",
193 		.type = REGULATOR_VOLTAGE,
194 		.n_voltages = 57,
195 		.ops = &lochnagar_vddcore_ops,
196 
197 		.id = LOCHNAGAR_VDDCORE,
198 		.of_match = of_match_ptr("VDDCORE"),
199 
200 		.enable_reg = LOCHNAGAR2_VDDCORE_CDC_CTRL1,
201 		.enable_mask = LOCHNAGAR2_VDDCORE_CDC_REG_ENA_MASK,
202 		.vsel_reg = LOCHNAGAR2_VDDCORE_CDC_CTRL2,
203 		.vsel_mask = LOCHNAGAR2_VDDCORE_CDC_VSEL_MASK,
204 
205 		.linear_ranges = lochnagar_vddcore_ranges,
206 		.n_linear_ranges = ARRAY_SIZE(lochnagar_vddcore_ranges),
207 
208 		.enable_time = 3000,
209 		.ramp_delay = 1000,
210 
211 		.owner = THIS_MODULE,
212 	},
213 };
214 
215 static int lochnagar_regulator_probe(struct platform_device *pdev)
216 {
217 	struct device *dev = &pdev->dev;
218 	struct lochnagar *lochnagar = dev_get_drvdata(dev->parent);
219 	struct regulator_config config = { };
220 	struct regulator_dev *rdev;
221 	int ret, i;
222 
223 	config.dev = lochnagar->dev;
224 	config.regmap = lochnagar->regmap;
225 	config.driver_data = lochnagar;
226 
227 	for (i = 0; i < ARRAY_SIZE(lochnagar_regulators); i++) {
228 		const struct regulator_desc *desc = &lochnagar_regulators[i];
229 
230 		rdev = devm_regulator_register(dev, desc, &config);
231 		if (IS_ERR(rdev)) {
232 			ret = PTR_ERR(rdev);
233 			dev_err(dev, "Failed to register %s regulator: %d\n",
234 				desc->name, ret);
235 			return ret;
236 		}
237 	}
238 
239 	return 0;
240 }
241 
242 static struct platform_driver lochnagar_regulator_driver = {
243 	.driver = {
244 		.name = "lochnagar-regulator",
245 	},
246 
247 	.probe = lochnagar_regulator_probe,
248 };
249 module_platform_driver(lochnagar_regulator_driver);
250 
251 MODULE_AUTHOR("Charles Keepax <ckeepax@opensource.cirrus.com>");
252 MODULE_DESCRIPTION("Regulator driver for Cirrus Logic Lochnagar Board");
253 MODULE_LICENSE("GPL v2");
254 MODULE_ALIAS("platform:lochnagar-regulator");
255