18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci// 38c2ecf20Sopenharmony_ci// Regulator support for WM8400 48c2ecf20Sopenharmony_ci// 58c2ecf20Sopenharmony_ci// Copyright 2008 Wolfson Microelectronics PLC. 68c2ecf20Sopenharmony_ci// 78c2ecf20Sopenharmony_ci// Author: Mark Brown <broonie@opensource.wolfsonmicro.com> 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/bug.h> 108c2ecf20Sopenharmony_ci#include <linux/err.h> 118c2ecf20Sopenharmony_ci#include <linux/kernel.h> 128c2ecf20Sopenharmony_ci#include <linux/module.h> 138c2ecf20Sopenharmony_ci#include <linux/regulator/driver.h> 148c2ecf20Sopenharmony_ci#include <linux/mfd/wm8400-private.h> 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_cistatic const struct linear_range wm8400_ldo_ranges[] = { 178c2ecf20Sopenharmony_ci REGULATOR_LINEAR_RANGE(900000, 0, 14, 50000), 188c2ecf20Sopenharmony_ci REGULATOR_LINEAR_RANGE(1700000, 15, 31, 100000), 198c2ecf20Sopenharmony_ci}; 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_cistatic const struct regulator_ops wm8400_ldo_ops = { 228c2ecf20Sopenharmony_ci .is_enabled = regulator_is_enabled_regmap, 238c2ecf20Sopenharmony_ci .enable = regulator_enable_regmap, 248c2ecf20Sopenharmony_ci .disable = regulator_disable_regmap, 258c2ecf20Sopenharmony_ci .list_voltage = regulator_list_voltage_linear_range, 268c2ecf20Sopenharmony_ci .get_voltage_sel = regulator_get_voltage_sel_regmap, 278c2ecf20Sopenharmony_ci .set_voltage_sel = regulator_set_voltage_sel_regmap, 288c2ecf20Sopenharmony_ci .map_voltage = regulator_map_voltage_linear_range, 298c2ecf20Sopenharmony_ci}; 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_cistatic unsigned int wm8400_dcdc_get_mode(struct regulator_dev *dev) 328c2ecf20Sopenharmony_ci{ 338c2ecf20Sopenharmony_ci struct regmap *rmap = rdev_get_regmap(dev); 348c2ecf20Sopenharmony_ci int offset = (rdev_get_id(dev) - WM8400_DCDC1) * 2; 358c2ecf20Sopenharmony_ci u16 data[2]; 368c2ecf20Sopenharmony_ci int ret; 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci ret = regmap_bulk_read(rmap, WM8400_DCDC1_CONTROL_1 + offset, data, 2); 398c2ecf20Sopenharmony_ci if (ret != 0) 408c2ecf20Sopenharmony_ci return 0; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci /* Datasheet: hibernate */ 438c2ecf20Sopenharmony_ci if (data[0] & WM8400_DC1_SLEEP) 448c2ecf20Sopenharmony_ci return REGULATOR_MODE_STANDBY; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci /* Datasheet: standby */ 478c2ecf20Sopenharmony_ci if (!(data[0] & WM8400_DC1_ACTIVE)) 488c2ecf20Sopenharmony_ci return REGULATOR_MODE_IDLE; 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci /* Datasheet: active with or without force PWM */ 518c2ecf20Sopenharmony_ci if (data[1] & WM8400_DC1_FRC_PWM) 528c2ecf20Sopenharmony_ci return REGULATOR_MODE_FAST; 538c2ecf20Sopenharmony_ci else 548c2ecf20Sopenharmony_ci return REGULATOR_MODE_NORMAL; 558c2ecf20Sopenharmony_ci} 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_cistatic int wm8400_dcdc_set_mode(struct regulator_dev *dev, unsigned int mode) 588c2ecf20Sopenharmony_ci{ 598c2ecf20Sopenharmony_ci struct regmap *rmap = rdev_get_regmap(dev); 608c2ecf20Sopenharmony_ci int offset = (rdev_get_id(dev) - WM8400_DCDC1) * 2; 618c2ecf20Sopenharmony_ci int ret; 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci switch (mode) { 648c2ecf20Sopenharmony_ci case REGULATOR_MODE_FAST: 658c2ecf20Sopenharmony_ci /* Datasheet: active with force PWM */ 668c2ecf20Sopenharmony_ci ret = regmap_update_bits(rmap, WM8400_DCDC1_CONTROL_2 + offset, 678c2ecf20Sopenharmony_ci WM8400_DC1_FRC_PWM, WM8400_DC1_FRC_PWM); 688c2ecf20Sopenharmony_ci if (ret != 0) 698c2ecf20Sopenharmony_ci return ret; 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci return regmap_update_bits(rmap, WM8400_DCDC1_CONTROL_1 + offset, 728c2ecf20Sopenharmony_ci WM8400_DC1_ACTIVE | WM8400_DC1_SLEEP, 738c2ecf20Sopenharmony_ci WM8400_DC1_ACTIVE); 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci case REGULATOR_MODE_NORMAL: 768c2ecf20Sopenharmony_ci /* Datasheet: active */ 778c2ecf20Sopenharmony_ci ret = regmap_update_bits(rmap, WM8400_DCDC1_CONTROL_2 + offset, 788c2ecf20Sopenharmony_ci WM8400_DC1_FRC_PWM, 0); 798c2ecf20Sopenharmony_ci if (ret != 0) 808c2ecf20Sopenharmony_ci return ret; 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci return regmap_update_bits(rmap, WM8400_DCDC1_CONTROL_1 + offset, 838c2ecf20Sopenharmony_ci WM8400_DC1_ACTIVE | WM8400_DC1_SLEEP, 848c2ecf20Sopenharmony_ci WM8400_DC1_ACTIVE); 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci case REGULATOR_MODE_IDLE: 878c2ecf20Sopenharmony_ci /* Datasheet: standby */ 888c2ecf20Sopenharmony_ci return regmap_update_bits(rmap, WM8400_DCDC1_CONTROL_1 + offset, 898c2ecf20Sopenharmony_ci WM8400_DC1_ACTIVE | WM8400_DC1_SLEEP, 0); 908c2ecf20Sopenharmony_ci default: 918c2ecf20Sopenharmony_ci return -EINVAL; 928c2ecf20Sopenharmony_ci } 938c2ecf20Sopenharmony_ci} 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_cistatic unsigned int wm8400_dcdc_get_optimum_mode(struct regulator_dev *dev, 968c2ecf20Sopenharmony_ci int input_uV, int output_uV, 978c2ecf20Sopenharmony_ci int load_uA) 988c2ecf20Sopenharmony_ci{ 998c2ecf20Sopenharmony_ci return REGULATOR_MODE_NORMAL; 1008c2ecf20Sopenharmony_ci} 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_cistatic const struct regulator_ops wm8400_dcdc_ops = { 1038c2ecf20Sopenharmony_ci .is_enabled = regulator_is_enabled_regmap, 1048c2ecf20Sopenharmony_ci .enable = regulator_enable_regmap, 1058c2ecf20Sopenharmony_ci .disable = regulator_disable_regmap, 1068c2ecf20Sopenharmony_ci .list_voltage = regulator_list_voltage_linear, 1078c2ecf20Sopenharmony_ci .map_voltage = regulator_map_voltage_linear, 1088c2ecf20Sopenharmony_ci .get_voltage_sel = regulator_get_voltage_sel_regmap, 1098c2ecf20Sopenharmony_ci .set_voltage_sel = regulator_set_voltage_sel_regmap, 1108c2ecf20Sopenharmony_ci .get_mode = wm8400_dcdc_get_mode, 1118c2ecf20Sopenharmony_ci .set_mode = wm8400_dcdc_set_mode, 1128c2ecf20Sopenharmony_ci .get_optimum_mode = wm8400_dcdc_get_optimum_mode, 1138c2ecf20Sopenharmony_ci}; 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_cistatic struct regulator_desc regulators[] = { 1168c2ecf20Sopenharmony_ci { 1178c2ecf20Sopenharmony_ci .name = "LDO1", 1188c2ecf20Sopenharmony_ci .id = WM8400_LDO1, 1198c2ecf20Sopenharmony_ci .ops = &wm8400_ldo_ops, 1208c2ecf20Sopenharmony_ci .enable_reg = WM8400_LDO1_CONTROL, 1218c2ecf20Sopenharmony_ci .enable_mask = WM8400_LDO1_ENA, 1228c2ecf20Sopenharmony_ci .n_voltages = WM8400_LDO1_VSEL_MASK + 1, 1238c2ecf20Sopenharmony_ci .linear_ranges = wm8400_ldo_ranges, 1248c2ecf20Sopenharmony_ci .n_linear_ranges = ARRAY_SIZE(wm8400_ldo_ranges), 1258c2ecf20Sopenharmony_ci .vsel_reg = WM8400_LDO1_CONTROL, 1268c2ecf20Sopenharmony_ci .vsel_mask = WM8400_LDO1_VSEL_MASK, 1278c2ecf20Sopenharmony_ci .type = REGULATOR_VOLTAGE, 1288c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 1298c2ecf20Sopenharmony_ci }, 1308c2ecf20Sopenharmony_ci { 1318c2ecf20Sopenharmony_ci .name = "LDO2", 1328c2ecf20Sopenharmony_ci .id = WM8400_LDO2, 1338c2ecf20Sopenharmony_ci .ops = &wm8400_ldo_ops, 1348c2ecf20Sopenharmony_ci .enable_reg = WM8400_LDO2_CONTROL, 1358c2ecf20Sopenharmony_ci .enable_mask = WM8400_LDO2_ENA, 1368c2ecf20Sopenharmony_ci .n_voltages = WM8400_LDO2_VSEL_MASK + 1, 1378c2ecf20Sopenharmony_ci .linear_ranges = wm8400_ldo_ranges, 1388c2ecf20Sopenharmony_ci .n_linear_ranges = ARRAY_SIZE(wm8400_ldo_ranges), 1398c2ecf20Sopenharmony_ci .type = REGULATOR_VOLTAGE, 1408c2ecf20Sopenharmony_ci .vsel_reg = WM8400_LDO2_CONTROL, 1418c2ecf20Sopenharmony_ci .vsel_mask = WM8400_LDO2_VSEL_MASK, 1428c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 1438c2ecf20Sopenharmony_ci }, 1448c2ecf20Sopenharmony_ci { 1458c2ecf20Sopenharmony_ci .name = "LDO3", 1468c2ecf20Sopenharmony_ci .id = WM8400_LDO3, 1478c2ecf20Sopenharmony_ci .ops = &wm8400_ldo_ops, 1488c2ecf20Sopenharmony_ci .enable_reg = WM8400_LDO3_CONTROL, 1498c2ecf20Sopenharmony_ci .enable_mask = WM8400_LDO3_ENA, 1508c2ecf20Sopenharmony_ci .n_voltages = WM8400_LDO3_VSEL_MASK + 1, 1518c2ecf20Sopenharmony_ci .linear_ranges = wm8400_ldo_ranges, 1528c2ecf20Sopenharmony_ci .n_linear_ranges = ARRAY_SIZE(wm8400_ldo_ranges), 1538c2ecf20Sopenharmony_ci .vsel_reg = WM8400_LDO3_CONTROL, 1548c2ecf20Sopenharmony_ci .vsel_mask = WM8400_LDO3_VSEL_MASK, 1558c2ecf20Sopenharmony_ci .type = REGULATOR_VOLTAGE, 1568c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 1578c2ecf20Sopenharmony_ci }, 1588c2ecf20Sopenharmony_ci { 1598c2ecf20Sopenharmony_ci .name = "LDO4", 1608c2ecf20Sopenharmony_ci .id = WM8400_LDO4, 1618c2ecf20Sopenharmony_ci .ops = &wm8400_ldo_ops, 1628c2ecf20Sopenharmony_ci .enable_reg = WM8400_LDO4_CONTROL, 1638c2ecf20Sopenharmony_ci .enable_mask = WM8400_LDO4_ENA, 1648c2ecf20Sopenharmony_ci .n_voltages = WM8400_LDO4_VSEL_MASK + 1, 1658c2ecf20Sopenharmony_ci .linear_ranges = wm8400_ldo_ranges, 1668c2ecf20Sopenharmony_ci .n_linear_ranges = ARRAY_SIZE(wm8400_ldo_ranges), 1678c2ecf20Sopenharmony_ci .vsel_reg = WM8400_LDO4_CONTROL, 1688c2ecf20Sopenharmony_ci .vsel_mask = WM8400_LDO4_VSEL_MASK, 1698c2ecf20Sopenharmony_ci .type = REGULATOR_VOLTAGE, 1708c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 1718c2ecf20Sopenharmony_ci }, 1728c2ecf20Sopenharmony_ci { 1738c2ecf20Sopenharmony_ci .name = "DCDC1", 1748c2ecf20Sopenharmony_ci .id = WM8400_DCDC1, 1758c2ecf20Sopenharmony_ci .ops = &wm8400_dcdc_ops, 1768c2ecf20Sopenharmony_ci .enable_reg = WM8400_DCDC1_CONTROL_1, 1778c2ecf20Sopenharmony_ci .enable_mask = WM8400_DC1_ENA_MASK, 1788c2ecf20Sopenharmony_ci .n_voltages = WM8400_DC1_VSEL_MASK + 1, 1798c2ecf20Sopenharmony_ci .vsel_reg = WM8400_DCDC1_CONTROL_1, 1808c2ecf20Sopenharmony_ci .vsel_mask = WM8400_DC1_VSEL_MASK, 1818c2ecf20Sopenharmony_ci .min_uV = 850000, 1828c2ecf20Sopenharmony_ci .uV_step = 25000, 1838c2ecf20Sopenharmony_ci .type = REGULATOR_VOLTAGE, 1848c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 1858c2ecf20Sopenharmony_ci }, 1868c2ecf20Sopenharmony_ci { 1878c2ecf20Sopenharmony_ci .name = "DCDC2", 1888c2ecf20Sopenharmony_ci .id = WM8400_DCDC2, 1898c2ecf20Sopenharmony_ci .ops = &wm8400_dcdc_ops, 1908c2ecf20Sopenharmony_ci .enable_reg = WM8400_DCDC2_CONTROL_1, 1918c2ecf20Sopenharmony_ci .enable_mask = WM8400_DC2_ENA_MASK, 1928c2ecf20Sopenharmony_ci .n_voltages = WM8400_DC2_VSEL_MASK + 1, 1938c2ecf20Sopenharmony_ci .vsel_reg = WM8400_DCDC2_CONTROL_1, 1948c2ecf20Sopenharmony_ci .vsel_mask = WM8400_DC2_VSEL_MASK, 1958c2ecf20Sopenharmony_ci .min_uV = 850000, 1968c2ecf20Sopenharmony_ci .uV_step = 25000, 1978c2ecf20Sopenharmony_ci .type = REGULATOR_VOLTAGE, 1988c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 1998c2ecf20Sopenharmony_ci }, 2008c2ecf20Sopenharmony_ci}; 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_cistatic int wm8400_regulator_probe(struct platform_device *pdev) 2038c2ecf20Sopenharmony_ci{ 2048c2ecf20Sopenharmony_ci struct wm8400 *wm8400 = container_of(pdev, struct wm8400, regulators[pdev->id]); 2058c2ecf20Sopenharmony_ci struct regulator_config config = { }; 2068c2ecf20Sopenharmony_ci struct regulator_dev *rdev; 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci config.dev = &pdev->dev; 2098c2ecf20Sopenharmony_ci config.init_data = dev_get_platdata(&pdev->dev); 2108c2ecf20Sopenharmony_ci config.driver_data = wm8400; 2118c2ecf20Sopenharmony_ci config.regmap = wm8400->regmap; 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci rdev = devm_regulator_register(&pdev->dev, ®ulators[pdev->id], 2148c2ecf20Sopenharmony_ci &config); 2158c2ecf20Sopenharmony_ci if (IS_ERR(rdev)) 2168c2ecf20Sopenharmony_ci return PTR_ERR(rdev); 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, rdev); 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_ci return 0; 2218c2ecf20Sopenharmony_ci} 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_cistatic struct platform_driver wm8400_regulator_driver = { 2248c2ecf20Sopenharmony_ci .driver = { 2258c2ecf20Sopenharmony_ci .name = "wm8400-regulator", 2268c2ecf20Sopenharmony_ci }, 2278c2ecf20Sopenharmony_ci .probe = wm8400_regulator_probe, 2288c2ecf20Sopenharmony_ci}; 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci/** 2318c2ecf20Sopenharmony_ci * wm8400_register_regulator - enable software control of a WM8400 regulator 2328c2ecf20Sopenharmony_ci * 2338c2ecf20Sopenharmony_ci * This function enables software control of a WM8400 regulator via 2348c2ecf20Sopenharmony_ci * the regulator API. It is intended to be called from the 2358c2ecf20Sopenharmony_ci * platform_init() callback of the WM8400 MFD driver. 2368c2ecf20Sopenharmony_ci * 2378c2ecf20Sopenharmony_ci * @dev: The WM8400 device to operate on. 2388c2ecf20Sopenharmony_ci * @reg: The regulator to control. 2398c2ecf20Sopenharmony_ci * @initdata: Regulator initdata for the regulator. 2408c2ecf20Sopenharmony_ci */ 2418c2ecf20Sopenharmony_ciint wm8400_register_regulator(struct device *dev, int reg, 2428c2ecf20Sopenharmony_ci struct regulator_init_data *initdata) 2438c2ecf20Sopenharmony_ci{ 2448c2ecf20Sopenharmony_ci struct wm8400 *wm8400 = dev_get_drvdata(dev); 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci if (wm8400->regulators[reg].name) 2478c2ecf20Sopenharmony_ci return -EBUSY; 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci initdata->driver_data = wm8400; 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci wm8400->regulators[reg].name = "wm8400-regulator"; 2528c2ecf20Sopenharmony_ci wm8400->regulators[reg].id = reg; 2538c2ecf20Sopenharmony_ci wm8400->regulators[reg].dev.parent = dev; 2548c2ecf20Sopenharmony_ci wm8400->regulators[reg].dev.platform_data = initdata; 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_ci return platform_device_register(&wm8400->regulators[reg]); 2578c2ecf20Sopenharmony_ci} 2588c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(wm8400_register_regulator); 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_cistatic int __init wm8400_regulator_init(void) 2618c2ecf20Sopenharmony_ci{ 2628c2ecf20Sopenharmony_ci return platform_driver_register(&wm8400_regulator_driver); 2638c2ecf20Sopenharmony_ci} 2648c2ecf20Sopenharmony_cisubsys_initcall(wm8400_regulator_init); 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_cistatic void __exit wm8400_regulator_exit(void) 2678c2ecf20Sopenharmony_ci{ 2688c2ecf20Sopenharmony_ci platform_driver_unregister(&wm8400_regulator_driver); 2698c2ecf20Sopenharmony_ci} 2708c2ecf20Sopenharmony_cimodule_exit(wm8400_regulator_exit); 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_ciMODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); 2738c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("WM8400 regulator driver"); 2748c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 2758c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:wm8400-regulator"); 276