From 511728c3b8f0957fb0e6a3f31b447428c852dbc1 Mon Sep 17 00:00:00 2001 From: Karel Balej Date: Sun, 24 Sep 2023 12:40:26 +0200 Subject: [PATCH] DONOTMERGE: add downstream regulator driver --- drivers/regulator/88pm88x-buck.c | 714 ++++++++++++++++++++++++++++++ drivers/regulator/88pm88x-ldo.c | 726 +++++++++++++++++++++++++++++++ drivers/regulator/88pm88x-vr.c | 666 ++++++++++++++++++++++++++++ drivers/regulator/Kconfig | 10 + drivers/regulator/Makefile | 3 + 5 files changed, 2119 insertions(+) create mode 100644 drivers/regulator/88pm88x-buck.c create mode 100644 drivers/regulator/88pm88x-ldo.c create mode 100644 drivers/regulator/88pm88x-vr.c diff --git a/drivers/regulator/88pm88x-buck.c b/drivers/regulator/88pm88x-buck.c new file mode 100644 index 000000000000..7546e0fb9d87 --- /dev/null +++ b/drivers/regulator/88pm88x-buck.c @@ -0,0 +1,714 @@ +/* + * Buck driver for Marvell 88PM88X + * + * Copyright (C) 2014 Marvell International Ltd. + * Yi Zhang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* max current in sleep */ +#define MAX_SLEEP_CURRENT 5000 + +/* BUCK enable2 register offset relative to enable1 register */ +#define PM88X_BUCK_EN2_OFF (0x06) +/* ------------- 88pm886 buck registers --------------- */ + +/* buck voltage */ +#define PM886_BUCK2_VOUT (0xb3) +#define PM886_BUCK3_VOUT (0xc1) +#define PM886_BUCK4_VOUT (0xcf) +#define PM886_BUCK5_VOUT (0xdd) + +/* set buck sleep voltage */ +#define PM886_BUCK1_SET_SLP (0xa3) +#define PM886_BUCK2_SET_SLP (0xb1) +#define PM886_BUCK3_SET_SLP (0xbf) +#define PM886_BUCK4_SET_SLP (0xcd) +#define PM886_BUCK5_SET_SLP (0xdb) + +/* control section */ +#define PM886_BUCK_EN (0x08) + +/* ------------- 88pm880 buck registers --------------- */ +/* buck voltage */ +#define PM880_BUCK2_VOUT (0x58) +#define PM880_BUCK3_VOUT (0x70) +#define PM880_BUCK4_VOUT (0x88) +#define PM880_BUCK5_VOUT (0x98) +#define PM880_BUCK6_VOUT (0xa8) + +/* set buck sleep voltage */ +#define PM880_BUCK1_SET_SLP (0x27) +#define PM880_BUCK1A_SET_SLP (0x27) +#define PM880_BUCK1B_SET_SLP (0x3c) + +#define PM880_BUCK2_SET_SLP (0x56) +#define PM880_BUCK3_SET_SLP (0x6e) +#define PM880_BUCK4_SET_SLP (0x86) +#define PM880_BUCK5_SET_SLP (0x96) +#define PM880_BUCK6_SET_SLP (0xa6) +#define PM880_BUCK7_SET_SLP (0xb6) + +/* control section */ +#define PM880_BUCK_EN (0x08) + +/* + * vreg - the buck regs string. + * ebit - the bit number in the enable register. + * amax - the current + * Buck has 2 kinds of voltage steps. It is easy to find voltage by ranges, + * not the constant voltage table. + */ +#define PM88X_BUCK(_pmic, vreg, ebit, amax, volt_ranges, n_volt, slp_en_msk, slp_en_off) \ +{ \ + .desc = { \ + .name = #vreg, \ + .ops = &pm88x_volt_buck_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = _pmic##_ID_##vreg, \ + .owner = THIS_MODULE, \ + .n_voltages = n_volt, \ + .linear_ranges = volt_ranges, \ + .n_linear_ranges = ARRAY_SIZE(volt_ranges), \ + .vsel_reg = _pmic##_##vreg##_VOUT, \ + .vsel_mask = 0x7f, \ + .enable_reg = _pmic##_BUCK_EN, \ + .enable_mask = 1 << (ebit), \ + }, \ + .max_ua = (amax), \ + .sleep_vsel_reg = _pmic##_##vreg##_SET_SLP, \ + .sleep_vsel_mask = 0x7f, \ + .sleep_enable_reg = _pmic##_##vreg##_SLP_CTRL, \ + .sleep_enable_mask = (slp_en_msk << slp_en_off), \ + .sleep_enable_off = (slp_en_off), \ +} + +#define PM886_BUCK(vreg, ebit, amax, volt_ranges, n_volt) \ + PM88X_BUCK(PM886, vreg, ebit, amax, volt_ranges, n_volt, 0x3, 4) + +#define PM880_BUCK(vreg, ebit, amax, volt_ranges, n_volt) \ + PM88X_BUCK(PM880, vreg, ebit, amax, volt_ranges, n_volt, 0x3, 4) + +#define PM886_BUCK_AUDIO(vreg, ebit, amax, volt_ranges, n_volt) \ + PM88X_BUCK(PM886, vreg, ebit, amax, volt_ranges, n_volt, 0x1, 7) + +#define PM880_BUCK_AUDIO(vreg, ebit, amax, volt_ranges, n_volt) \ + PM88X_BUCK(PM880, vreg, ebit, amax, volt_ranges, n_volt, 0x1, 7) + +/* + * 88pm886 buck1 and 88pm880 buck1. both have dvc function + * from 0x00 to 0x4F: step is 12.5mV, range is from 0.6V to 1.6V + * from 0x50 to 0x3F step is 50mV, range is from 1.6V to 1.8V + */ +static const struct regulator_linear_range buck_volt_range1[] = { + REGULATOR_LINEAR_RANGE(600000, 0, 0x4f, 12500), + REGULATOR_LINEAR_RANGE(1600000, 0x50, 0x54, 50000), +}; + +/* + * 88pm886 buck 2-5, and 88pm880 buck 2-7 + * from 0x00 to 0x4F VOUT step is 12.5mV, range is from 0.6V to 1.6V + * from 0x50 to 0x72 step is 50mV, range is from 1.6V to 3.3V + */ +static const struct regulator_linear_range buck_volt_range2[] = { + REGULATOR_LINEAR_RANGE(600000, 0, 0x4f, 12500), + REGULATOR_LINEAR_RANGE(1600000, 0x50, 0x72, 50000), +}; + +struct pm88x_buck_info { + struct regulator_desc desc; + int max_ua; + u8 sleep_enable_mask; + u8 sleep_enable_reg; + u8 sleep_enable_off; + u8 sleep_vsel_reg; + u8 sleep_vsel_mask; +}; + +struct pm88x_regulators { + struct regulator_dev *rdev; + struct pm88x_chip *chip; + struct regmap *map; +}; + +struct pm88x_buck_print { + char name[15]; + char enable[15]; + char slp_mode[15]; + char set_slp[15]; + char volt[10]; + char audio_en[15]; + char audio[10]; +}; + +struct pm88x_buck_extra { + const char *name; + bool dvc; + u8 audio_enable_reg; + u8 audio_enable_mask; + u8 audio_enable_off; + u8 audio_vsel_reg; + u8 audio_vsel_mask; +}; + +#define PM88X_BUCK_EXTRA(_name, _dvc, _areg, _amsk, _aoff, _vreg, _vmsk) \ +{ \ + .name = _name, \ + .dvc = _dvc, \ + .audio_enable_reg = _areg, \ + .audio_enable_mask = _amsk, \ + .audio_enable_off = _aoff, \ + .audio_vsel_reg = _vreg, \ + .audio_vsel_mask = _vmsk \ +} + +static struct pm88x_buck_extra pm886_buck_extra_info[] = { + PM88X_BUCK_EXTRA("BUCK1", 1, 0xa4, 0x80, 0x07, 0xa4, 0x7f), + PM88X_BUCK_EXTRA("BUCK2", 0, 0xb2, 0x80, 0x07, 0xb2, 0x7f), + PM88X_BUCK_EXTRA("BUCK3", 0, 0xc0, 0x80, 0x07, 0xc0, 0x7f), + PM88X_BUCK_EXTRA("BUCK4", 0, 0, 0, 0, 0, 0), + PM88X_BUCK_EXTRA("BUCK5", 0, 0, 0, 0, 0, 0), +}; + +static struct pm88x_buck_extra pm880_buck_extra_info[] = { + PM88X_BUCK_EXTRA("BUCK1A", 1, 0x27, 0x80, 0x07, 0x27, 0x7f), + PM88X_BUCK_EXTRA("BUCK2", 0, 0x57, 0x80, 0x07, 0x57, 0x7f), + PM88X_BUCK_EXTRA("BUCK3", 0, 0x6f, 0x80, 0x07, 0x6f, 0x7f), + PM88X_BUCK_EXTRA("BUCK4", 0, 0, 0, 0, 0, 0), + PM88X_BUCK_EXTRA("BUCK5", 0, 0, 0, 0, 0, 0), + PM88X_BUCK_EXTRA("BUCK6", 0, 0, 0, 0, 0, 0), + PM88X_BUCK_EXTRA("BUCK7", 1, 0, 0, 0, 0, 0), +}; + +#define BUCK_OFF (0x0) +#define BUCK_ACTIVE_VOLT_SLP (0x1) +#define BUCK_SLP_VOLT_SLP (0x2) +#define BUCK_ACTIVE_VOLT_ACTIVE (0x3) +#define BUCK_AUDIO_MODE_EN (0x1) + +int pm88x_buck_set_suspend_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct pm88x_buck_info *info = rdev_get_drvdata(rdev); + u8 val; + int ret, rid; + + if (!info) + return -EINVAL; + + rid = rdev_get_id(rdev); + /* we enabled buck1 audio mode enable/disable for 88pm886 and 88pm880 */ + if (rid == PM880_ID_BUCK1A) { + switch (mode) { + case REGULATOR_MODE_NORMAL: + val = 0; + break; + case REGULATOR_MODE_IDLE: + val = BUCK_AUDIO_MODE_EN; + break; + default: + return -EINVAL; + } + } else { + switch (mode) { + case REGULATOR_MODE_NORMAL: + /* regulator will be active with normal voltage */ + val = BUCK_ACTIVE_VOLT_ACTIVE; + break; + case REGULATOR_MODE_IDLE: + /* regulator will be in sleep with sleep voltage */ + val = BUCK_SLP_VOLT_SLP; + break; + case REGULATOR_MODE_STANDBY: + /* regulator will be off */ + val = BUCK_OFF; + break; + default: + /* regulator will be active with sleep voltage */ + val = BUCK_ACTIVE_VOLT_SLP; + break; + } + } + + ret = regmap_update_bits(rdev->regmap, info->sleep_enable_reg, + info->sleep_enable_mask, (val << info->sleep_enable_off)); + return ret; +} + +static unsigned int pm88x_buck_get_optimum_mode(struct regulator_dev *rdev, + int input_uV, int output_uV, + int output_uA) +{ + struct pm88x_buck_info *info = rdev_get_drvdata(rdev); + if (!info) + return REGULATOR_MODE_IDLE; + + if (output_uA < 0) { + dev_err(rdev_get_dev(rdev), "current needs to be > 0.\n"); + return REGULATOR_MODE_IDLE; + } + + return (output_uA < MAX_SLEEP_CURRENT) ? + REGULATOR_MODE_IDLE : REGULATOR_MODE_NORMAL; +} + +static int pm88x_buck_get_current_limit(struct regulator_dev *rdev) +{ + struct pm88x_buck_info *info = rdev_get_drvdata(rdev); + if (!info) + return 0; + return info->max_ua; +} + +static int pm88x_buck_set_suspend_voltage(struct regulator_dev *rdev, int uv) +{ + int ret, sel; + struct pm88x_buck_info *info = rdev_get_drvdata(rdev); + if (!info || !info->desc.ops) + return -EINVAL; + if (!info->desc.ops->set_suspend_mode) + return 0; + /* + * two steps: + * 1) set the suspend voltage to *_set_slp registers + * 2) set regulator mode via set_suspend_mode() interface to enable output + */ + /* the suspend voltage mapping is the same as active */ + sel = regulator_map_voltage_linear_range(rdev, uv, uv); + if (sel < 0) + return -EINVAL; + + sel <<= ffs(info->sleep_vsel_mask) - 1; + + ret = regmap_update_bits(rdev->regmap, info->sleep_vsel_reg, + info->sleep_vsel_mask, sel); + if (ret < 0) + return -EINVAL; + + /* TODO: do we need this? */ + ret = pm88x_buck_set_suspend_mode(rdev, REGULATOR_MODE_IDLE); + return ret; +} + +/* + * about the get_optimum_mode()/set_suspend_mode()/set_suspend_voltage() interface: + * - 88pm88x has two sets of registers to set and enable/disable regulators + * in active and suspend(sleep) status: + * the following focues on the sleep part: + * - there are two control bits: 00-disable, + * 01/10-use sleep voltage, + * 11-use active voltage, + *- in most of the scenario, these registers are configured when the whole PMIC + * initialized, when the system enters into suspend(sleep) mode, the regulator + * works according to the setting or disabled; + *- there is also case that the device driver needs to: + * - set the sleep voltage; + * - choose to use sleep voltage or active voltage depends on the load; + * so: + * set_suspend_voltage() is used to manipulate the registers to set sleep volt; + * set_suspend_mode() is used to switch between sleep voltage and active voltage + * get_optimum_mode() is used to get right mode + */ +static struct regulator_ops pm88x_volt_buck_ops = { + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = regulator_map_voltage_linear_range, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .get_current_limit = pm88x_buck_get_current_limit, + .get_optimum_mode = pm88x_buck_get_optimum_mode, + .set_suspend_mode = pm88x_buck_set_suspend_mode, + .set_suspend_voltage = pm88x_buck_set_suspend_voltage, +}; + +/* The array is indexed by id(PM886_ID_BUCK*) */ +static struct pm88x_buck_info pm886_buck_configs[] = { + PM886_BUCK(BUCK1, 0, 3000000, buck_volt_range1, 0x55), + PM886_BUCK(BUCK2, 1, 1200000, buck_volt_range2, 0x73), + PM886_BUCK(BUCK3, 2, 1200000, buck_volt_range2, 0x73), + PM886_BUCK(BUCK4, 3, 1200000, buck_volt_range2, 0x73), + PM886_BUCK(BUCK5, 4, 1200000, buck_volt_range2, 0x73), +}; + +/* The array is indexed by id(PM880_ID_BUCK*) */ +static struct pm88x_buck_info pm880_buck_configs[] = { + PM880_BUCK_AUDIO(BUCK1A, 0, 3000000, buck_volt_range1, 0x55), + PM880_BUCK(BUCK2, 2, 1200000, buck_volt_range2, 0x73), + PM880_BUCK(BUCK3, 3, 1200000, buck_volt_range2, 0x73), + PM880_BUCK(BUCK4, 4, 1200000, buck_volt_range2, 0x73), + PM880_BUCK(BUCK5, 5, 1200000, buck_volt_range2, 0x73), + PM880_BUCK(BUCK6, 6, 1200000, buck_volt_range2, 0x73), + PM880_BUCK(BUCK7, 7, 1200000, buck_volt_range2, 0x73), +}; + +#define PM88X_BUCK_OF_MATCH(_pmic, id, comp, label) \ + { \ + .compatible = comp, \ + .data = &_pmic##_buck_configs[id##_##label], \ + } +#define PM886_BUCK_OF_MATCH(comp, label) \ + PM88X_BUCK_OF_MATCH(pm886, PM886_ID, comp, label) + +#define PM880_BUCK_OF_MATCH(comp, label) \ + PM88X_BUCK_OF_MATCH(pm880, PM880_ID, comp, label) + +static const struct of_device_id pm88x_bucks_of_match[] = { + PM886_BUCK_OF_MATCH("marvell,88pm886-buck1", BUCK1), + PM886_BUCK_OF_MATCH("marvell,88pm886-buck2", BUCK2), + PM886_BUCK_OF_MATCH("marvell,88pm886-buck3", BUCK3), + PM886_BUCK_OF_MATCH("marvell,88pm886-buck4", BUCK4), + PM886_BUCK_OF_MATCH("marvell,88pm886-buck5", BUCK5), + + PM880_BUCK_OF_MATCH("marvell,88pm880-buck1a", BUCK1A), + PM880_BUCK_OF_MATCH("marvell,88pm880-buck2", BUCK2), + PM880_BUCK_OF_MATCH("marvell,88pm880-buck3", BUCK3), + PM880_BUCK_OF_MATCH("marvell,88pm880-buck4", BUCK4), + PM880_BUCK_OF_MATCH("marvell,88pm880-buck5", BUCK5), + PM880_BUCK_OF_MATCH("marvell,88pm880-buck6", BUCK6), + PM880_BUCK_OF_MATCH("marvell,88pm880-buck7", BUCK7), +}; + +/* + * The function convert the buck voltage register value + * to a real voltage value (in uV) according to the voltage table. + */ +static int pm88x_get_vbuck_vol(unsigned int val, struct pm88x_buck_info *info) +{ + const struct regulator_linear_range *range; + int i, volt = -EINVAL; + + /* get the voltage via the register value */ + for (i = 0; i < info->desc.n_linear_ranges; i++) { + range = &info->desc.linear_ranges[i]; + if (!range) + return -EINVAL; + + if (val >= range->min_sel && val <= range->max_sel) { + volt = (val - range->min_sel) * range->uV_step + range->min_uV; + break; + } + } + return volt; +} + +/* The function check if the regulator register is configured to enable/disable */ +static int pm88x_check_en(struct pm88x_chip *chip, unsigned int reg, unsigned int mask, + unsigned int reg2) +{ + struct regmap *map = chip->buck_regmap; + int ret, value; + unsigned int enable1, enable2; + + ret = regmap_read(map, reg, &enable1); + if (ret < 0) + return ret; + + ret = regmap_read(map, reg2, &enable2); + if (ret < 0) + return ret; + + value = (enable1 | enable2) & mask; + + return value; +} + +/* The function check the regulator sleep mode as configured in his register */ +static int pm88x_check_slp_mode(struct regmap *map, unsigned int reg, int off) +{ + int ret; + unsigned int slp_mode; + + ret = regmap_read(map, reg, &slp_mode); + if (ret < 0) + return ret; + + slp_mode = (slp_mode >> off) & 0x3; + + return slp_mode; +} + +/* The function return the value in the regulator voltage register */ +static unsigned int pm88x_check_vol(struct regmap *map, unsigned int reg, unsigned int mask) +{ + int ret; + unsigned int vol_val; + + ret = regmap_bulk_read(map, reg, &vol_val, 1); + if (ret < 0) + return ret; + + /* mask and shift the relevant value from the register */ + vol_val = (vol_val & mask) >> (ffs(mask) - 1); + + return vol_val; +} + +static int pm88x_update_print(struct pm88x_chip *chip, struct pm88x_buck_info *info, + struct pm88x_buck_extra *extra, struct pm88x_buck_print *print_temp, + int index, int buck_num) +{ + int ret, volt; + struct regmap *map = chip->buck_regmap; + char *slp_mode_str[] = {"off", "active_slp", "sleep", "active"}; + int slp_mode_num = ARRAY_SIZE(slp_mode_str); + + sprintf(print_temp->name, "%s", info[index].desc.name); + + /* check enable/disable */ + ret = pm88x_check_en(chip, info[index].desc.enable_reg, info[index].desc.enable_mask, + info[index].desc.enable_reg + PM88X_BUCK_EN2_OFF); + if (ret < 0) + return ret; + else if (ret) + strcpy(print_temp->enable, "enable"); + else + strcpy(print_temp->enable, "disable"); + + if (!strcmp(print_temp->name, "BUCK1A")) { + sprintf(print_temp->slp_mode, " VR"); + sprintf(print_temp->set_slp, " VR"); + } else { + /* check sleep mode */ + ret = pm88x_check_slp_mode(map, info[index].sleep_enable_reg, + info[index].sleep_enable_off); + if (ret < 0) + return ret; + if (ret < slp_mode_num) + strcpy(print_temp->slp_mode, slp_mode_str[ret]); + else + strcpy(print_temp->slp_mode, "unknown"); + + /* print sleep voltage */ + ret = pm88x_check_vol(map, info[index].sleep_vsel_reg, info[index].sleep_vsel_mask); + if (ret < 0) + return ret; + + volt = pm88x_get_vbuck_vol(ret, &info[index]); + if (volt < 0) + return volt; + else + sprintf(print_temp->set_slp, "%4d", volt/1000); + } + + /* print active voltage(s) */ + if (extra[index].dvc) { + sprintf(print_temp->volt, " DVC "); + } else { + ret = pm88x_check_vol(map, info[index].desc.vsel_reg, + info[index].desc.vsel_mask); + if (ret < 0) + return ret; + + volt = pm88x_get_vbuck_vol(ret, &info[index]); + if (volt < 0) + return volt; + else + sprintf(print_temp->volt, "%4d", volt/1000); + } + + /* print audio voltage */ + if (extra[index].audio_enable_reg) { + ret = pm88x_check_en(chip, extra[index].audio_enable_reg, + extra[index].audio_enable_mask, + extra[index].audio_enable_reg); + if (ret < 0) + return ret; + else if (ret) + strcpy(print_temp->audio_en, "enable"); + else + strcpy(print_temp->audio_en, "disable"); + + ret = pm88x_check_vol(map, extra[index].audio_vsel_reg, + extra[index].audio_vsel_mask); + if (ret < 0) + return ret; + + volt = pm88x_get_vbuck_vol(ret, &info[index]); + if (volt < 0) + return volt; + else + sprintf(print_temp->audio, "%4d", volt/1000); + } else { + strcpy(print_temp->audio_en, " - "); + sprintf(print_temp->audio, " -"); + } + + return 0; +} + +int pm88x_display_buck(struct pm88x_chip *chip, char *buf) +{ + struct pm88x_buck_print *print_temp; + struct pm88x_buck_info *info; + struct pm88x_buck_extra *extra; + int buck_num, i, len = 0; + ssize_t ret; + + switch (chip->type) { + case PM886: + info = pm886_buck_configs; + extra = pm886_buck_extra_info; + buck_num = ARRAY_SIZE(pm886_buck_configs); + break; + case PM880: + info = pm880_buck_configs; + extra = pm880_buck_extra_info; + buck_num = ARRAY_SIZE(pm880_buck_configs); + break; + default: + pr_err("%s: Cannot find chip type.\n", __func__); + return -ENODEV; + } + + print_temp = kmalloc(sizeof(struct pm88x_buck_print), GFP_KERNEL); + if (!print_temp) { + pr_err("%s: Cannot allocate print template.\n", __func__); + return -ENOMEM; + } + + len += sprintf(buf + len, "\nBUCK"); + len += sprintf(buf + len, "\n-----------------------------------"); + len += sprintf(buf + len, "-------------------------------------\n"); + len += sprintf(buf + len, "| name | status | slp_mode |slp_volt"); + len += sprintf(buf + len, "| volt | audio_en| audio |\n"); + len += sprintf(buf + len, "------------------------------------"); + len += sprintf(buf + len, "------------------------------------\n"); + + for (i = 0; i < buck_num; i++) { + ret = pm88x_update_print(chip, info, extra, print_temp, i, buck_num); + if (ret < 0) { + pr_err("Print of regulator %s failed\n", print_temp->name); + goto out_print; + } + len += sprintf(buf + len, "| %-8s |", print_temp->name); + len += sprintf(buf + len, " %-7s |", print_temp->enable); + len += sprintf(buf + len, " %-10s|", print_temp->slp_mode); + len += sprintf(buf + len, " %-5s |", print_temp->set_slp); + len += sprintf(buf + len, " %-5s |", print_temp->volt); + len += sprintf(buf + len, " %-7s |", print_temp->audio_en); + len += sprintf(buf + len, " %-5s |\n", print_temp->audio); + } + + len += sprintf(buf + len, "------------------------------------"); + len += sprintf(buf + len, "------------------------------------\n"); + + ret = len; +out_print: + kfree(print_temp); + return ret; +} + +static int pm88x_buck_probe(struct platform_device *pdev) +{ + struct pm88x_chip *chip = dev_get_drvdata(pdev->dev.parent); + struct pm88x_regulators *data; + struct regulator_config config = { }; + struct regulator_init_data *init_data; + struct regulation_constraints *c; + const struct of_device_id *match; + const struct pm88x_buck_info *const_info; + struct pm88x_buck_info *info; + int ret; + + match = of_match_device(pm88x_bucks_of_match, &pdev->dev); + if (match) { + const_info = match->data; + init_data = of_get_regulator_init_data(&pdev->dev, + pdev->dev.of_node); + } else { + dev_err(&pdev->dev, "parse dts fails!\n"); + return -EINVAL; + } + + info = kmemdup(const_info, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + data = devm_kzalloc(&pdev->dev, sizeof(struct pm88x_regulators), + GFP_KERNEL); + if (!data) { + dev_err(&pdev->dev, "failed to allocate pm88x_regualtors"); + return -ENOMEM; + } + data->map = chip->buck_regmap; + data->chip = chip; + + /* add regulator config */ + config.dev = &pdev->dev; + config.init_data = init_data; + config.driver_data = info; + config.regmap = data->map; + config.of_node = pdev->dev.of_node; + + data->rdev = devm_regulator_register(&pdev->dev, &info->desc, &config); + if (IS_ERR(data->rdev)) { + dev_err(&pdev->dev, "cannot register %s\n", info->desc.name); + ret = PTR_ERR(data->rdev); + return ret; + } + + c = data->rdev->constraints; + c->valid_ops_mask |= REGULATOR_CHANGE_DRMS | REGULATOR_CHANGE_MODE + | REGULATOR_CHANGE_VOLTAGE; + c->valid_modes_mask |= REGULATOR_MODE_NORMAL + | REGULATOR_MODE_IDLE; + c->input_uV = 1000; + + platform_set_drvdata(pdev, data); + + return 0; +} + +static int pm88x_buck_remove(struct platform_device *pdev) +{ + struct pm88x_regulators *data = platform_get_drvdata(pdev); + devm_kfree(&pdev->dev, data); + return 0; +} + +static struct platform_driver pm88x_buck_driver = { + .driver = { + .name = "88pm88x-buck", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(pm88x_bucks_of_match), + }, + .probe = pm88x_buck_probe, + .remove = pm88x_buck_remove, +}; + +static int pm88x_buck_init(void) +{ + return platform_driver_register(&pm88x_buck_driver); +} +subsys_initcall(pm88x_buck_init); + +static void pm88x_buck_exit(void) +{ + platform_driver_unregister(&pm88x_buck_driver); +} +module_exit(pm88x_buck_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Yi Zhang"); +MODULE_DESCRIPTION("Buck for Marvell 88PM88X PMIC"); +MODULE_ALIAS("platform:88pm88x-buck"); diff --git a/drivers/regulator/88pm88x-ldo.c b/drivers/regulator/88pm88x-ldo.c new file mode 100644 index 000000000000..d2bf731e2dcc --- /dev/null +++ b/drivers/regulator/88pm88x-ldo.c @@ -0,0 +1,726 @@ +/* + * LDO driver for Marvell 88PM88X + * + * Copyright (C) 2014 Marvell International Ltd. + * Yi Zhang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* max current in sleep */ +#define MAX_SLEEP_CURRENT 5000 + +/* ------------- 88pm886 ldo registers --------------- */ +/* control section */ +#define PM886_LDO_EN1 (0x09) +#define PM886_LDO_EN2 (0x0a) + +/* LDO enable2 register offset relative to enable1 register */ +#define PM88X_LDO_EN2_OFF (0x06) + +/* + * 88pm886 ldo voltage: + * ldox_set_slp[7: 4] ldox_set [3: 0] + */ +#define PM886_LDO1_VOUT (0x20) +#define PM886_LDO2_VOUT (0x26) +#define PM886_LDO3_VOUT (0x2c) +#define PM886_LDO4_VOUT (0x32) +#define PM886_LDO5_VOUT (0x38) +#define PM886_LDO6_VOUT (0x3e) +#define PM886_LDO7_VOUT (0x44) +#define PM886_LDO8_VOUT (0x4a) +#define PM886_LDO9_VOUT (0x50) +#define PM886_LDO10_VOUT (0x56) +#define PM886_LDO11_VOUT (0x5c) +#define PM886_LDO12_VOUT (0x62) +#define PM886_LDO13_VOUT (0x68) +#define PM886_LDO14_VOUT (0x6e) +#define PM886_LDO15_VOUT (0x74) +#define PM886_LDO16_VOUT (0x7a) + +/* ------------- 88pm880 ldo registers --------------- */ +/* control section */ +#define PM880_LDO_EN1 (0x09) +#define PM880_LDO_EN2 (0x0a) +#define PM880_LDO_EN3 (0x0b) + +/* + * 88pm880 ldo voltage: + * ldox_set_slp[7: 4] ldox_set [3: 0] + */ +#define PM880_LDO1_VOUT (0x20) +#define PM880_LDO2_VOUT (0x26) +#define PM880_LDO3_VOUT (0x2c) +#define PM880_LDO4_VOUT (0x32) +#define PM880_LDO5_VOUT (0x38) +#define PM880_LDO6_VOUT (0x3e) +#define PM880_LDO7_VOUT (0x44) +#define PM880_LDO8_VOUT (0x4a) +#define PM880_LDO9_VOUT (0x50) +#define PM880_LDO10_VOUT (0x56) +#define PM880_LDO11_VOUT (0x5c) +#define PM880_LDO12_VOUT (0x62) +#define PM880_LDO13_VOUT (0x68) +#define PM880_LDO14_VOUT (0x6e) +#define PM880_LDO15_VOUT (0x74) +#define PM880_LDO16_VOUT (0x7a) +#define PM880_LDO17_VOUT (0x80) +#define PM880_LDO18_VOUT (0x86) + +/* + * set ldo sleep voltage register is the same as the active registers; + * only the mask is not the same: + * bit [7 : 4] --> to set sleep voltage + * bit [3 : 0] --> to set active voltage + * no need to give definition here + */ + +/* + * vreg - the LDO regs string + * ebit - the bit number in the enable register. + * ereg - the enable register + * amax - the current + * volt_table - the LDO voltage table + * For all the LDOes, there are too many ranges. Using volt_table will be + * simpler and faster. + */ +#define PM88X_LDO(_pmic, vreg, ereg, ebit, amax, ldo_volt_table) \ +{ \ + .desc = { \ + .name = #vreg, \ + .ops = &pm88x_volt_ldo_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = _pmic##_ID_##vreg, \ + .owner = THIS_MODULE, \ + .n_voltages = ARRAY_SIZE(ldo_volt_table), \ + .vsel_reg = _pmic##_##vreg##_VOUT, \ + .vsel_mask = 0xf, \ + .enable_reg = _pmic##_LDO_##ereg, \ + .enable_mask = 1 << (ebit), \ + .volt_table = ldo_volt_table, \ + }, \ + .max_ua = (amax), \ + .sleep_vsel_reg = _pmic##_##vreg##_VOUT, \ + .sleep_vsel_mask = (0xf << 4), \ + .sleep_enable_reg = _pmic##_##vreg##_SLP_CTRL, \ + .sleep_enable_mask = (0x3 << 4), \ +} + +#define PM886_LDO(vreg, ereg, ebit, amax, ldo_volt_table) \ + PM88X_LDO(PM886, vreg, ereg, ebit, amax, ldo_volt_table) + +#define PM880_LDO(vreg, ereg, ebit, amax, ldo_volt_table) \ + PM88X_LDO(PM880, vreg, ereg, ebit, amax, ldo_volt_table) + +/* 88pm886 ldo1~3, 88pm880 ldo1-3 */ +static const unsigned int ldo_volt_table1[] = { + 1700000, 1800000, 1900000, 2500000, 2800000, 2900000, 3100000, 3300000, +}; +/* 88pm886 ldo4~15, 88pm880 ldo4-17 */ +static const unsigned int ldo_volt_table2[] = { + 1200000, 1250000, 1700000, 1800000, 1850000, 1900000, 2500000, 2600000, + 2700000, 2750000, 2800000, 2850000, 2900000, 3000000, 3100000, 3300000, +}; +/* 88pm886 ldo16, 88pm880 ldo18 */ +static const unsigned int ldo_volt_table3[] = { + 1700000, 1800000, 1900000, 2000000, 2100000, 2500000, 2700000, 2800000, +}; + +struct pm88x_ldo_info { + struct regulator_desc desc; + int max_ua; + u8 sleep_enable_mask; + u8 sleep_enable_reg; + u8 sleep_vsel_reg; + u8 sleep_vsel_mask; +}; + +struct pm88x_ldos { + struct regulator_dev *rdev; + struct pm88x_chip *chip; + struct regmap *map; +}; + +struct pm88x_ldo_print { + char name[15]; + char enable[15]; + char slp_mode[15]; + char set_slp[15]; + char volt[10]; + char audio_en[15]; + char audio[10]; +}; + +struct pm88x_ldo_extra { + const char *name; + u8 audio_enable_reg; + u8 audio_enable_mask; + u8 audio_enable_off; + u8 audio_vsel_reg; + u8 audio_vsel_mask; +}; + +#define PM88X_LDO_EXTRA(_name, _areg, _amsk, _aoff, _vreg, _vmsk) \ +{ \ + .name = _name, \ + .audio_enable_reg = _areg, \ + .audio_enable_mask = _amsk, \ + .audio_enable_off = _aoff, \ + .audio_vsel_reg = _vreg, \ + .audio_vsel_mask = _vmsk \ +} + +static struct pm88x_ldo_extra pm88x_ldo_extra_info[] = { + PM88X_LDO_EXTRA("LDO2", 0x28, 0x10, 0x04, 0x28, 0x0f), +}; + +#define LDO_OFF (0x0 << 4) +#define LDO_ACTIVE_VOLT_SLP (0x1 << 4) +#define LDO_SLP_VOLT_SLP (0x2 << 4) +#define LDO_ACTIVE_VOLT_ACTIVE (0x3 << 4) + +int pm88x_ldo_set_suspend_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct pm88x_ldo_info *info = rdev_get_drvdata(rdev); + u8 val; + int ret; + + if (!info) + return -EINVAL; + + switch (mode) { + case REGULATOR_MODE_NORMAL: + /* regulator will be active with normal voltage */ + val = LDO_ACTIVE_VOLT_ACTIVE; + break; + case REGULATOR_MODE_IDLE: + /* regulator will be in sleep with sleep voltage */ + val = LDO_SLP_VOLT_SLP; + break; + case REGULATOR_MODE_STANDBY: + /* regulator will be off */ + val = LDO_OFF; + break; + default: + /* regulator will be active with sleep voltage */ + val = LDO_ACTIVE_VOLT_SLP; + break; + } + + ret = regmap_update_bits(rdev->regmap, info->sleep_enable_reg, + info->sleep_enable_mask, val); + return ret; +} + +static unsigned int pm88x_ldo_get_optimum_mode(struct regulator_dev *rdev, + int input_uV, int output_uV, + int output_uA) +{ + struct pm88x_ldo_info *info = rdev_get_drvdata(rdev); + if (!info) + return REGULATOR_MODE_IDLE; + + if (output_uA < 0) { + dev_err(rdev_get_dev(rdev), "current needs to be > 0.\n"); + return REGULATOR_MODE_IDLE; + } + + return (output_uA < MAX_SLEEP_CURRENT) ? + REGULATOR_MODE_IDLE : REGULATOR_MODE_NORMAL; +} + +static int pm88x_ldo_get_current_limit(struct regulator_dev *rdev) +{ + struct pm88x_ldo_info *info = rdev_get_drvdata(rdev); + if (!info) + return 0; + return info->max_ua; +} + +static int pm88x_ldo_set_suspend_voltage(struct regulator_dev *rdev, int uv) +{ + int ret, sel; + struct pm88x_ldo_info *info = rdev_get_drvdata(rdev); + if (!info || !info->desc.ops) + return -EINVAL; + if (!info->desc.ops->set_suspend_mode) + return 0; + /* + * two steps: + * 1) set the suspend voltage to *_set_slp registers + * 2) set regulator mode via set_suspend_mode() interface to enable output + */ + /* the suspend voltage mapping is the same as active */ + sel = regulator_map_voltage_iterate(rdev, uv, uv); + if (sel < 0) + return -EINVAL; + + sel <<= ffs(info->sleep_vsel_mask) - 1; + + ret = regmap_update_bits(rdev->regmap, info->sleep_vsel_reg, + info->sleep_vsel_mask, sel); + if (ret < 0) + return -EINVAL; + + /* TODO: do we need this? */ + ret = pm88x_ldo_set_suspend_mode(rdev, REGULATOR_MODE_IDLE); + return ret; +} + +/* + * about the get_optimum_mode()/set_suspend_mode()/set_suspend_voltage() interface: + * - 88pm88x has two sets of registers to set and enable/disable regulators + * in active and suspend(sleep) status: + * the following focues on the sleep part: + * - there are two control bits: 00-disable, + * 01/10-use sleep voltage, + * 11-use active voltage, + *- in most of the scenario, these registers are configured when the whole PMIC + * initialized, when the system enters into suspend(sleep) mode, the regulator + * works according to the setting or disabled; + *- there is also case that the device driver needs to: + * - set the sleep voltage; + * - choose to use sleep voltage or active voltage depends on the load; + * so: + * set_suspend_voltage() is used to manipulate the registers to set sleep volt; + * set_suspend_mode() is used to switch between sleep voltage and active voltage + * get_optimum_mode() is used to get right mode + */ +static struct regulator_ops pm88x_volt_ldo_ops = { + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_iterate, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .get_current_limit = pm88x_ldo_get_current_limit, + .get_optimum_mode = pm88x_ldo_get_optimum_mode, + .set_suspend_mode = pm88x_ldo_set_suspend_mode, + .set_suspend_voltage = pm88x_ldo_set_suspend_voltage, +}; + +/* The array is indexed by id(PM886_ID_LDO*) */ +static struct pm88x_ldo_info pm886_ldo_configs[] = { + /* 88pm886 ldo */ + PM886_LDO(LDO1, EN1, 0, 100000, ldo_volt_table1), + PM886_LDO(LDO2, EN1, 1, 100000, ldo_volt_table1), + PM886_LDO(LDO3, EN1, 2, 100000, ldo_volt_table1), + PM886_LDO(LDO4, EN1, 3, 400000, ldo_volt_table2), + PM886_LDO(LDO5, EN1, 4, 400000, ldo_volt_table2), + PM886_LDO(LDO6, EN1, 5, 400000, ldo_volt_table2), + PM886_LDO(LDO7, EN1, 6, 400000, ldo_volt_table2), + PM886_LDO(LDO8, EN1, 7, 400000, ldo_volt_table2), + PM886_LDO(LDO9, EN2, 0, 400000, ldo_volt_table2), + PM886_LDO(LDO10, EN2, 1, 200000, ldo_volt_table2), + PM886_LDO(LDO11, EN2, 2, 200000, ldo_volt_table2), + PM886_LDO(LDO12, EN2, 3, 200000, ldo_volt_table2), + PM886_LDO(LDO13, EN2, 4, 200000, ldo_volt_table2), + PM886_LDO(LDO14, EN2, 5, 200000, ldo_volt_table2), + PM886_LDO(LDO15, EN2, 6, 200000, ldo_volt_table2), + PM886_LDO(LDO16, EN2, 7, 200000, ldo_volt_table3), +}; + +/* The array is indexed by id(PM880_ID_LDO*) */ +static struct pm88x_ldo_info pm880_ldo_configs[] = { + /* 88pm880 ldo */ + PM880_LDO(LDO1, EN1, 0, 100000, ldo_volt_table1), + PM880_LDO(LDO2, EN1, 1, 100000, ldo_volt_table1), + PM880_LDO(LDO3, EN1, 2, 100000, ldo_volt_table1), + PM880_LDO(LDO4, EN1, 3, 400000, ldo_volt_table2), + PM880_LDO(LDO5, EN1, 4, 400000, ldo_volt_table2), + PM880_LDO(LDO6, EN1, 5, 400000, ldo_volt_table2), + PM880_LDO(LDO7, EN1, 6, 400000, ldo_volt_table2), + PM880_LDO(LDO8, EN1, 7, 400000, ldo_volt_table2), + PM880_LDO(LDO9, EN2, 0, 400000, ldo_volt_table2), + PM880_LDO(LDO10, EN2, 1, 200000, ldo_volt_table2), + PM880_LDO(LDO11, EN2, 2, 200000, ldo_volt_table2), + PM880_LDO(LDO12, EN2, 3, 200000, ldo_volt_table2), + PM880_LDO(LDO13, EN2, 4, 200000, ldo_volt_table2), + PM880_LDO(LDO14, EN2, 5, 200000, ldo_volt_table2), + PM880_LDO(LDO15, EN2, 6, 200000, ldo_volt_table2), + PM880_LDO(LDO16, EN2, 7, 200000, ldo_volt_table2), + PM880_LDO(LDO17, EN3, 0, 200000, ldo_volt_table2), + PM880_LDO(LDO18, EN3, 1, 200000, ldo_volt_table3), +}; + +#define PM88X_LDO_OF_MATCH(_pmic, id, comp, label) \ + { \ + .compatible = comp, \ + .data = &_pmic##_ldo_configs[id##_##label], \ + } + +#define PM886_LDO_OF_MATCH(comp, label) \ + PM88X_LDO_OF_MATCH(pm886, PM886_ID, comp, label) + +#define PM880_LDO_OF_MATCH(comp, label) \ + PM88X_LDO_OF_MATCH(pm880, PM880_ID, comp, label) + +static const struct of_device_id pm88x_ldos_of_match[] = { + /* 88pm886 */ + PM886_LDO_OF_MATCH("marvell,88pm886-ldo1", LDO1), + PM886_LDO_OF_MATCH("marvell,88pm886-ldo2", LDO2), + PM886_LDO_OF_MATCH("marvell,88pm886-ldo3", LDO3), + PM886_LDO_OF_MATCH("marvell,88pm886-ldo4", LDO4), + PM886_LDO_OF_MATCH("marvell,88pm886-ldo5", LDO5), + PM886_LDO_OF_MATCH("marvell,88pm886-ldo6", LDO6), + PM886_LDO_OF_MATCH("marvell,88pm886-ldo7", LDO7), + PM886_LDO_OF_MATCH("marvell,88pm886-ldo8", LDO8), + PM886_LDO_OF_MATCH("marvell,88pm886-ldo9", LDO9), + PM886_LDO_OF_MATCH("marvell,88pm886-ldo10", LDO10), + PM886_LDO_OF_MATCH("marvell,88pm886-ldo11", LDO11), + PM886_LDO_OF_MATCH("marvell,88pm886-ldo12", LDO12), + PM886_LDO_OF_MATCH("marvell,88pm886-ldo13", LDO13), + PM886_LDO_OF_MATCH("marvell,88pm886-ldo14", LDO14), + PM886_LDO_OF_MATCH("marvell,88pm886-ldo15", LDO15), + PM886_LDO_OF_MATCH("marvell,88pm886-ldo16", LDO16), + /* 88pm880 */ + PM880_LDO_OF_MATCH("marvell,88pm880-ldo1", LDO1), + PM880_LDO_OF_MATCH("marvell,88pm880-ldo2", LDO2), + PM880_LDO_OF_MATCH("marvell,88pm880-ldo3", LDO3), + PM880_LDO_OF_MATCH("marvell,88pm880-ldo4", LDO4), + PM880_LDO_OF_MATCH("marvell,88pm880-ldo5", LDO5), + PM880_LDO_OF_MATCH("marvell,88pm880-ldo6", LDO6), + PM880_LDO_OF_MATCH("marvell,88pm880-ldo7", LDO7), + PM880_LDO_OF_MATCH("marvell,88pm880-ldo8", LDO8), + PM880_LDO_OF_MATCH("marvell,88pm880-ldo9", LDO9), + PM880_LDO_OF_MATCH("marvell,88pm880-ldo10", LDO10), + PM880_LDO_OF_MATCH("marvell,88pm880-ldo11", LDO11), + PM880_LDO_OF_MATCH("marvell,88pm880-ldo12", LDO12), + PM880_LDO_OF_MATCH("marvell,88pm880-ldo13", LDO13), + PM880_LDO_OF_MATCH("marvell,88pm880-ldo14", LDO14), + PM880_LDO_OF_MATCH("marvell,88pm880-ldo15", LDO15), + PM880_LDO_OF_MATCH("marvell,88pm880-ldo16", LDO16), + PM880_LDO_OF_MATCH("marvell,88pm880-ldo17", LDO17), + PM880_LDO_OF_MATCH("marvell,88pm880-ldo18", LDO18), +}; + +/* + * The function convert the ldo voltage register value + * to a real voltage value (in uV) according to the voltage table. + */ +static int pm88x_get_vldo_vol(unsigned int val, struct pm88x_ldo_info *info) +{ + int volt = -EINVAL; + + /* get the voltage via the register value */ + volt = info->desc.volt_table[val]; + return volt; +} + +/* The function check if the regulator register is configured to enable/disable */ +static int pm88x_check_en(struct pm88x_chip *chip, unsigned int reg, unsigned int mask, + unsigned int reg2) +{ + struct regmap *map = chip->ldo_regmap; + int ret, value; + unsigned int enable1, enable2; + + ret = regmap_read(map, reg, &enable1); + if (ret < 0) + return ret; + + ret = regmap_read(map, reg2, &enable2); + if (ret < 0) + return ret; + + value = (enable1 | enable2) & mask; + + return value; +} + +/* The function check the regulator sleep mode as configured in his register */ +static int pm88x_check_slp_mode(struct regmap *map, unsigned int reg, int mask) +{ + int ret; + unsigned int slp_mode; + + ret = regmap_read(map, reg, &slp_mode); + if (ret < 0) + return ret; + + slp_mode = (slp_mode & mask) >> (ffs(mask) - 1); + + return slp_mode; +} + +/* The function return the value in the regulator voltage register */ +static unsigned int pm88x_check_vol(struct regmap *map, unsigned int reg, unsigned int mask) +{ + int ret; + unsigned int vol_val; + + ret = regmap_bulk_read(map, reg, &vol_val, 1); + if (ret < 0) + return ret; + + /* mask and shift the relevant value from the register */ + vol_val = (vol_val & mask) >> (ffs(mask) - 1); + + return vol_val; +} + +static int pm88x_update_print(struct pm88x_chip *chip, struct pm88x_ldo_info *info, + struct pm88x_ldo_extra *extra, struct pm88x_ldo_print *print_temp, + int index, int ldo_num, int extra_info_num) +{ + int i, ret, volt, flag = 0; + struct regmap *map = chip->ldo_regmap; + char *slp_mode_str[] = {"off", "active_slp", "sleep", "active"}; + int slp_mode_num = sizeof(slp_mode_str)/sizeof(slp_mode_str[0]); + + sprintf(print_temp->name, "%s", info[index].desc.name); + + /* check enable/disable */ + ret = pm88x_check_en(chip, info[index].desc.enable_reg, info[index].desc.enable_mask, + info[index].desc.enable_reg + PM88X_LDO_EN2_OFF); + if (ret < 0) + return ret; + else if (ret) + strcpy(print_temp->enable, "enable"); + else + strcpy(print_temp->enable, "disable"); + + /* check sleep mode */ + ret = pm88x_check_slp_mode(map, info[index].sleep_enable_reg, + info[index].sleep_enable_mask); + if (ret < 0) + return ret; + if (ret < slp_mode_num) + strcpy(print_temp->slp_mode, slp_mode_str[ret]); + else + strcpy(print_temp->slp_mode, "unknown"); + + /* print sleep voltage */ + ret = pm88x_check_vol(map, info[index].sleep_vsel_reg, info[index].sleep_vsel_mask); + if (ret < 0) + return ret; + + volt = pm88x_get_vldo_vol(ret, &info[index]); + if (volt < 0) + return volt; + else + sprintf(print_temp->set_slp, "%4d", volt/1000); + + /* print active voltage(s) */ + ret = pm88x_check_vol(map, info[index].desc.vsel_reg, + info[index].desc.vsel_mask); + if (ret < 0) + return ret; + + volt = pm88x_get_vldo_vol(ret, &info[index]); + if (volt < 0) + return volt; + else + sprintf(print_temp->volt, "%4d", volt/1000); + + for (i = 0; i < extra_info_num; i++) { + /* print audio voltage */ + if (!strcmp(print_temp->name, extra[i].name) && extra[i].audio_enable_reg) { + ret = pm88x_check_en(chip, extra[i].audio_enable_reg, + extra[i].audio_enable_mask, + extra[i].audio_enable_reg); + if (ret < 0) + return ret; + else if (ret) + strcpy(print_temp->audio_en, "enable"); + else + strcpy(print_temp->audio_en, "disable"); + ret = pm88x_check_vol(map, extra[i].audio_vsel_reg, + extra[i].audio_vsel_mask); + if (ret < 0) + return ret; + + volt = pm88x_get_vldo_vol(ret, &info[index]); + if (volt < 0) + return volt; + else + sprintf(print_temp->audio, "%4d", volt/1000); + flag = 1; + } + } + if (!flag) { + strcpy(print_temp->audio_en, " - "); + sprintf(print_temp->audio, " -"); + flag = 0; + } + return 0; +} + +int pm88x_display_ldo(struct pm88x_chip *chip, char *buf) +{ + struct pm88x_ldo_print *print_temp; + struct pm88x_ldo_info *info; + struct pm88x_ldo_extra *extra; + int ldo_num, extra_info_num, i, len = 0; + ssize_t ret; + + switch (chip->type) { + case PM886: + info = pm886_ldo_configs; + ldo_num = sizeof(pm886_ldo_configs) / sizeof(pm886_ldo_configs[0]); + extra = pm88x_ldo_extra_info; + extra_info_num = sizeof(pm88x_ldo_extra_info) / sizeof(pm88x_ldo_extra_info[0]); + break; + case PM880: + info = pm880_ldo_configs; + ldo_num = sizeof(pm880_ldo_configs) / sizeof(pm880_ldo_configs[0]); + extra = pm88x_ldo_extra_info; + extra_info_num = sizeof(pm88x_ldo_extra_info) / sizeof(pm88x_ldo_extra_info[0]); + break; + default: + pr_err("%s: Cannot find chip type.\n", __func__); + return -ENODEV; + } + + print_temp = kmalloc(sizeof(struct pm88x_ldo_print), GFP_KERNEL); + if (!print_temp) { + pr_err("%s: Cannot allocate print template.\n", __func__); + return -ENOMEM; + } + + len += sprintf(buf + len, "\nLDO"); + len += sprintf(buf + len, "\n-----------------------------------"); + len += sprintf(buf + len, "-------------------------------------\n"); + len += sprintf(buf + len, "| name | status | slp_mode |slp_volt"); + len += sprintf(buf + len, "| volt | audio_en| audio |\n"); + len += sprintf(buf + len, "------------------------------------"); + len += sprintf(buf + len, "------------------------------------\n"); + + for (i = 0; i < ldo_num; i++) { + ret = pm88x_update_print(chip, info, extra, print_temp, i, ldo_num, extra_info_num); + if (ret < 0) { + pr_err("Print of regulator %s failed\n", print_temp->name); + goto out_print; + } + len += sprintf(buf + len, "| %-8s |", print_temp->name); + len += sprintf(buf + len, " %-7s |", print_temp->enable); + len += sprintf(buf + len, " %-10s|", print_temp->slp_mode); + len += sprintf(buf + len, " %-5s |", print_temp->set_slp); + len += sprintf(buf + len, " %-5s |", print_temp->volt); + len += sprintf(buf + len, " %-7s |", print_temp->audio_en); + len += sprintf(buf + len, " %-5s |\n", print_temp->audio); + } + + len += sprintf(buf + len, "------------------------------------"); + len += sprintf(buf + len, "------------------------------------\n"); + + ret = len; +out_print: + kfree(print_temp); + return ret; +} + +static int pm88x_ldo_probe(struct platform_device *pdev) +{ + struct pm88x_chip *chip = dev_get_drvdata(pdev->dev.parent); + struct pm88x_ldos *data; + struct regulator_config config = { }; + struct regulator_init_data *init_data; + struct regulation_constraints *c; + const struct of_device_id *match; + const struct pm88x_ldo_info *const_info; + struct pm88x_ldo_info *info; + int ret; + + match = of_match_device(pm88x_ldos_of_match, &pdev->dev); + if (match) { + const_info = match->data; + init_data = of_get_regulator_init_data(&pdev->dev, + pdev->dev.of_node); + } else { + dev_err(&pdev->dev, "parse dts fails!\n"); + return -EINVAL; + } + + info = kmemdup(const_info, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + data = devm_kzalloc(&pdev->dev, sizeof(struct pm88x_ldos), + GFP_KERNEL); + if (!data) { + dev_err(&pdev->dev, "failed to allocate pm88x_regualtors"); + return -ENOMEM; + } + data->map = chip->ldo_regmap; + data->chip = chip; + + /* add regulator config */ + config.dev = &pdev->dev; + config.init_data = init_data; + config.driver_data = info; + config.regmap = data->map; + config.of_node = pdev->dev.of_node; + + data->rdev = devm_regulator_register(&pdev->dev, &info->desc, &config); + if (IS_ERR(data->rdev)) { + dev_err(&pdev->dev, "cannot register %s\n", info->desc.name); + ret = PTR_ERR(data->rdev); + return ret; + } + + c = data->rdev->constraints; + c->valid_ops_mask |= REGULATOR_CHANGE_DRMS | REGULATOR_CHANGE_MODE + | REGULATOR_CHANGE_VOLTAGE; + c->valid_modes_mask |= REGULATOR_MODE_NORMAL + | REGULATOR_MODE_IDLE; + c->input_uV = 1000; + + platform_set_drvdata(pdev, data); + + return 0; +} + +static int pm88x_ldo_remove(struct platform_device *pdev) +{ + struct pm88x_ldos *data = platform_get_drvdata(pdev); + devm_kfree(&pdev->dev, data); + return 0; +} + +static struct platform_driver pm88x_ldo_driver = { + .driver = { + .name = "88pm88x-ldo", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(pm88x_ldos_of_match), + }, + .probe = pm88x_ldo_probe, + .remove = pm88x_ldo_remove, +}; + +static int pm88x_ldo_init(void) +{ + return platform_driver_register(&pm88x_ldo_driver); +} +subsys_initcall(pm88x_ldo_init); + +static void pm88x_ldo_exit(void) +{ + platform_driver_unregister(&pm88x_ldo_driver); +} +module_exit(pm88x_ldo_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Yi Zhang"); +MODULE_DESCRIPTION("LDO Driver for Marvell 88PM88X PMIC"); +MODULE_ALIAS("platform:88pm88x-ldo"); diff --git a/drivers/regulator/88pm88x-vr.c b/drivers/regulator/88pm88x-vr.c new file mode 100644 index 000000000000..2175859b8727 --- /dev/null +++ b/drivers/regulator/88pm88x-vr.c @@ -0,0 +1,666 @@ +/* + * virtual regulator driver for Marvell 88PM88X + * + * Copyright (C) 2015 Marvell International Ltd. + * Yi Zhang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PM88X_VR_EN (0x28) + +#define PM886_BUCK1_SLP_VOUT (0xa3) +#define PM886_BUCK1_SLP_EN (0xa2) + +#define PM880_BUCK1A_SLP_VOUT (0x26) +#define PM880_BUCK1A_SLP_EN (0x24) +#define PM880_BUCK1B_SLP_VOUT (0x3e) +#define PM880_BUCK1B_SLP_EN (0x3c) + +#define PM880_BUCK1A_AUDIO_VOUT (0x27) +#define PM880_BUCK1A_AUDIO_EN (0x27) +#define PM880_BUCK1B_AUDIO_EN (0x3f) +#define PM880_BUCK1B_AUDIO_VOUT (0x3f) + +#define PM88X_VR(vreg, ebit, nr) \ +{ \ + .desc = { \ + .name = #vreg, \ + .ops = &pm88x_virtual_regulator_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = PM88X##_ID_##vreg, \ + .owner = THIS_MODULE, \ + .enable_reg = PM88X##_VR_EN, \ + .enable_mask = 1 << (ebit), \ + }, \ + .page_nr = nr, \ +} + +#define PM88X_BUCK_SLP(_pmic, vreg, ebit, nr, volt_ranges, n_volt) \ +{ \ + .desc = { \ + .name = #vreg, \ + .ops = &pm88x_buck_slp_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = _pmic##_ID_##vreg, \ + .owner = THIS_MODULE, \ + .n_voltages = n_volt, \ + .linear_ranges = volt_ranges, \ + .n_linear_ranges = ARRAY_SIZE(volt_ranges), \ + .vsel_reg = _pmic##_##vreg##_VOUT, \ + .vsel_mask = 0x7f, \ + .enable_reg = _pmic##_##vreg##_EN, \ + .enable_mask = (0x3) << (ebit), \ + }, \ + .page_nr = nr, \ +} + +#define PM88X_BUCK_AUDIO(_pmic, vreg, ebit, nr, volt_ranges, n_volt) \ +{ \ + .desc = { \ + .name = #vreg, \ + .ops = &pm88x_buck_audio_ops, \ + .type = REGULATOR_VOLTAGE, \ + .id = _pmic##_ID_##vreg, \ + .owner = THIS_MODULE, \ + .n_voltages = n_volt, \ + .linear_ranges = volt_ranges, \ + .n_linear_ranges = ARRAY_SIZE(volt_ranges), \ + .vsel_reg = _pmic##_##vreg##_VOUT, \ + .vsel_mask = 0x7f, \ + .enable_reg = _pmic##_##vreg##_EN, \ + .enable_mask = (0x1) << (ebit), \ + }, \ + .page_nr = nr, \ +} + +#define PM880_BUCK_SLP(vreg, ebit, nr, volt_ranges, n_volt) \ + PM88X_BUCK_SLP(PM880, vreg, ebit, nr, volt_ranges, n_volt) + +#define PM886_BUCK_SLP(vreg, ebit, nr, volt_ranges, n_volt) \ + PM88X_BUCK_SLP(PM886, vreg, ebit, nr, volt_ranges, n_volt) + +#define PM880_BUCK_AUDIO(vreg, ebit, nr, volt_ranges, n_volt) \ + PM88X_BUCK_AUDIO(PM880, vreg, ebit, nr, volt_ranges, n_volt) + +#define PM88X_VR_OF_MATCH(comp, label) \ + { \ + .compatible = comp, \ + .data = &pm88x_vr_configs[PM88X_ID##_##label], \ + } + +#define PM88X_BUCK_SLP_OF_MATCH(_pmic, id, comp, label) \ + { \ + .compatible = comp, \ + .data = &_pmic##_buck_slp_configs[id##_##label], \ + } + +#define PM88X_BUCK_AUDIO_OF_MATCH(_pmic, id, comp, label) \ + { \ + .compatible = comp, \ + .data = &_pmic##_buck_audio_configs[id##_##label], \ + } + +#define PM880_BUCK_SLP_OF_MATCH(comp, label) \ + PM88X_BUCK_SLP_OF_MATCH(pm880, PM880_ID, comp, label) + +#define PM886_BUCK_SLP_OF_MATCH(comp, label) \ + PM88X_BUCK_SLP_OF_MATCH(pm886, PM886_ID, comp, label) + +#define PM880_BUCK_AUDIO_OF_MATCH(comp, label) \ + PM88X_BUCK_AUDIO_OF_MATCH(pm880, PM880_ID, comp, label) + +static const struct regulator_linear_range buck_slp_volt_range1[] = { + REGULATOR_LINEAR_RANGE(600000, 0, 0x4f, 12500), + REGULATOR_LINEAR_RANGE(1600000, 0x50, 0x54, 50000), +}; + +static const struct regulator_linear_range buck_audio_volt_range1[] = { + REGULATOR_LINEAR_RANGE(600000, 0, 0x54, 12500), +}; + +struct pm88x_vr_info { + struct regulator_desc desc; + unsigned int page_nr; +}; + +struct pm88x_buck_slp_info { + struct regulator_desc desc; + unsigned int page_nr; +}; + +struct pm88x_buck_audio_info { + struct regulator_desc desc; + unsigned int page_nr; +}; + +struct pm88x_regulators { + struct regulator_dev *rdev; + struct pm88x_chip *chip; + struct regmap *map; +}; + +struct pm88x_vr_print { + char name[15]; + char enable[15]; + char slp_mode[15]; + char set_slp[15]; + char volt[10]; + char audio_en[15]; + char audio[10]; +}; + +static struct regulator_ops pm88x_virtual_regulator_ops = { + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, +}; + +static int pm88x_buck_slp_enable(struct regulator_dev *rdev) +{ + return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + rdev->desc->enable_mask, 0x2); +} + +static int pm88x_buck_slp_disable(struct regulator_dev *rdev) +{ + dev_info(&rdev->dev, "%s: this buck is _off_ in suspend.\n", __func__); + return regmap_update_bits(rdev->regmap, rdev->desc->enable_reg, + rdev->desc->enable_mask, 0x0); +} + +static int pm88x_buck_slp_is_enabled(struct regulator_dev *rdev) +{ + unsigned int val; + int ret; + + ret = regmap_read(rdev->regmap, rdev->desc->enable_reg, &val); + if (ret != 0) + return ret; + + return !!((val & rdev->desc->enable_mask) == (0x2 << 5)); +} + +static int pm88x_buck_slp_map_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV) +{ + /* use higest voltage */ + if (max_uV >= 1600000) + return 0x50 + (max_uV - 1600000) / 50000; + else + return (max_uV - 600000) / 12500; + + return -EINVAL; +} + +static struct regulator_ops pm88x_buck_slp_ops = { + .enable = pm88x_buck_slp_enable, + .disable = pm88x_buck_slp_disable, + .is_enabled = pm88x_buck_slp_is_enabled, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear_range, + .map_voltage = pm88x_buck_slp_map_voltage, +}; + +static int pm88x_buck_audio_enable(struct regulator_dev *rdev) +{ + dev_info(&rdev->dev, "%s is empty.\n", __func__); + return 0; +} + +static int pm88x_buck_audio_disable(struct regulator_dev *rdev) +{ + dev_info(&rdev->dev, "%s is empty.\n", __func__); + return 0; +} + +static int pm88x_buck_audio_is_enabled(struct regulator_dev *rdev) +{ + dev_info(&rdev->dev, "%s is empty.\n", __func__); + return 0; +} + +static struct regulator_ops pm88x_buck_audio_ops = { + .enable = pm88x_buck_audio_enable, + .disable = pm88x_buck_audio_disable, + .is_enabled = pm88x_buck_audio_is_enabled, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .list_voltage = regulator_list_voltage_linear_range, +}; + +static struct pm88x_vr_info pm88x_vr_configs[] = { + PM88X_VR(VOTG, 7, 3), +}; + +static struct pm88x_buck_slp_info pm880_buck_slp_configs[] = { + PM880_BUCK_SLP(BUCK1A_SLP, 4, 4, buck_slp_volt_range1, 0x55), + PM880_BUCK_SLP(BUCK1B_SLP, 4, 4, buck_slp_volt_range1, 0x55), +}; + +static struct pm88x_buck_slp_info pm886_buck_slp_configs[] = { + PM886_BUCK_SLP(BUCK1_SLP, 4, 1, buck_slp_volt_range1, 0x55), +}; + +static struct pm88x_buck_audio_info pm880_buck_audio_configs[] = { + PM880_BUCK_AUDIO(BUCK1A_AUDIO, 7, 4, buck_audio_volt_range1, 0x50), + PM880_BUCK_AUDIO(BUCK1B_AUDIO, 7, 4, buck_audio_volt_range1, 0x50), +}; + +static const struct of_device_id pm88x_vrs_of_match[] = { + PM88X_VR_OF_MATCH("marvell,88pm88x-votg", VOTG), + + PM886_BUCK_SLP_OF_MATCH("marvell,88pm886-buck1-slp", BUCK1_SLP), + + PM880_BUCK_SLP_OF_MATCH("marvell,88pm880-buck1a-slp", BUCK1A_SLP), + PM880_BUCK_SLP_OF_MATCH("marvell,88pm880-buck1b-slp", BUCK1B_SLP), + + PM880_BUCK_AUDIO_OF_MATCH("marvell,88pm880-buck1a-audio", BUCK1A_AUDIO), + PM880_BUCK_AUDIO_OF_MATCH("marvell,88pm880-buck1b-audio", BUCK1B_AUDIO), +}; + +static struct regmap *nr_to_regmap(struct pm88x_chip *chip, unsigned int nr) +{ + switch (nr) { + case 0: + return chip->base_regmap; + case 1: + return chip->ldo_regmap; + case 2: + return chip->gpadc_regmap; + case 3: + return chip->battery_regmap; + case 4: + return chip->buck_regmap; + case 7: + return chip->test_regmap; + default: + pr_err("unsupported pages.\n"); + return NULL; + } +} + +static int of_get_legacy_init_data(struct device *dev, + struct regulator_init_data **init_data) +{ + struct device_node *node = dev->of_node; + struct regulator_consumer_supply *consumer_supplies; + int len, num, ret, i; + + len = of_property_count_strings(node, "marvell,consumer-supplies"); + if (len <= 0) + return 0; + + /* format: marvell,consumer-supplies = "supply-name", "dev-name" */ + if (len % 2 != 0) { + dev_err(dev, + "format should be: marvell,consumer-supplies = supply-name, dev-name\n"); + + return -EINVAL; + } + + num = len / 2; + consumer_supplies = devm_kzalloc(dev, + sizeof(struct regulator_consumer_supply) * num, + GFP_KERNEL); + if (!consumer_supplies) { + dev_err(dev, "alloc memory fails.\n"); + return -ENOMEM; + } + + for (i = 0; i < num; i++) { + ret = of_property_read_string_index(node, + "marvell,consumer-supplies", + i * 2, + &consumer_supplies[i].supply); + if (ret) { + dev_err(dev, "read property fails.\n"); + devm_kfree(dev, consumer_supplies); + return ret; + } + + ret = of_property_read_string_index(node, + "marvell,consumer-supplies", + i * 2 + 1, + &consumer_supplies[i].dev_name); + if (ret) { + dev_err(dev, "read property fails.\n"); + devm_kfree(dev, consumer_supplies); + return ret; + } + + if (!strcmp((consumer_supplies[i].dev_name), "nameless")) + consumer_supplies[i].dev_name = NULL; + + } + + (*init_data)->consumer_supplies = consumer_supplies; + (*init_data)->num_consumer_supplies = num; + + return 0; +} + +/* + * The function convert the vr voltage register value + * to a real voltage value (in uV) according to the voltage table. + */ +static int pm88x_get_vvr_vol(unsigned int val, unsigned int n_linear_ranges, + const struct regulator_linear_range *ranges) +{ + const struct regulator_linear_range *range; + int i, volt = -EINVAL; + + /* get the voltage via the register value */ + for (i = 0; i < n_linear_ranges; i++) { + range = &ranges[i]; + if (!range) + return -EINVAL; + + if (val >= range->min_sel && val <= range->max_sel) { + volt = (val - range->min_sel) * range->uV_step + range->min_uV; + break; + } + } + return volt; +} + +/* The function check if the regulator register is configured to enable/disable */ +static int pm88x_check_en(struct regmap *map, unsigned int reg, unsigned int mask) +{ + int ret, value; + unsigned int enable1; + + ret = regmap_read(map, reg, &enable1); + if (ret < 0) + return ret; + + value = enable1 & mask; + + return value; +} + +/* The function return the value in the regulator voltage register */ +static unsigned int pm88x_check_vol(struct regmap *map, unsigned int reg, unsigned int mask) +{ + int ret; + unsigned int vol_val; + + ret = regmap_bulk_read(map, reg, &vol_val, 1); + if (ret < 0) + return ret; + + /* mask and shift the relevant value from the register */ + vol_val = (vol_val & mask) >> (ffs(mask) - 1); + + return vol_val; +} + +static int pm88x_update_print(struct pm88x_chip *chip, struct regmap *map, + struct regulator_desc *desc, struct pm88x_vr_print *print_temp, + int index, int num) +{ + int ret, volt; + + sprintf(print_temp->name, "%s", desc->name); + + /* check enable/disable */ + ret = pm88x_check_en(map, desc->enable_reg, desc->enable_mask); + if (ret < 0) + return ret; + else if (ret) + strcpy(print_temp->enable, "enable"); + else + strcpy(print_temp->enable, "disable"); + + /* no sleep mode */ + strcpy(print_temp->slp_mode, " -"); + + /* no sleep voltage */ + sprintf(print_temp->set_slp, " -"); + + /* print active voltage(s) */ + ret = pm88x_check_vol(map, desc->vsel_reg, desc->vsel_mask); + if (ret < 0) + return ret; + + if (desc->n_linear_ranges) { + volt = pm88x_get_vvr_vol(ret, desc->n_linear_ranges, desc->linear_ranges); + if (volt < 0) + return volt; + else + sprintf(print_temp->volt, "%4d", volt/1000); + } else { + sprintf(print_temp->volt, " -"); + } + /* no audio mode*/ + strcpy(print_temp->audio_en, " - "); + sprintf(print_temp->audio, " -"); + + return 0; +} + +int pm88x_display_vr(struct pm88x_chip *chip, char *buf) +{ + struct pm88x_vr_print *print_temp; + struct pm88x_buck_slp_info *slp_info = NULL; + struct pm88x_buck_audio_info *audio_info = NULL; + struct pm88x_vr_info *vr_info = NULL; + struct regmap *map; + int slp_num, audio_num, vr_num, i, len = 0; + ssize_t ret; + + switch (chip->type) { + case PM886: + slp_info = pm886_buck_slp_configs; + slp_num = ARRAY_SIZE(pm886_buck_slp_configs); + vr_info = pm88x_vr_configs; + vr_num = ARRAY_SIZE(pm88x_vr_configs); + break; + case PM880: + slp_info = pm880_buck_slp_configs; + slp_num = ARRAY_SIZE(pm880_buck_slp_configs); + audio_info = pm880_buck_audio_configs; + audio_num = ARRAY_SIZE(pm880_buck_audio_configs); + vr_info = pm88x_vr_configs; + vr_num = ARRAY_SIZE(pm88x_vr_configs); + break; + default: + pr_err("%s: Cannot find chip type.\n", __func__); + return -ENODEV; + } + + print_temp = kmalloc(sizeof(struct pm88x_vr_print), GFP_KERNEL); + if (!print_temp) { + pr_err("%s: Cannot allocate print template.\n", __func__); + return -ENOMEM; + } + + len += sprintf(buf + len, "\nVirtual Regulator"); + len += sprintf(buf + len, "\n------------------------------------"); + len += sprintf(buf + len, "--------------------------------------\n"); + len += sprintf(buf + len, "| name | status | slp_mode |slp_volt"); + len += sprintf(buf + len, "| volt | audio_en| audio |\n"); + len += sprintf(buf + len, "-------------------------------------"); + len += sprintf(buf + len, "-------------------------------------\n"); + + if (slp_info) { + map = nr_to_regmap(chip, slp_info->page_nr); + for (i = 0; i < slp_num; i++) { + ret = pm88x_update_print(chip, map, &slp_info[i].desc, + print_temp, i, slp_num); + if (ret < 0) { + pr_err("Print of regulator %s failed\n", print_temp->name); + goto out_print; + } + len += sprintf(buf + len, "|%-12s|", print_temp->name); + len += sprintf(buf + len, " %-7s |", print_temp->enable); + len += sprintf(buf + len, " %-10s|", print_temp->slp_mode); + len += sprintf(buf + len, " %-5s |", print_temp->set_slp); + len += sprintf(buf + len, " %-5s |", print_temp->volt); + len += sprintf(buf + len, " %-7s |", print_temp->audio_en); + len += sprintf(buf + len, " %-5s |\n", print_temp->audio); + } + } + + if (audio_info) { + map = nr_to_regmap(chip, audio_info->page_nr); + for (i = 0; i < audio_num; i++) { + ret = pm88x_update_print(chip, map, &audio_info[i].desc, + print_temp, i, audio_num); + if (ret < 0) { + pr_err("Print of regulator %s failed\n", print_temp->name); + goto out_print; + } + len += sprintf(buf + len, "|%-12s|", print_temp->name); + len += sprintf(buf + len, " %-7s |", print_temp->enable); + len += sprintf(buf + len, " %-10s|", print_temp->slp_mode); + len += sprintf(buf + len, " %-5s |", print_temp->set_slp); + len += sprintf(buf + len, " %-5s |", print_temp->volt); + len += sprintf(buf + len, " %-7s |", print_temp->audio_en); + len += sprintf(buf + len, " %-5s |\n", print_temp->audio); + } + } + + if (vr_info) { + map = nr_to_regmap(chip, vr_info->page_nr); + for (i = 0; i < vr_num; i++) { + ret = pm88x_update_print(chip, map, &vr_info[i].desc, + print_temp, i, vr_num); + if (ret < 0) { + pr_err("Print of regulator %s failed\n", print_temp->name); + goto out_print; + } + len += sprintf(buf + len, "|%-12s|", print_temp->name); + len += sprintf(buf + len, " %-7s |", print_temp->enable); + len += sprintf(buf + len, " %-10s|", print_temp->slp_mode); + len += sprintf(buf + len, " %-5s |", print_temp->set_slp); + len += sprintf(buf + len, " %-5s |", print_temp->volt); + len += sprintf(buf + len, " %-7s |", print_temp->audio_en); + len += sprintf(buf + len, " %-5s |\n", print_temp->audio); + } + } + + len += sprintf(buf + len, "-------------------------------------"); + len += sprintf(buf + len, "-------------------------------------\n"); + + ret = len; +out_print: + kfree(print_temp); + return ret; +} + +static int pm88x_virtual_regulator_probe(struct platform_device *pdev) +{ + struct pm88x_chip *chip = dev_get_drvdata(pdev->dev.parent); + struct pm88x_regulators *data; + struct regulator_config config = { }; + struct regulator_init_data *init_data; + struct regulation_constraints *c; + const struct of_device_id *match; + const struct pm88x_vr_info *const_info; + struct pm88x_vr_info *info; + int ret; + + match = of_match_device(pm88x_vrs_of_match, &pdev->dev); + if (match) { + const_info = match->data; + init_data = of_get_regulator_init_data(&pdev->dev, + pdev->dev.of_node); + ret = of_get_legacy_init_data(&pdev->dev, &init_data); + if (ret < 0) + return ret; + } else { + dev_err(&pdev->dev, "parse dts fails!\n"); + return -EINVAL; + } + + info = kmemdup(const_info, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + data = devm_kzalloc(&pdev->dev, sizeof(struct pm88x_regulators), + GFP_KERNEL); + if (!data) { + dev_err(&pdev->dev, "failed to allocate pm88x_regualtors"); + return -ENOMEM; + } + data->map = nr_to_regmap(chip, info->page_nr); + data->chip = chip; + + /* add regulator config */ + config.dev = &pdev->dev; + config.init_data = init_data; + config.driver_data = info; + config.regmap = data->map; + config.of_node = pdev->dev.of_node; + + data->rdev = devm_regulator_register(&pdev->dev, &info->desc, &config); + if (IS_ERR(data->rdev)) { + dev_err(&pdev->dev, "cannot register %s\n", info->desc.name); + ret = PTR_ERR(data->rdev); + return ret; + } + + c = data->rdev->constraints; + if (info->desc.ops->enable) + c->valid_ops_mask |= REGULATOR_CHANGE_STATUS; + if (info->desc.ops->set_voltage_sel) + c->valid_ops_mask |= REGULATOR_CHANGE_VOLTAGE; + + platform_set_drvdata(pdev, data); + + return 0; +} + +static int pm88x_virtual_regulator_remove(struct platform_device *pdev) +{ + struct pm88x_regulators *data = platform_get_drvdata(pdev); + devm_kfree(&pdev->dev, data); + return 0; +} + +static struct platform_driver pm88x_virtual_regulator_driver = { + .driver = { + .name = "88pm88x-virtual-regulator", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(pm88x_vrs_of_match), + }, + .probe = pm88x_virtual_regulator_probe, + .remove = pm88x_virtual_regulator_remove, +}; + +static int pm88x_virtual_regulator_init(void) +{ + return platform_driver_register(&pm88x_virtual_regulator_driver); +} +subsys_initcall(pm88x_virtual_regulator_init); + +static void pm88x_virtual_regulator_exit(void) +{ + platform_driver_unregister(&pm88x_virtual_regulator_driver); +} +module_exit(pm88x_virtual_regulator_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Yi Zhang "); +MODULE_DESCRIPTION("for virtual supply in Marvell 88PM88X PMIC"); +MODULE_ALIAS("platform:88pm88x-vr"); diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 965d4f0c18a6..484baabf3c41 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -1663,4 +1663,14 @@ config REGULATOR_QCOM_LABIBB boost regulator and IBB can be used as a negative boost regulator for LCD display panel. +config REGULATOR_88PM88X + tristate "Marvell 88PM88X Power regulators" + depends on MFD_88PM88X + depends on I2C + help + This driver supports Marvell 88PM88X voltage regulator chips. + It delivers digitally programmable output, the voltage is + programmed via I2C interface. + It has 5 bucks and 16 ldos. Buck1 has DVC feature + endif diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 23074714a81a..8a1e6f454760 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -63,6 +63,9 @@ obj-$(CONFIG_REGULATOR_LP873X) += lp873x-regulator.o obj-$(CONFIG_REGULATOR_LP87565) += lp87565-regulator.o obj-$(CONFIG_REGULATOR_LP8788) += lp8788-buck.o obj-$(CONFIG_REGULATOR_LP8788) += lp8788-ldo.o +obj-$(CONFIG_REGULATOR_88PM88X) += 88pm88x-ldo.o +obj-$(CONFIG_REGULATOR_88PM88X) += 88pm88x-buck.o +obj-$(CONFIG_REGULATOR_88PM88X) += 88pm88x-vr.o obj-$(CONFIG_REGULATOR_LP8755) += lp8755.o obj-$(CONFIG_REGULATOR_LTC3589) += ltc3589.o obj-$(CONFIG_REGULATOR_LTC3676) += ltc3676.o -- 2.42.0