mirror of
https://github.com/torvalds/linux.git
synced 2024-12-15 07:33:56 +00:00
f7684f5a04
By default the PCA9450 doesn't handle the assertion of the WDOG_B signal, but this is required to guarantee that things like software resets triggered by the watchdog work reliably. As we don't want to rely on the bootloader to enable this, we tell the PMIC to issue a cold reset in case the WDOG_B signal is asserted (WDOG_B_CFG = 10), just as the NXP U-Boot code does. Signed-off-by: Frieder Schrempf <frieder.schrempf@kontron.de> Link: https://lore.kernel.org/r/20210211105534.38972-3-frieder.schrempf@kontron.de Signed-off-by: Mark Brown <broonie@kernel.org>
856 lines
23 KiB
C
856 lines
23 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright 2020 NXP.
|
|
* NXP PCA9450 pmic driver
|
|
*/
|
|
|
|
#include <linux/err.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/regulator/driver.h>
|
|
#include <linux/regulator/machine.h>
|
|
#include <linux/regulator/of_regulator.h>
|
|
#include <linux/regulator/pca9450.h>
|
|
|
|
struct pc9450_dvs_config {
|
|
unsigned int run_reg; /* dvs0 */
|
|
unsigned int run_mask;
|
|
unsigned int standby_reg; /* dvs1 */
|
|
unsigned int standby_mask;
|
|
};
|
|
|
|
struct pca9450_regulator_desc {
|
|
struct regulator_desc desc;
|
|
const struct pc9450_dvs_config dvs;
|
|
};
|
|
|
|
struct pca9450 {
|
|
struct device *dev;
|
|
struct regmap *regmap;
|
|
struct gpio_desc *sd_vsel_gpio;
|
|
enum pca9450_chip_type type;
|
|
unsigned int rcnt;
|
|
int irq;
|
|
};
|
|
|
|
static const struct regmap_range pca9450_status_range = {
|
|
.range_min = PCA9450_REG_INT1,
|
|
.range_max = PCA9450_REG_PWRON_STAT,
|
|
};
|
|
|
|
static const struct regmap_access_table pca9450_volatile_regs = {
|
|
.yes_ranges = &pca9450_status_range,
|
|
.n_yes_ranges = 1,
|
|
};
|
|
|
|
static const struct regmap_config pca9450_regmap_config = {
|
|
.reg_bits = 8,
|
|
.val_bits = 8,
|
|
.volatile_table = &pca9450_volatile_regs,
|
|
.max_register = PCA9450_MAX_REGISTER - 1,
|
|
.cache_type = REGCACHE_RBTREE,
|
|
};
|
|
|
|
/*
|
|
* BUCK1/2/3
|
|
* BUCK1RAM[1:0] BUCK1 DVS ramp rate setting
|
|
* 00: 25mV/1usec
|
|
* 01: 25mV/2usec
|
|
* 10: 25mV/4usec
|
|
* 11: 25mV/8usec
|
|
*/
|
|
static int pca9450_dvs_set_ramp_delay(struct regulator_dev *rdev,
|
|
int ramp_delay)
|
|
{
|
|
int id = rdev_get_id(rdev);
|
|
unsigned int ramp_value;
|
|
|
|
switch (ramp_delay) {
|
|
case 1 ... 3125:
|
|
ramp_value = BUCK1_RAMP_3P125MV;
|
|
break;
|
|
case 3126 ... 6250:
|
|
ramp_value = BUCK1_RAMP_6P25MV;
|
|
break;
|
|
case 6251 ... 12500:
|
|
ramp_value = BUCK1_RAMP_12P5MV;
|
|
break;
|
|
case 12501 ... 25000:
|
|
ramp_value = BUCK1_RAMP_25MV;
|
|
break;
|
|
default:
|
|
ramp_value = BUCK1_RAMP_25MV;
|
|
}
|
|
|
|
return regmap_update_bits(rdev->regmap, PCA9450_REG_BUCK1CTRL + id * 3,
|
|
BUCK1_RAMP_MASK, ramp_value << 6);
|
|
}
|
|
|
|
static const struct regulator_ops pca9450_dvs_buck_regulator_ops = {
|
|
.enable = regulator_enable_regmap,
|
|
.disable = regulator_disable_regmap,
|
|
.is_enabled = regulator_is_enabled_regmap,
|
|
.list_voltage = regulator_list_voltage_linear_range,
|
|
.set_voltage_sel = regulator_set_voltage_sel_regmap,
|
|
.get_voltage_sel = regulator_get_voltage_sel_regmap,
|
|
.set_voltage_time_sel = regulator_set_voltage_time_sel,
|
|
.set_ramp_delay = pca9450_dvs_set_ramp_delay,
|
|
};
|
|
|
|
static const struct regulator_ops pca9450_buck_regulator_ops = {
|
|
.enable = regulator_enable_regmap,
|
|
.disable = regulator_disable_regmap,
|
|
.is_enabled = regulator_is_enabled_regmap,
|
|
.list_voltage = regulator_list_voltage_linear_range,
|
|
.set_voltage_sel = regulator_set_voltage_sel_regmap,
|
|
.get_voltage_sel = regulator_get_voltage_sel_regmap,
|
|
.set_voltage_time_sel = regulator_set_voltage_time_sel,
|
|
};
|
|
|
|
static const struct regulator_ops pca9450_ldo_regulator_ops = {
|
|
.enable = regulator_enable_regmap,
|
|
.disable = regulator_disable_regmap,
|
|
.is_enabled = regulator_is_enabled_regmap,
|
|
.list_voltage = regulator_list_voltage_linear_range,
|
|
.set_voltage_sel = regulator_set_voltage_sel_regmap,
|
|
.get_voltage_sel = regulator_get_voltage_sel_regmap,
|
|
};
|
|
|
|
/*
|
|
* BUCK1/2/3
|
|
* 0.60 to 2.1875V (12.5mV step)
|
|
*/
|
|
static const struct linear_range pca9450_dvs_buck_volts[] = {
|
|
REGULATOR_LINEAR_RANGE(600000, 0x00, 0x7F, 12500),
|
|
};
|
|
|
|
/*
|
|
* BUCK4/5/6
|
|
* 0.6V to 3.4V (25mV step)
|
|
*/
|
|
static const struct linear_range pca9450_buck_volts[] = {
|
|
REGULATOR_LINEAR_RANGE(600000, 0x00, 0x70, 25000),
|
|
REGULATOR_LINEAR_RANGE(3400000, 0x71, 0x7F, 0),
|
|
};
|
|
|
|
/*
|
|
* LDO1
|
|
* 1.6 to 3.3V ()
|
|
*/
|
|
static const struct linear_range pca9450_ldo1_volts[] = {
|
|
REGULATOR_LINEAR_RANGE(1600000, 0x00, 0x03, 100000),
|
|
REGULATOR_LINEAR_RANGE(3000000, 0x04, 0x07, 100000),
|
|
};
|
|
|
|
/*
|
|
* LDO2
|
|
* 0.8 to 1.15V (50mV step)
|
|
*/
|
|
static const struct linear_range pca9450_ldo2_volts[] = {
|
|
REGULATOR_LINEAR_RANGE(800000, 0x00, 0x07, 50000),
|
|
};
|
|
|
|
/*
|
|
* LDO3/4
|
|
* 0.8 to 3.3V (100mV step)
|
|
*/
|
|
static const struct linear_range pca9450_ldo34_volts[] = {
|
|
REGULATOR_LINEAR_RANGE(800000, 0x00, 0x19, 100000),
|
|
REGULATOR_LINEAR_RANGE(3300000, 0x1A, 0x1F, 0),
|
|
};
|
|
|
|
/*
|
|
* LDO5
|
|
* 1.8 to 3.3V (100mV step)
|
|
*/
|
|
static const struct linear_range pca9450_ldo5_volts[] = {
|
|
REGULATOR_LINEAR_RANGE(1800000, 0x00, 0x0F, 100000),
|
|
};
|
|
|
|
static int buck_set_dvs(const struct regulator_desc *desc,
|
|
struct device_node *np, struct regmap *regmap,
|
|
char *prop, unsigned int reg, unsigned int mask)
|
|
{
|
|
int ret, i;
|
|
uint32_t uv;
|
|
|
|
ret = of_property_read_u32(np, prop, &uv);
|
|
if (ret == -EINVAL)
|
|
return 0;
|
|
else if (ret)
|
|
return ret;
|
|
|
|
for (i = 0; i < desc->n_voltages; i++) {
|
|
ret = regulator_desc_list_voltage_linear_range(desc, i);
|
|
if (ret < 0)
|
|
continue;
|
|
if (ret == uv) {
|
|
i <<= ffs(desc->vsel_mask) - 1;
|
|
ret = regmap_update_bits(regmap, reg, mask, i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int pca9450_set_dvs_levels(struct device_node *np,
|
|
const struct regulator_desc *desc,
|
|
struct regulator_config *cfg)
|
|
{
|
|
struct pca9450_regulator_desc *data = container_of(desc,
|
|
struct pca9450_regulator_desc, desc);
|
|
const struct pc9450_dvs_config *dvs = &data->dvs;
|
|
unsigned int reg, mask;
|
|
char *prop;
|
|
int i, ret = 0;
|
|
|
|
for (i = 0; i < PCA9450_DVS_LEVEL_MAX; i++) {
|
|
switch (i) {
|
|
case PCA9450_DVS_LEVEL_RUN:
|
|
prop = "nxp,dvs-run-voltage";
|
|
reg = dvs->run_reg;
|
|
mask = dvs->run_mask;
|
|
break;
|
|
case PCA9450_DVS_LEVEL_STANDBY:
|
|
prop = "nxp,dvs-standby-voltage";
|
|
reg = dvs->standby_reg;
|
|
mask = dvs->standby_mask;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = buck_set_dvs(desc, np, cfg->regmap, prop, reg, mask);
|
|
if (ret)
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct pca9450_regulator_desc pca9450a_regulators[] = {
|
|
{
|
|
.desc = {
|
|
.name = "buck1",
|
|
.of_match = of_match_ptr("BUCK1"),
|
|
.regulators_node = of_match_ptr("regulators"),
|
|
.id = PCA9450_BUCK1,
|
|
.ops = &pca9450_dvs_buck_regulator_ops,
|
|
.type = REGULATOR_VOLTAGE,
|
|
.n_voltages = PCA9450_BUCK1_VOLTAGE_NUM,
|
|
.linear_ranges = pca9450_dvs_buck_volts,
|
|
.n_linear_ranges = ARRAY_SIZE(pca9450_dvs_buck_volts),
|
|
.vsel_reg = PCA9450_REG_BUCK1OUT_DVS0,
|
|
.vsel_mask = BUCK1OUT_DVS0_MASK,
|
|
.enable_reg = PCA9450_REG_BUCK1CTRL,
|
|
.enable_mask = BUCK1_ENMODE_MASK,
|
|
.owner = THIS_MODULE,
|
|
.of_parse_cb = pca9450_set_dvs_levels,
|
|
},
|
|
.dvs = {
|
|
.run_reg = PCA9450_REG_BUCK1OUT_DVS0,
|
|
.run_mask = BUCK1OUT_DVS0_MASK,
|
|
.standby_reg = PCA9450_REG_BUCK1OUT_DVS1,
|
|
.standby_mask = BUCK1OUT_DVS1_MASK,
|
|
},
|
|
},
|
|
{
|
|
.desc = {
|
|
.name = "buck2",
|
|
.of_match = of_match_ptr("BUCK2"),
|
|
.regulators_node = of_match_ptr("regulators"),
|
|
.id = PCA9450_BUCK2,
|
|
.ops = &pca9450_dvs_buck_regulator_ops,
|
|
.type = REGULATOR_VOLTAGE,
|
|
.n_voltages = PCA9450_BUCK2_VOLTAGE_NUM,
|
|
.linear_ranges = pca9450_dvs_buck_volts,
|
|
.n_linear_ranges = ARRAY_SIZE(pca9450_dvs_buck_volts),
|
|
.vsel_reg = PCA9450_REG_BUCK2OUT_DVS0,
|
|
.vsel_mask = BUCK2OUT_DVS0_MASK,
|
|
.enable_reg = PCA9450_REG_BUCK2CTRL,
|
|
.enable_mask = BUCK1_ENMODE_MASK,
|
|
.owner = THIS_MODULE,
|
|
.of_parse_cb = pca9450_set_dvs_levels,
|
|
},
|
|
.dvs = {
|
|
.run_reg = PCA9450_REG_BUCK2OUT_DVS0,
|
|
.run_mask = BUCK2OUT_DVS0_MASK,
|
|
.standby_reg = PCA9450_REG_BUCK2OUT_DVS1,
|
|
.standby_mask = BUCK2OUT_DVS1_MASK,
|
|
},
|
|
},
|
|
{
|
|
.desc = {
|
|
.name = "buck3",
|
|
.of_match = of_match_ptr("BUCK3"),
|
|
.regulators_node = of_match_ptr("regulators"),
|
|
.id = PCA9450_BUCK3,
|
|
.ops = &pca9450_dvs_buck_regulator_ops,
|
|
.type = REGULATOR_VOLTAGE,
|
|
.n_voltages = PCA9450_BUCK3_VOLTAGE_NUM,
|
|
.linear_ranges = pca9450_dvs_buck_volts,
|
|
.n_linear_ranges = ARRAY_SIZE(pca9450_dvs_buck_volts),
|
|
.vsel_reg = PCA9450_REG_BUCK3OUT_DVS0,
|
|
.vsel_mask = BUCK3OUT_DVS0_MASK,
|
|
.enable_reg = PCA9450_REG_BUCK3CTRL,
|
|
.enable_mask = BUCK3_ENMODE_MASK,
|
|
.owner = THIS_MODULE,
|
|
.of_parse_cb = pca9450_set_dvs_levels,
|
|
},
|
|
.dvs = {
|
|
.run_reg = PCA9450_REG_BUCK3OUT_DVS0,
|
|
.run_mask = BUCK3OUT_DVS0_MASK,
|
|
.standby_reg = PCA9450_REG_BUCK3OUT_DVS1,
|
|
.standby_mask = BUCK3OUT_DVS1_MASK,
|
|
},
|
|
},
|
|
{
|
|
.desc = {
|
|
.name = "buck4",
|
|
.of_match = of_match_ptr("BUCK4"),
|
|
.regulators_node = of_match_ptr("regulators"),
|
|
.id = PCA9450_BUCK4,
|
|
.ops = &pca9450_buck_regulator_ops,
|
|
.type = REGULATOR_VOLTAGE,
|
|
.n_voltages = PCA9450_BUCK4_VOLTAGE_NUM,
|
|
.linear_ranges = pca9450_buck_volts,
|
|
.n_linear_ranges = ARRAY_SIZE(pca9450_buck_volts),
|
|
.vsel_reg = PCA9450_REG_BUCK4OUT,
|
|
.vsel_mask = BUCK4OUT_MASK,
|
|
.enable_reg = PCA9450_REG_BUCK4CTRL,
|
|
.enable_mask = BUCK4_ENMODE_MASK,
|
|
.owner = THIS_MODULE,
|
|
},
|
|
},
|
|
{
|
|
.desc = {
|
|
.name = "buck5",
|
|
.of_match = of_match_ptr("BUCK5"),
|
|
.regulators_node = of_match_ptr("regulators"),
|
|
.id = PCA9450_BUCK5,
|
|
.ops = &pca9450_buck_regulator_ops,
|
|
.type = REGULATOR_VOLTAGE,
|
|
.n_voltages = PCA9450_BUCK5_VOLTAGE_NUM,
|
|
.linear_ranges = pca9450_buck_volts,
|
|
.n_linear_ranges = ARRAY_SIZE(pca9450_buck_volts),
|
|
.vsel_reg = PCA9450_REG_BUCK5OUT,
|
|
.vsel_mask = BUCK5OUT_MASK,
|
|
.enable_reg = PCA9450_REG_BUCK5CTRL,
|
|
.enable_mask = BUCK5_ENMODE_MASK,
|
|
.owner = THIS_MODULE,
|
|
},
|
|
},
|
|
{
|
|
.desc = {
|
|
.name = "buck6",
|
|
.of_match = of_match_ptr("BUCK6"),
|
|
.regulators_node = of_match_ptr("regulators"),
|
|
.id = PCA9450_BUCK6,
|
|
.ops = &pca9450_buck_regulator_ops,
|
|
.type = REGULATOR_VOLTAGE,
|
|
.n_voltages = PCA9450_BUCK6_VOLTAGE_NUM,
|
|
.linear_ranges = pca9450_buck_volts,
|
|
.n_linear_ranges = ARRAY_SIZE(pca9450_buck_volts),
|
|
.vsel_reg = PCA9450_REG_BUCK6OUT,
|
|
.vsel_mask = BUCK6OUT_MASK,
|
|
.enable_reg = PCA9450_REG_BUCK6CTRL,
|
|
.enable_mask = BUCK6_ENMODE_MASK,
|
|
.owner = THIS_MODULE,
|
|
},
|
|
},
|
|
{
|
|
.desc = {
|
|
.name = "ldo1",
|
|
.of_match = of_match_ptr("LDO1"),
|
|
.regulators_node = of_match_ptr("regulators"),
|
|
.id = PCA9450_LDO1,
|
|
.ops = &pca9450_ldo_regulator_ops,
|
|
.type = REGULATOR_VOLTAGE,
|
|
.n_voltages = PCA9450_LDO1_VOLTAGE_NUM,
|
|
.linear_ranges = pca9450_ldo1_volts,
|
|
.n_linear_ranges = ARRAY_SIZE(pca9450_ldo1_volts),
|
|
.vsel_reg = PCA9450_REG_LDO1CTRL,
|
|
.vsel_mask = LDO1OUT_MASK,
|
|
.enable_reg = PCA9450_REG_LDO1CTRL,
|
|
.enable_mask = LDO1_EN_MASK,
|
|
.owner = THIS_MODULE,
|
|
},
|
|
},
|
|
{
|
|
.desc = {
|
|
.name = "ldo2",
|
|
.of_match = of_match_ptr("LDO2"),
|
|
.regulators_node = of_match_ptr("regulators"),
|
|
.id = PCA9450_LDO2,
|
|
.ops = &pca9450_ldo_regulator_ops,
|
|
.type = REGULATOR_VOLTAGE,
|
|
.n_voltages = PCA9450_LDO2_VOLTAGE_NUM,
|
|
.linear_ranges = pca9450_ldo2_volts,
|
|
.n_linear_ranges = ARRAY_SIZE(pca9450_ldo2_volts),
|
|
.vsel_reg = PCA9450_REG_LDO2CTRL,
|
|
.vsel_mask = LDO2OUT_MASK,
|
|
.enable_reg = PCA9450_REG_LDO2CTRL,
|
|
.enable_mask = LDO2_EN_MASK,
|
|
.owner = THIS_MODULE,
|
|
},
|
|
},
|
|
{
|
|
.desc = {
|
|
.name = "ldo3",
|
|
.of_match = of_match_ptr("LDO3"),
|
|
.regulators_node = of_match_ptr("regulators"),
|
|
.id = PCA9450_LDO3,
|
|
.ops = &pca9450_ldo_regulator_ops,
|
|
.type = REGULATOR_VOLTAGE,
|
|
.n_voltages = PCA9450_LDO3_VOLTAGE_NUM,
|
|
.linear_ranges = pca9450_ldo34_volts,
|
|
.n_linear_ranges = ARRAY_SIZE(pca9450_ldo34_volts),
|
|
.vsel_reg = PCA9450_REG_LDO3CTRL,
|
|
.vsel_mask = LDO3OUT_MASK,
|
|
.enable_reg = PCA9450_REG_LDO3CTRL,
|
|
.enable_mask = LDO3_EN_MASK,
|
|
.owner = THIS_MODULE,
|
|
},
|
|
},
|
|
{
|
|
.desc = {
|
|
.name = "ldo4",
|
|
.of_match = of_match_ptr("LDO4"),
|
|
.regulators_node = of_match_ptr("regulators"),
|
|
.id = PCA9450_LDO4,
|
|
.ops = &pca9450_ldo_regulator_ops,
|
|
.type = REGULATOR_VOLTAGE,
|
|
.n_voltages = PCA9450_LDO4_VOLTAGE_NUM,
|
|
.linear_ranges = pca9450_ldo34_volts,
|
|
.n_linear_ranges = ARRAY_SIZE(pca9450_ldo34_volts),
|
|
.vsel_reg = PCA9450_REG_LDO4CTRL,
|
|
.vsel_mask = LDO4OUT_MASK,
|
|
.enable_reg = PCA9450_REG_LDO4CTRL,
|
|
.enable_mask = LDO4_EN_MASK,
|
|
.owner = THIS_MODULE,
|
|
},
|
|
},
|
|
{
|
|
.desc = {
|
|
.name = "ldo5",
|
|
.of_match = of_match_ptr("LDO5"),
|
|
.regulators_node = of_match_ptr("regulators"),
|
|
.id = PCA9450_LDO5,
|
|
.ops = &pca9450_ldo_regulator_ops,
|
|
.type = REGULATOR_VOLTAGE,
|
|
.n_voltages = PCA9450_LDO5_VOLTAGE_NUM,
|
|
.linear_ranges = pca9450_ldo5_volts,
|
|
.n_linear_ranges = ARRAY_SIZE(pca9450_ldo5_volts),
|
|
.vsel_reg = PCA9450_REG_LDO5CTRL_H,
|
|
.vsel_mask = LDO5HOUT_MASK,
|
|
.enable_reg = PCA9450_REG_LDO5CTRL_H,
|
|
.enable_mask = LDO5H_EN_MASK,
|
|
.owner = THIS_MODULE,
|
|
},
|
|
},
|
|
};
|
|
|
|
/*
|
|
* Buck3 removed on PCA9450B and connected with Buck1 internal for dual phase
|
|
* on PCA9450C as no Buck3.
|
|
*/
|
|
static const struct pca9450_regulator_desc pca9450bc_regulators[] = {
|
|
{
|
|
.desc = {
|
|
.name = "buck1",
|
|
.of_match = of_match_ptr("BUCK1"),
|
|
.regulators_node = of_match_ptr("regulators"),
|
|
.id = PCA9450_BUCK1,
|
|
.ops = &pca9450_dvs_buck_regulator_ops,
|
|
.type = REGULATOR_VOLTAGE,
|
|
.n_voltages = PCA9450_BUCK1_VOLTAGE_NUM,
|
|
.linear_ranges = pca9450_dvs_buck_volts,
|
|
.n_linear_ranges = ARRAY_SIZE(pca9450_dvs_buck_volts),
|
|
.vsel_reg = PCA9450_REG_BUCK1OUT_DVS0,
|
|
.vsel_mask = BUCK1OUT_DVS0_MASK,
|
|
.enable_reg = PCA9450_REG_BUCK1CTRL,
|
|
.enable_mask = BUCK1_ENMODE_MASK,
|
|
.owner = THIS_MODULE,
|
|
.of_parse_cb = pca9450_set_dvs_levels,
|
|
},
|
|
.dvs = {
|
|
.run_reg = PCA9450_REG_BUCK1OUT_DVS0,
|
|
.run_mask = BUCK1OUT_DVS0_MASK,
|
|
.standby_reg = PCA9450_REG_BUCK1OUT_DVS1,
|
|
.standby_mask = BUCK1OUT_DVS1_MASK,
|
|
},
|
|
},
|
|
{
|
|
.desc = {
|
|
.name = "buck2",
|
|
.of_match = of_match_ptr("BUCK2"),
|
|
.regulators_node = of_match_ptr("regulators"),
|
|
.id = PCA9450_BUCK2,
|
|
.ops = &pca9450_dvs_buck_regulator_ops,
|
|
.type = REGULATOR_VOLTAGE,
|
|
.n_voltages = PCA9450_BUCK2_VOLTAGE_NUM,
|
|
.linear_ranges = pca9450_dvs_buck_volts,
|
|
.n_linear_ranges = ARRAY_SIZE(pca9450_dvs_buck_volts),
|
|
.vsel_reg = PCA9450_REG_BUCK2OUT_DVS0,
|
|
.vsel_mask = BUCK2OUT_DVS0_MASK,
|
|
.enable_reg = PCA9450_REG_BUCK2CTRL,
|
|
.enable_mask = BUCK1_ENMODE_MASK,
|
|
.owner = THIS_MODULE,
|
|
.of_parse_cb = pca9450_set_dvs_levels,
|
|
},
|
|
.dvs = {
|
|
.run_reg = PCA9450_REG_BUCK2OUT_DVS0,
|
|
.run_mask = BUCK2OUT_DVS0_MASK,
|
|
.standby_reg = PCA9450_REG_BUCK2OUT_DVS1,
|
|
.standby_mask = BUCK2OUT_DVS1_MASK,
|
|
},
|
|
},
|
|
{
|
|
.desc = {
|
|
.name = "buck4",
|
|
.of_match = of_match_ptr("BUCK4"),
|
|
.regulators_node = of_match_ptr("regulators"),
|
|
.id = PCA9450_BUCK4,
|
|
.ops = &pca9450_buck_regulator_ops,
|
|
.type = REGULATOR_VOLTAGE,
|
|
.n_voltages = PCA9450_BUCK4_VOLTAGE_NUM,
|
|
.linear_ranges = pca9450_buck_volts,
|
|
.n_linear_ranges = ARRAY_SIZE(pca9450_buck_volts),
|
|
.vsel_reg = PCA9450_REG_BUCK4OUT,
|
|
.vsel_mask = BUCK4OUT_MASK,
|
|
.enable_reg = PCA9450_REG_BUCK4CTRL,
|
|
.enable_mask = BUCK4_ENMODE_MASK,
|
|
.owner = THIS_MODULE,
|
|
},
|
|
},
|
|
{
|
|
.desc = {
|
|
.name = "buck5",
|
|
.of_match = of_match_ptr("BUCK5"),
|
|
.regulators_node = of_match_ptr("regulators"),
|
|
.id = PCA9450_BUCK5,
|
|
.ops = &pca9450_buck_regulator_ops,
|
|
.type = REGULATOR_VOLTAGE,
|
|
.n_voltages = PCA9450_BUCK5_VOLTAGE_NUM,
|
|
.linear_ranges = pca9450_buck_volts,
|
|
.n_linear_ranges = ARRAY_SIZE(pca9450_buck_volts),
|
|
.vsel_reg = PCA9450_REG_BUCK5OUT,
|
|
.vsel_mask = BUCK5OUT_MASK,
|
|
.enable_reg = PCA9450_REG_BUCK5CTRL,
|
|
.enable_mask = BUCK5_ENMODE_MASK,
|
|
.owner = THIS_MODULE,
|
|
},
|
|
},
|
|
{
|
|
.desc = {
|
|
.name = "buck6",
|
|
.of_match = of_match_ptr("BUCK6"),
|
|
.regulators_node = of_match_ptr("regulators"),
|
|
.id = PCA9450_BUCK6,
|
|
.ops = &pca9450_buck_regulator_ops,
|
|
.type = REGULATOR_VOLTAGE,
|
|
.n_voltages = PCA9450_BUCK6_VOLTAGE_NUM,
|
|
.linear_ranges = pca9450_buck_volts,
|
|
.n_linear_ranges = ARRAY_SIZE(pca9450_buck_volts),
|
|
.vsel_reg = PCA9450_REG_BUCK6OUT,
|
|
.vsel_mask = BUCK6OUT_MASK,
|
|
.enable_reg = PCA9450_REG_BUCK6CTRL,
|
|
.enable_mask = BUCK6_ENMODE_MASK,
|
|
.owner = THIS_MODULE,
|
|
},
|
|
},
|
|
{
|
|
.desc = {
|
|
.name = "ldo1",
|
|
.of_match = of_match_ptr("LDO1"),
|
|
.regulators_node = of_match_ptr("regulators"),
|
|
.id = PCA9450_LDO1,
|
|
.ops = &pca9450_ldo_regulator_ops,
|
|
.type = REGULATOR_VOLTAGE,
|
|
.n_voltages = PCA9450_LDO1_VOLTAGE_NUM,
|
|
.linear_ranges = pca9450_ldo1_volts,
|
|
.n_linear_ranges = ARRAY_SIZE(pca9450_ldo1_volts),
|
|
.vsel_reg = PCA9450_REG_LDO1CTRL,
|
|
.vsel_mask = LDO1OUT_MASK,
|
|
.enable_reg = PCA9450_REG_LDO1CTRL,
|
|
.enable_mask = LDO1_EN_MASK,
|
|
.owner = THIS_MODULE,
|
|
},
|
|
},
|
|
{
|
|
.desc = {
|
|
.name = "ldo2",
|
|
.of_match = of_match_ptr("LDO2"),
|
|
.regulators_node = of_match_ptr("regulators"),
|
|
.id = PCA9450_LDO2,
|
|
.ops = &pca9450_ldo_regulator_ops,
|
|
.type = REGULATOR_VOLTAGE,
|
|
.n_voltages = PCA9450_LDO2_VOLTAGE_NUM,
|
|
.linear_ranges = pca9450_ldo2_volts,
|
|
.n_linear_ranges = ARRAY_SIZE(pca9450_ldo2_volts),
|
|
.vsel_reg = PCA9450_REG_LDO2CTRL,
|
|
.vsel_mask = LDO2OUT_MASK,
|
|
.enable_reg = PCA9450_REG_LDO2CTRL,
|
|
.enable_mask = LDO2_EN_MASK,
|
|
.owner = THIS_MODULE,
|
|
},
|
|
},
|
|
{
|
|
.desc = {
|
|
.name = "ldo3",
|
|
.of_match = of_match_ptr("LDO3"),
|
|
.regulators_node = of_match_ptr("regulators"),
|
|
.id = PCA9450_LDO3,
|
|
.ops = &pca9450_ldo_regulator_ops,
|
|
.type = REGULATOR_VOLTAGE,
|
|
.n_voltages = PCA9450_LDO3_VOLTAGE_NUM,
|
|
.linear_ranges = pca9450_ldo34_volts,
|
|
.n_linear_ranges = ARRAY_SIZE(pca9450_ldo34_volts),
|
|
.vsel_reg = PCA9450_REG_LDO3CTRL,
|
|
.vsel_mask = LDO3OUT_MASK,
|
|
.enable_reg = PCA9450_REG_LDO3CTRL,
|
|
.enable_mask = LDO3_EN_MASK,
|
|
.owner = THIS_MODULE,
|
|
},
|
|
},
|
|
{
|
|
.desc = {
|
|
.name = "ldo4",
|
|
.of_match = of_match_ptr("LDO4"),
|
|
.regulators_node = of_match_ptr("regulators"),
|
|
.id = PCA9450_LDO4,
|
|
.ops = &pca9450_ldo_regulator_ops,
|
|
.type = REGULATOR_VOLTAGE,
|
|
.n_voltages = PCA9450_LDO4_VOLTAGE_NUM,
|
|
.linear_ranges = pca9450_ldo34_volts,
|
|
.n_linear_ranges = ARRAY_SIZE(pca9450_ldo34_volts),
|
|
.vsel_reg = PCA9450_REG_LDO4CTRL,
|
|
.vsel_mask = LDO4OUT_MASK,
|
|
.enable_reg = PCA9450_REG_LDO4CTRL,
|
|
.enable_mask = LDO4_EN_MASK,
|
|
.owner = THIS_MODULE,
|
|
},
|
|
},
|
|
{
|
|
.desc = {
|
|
.name = "ldo5",
|
|
.of_match = of_match_ptr("LDO5"),
|
|
.regulators_node = of_match_ptr("regulators"),
|
|
.id = PCA9450_LDO5,
|
|
.ops = &pca9450_ldo_regulator_ops,
|
|
.type = REGULATOR_VOLTAGE,
|
|
.n_voltages = PCA9450_LDO5_VOLTAGE_NUM,
|
|
.linear_ranges = pca9450_ldo5_volts,
|
|
.n_linear_ranges = ARRAY_SIZE(pca9450_ldo5_volts),
|
|
.vsel_reg = PCA9450_REG_LDO5CTRL_H,
|
|
.vsel_mask = LDO5HOUT_MASK,
|
|
.enable_reg = PCA9450_REG_LDO5CTRL_H,
|
|
.enable_mask = LDO5H_EN_MASK,
|
|
.owner = THIS_MODULE,
|
|
},
|
|
},
|
|
};
|
|
|
|
static irqreturn_t pca9450_irq_handler(int irq, void *data)
|
|
{
|
|
struct pca9450 *pca9450 = data;
|
|
struct regmap *regmap = pca9450->regmap;
|
|
unsigned int status;
|
|
int ret;
|
|
|
|
ret = regmap_read(regmap, PCA9450_REG_INT1, &status);
|
|
if (ret < 0) {
|
|
dev_err(pca9450->dev,
|
|
"Failed to read INT1(%d)\n", ret);
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
if (status & IRQ_PWRON)
|
|
dev_warn(pca9450->dev, "PWRON interrupt.\n");
|
|
|
|
if (status & IRQ_WDOGB)
|
|
dev_warn(pca9450->dev, "WDOGB interrupt.\n");
|
|
|
|
if (status & IRQ_VR_FLT1)
|
|
dev_warn(pca9450->dev, "VRFLT1 interrupt.\n");
|
|
|
|
if (status & IRQ_VR_FLT2)
|
|
dev_warn(pca9450->dev, "VRFLT2 interrupt.\n");
|
|
|
|
if (status & IRQ_LOWVSYS)
|
|
dev_warn(pca9450->dev, "LOWVSYS interrupt.\n");
|
|
|
|
if (status & IRQ_THERM_105)
|
|
dev_warn(pca9450->dev, "IRQ_THERM_105 interrupt.\n");
|
|
|
|
if (status & IRQ_THERM_125)
|
|
dev_warn(pca9450->dev, "IRQ_THERM_125 interrupt.\n");
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int pca9450_i2c_probe(struct i2c_client *i2c,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
enum pca9450_chip_type type = (unsigned int)(uintptr_t)
|
|
of_device_get_match_data(&i2c->dev);
|
|
const struct pca9450_regulator_desc *regulator_desc;
|
|
struct regulator_config config = { };
|
|
struct pca9450 *pca9450;
|
|
unsigned int device_id, i;
|
|
int ret;
|
|
|
|
if (!i2c->irq) {
|
|
dev_err(&i2c->dev, "No IRQ configured?\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
pca9450 = devm_kzalloc(&i2c->dev, sizeof(struct pca9450), GFP_KERNEL);
|
|
if (!pca9450)
|
|
return -ENOMEM;
|
|
|
|
switch (type) {
|
|
case PCA9450_TYPE_PCA9450A:
|
|
regulator_desc = pca9450a_regulators;
|
|
pca9450->rcnt = ARRAY_SIZE(pca9450a_regulators);
|
|
break;
|
|
case PCA9450_TYPE_PCA9450BC:
|
|
regulator_desc = pca9450bc_regulators;
|
|
pca9450->rcnt = ARRAY_SIZE(pca9450bc_regulators);
|
|
break;
|
|
default:
|
|
dev_err(&i2c->dev, "Unknown device type");
|
|
return -EINVAL;
|
|
}
|
|
|
|
pca9450->irq = i2c->irq;
|
|
pca9450->type = type;
|
|
pca9450->dev = &i2c->dev;
|
|
|
|
dev_set_drvdata(&i2c->dev, pca9450);
|
|
|
|
pca9450->regmap = devm_regmap_init_i2c(i2c,
|
|
&pca9450_regmap_config);
|
|
if (IS_ERR(pca9450->regmap)) {
|
|
dev_err(&i2c->dev, "regmap initialization failed\n");
|
|
return PTR_ERR(pca9450->regmap);
|
|
}
|
|
|
|
ret = regmap_read(pca9450->regmap, PCA9450_REG_DEV_ID, &device_id);
|
|
if (ret) {
|
|
dev_err(&i2c->dev, "Read device id error\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Check your board and dts for match the right pmic */
|
|
if (((device_id >> 4) != 0x1 && type == PCA9450_TYPE_PCA9450A) ||
|
|
((device_id >> 4) != 0x3 && type == PCA9450_TYPE_PCA9450BC)) {
|
|
dev_err(&i2c->dev, "Device id(%x) mismatched\n",
|
|
device_id >> 4);
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < pca9450->rcnt; i++) {
|
|
const struct regulator_desc *desc;
|
|
struct regulator_dev *rdev;
|
|
const struct pca9450_regulator_desc *r;
|
|
|
|
r = ®ulator_desc[i];
|
|
desc = &r->desc;
|
|
|
|
config.regmap = pca9450->regmap;
|
|
config.dev = pca9450->dev;
|
|
|
|
rdev = devm_regulator_register(pca9450->dev, desc, &config);
|
|
if (IS_ERR(rdev)) {
|
|
ret = PTR_ERR(rdev);
|
|
dev_err(pca9450->dev,
|
|
"Failed to register regulator(%s): %d\n",
|
|
desc->name, ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
ret = devm_request_threaded_irq(pca9450->dev, pca9450->irq, NULL,
|
|
pca9450_irq_handler,
|
|
(IRQF_TRIGGER_FALLING | IRQF_ONESHOT),
|
|
"pca9450-irq", pca9450);
|
|
if (ret != 0) {
|
|
dev_err(pca9450->dev, "Failed to request IRQ: %d\n",
|
|
pca9450->irq);
|
|
return ret;
|
|
}
|
|
/* Unmask all interrupt except PWRON/WDOG/RSVD */
|
|
ret = regmap_update_bits(pca9450->regmap, PCA9450_REG_INT1_MSK,
|
|
IRQ_VR_FLT1 | IRQ_VR_FLT2 | IRQ_LOWVSYS |
|
|
IRQ_THERM_105 | IRQ_THERM_125,
|
|
IRQ_PWRON | IRQ_WDOGB | IRQ_RSVD);
|
|
if (ret) {
|
|
dev_err(&i2c->dev, "Unmask irq error\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Set reset behavior on assertion of WDOG_B signal */
|
|
ret = regmap_update_bits(pca9450->regmap, PCA9450_REG_RESET_CTRL,
|
|
WDOG_B_CFG_MASK, WDOG_B_CFG_COLD_LDO12);
|
|
if (ret) {
|
|
dev_err(&i2c->dev, "Failed to set WDOG_B reset behavior\n");
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* The driver uses the LDO5CTRL_H register to control the LDO5 regulator.
|
|
* This is only valid if the SD_VSEL input of the PMIC is high. Let's
|
|
* check if the pin is available as GPIO and set it to high.
|
|
*/
|
|
pca9450->sd_vsel_gpio = gpiod_get_optional(pca9450->dev, "sd-vsel", GPIOD_OUT_HIGH);
|
|
|
|
if (IS_ERR(pca9450->sd_vsel_gpio)) {
|
|
dev_err(&i2c->dev, "Failed to get SD_VSEL GPIO\n");
|
|
return ret;
|
|
}
|
|
|
|
dev_info(&i2c->dev, "%s probed.\n",
|
|
type == PCA9450_TYPE_PCA9450A ? "pca9450a" : "pca9450bc");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id pca9450_of_match[] = {
|
|
{
|
|
.compatible = "nxp,pca9450a",
|
|
.data = (void *)PCA9450_TYPE_PCA9450A,
|
|
},
|
|
{
|
|
.compatible = "nxp,pca9450b",
|
|
.data = (void *)PCA9450_TYPE_PCA9450BC,
|
|
},
|
|
{
|
|
.compatible = "nxp,pca9450c",
|
|
.data = (void *)PCA9450_TYPE_PCA9450BC,
|
|
},
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, pca9450_of_match);
|
|
|
|
static struct i2c_driver pca9450_i2c_driver = {
|
|
.driver = {
|
|
.name = "nxp-pca9450",
|
|
.of_match_table = pca9450_of_match,
|
|
},
|
|
.probe = pca9450_i2c_probe,
|
|
};
|
|
|
|
module_i2c_driver(pca9450_i2c_driver);
|
|
|
|
MODULE_AUTHOR("Robin Gong <yibin.gong@nxp.com>");
|
|
MODULE_DESCRIPTION("NXP PCA9450 Power Management IC driver");
|
|
MODULE_LICENSE("GPL");
|