| // SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
| /* |
| * Copyright (c) 2019 Amlogic, Inc. All rights reserved. |
| */ |
| |
| #include <linux/amlogic/pmic/meson_pmic6.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/platform_device.h> |
| #include <linux/regmap.h> |
| #include <linux/regulator/driver.h> |
| #include <linux/regulator/of_regulator.h> |
| |
| /* Voltage Values */ |
| static const int pmic6_vdd_core[][450] = {}; |
| |
| static const unsigned int pmic6_vddq[] = { |
| 1350000, |
| }; |
| |
| static const unsigned int pmic6_vddao3_3[] = { |
| 3300000, |
| }; |
| |
| static const unsigned int pmic6_avdd1_8[] = { |
| 1800000, |
| }; |
| |
| static const unsigned int pmic6_mic_bia[] = { |
| 3300000, |
| }; |
| |
| static const unsigned int pmic6_vcc1_8[] = { |
| 1800000, |
| }; |
| |
| static int get_dcdc1_current_voltage(struct regulator_dev *rdev) |
| { |
| struct regmap *regmap = rdev->regmap; |
| struct device *dev = rdev_get_dev(rdev); |
| u8 reg[2] = {0}; |
| u64 val; |
| int ret; |
| u64 div; |
| |
| ret = regmap_write(regmap, PMIC6_SAR_CNTL_REG14, 0x11); |
| if (ret < 0) { |
| dev_dbg(dev, |
| "failed in func: %s line: %d\n", __func__, __LINE__); |
| return ret; |
| } |
| |
| ret = regmap_write(regmap, PMIC6_SAR_CNTL_REG15, 0x40); |
| if (ret < 0) { |
| dev_dbg(dev, |
| "failed in func: %s line: %d\n", __func__, __LINE__); |
| return ret; |
| } |
| |
| ret = regmap_write(regmap, PMIC6_SAR_CNTL_REG0, 0x16); |
| if (ret < 0) { |
| dev_dbg(dev, |
| "failed in func: %s line: %d\n", __func__, __LINE__); |
| return ret; |
| } |
| |
| ret = regmap_write(regmap, PMIC6_SAR_CNTL_REG2, 0x04); |
| if (ret < 0) { |
| dev_dbg(dev, |
| "failed in func: %s line: %d\n", __func__, __LINE__); |
| return ret; |
| } |
| |
| ret = regmap_write(regmap, PMIC6_SAR_SW_EN_FIELD, 0x08); |
| if (ret < 0) { |
| dev_dbg(dev, |
| "failed in func: %s line: %d\n", __func__, __LINE__); |
| return ret; |
| } |
| |
| ret = regmap_bulk_read(regmap, PMIC6_SAR_RD_RAW, reg, 2); |
| if (ret < 0) { |
| dev_dbg(dev, |
| "failed in func: %s line: %d\n", __func__, __LINE__); |
| return ret; |
| } |
| val = ((reg[1] & 0x1f) << 8) | reg[0]; |
| div = val * 1600UL; |
| div *= 1000; |
| do_div(div, 4096); |
| |
| return (int)(div); |
| } |
| |
| static int pmic6_dcdc1_set_voltage(struct regulator_dev *rdev, int min_uV, |
| int max_uV, unsigned int *selector) |
| { |
| u8 reg[2] = {0}; |
| unsigned int val; |
| int ret, current_uV; |
| unsigned int sel; |
| int min_reg_val; |
| |
| if (max_uV < rdev->desc->min_uV) |
| return -EINVAL; |
| |
| ret = regmap_bulk_read(rdev->regmap, rdev->desc->vsel_reg, reg, 2); |
| if (ret < 0) { |
| dev_dbg(rdev_get_dev(rdev), |
| "failed in func: %s line: %d\n", __func__, __LINE__); |
| return ret; |
| } |
| |
| val = ((reg[1] & 0x7) << 8) | reg[0]; |
| val >>= ffs(rdev->desc->vsel_mask) - 1; |
| dev_info(rdev_get_dev(rdev), "%s val: %d\n", __func__, val); |
| |
| current_uV = get_dcdc1_current_voltage(rdev); |
| dev_info(rdev_get_dev(rdev), "%s current_uV: %d\n", __func__, |
| current_uV); |
| |
| WARN_ON(current_uV < rdev->desc->min_uV); |
| |
| min_reg_val = val - (current_uV - rdev->desc->min_uV) / |
| rdev->desc->uV_step; |
| |
| *selector = (min_uV - rdev->desc->min_uV) / rdev->desc->uV_step; |
| sel = min_reg_val + *selector; |
| ret = regmap_bulk_write(rdev->regmap, rdev->desc->vsel_reg, |
| &sel, 2); |
| if (ret < 0) { |
| dev_dbg(rdev_get_dev(rdev), |
| "failed in func: %s line: %d\n", __func__, __LINE__); |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| static int pmic6_dcdc1_get_voltage(struct regulator_dev *rdev) |
| { |
| /*int id = rdev_get_id(rdev);*/ |
| |
| /*dev_info(rdev_get_dev(rdev), "%s id: %d\n", __func__, id);*/ |
| |
| return get_dcdc1_current_voltage(rdev); |
| } |
| |
| static const struct regulator_ops pmic6_dcdc1_ops = { |
| .enable = regulator_enable_regmap, |
| .disable = regulator_disable_regmap, |
| .is_enabled = regulator_is_enabled_regmap, |
| .set_voltage = pmic6_dcdc1_set_voltage, |
| .get_voltage = pmic6_dcdc1_get_voltage, |
| }; |
| |
| static int pmic6_set_voltage(struct regulator_dev *rdev, int min_uV, |
| int max_uV, unsigned int *selector) |
| { |
| int id = rdev_get_id(rdev); |
| |
| dev_info(rdev_get_dev(rdev), "%s id: %d min_uV: %d max_uV: %d\n", |
| __func__, id, min_uV, max_uV); |
| |
| if (min_uV <= rdev->desc->volt_table[0] && |
| rdev->desc->volt_table[0] <= max_uV) { |
| *selector = 0; |
| return 0; |
| } else { |
| return -EINVAL; |
| } |
| } |
| |
| static int pmic6_get_voltage(struct regulator_dev *rdev) |
| { |
| int id = rdev_get_id(rdev); |
| |
| dev_info(rdev_get_dev(rdev), "%s id: %d\n", __func__, id); |
| |
| return rdev->desc->volt_table[0]; |
| } |
| |
| static int pmic6_ldo1_get_voltage(struct regulator_dev *rdev) |
| { |
| int id = rdev_get_id(rdev); |
| |
| dev_info(rdev_get_dev(rdev), "%s id: %d\n", __func__, id); |
| |
| return rdev->desc->volt_table[0]; |
| } |
| |
| static const struct regulator_ops pmic6_ops = { |
| .enable = regulator_enable_regmap, |
| .disable = regulator_disable_regmap, |
| .is_enabled = regulator_is_enabled_regmap, |
| .set_voltage = pmic6_set_voltage, |
| .get_voltage = pmic6_get_voltage, |
| .list_voltage = regulator_list_voltage_table, |
| }; |
| |
| static const struct regulator_ops pmic6_ldo1_ops = { |
| .enable = regulator_enable_regmap, |
| .disable = regulator_disable_regmap, |
| .is_enabled = regulator_is_enabled_regmap, |
| .get_voltage = pmic6_ldo1_get_voltage, |
| .list_voltage = regulator_list_voltage_table, |
| }; |
| |
| #define REG_DCDC(rid, ereg, emask, vreg, vmask, min1, max, step1) \ |
| { \ |
| .name = #rid, \ |
| .of_match = of_match_ptr(#rid), \ |
| .regulators_node = of_match_ptr("regulators"), \ |
| .id = PMIC6_##rid, \ |
| .type = REGULATOR_VOLTAGE, \ |
| .owner = THIS_MODULE, \ |
| .ops = &pmic6_dcdc1_ops, \ |
| .n_voltages = ((max) - (min1)) / (step1) + 1, \ |
| .min_uV = (min1), \ |
| .uV_step = (step1), \ |
| .enable_reg = PMIC6_##ereg, \ |
| .enable_mask = (emask), \ |
| .vsel_reg = PMIC6_##vreg, \ |
| .vsel_mask = (vmask), \ |
| } |
| |
| #define REG_DESC(rid, ereg, emask, vreg, vmask, voltages, ops1) \ |
| { \ |
| .name = #rid, \ |
| .of_match = of_match_ptr(#rid), \ |
| .regulators_node = of_match_ptr("regulators"), \ |
| .id = PMIC6_##rid, \ |
| .type = REGULATOR_VOLTAGE, \ |
| .owner = THIS_MODULE, \ |
| .ops = &(ops1), \ |
| .n_voltages = ARRAY_SIZE(voltages), \ |
| .volt_table = (voltages), \ |
| .enable_reg = PMIC6_##ereg, \ |
| .enable_mask = (emask), \ |
| .vsel_reg = PMIC6_##vreg, \ |
| .vsel_mask = (vmask), \ |
| } |
| |
| #define REG_LDO(rid, ereg, emask, voltage, ops2) \ |
| { \ |
| .name = #rid, \ |
| .of_match = of_match_ptr(#rid), \ |
| .regulators_node = of_match_ptr("regulators"), \ |
| .id = PMIC6_##rid, \ |
| .type = REGULATOR_VOLTAGE, \ |
| .owner = THIS_MODULE, \ |
| .ops = &(ops2), \ |
| .n_voltages = ARRAY_SIZE(voltage), \ |
| .volt_table = (voltage), \ |
| .enable_reg = PMIC6_##ereg, \ |
| .enable_mask = (emask), \ |
| } |
| |
| static struct regulator_desc pmic6_regulators[] = { |
| /* DCDC */ |
| REG_DCDC(DCDC1, ANALOG_REG9, BIT(0), OTP_REG_0x50, |
| 0x7ff, 600000, 1500000, 2000), |
| REG_DESC(DCDC2, ANALOG_REG9, BIT(1), OTP_REG_0x52, 0x7ff, |
| pmic6_vddq, pmic6_ops), |
| REG_DESC(DCDC3, ANALOG_REG9, BIT(2), OTP_REG_0x54, |
| 0x7ff, pmic6_vddao3_3, pmic6_ops), |
| /* LDO */ |
| REG_LDO(LDO1, ANALOG_REG9, BIT(3), pmic6_avdd1_8, pmic6_ldo1_ops), |
| REG_DESC(LDO2, ANALOG_REG9, BIT(4), OTP_REG_0x73, 0x3f, |
| pmic6_mic_bia, pmic6_ops), |
| REG_DESC(LDO3, ANALOG_REG9, BIT(5), OTP_REG_0x25, 0x3f, |
| pmic6_vcc1_8, pmic6_ops), |
| }; |
| |
| static int meson_pmic_regulator_probe(struct platform_device *pdev) |
| { |
| struct meson_pmic *meson_pmic = dev_get_drvdata(pdev->dev.parent); |
| struct regulator_config config = { }; |
| struct regulator_dev *rdev; |
| struct regulator_desc *regulators; |
| int i; |
| int num_regulators = 0; |
| |
| regulators = pmic6_regulators; |
| num_regulators = ARRAY_SIZE(pmic6_regulators); |
| |
| config.dev = pdev->dev.parent; |
| config.regmap = meson_pmic->regmap; |
| |
| for (i = 0; i < num_regulators; i++) { |
| rdev = devm_regulator_register(&pdev->dev, |
| ®ulators[i], |
| &config); |
| if (IS_ERR(rdev)) { |
| dev_err(&pdev->dev, "failed to register %s regulator\n", |
| regulators[i].name); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static struct platform_driver pmic6_regulator_driver = { |
| .probe = meson_pmic_regulator_probe, |
| .driver = { |
| .name = PMIC6_DRVNAME_REGULATORS, |
| }, |
| }; |
| |
| static int __init meson_pmic6_regulator_init(void) |
| { |
| int ret; |
| |
| ret = platform_driver_register(&pmic6_regulator_driver); |
| return ret; |
| } |
| |
| static void __exit meson_pmic6_regulator_exit(void) |
| { |
| platform_driver_unregister(&pmic6_regulator_driver); |
| } |
| subsys_initcall(meson_pmic6_regulator_init); |
| module_exit(meson_pmic6_regulator_exit); |
| |
| MODULE_AUTHOR("Amlogic"); |
| MODULE_DESCRIPTION("PMIC6 regulator driver"); |
| MODULE_LICENSE("GPL v2"); |