1 // 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> 8 9 #include <linux/bug.h> 10 #include <linux/err.h> 11 #include <linux/kernel.h> 12 #include <linux/module.h> 13 #include <linux/regulator/driver.h> 14 #include <linux/mfd/wm8400-private.h> 15 16 static const struct linear_range wm8400_ldo_ranges[] = { 17 REGULATOR_LINEAR_RANGE(900000, 0, 14, 50000), 18 REGULATOR_LINEAR_RANGE(1700000, 15, 31, 100000), 19 }; 20 21 static const struct regulator_ops wm8400_ldo_ops = { 22 .is_enabled = regulator_is_enabled_regmap, 23 .enable = regulator_enable_regmap, 24 .disable = regulator_disable_regmap, 25 .list_voltage = regulator_list_voltage_linear_range, 26 .get_voltage_sel = regulator_get_voltage_sel_regmap, 27 .set_voltage_sel = regulator_set_voltage_sel_regmap, 28 .map_voltage = regulator_map_voltage_linear_range, 29 }; 30 31 static unsigned int wm8400_dcdc_get_mode(struct regulator_dev *dev) 32 { 33 struct regmap *rmap = rdev_get_regmap(dev); 34 int offset = (rdev_get_id(dev) - WM8400_DCDC1) * 2; 35 u16 data[2]; 36 int ret; 37 38 ret = regmap_bulk_read(rmap, WM8400_DCDC1_CONTROL_1 + offset, data, 2); 39 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 57 static int wm8400_dcdc_set_mode(struct regulator_dev *dev, unsigned int mode) 58 { 59 struct regmap *rmap = rdev_get_regmap(dev); 60 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 */ 66 ret = regmap_update_bits(rmap, WM8400_DCDC1_CONTROL_2 + offset, 67 WM8400_DC1_FRC_PWM, WM8400_DC1_FRC_PWM); 68 if (ret != 0) 69 return ret; 70 71 return regmap_update_bits(rmap, WM8400_DCDC1_CONTROL_1 + offset, 72 WM8400_DC1_ACTIVE | WM8400_DC1_SLEEP, 73 WM8400_DC1_ACTIVE); 74 75 case REGULATOR_MODE_NORMAL: 76 /* Datasheet: active */ 77 ret = regmap_update_bits(rmap, WM8400_DCDC1_CONTROL_2 + offset, 78 WM8400_DC1_FRC_PWM, 0); 79 if (ret != 0) 80 return ret; 81 82 return regmap_update_bits(rmap, WM8400_DCDC1_CONTROL_1 + offset, 83 WM8400_DC1_ACTIVE | WM8400_DC1_SLEEP, 84 WM8400_DC1_ACTIVE); 85 86 case REGULATOR_MODE_IDLE: 87 /* Datasheet: standby */ 88 return regmap_update_bits(rmap, WM8400_DCDC1_CONTROL_1 + offset, 89 WM8400_DC1_ACTIVE | WM8400_DC1_SLEEP, 0); 90 default: 91 return -EINVAL; 92 } 93 } 94 95 static 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 102 static const struct regulator_ops wm8400_dcdc_ops = { 103 .is_enabled = regulator_is_enabled_regmap, 104 .enable = regulator_enable_regmap, 105 .disable = regulator_disable_regmap, 106 .list_voltage = regulator_list_voltage_linear, 107 .map_voltage = regulator_map_voltage_linear, 108 .get_voltage_sel = regulator_get_voltage_sel_regmap, 109 .set_voltage_sel = regulator_set_voltage_sel_regmap, 110 .get_mode = wm8400_dcdc_get_mode, 111 .set_mode = wm8400_dcdc_set_mode, 112 .get_optimum_mode = wm8400_dcdc_get_optimum_mode, 113 }; 114 115 static struct regulator_desc regulators[] = { 116 { 117 .name = "LDO1", 118 .id = WM8400_LDO1, 119 .ops = &wm8400_ldo_ops, 120 .enable_reg = WM8400_LDO1_CONTROL, 121 .enable_mask = WM8400_LDO1_ENA, 122 .n_voltages = WM8400_LDO1_VSEL_MASK + 1, 123 .linear_ranges = wm8400_ldo_ranges, 124 .n_linear_ranges = ARRAY_SIZE(wm8400_ldo_ranges), 125 .vsel_reg = WM8400_LDO1_CONTROL, 126 .vsel_mask = WM8400_LDO1_VSEL_MASK, 127 .type = REGULATOR_VOLTAGE, 128 .owner = THIS_MODULE, 129 }, 130 { 131 .name = "LDO2", 132 .id = WM8400_LDO2, 133 .ops = &wm8400_ldo_ops, 134 .enable_reg = WM8400_LDO2_CONTROL, 135 .enable_mask = WM8400_LDO2_ENA, 136 .n_voltages = WM8400_LDO2_VSEL_MASK + 1, 137 .linear_ranges = wm8400_ldo_ranges, 138 .n_linear_ranges = ARRAY_SIZE(wm8400_ldo_ranges), 139 .type = REGULATOR_VOLTAGE, 140 .vsel_reg = WM8400_LDO2_CONTROL, 141 .vsel_mask = WM8400_LDO2_VSEL_MASK, 142 .owner = THIS_MODULE, 143 }, 144 { 145 .name = "LDO3", 146 .id = WM8400_LDO3, 147 .ops = &wm8400_ldo_ops, 148 .enable_reg = WM8400_LDO3_CONTROL, 149 .enable_mask = WM8400_LDO3_ENA, 150 .n_voltages = WM8400_LDO3_VSEL_MASK + 1, 151 .linear_ranges = wm8400_ldo_ranges, 152 .n_linear_ranges = ARRAY_SIZE(wm8400_ldo_ranges), 153 .vsel_reg = WM8400_LDO3_CONTROL, 154 .vsel_mask = WM8400_LDO3_VSEL_MASK, 155 .type = REGULATOR_VOLTAGE, 156 .owner = THIS_MODULE, 157 }, 158 { 159 .name = "LDO4", 160 .id = WM8400_LDO4, 161 .ops = &wm8400_ldo_ops, 162 .enable_reg = WM8400_LDO4_CONTROL, 163 .enable_mask = WM8400_LDO4_ENA, 164 .n_voltages = WM8400_LDO4_VSEL_MASK + 1, 165 .linear_ranges = wm8400_ldo_ranges, 166 .n_linear_ranges = ARRAY_SIZE(wm8400_ldo_ranges), 167 .vsel_reg = WM8400_LDO4_CONTROL, 168 .vsel_mask = WM8400_LDO4_VSEL_MASK, 169 .type = REGULATOR_VOLTAGE, 170 .owner = THIS_MODULE, 171 }, 172 { 173 .name = "DCDC1", 174 .id = WM8400_DCDC1, 175 .ops = &wm8400_dcdc_ops, 176 .enable_reg = WM8400_DCDC1_CONTROL_1, 177 .enable_mask = WM8400_DC1_ENA_MASK, 178 .n_voltages = WM8400_DC1_VSEL_MASK + 1, 179 .vsel_reg = WM8400_DCDC1_CONTROL_1, 180 .vsel_mask = WM8400_DC1_VSEL_MASK, 181 .min_uV = 850000, 182 .uV_step = 25000, 183 .type = REGULATOR_VOLTAGE, 184 .owner = THIS_MODULE, 185 }, 186 { 187 .name = "DCDC2", 188 .id = WM8400_DCDC2, 189 .ops = &wm8400_dcdc_ops, 190 .enable_reg = WM8400_DCDC2_CONTROL_1, 191 .enable_mask = WM8400_DC2_ENA_MASK, 192 .n_voltages = WM8400_DC2_VSEL_MASK + 1, 193 .vsel_reg = WM8400_DCDC2_CONTROL_1, 194 .vsel_mask = WM8400_DC2_VSEL_MASK, 195 .min_uV = 850000, 196 .uV_step = 25000, 197 .type = REGULATOR_VOLTAGE, 198 .owner = THIS_MODULE, 199 }, 200 }; 201 202 static int wm8400_regulator_probe(struct platform_device *pdev) 203 { 204 struct wm8400 *wm8400 = container_of(pdev, struct wm8400, regulators[pdev->id]); 205 struct regulator_config config = { }; 206 struct regulator_dev *rdev; 207 208 config.dev = &pdev->dev; 209 config.init_data = dev_get_platdata(&pdev->dev); 210 config.driver_data = wm8400; 211 config.regmap = wm8400->regmap; 212 213 rdev = devm_regulator_register(&pdev->dev, ®ulators[pdev->id], 214 &config); 215 if (IS_ERR(rdev)) 216 return PTR_ERR(rdev); 217 218 platform_set_drvdata(pdev, rdev); 219 220 return 0; 221 } 222 223 static struct platform_driver wm8400_regulator_driver = { 224 .driver = { 225 .name = "wm8400-regulator", 226 }, 227 .probe = wm8400_regulator_probe, 228 }; 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 * 237 * @dev: The WM8400 device to operate on. 238 * @reg: The regulator to control. 239 * @initdata: Regulator initdata for the regulator. 240 */ 241 int wm8400_register_regulator(struct device *dev, int reg, 242 struct regulator_init_data *initdata) 243 { 244 struct wm8400 *wm8400 = dev_get_drvdata(dev); 245 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; 254 wm8400->regulators[reg].dev.platform_data = initdata; 255 256 return platform_device_register(&wm8400->regulators[reg]); 257 } 258 EXPORT_SYMBOL_GPL(wm8400_register_regulator); 259 260 static int __init wm8400_regulator_init(void) 261 { 262 return platform_driver_register(&wm8400_regulator_driver); 263 } 264 subsys_initcall(wm8400_regulator_init); 265 266 static void __exit wm8400_regulator_exit(void) 267 { 268 platform_driver_unregister(&wm8400_regulator_driver); 269 } 270 module_exit(wm8400_regulator_exit); 271 272 MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); 273 MODULE_DESCRIPTION("WM8400 regulator driver"); 274 MODULE_LICENSE("GPL"); 275 MODULE_ALIAS("platform:wm8400-regulator"); 276