| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * (C) Copyright 2016 |
| * Texas Instruments Incorporated, <www.ti.com> |
| * |
| * Keerthy <j-keerthy@ti.com> |
| */ |
| |
| #include <fdtdec.h> |
| #include <errno.h> |
| #include <dm.h> |
| #include <power/pmic.h> |
| #include <power/regulator.h> |
| #include <power/palmas.h> |
| |
| #define REGULATOR_ON 0x1 |
| #define REGULATOR_OFF 0x0 |
| |
| #define SMPS_MODE_MASK 0x3 |
| #define SMPS_MODE_SHIFT 0x0 |
| #define LDO_MODE_MASK 0x1 |
| #define LDO_MODE_SHIFT 0x0 |
| |
| static const char palmas_smps_ctrl[][PALMAS_SMPS_NUM] = { |
| {0x20, 0x24, 0x28, 0x2c, 0x30, 0x34, 0x38, 0x3c}, |
| {0x20, 0x24, 0x28, 0x2c, 0x30, 0x34, 0x38}, |
| {0x20, 0x24, 0x2c, 0x30, 0x38}, |
| }; |
| |
| static const char palmas_smps_volt[][PALMAS_SMPS_NUM] = { |
| {0x23, 0x27, 0x2b, 0x2f, 0x33, 0x37, 0x3b, 0x3c}, |
| {0x23, 0x27, 0x2b, 0x2f, 0x33, 0x37, 0x3b}, |
| {0x23, 0x27, 0x2f, 0x33, 0x3B} |
| }; |
| |
| static const char palmas_ldo_ctrl[][PALMAS_LDO_NUM] = { |
| {0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 0x60, 0x62, 0x64}, |
| {0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 0x60, 0x62, 0x64}, |
| {0x50, 0x52, 0x54, 0x5e, 0x62} |
| }; |
| |
| static const char palmas_ldo_volt[][PALMAS_LDO_NUM] = { |
| {0x51, 0x53, 0x55, 0x57, 0x59, 0x5b, 0x5d, 0x5f, 0x61, 0x63, 0x65}, |
| {0x51, 0x53, 0x55, 0x57, 0x59, 0x5b, 0x5d, 0x5f, 0x61, 0x63, 0x65}, |
| {0x51, 0x53, 0x55, 0x5f, 0x63} |
| }; |
| |
| static int palmas_smps_enable(struct udevice *dev, int op, bool *enable) |
| { |
| int ret; |
| unsigned int adr; |
| struct dm_regulator_uclass_plat *uc_pdata; |
| |
| uc_pdata = dev_get_uclass_plat(dev); |
| adr = uc_pdata->ctrl_reg; |
| |
| ret = pmic_reg_read(dev->parent, adr); |
| if (ret < 0) |
| return ret; |
| |
| if (op == PMIC_OP_GET) { |
| ret &= PALMAS_SMPS_STATUS_MASK; |
| |
| if (ret) |
| *enable = true; |
| else |
| *enable = false; |
| |
| return 0; |
| } else if (op == PMIC_OP_SET) { |
| if (*enable) |
| ret |= PALMAS_SMPS_MODE_MASK; |
| else |
| ret &= ~(PALMAS_SMPS_MODE_MASK); |
| |
| ret = pmic_reg_write(dev->parent, adr, ret); |
| if (ret) |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int palmas_smps_volt2hex(int uV) |
| { |
| if (uV > PALMAS_LDO_VOLT_MAX) |
| return -EINVAL; |
| |
| if (uV > 1650000) |
| return (uV - 1000000) / 20000 + 0x6; |
| |
| if (uV == 500000) |
| return 0x6; |
| else |
| return 0x6 + ((uV - 500000) / 10000); |
| } |
| |
| static int palmas_smps_hex2volt(int hex, bool range) |
| { |
| unsigned int uV = 0; |
| |
| if (hex > PALMAS_SMPS_VOLT_MAX_HEX) |
| return -EINVAL; |
| |
| if (hex < 0x7) |
| uV = 500000; |
| else |
| uV = 500000 + (hex - 0x6) * 10000; |
| |
| if (range) |
| uV *= 2; |
| |
| return uV; |
| } |
| |
| static int palmas_smps_val(struct udevice *dev, int op, int *uV) |
| { |
| unsigned int hex, adr; |
| int ret; |
| bool range; |
| struct dm_regulator_uclass_plat *uc_pdata; |
| |
| uc_pdata = dev_get_uclass_plat(dev); |
| |
| if (op == PMIC_OP_GET) |
| *uV = 0; |
| |
| adr = uc_pdata->volt_reg; |
| |
| ret = pmic_reg_read(dev->parent, adr); |
| if (ret < 0) |
| return ret; |
| |
| if (op == PMIC_OP_GET) { |
| if (ret & PALMAS_SMPS_RANGE_MASK) |
| range = true; |
| else |
| range = false; |
| |
| ret &= PALMAS_SMPS_VOLT_MASK; |
| ret = palmas_smps_hex2volt(ret, range); |
| if (ret < 0) |
| return ret; |
| *uV = ret; |
| |
| return 0; |
| } |
| |
| hex = palmas_smps_volt2hex(*uV); |
| if (hex < 0) |
| return hex; |
| |
| ret &= ~PALMAS_SMPS_VOLT_MASK; |
| ret |= hex; |
| if (*uV > 1650000) |
| ret |= PALMAS_SMPS_RANGE_MASK; |
| |
| return pmic_reg_write(dev->parent, adr, ret); |
| } |
| |
| static int palmas_ldo_bypass_enable(struct udevice *dev, bool enabled) |
| { |
| int type = dev_get_driver_data(dev_get_parent(dev)); |
| struct dm_regulator_uclass_plat *p; |
| unsigned int adr; |
| int reg; |
| |
| if (type == TPS65917) { |
| /* bypass available only on LDO1 and LDO2 */ |
| if (dev->driver_data > 2) |
| return -ENOTSUPP; |
| } else if (type == TPS659038) { |
| /* bypass available only on LDO9 */ |
| if (dev->driver_data != 9) |
| return -ENOTSUPP; |
| } |
| |
| p = dev_get_uclass_plat(dev); |
| adr = p->ctrl_reg; |
| |
| reg = pmic_reg_read(dev->parent, adr); |
| if (reg < 0) |
| return reg; |
| |
| if (enabled) |
| reg |= PALMAS_LDO_BYPASS_EN; |
| else |
| reg &= ~PALMAS_LDO_BYPASS_EN; |
| |
| return pmic_reg_write(dev->parent, adr, reg); |
| } |
| |
| static int palmas_ldo_enable(struct udevice *dev, int op, bool *enable) |
| { |
| int ret; |
| unsigned int adr; |
| struct dm_regulator_uclass_plat *uc_pdata; |
| |
| uc_pdata = dev_get_uclass_plat(dev); |
| adr = uc_pdata->ctrl_reg; |
| |
| ret = pmic_reg_read(dev->parent, adr); |
| if (ret < 0) |
| return ret; |
| |
| if (op == PMIC_OP_GET) { |
| ret &= PALMAS_LDO_STATUS_MASK; |
| |
| if (ret) |
| *enable = true; |
| else |
| *enable = false; |
| |
| return 0; |
| } else if (op == PMIC_OP_SET) { |
| if (*enable) |
| ret |= PALMAS_LDO_MODE_MASK; |
| else |
| ret &= ~(PALMAS_LDO_MODE_MASK); |
| |
| ret = pmic_reg_write(dev->parent, adr, ret); |
| if (ret) |
| return ret; |
| |
| ret = palmas_ldo_bypass_enable(dev, false); |
| if (ret && (ret != -ENOTSUPP)) |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int palmas_ldo_volt2hex(int uV) |
| { |
| if (uV > PALMAS_LDO_VOLT_MAX) |
| return -EINVAL; |
| |
| return (uV - 850000) / 50000; |
| } |
| |
| static int palmas_ldo_hex2volt(int hex) |
| { |
| if (hex > PALMAS_LDO_VOLT_MAX_HEX) |
| return -EINVAL; |
| |
| if (!hex) |
| return 0; |
| |
| return (hex * 50000) + 850000; |
| } |
| |
| static int palmas_ldo_val(struct udevice *dev, int op, int *uV) |
| { |
| unsigned int hex, adr; |
| int ret; |
| |
| struct dm_regulator_uclass_plat *uc_pdata; |
| |
| if (op == PMIC_OP_GET) |
| *uV = 0; |
| |
| uc_pdata = dev_get_uclass_plat(dev); |
| |
| adr = uc_pdata->volt_reg; |
| |
| ret = pmic_reg_read(dev->parent, adr); |
| if (ret < 0) |
| return ret; |
| |
| if (op == PMIC_OP_GET) { |
| ret &= PALMAS_LDO_VOLT_MASK; |
| ret = palmas_ldo_hex2volt(ret); |
| if (ret < 0) |
| return ret; |
| *uV = ret; |
| return 0; |
| } |
| |
| hex = palmas_ldo_volt2hex(*uV); |
| if (hex < 0) |
| return hex; |
| |
| ret &= ~PALMAS_LDO_VOLT_MASK; |
| ret |= hex; |
| if (*uV > 1650000) |
| ret |= 0x80; |
| |
| return pmic_reg_write(dev->parent, adr, ret); |
| } |
| |
| static int palmas_ldo_probe(struct udevice *dev) |
| { |
| struct dm_regulator_uclass_plat *uc_pdata; |
| struct udevice *parent; |
| |
| uc_pdata = dev_get_uclass_plat(dev); |
| |
| parent = dev_get_parent(dev); |
| int type = dev_get_driver_data(parent); |
| |
| uc_pdata->type = REGULATOR_TYPE_LDO; |
| |
| /* check for ldoln and ldousb cases */ |
| if (!strcmp("ldoln", dev->name)) { |
| uc_pdata->ctrl_reg = palmas_ldo_ctrl[type][9]; |
| uc_pdata->volt_reg = palmas_ldo_volt[type][9]; |
| return 0; |
| } |
| |
| if (!strcmp("ldousb", dev->name)) { |
| uc_pdata->ctrl_reg = palmas_ldo_ctrl[type][10]; |
| uc_pdata->volt_reg = palmas_ldo_volt[type][10]; |
| return 0; |
| } |
| |
| if (dev->driver_data > 0) { |
| u8 idx = dev->driver_data - 1; |
| uc_pdata->ctrl_reg = palmas_ldo_ctrl[type][idx]; |
| uc_pdata->volt_reg = palmas_ldo_volt[type][idx]; |
| } |
| |
| return 0; |
| } |
| |
| static int ldo_get_value(struct udevice *dev) |
| { |
| int uV; |
| int ret; |
| |
| ret = palmas_ldo_val(dev, PMIC_OP_GET, &uV); |
| if (ret) |
| return ret; |
| |
| return uV; |
| } |
| |
| static int ldo_set_value(struct udevice *dev, int uV) |
| { |
| return palmas_ldo_val(dev, PMIC_OP_SET, &uV); |
| } |
| |
| static int ldo_get_enable(struct udevice *dev) |
| { |
| bool enable = false; |
| int ret; |
| |
| ret = palmas_ldo_enable(dev, PMIC_OP_GET, &enable); |
| if (ret) |
| return ret; |
| |
| return enable; |
| } |
| |
| static int ldo_set_enable(struct udevice *dev, bool enable) |
| { |
| return palmas_ldo_enable(dev, PMIC_OP_SET, &enable); |
| } |
| |
| static int palmas_smps_probe(struct udevice *dev) |
| { |
| struct dm_regulator_uclass_plat *uc_pdata; |
| struct udevice *parent; |
| int idx; |
| |
| uc_pdata = dev_get_uclass_plat(dev); |
| |
| parent = dev_get_parent(dev); |
| int type = dev_get_driver_data(parent); |
| |
| uc_pdata->type = REGULATOR_TYPE_BUCK; |
| |
| switch (type) { |
| case PALMAS: |
| case TPS659038: |
| switch (dev->driver_data) { |
| case 123: |
| case 12: |
| uc_pdata->ctrl_reg = palmas_smps_ctrl[type][0]; |
| uc_pdata->volt_reg = palmas_smps_volt[type][0]; |
| break; |
| case 3: |
| uc_pdata->ctrl_reg = palmas_smps_ctrl[type][1]; |
| uc_pdata->volt_reg = palmas_smps_volt[type][1]; |
| break; |
| case 45: |
| uc_pdata->ctrl_reg = palmas_smps_ctrl[type][2]; |
| uc_pdata->volt_reg = palmas_smps_volt[type][2]; |
| break; |
| case 6: |
| case 7: |
| case 8: |
| case 9: |
| case 10: |
| idx = dev->driver_data - 3; |
| uc_pdata->ctrl_reg = palmas_smps_ctrl[type][idx]; |
| uc_pdata->volt_reg = palmas_smps_volt[type][idx]; |
| break; |
| |
| default: |
| printf("Wrong ID for regulator\n"); |
| } |
| break; |
| |
| case TPS65917: |
| switch (dev->driver_data) { |
| case 1: |
| case 2: |
| case 3: |
| case 4: |
| case 5: |
| idx = dev->driver_data - 1; |
| uc_pdata->ctrl_reg = palmas_smps_ctrl[type][idx]; |
| uc_pdata->volt_reg = palmas_smps_volt[type][idx]; |
| break; |
| case 12: |
| idx = 0; |
| uc_pdata->ctrl_reg = palmas_smps_ctrl[type][idx]; |
| uc_pdata->volt_reg = palmas_smps_volt[type][idx]; |
| break; |
| default: |
| printf("Wrong ID for regulator\n"); |
| } |
| break; |
| |
| default: |
| printf("Invalid PMIC ID\n"); |
| } |
| |
| return 0; |
| } |
| |
| static int smps_get_value(struct udevice *dev) |
| { |
| int uV; |
| int ret; |
| |
| ret = palmas_smps_val(dev, PMIC_OP_GET, &uV); |
| if (ret) |
| return ret; |
| |
| return uV; |
| } |
| |
| static int smps_set_value(struct udevice *dev, int uV) |
| { |
| return palmas_smps_val(dev, PMIC_OP_SET, &uV); |
| } |
| |
| static int smps_get_enable(struct udevice *dev) |
| { |
| bool enable = false; |
| int ret; |
| |
| ret = palmas_smps_enable(dev, PMIC_OP_GET, &enable); |
| if (ret) |
| return ret; |
| |
| return enable; |
| } |
| |
| static int smps_set_enable(struct udevice *dev, bool enable) |
| { |
| return palmas_smps_enable(dev, PMIC_OP_SET, &enable); |
| } |
| |
| static const struct dm_regulator_ops palmas_ldo_ops = { |
| .get_value = ldo_get_value, |
| .set_value = ldo_set_value, |
| .get_enable = ldo_get_enable, |
| .set_enable = ldo_set_enable, |
| }; |
| |
| U_BOOT_DRIVER(palmas_ldo) = { |
| .name = PALMAS_LDO_DRIVER, |
| .id = UCLASS_REGULATOR, |
| .ops = &palmas_ldo_ops, |
| .probe = palmas_ldo_probe, |
| }; |
| |
| static const struct dm_regulator_ops palmas_smps_ops = { |
| .get_value = smps_get_value, |
| .set_value = smps_set_value, |
| .get_enable = smps_get_enable, |
| .set_enable = smps_set_enable, |
| }; |
| |
| U_BOOT_DRIVER(palmas_smps) = { |
| .name = PALMAS_SMPS_DRIVER, |
| .id = UCLASS_REGULATOR, |
| .ops = &palmas_smps_ops, |
| .probe = palmas_smps_probe, |
| }; |