diff options
Diffstat (limited to 'drivers/mfd/axp20x.c')
-rw-r--r-- | drivers/mfd/axp20x.c | 382 |
1 files changed, 382 insertions, 0 deletions
diff --git a/drivers/mfd/axp20x.c b/drivers/mfd/axp20x.c new file mode 100644 index 0000000000..666b9ea98c --- /dev/null +++ b/drivers/mfd/axp20x.c @@ -0,0 +1,382 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * MFD core driver for the X-Powers' Power Management ICs + * + * AXP20x typically comprises an adaptive USB-Compatible PWM charger, BUCK DC-DC + * converters, LDOs, multiple 12-bit ADCs of voltage, current and temperature + * as well as configurable GPIOs. + * + * This file contains the interface independent core functions. + * + * Copyright (C) 2014 Carlo Caione + * + * Author: Carlo Caione <carlo@caione.org> + */ + +#include <common.h> +#include <linux/bitops.h> +#include <clock.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/mfd/axp20x.h> +#include <linux/mfd/core.h> +#include <module.h> +#include <of.h> +#include <of_device.h> +#include <linux/regmap.h> +#include <regulator.h> + +#define AXP20X_OFF BIT(7) + +#define AXP806_REG_ADDR_EXT_ADDR_MASTER_MODE 0 +#define AXP806_REG_ADDR_EXT_ADDR_SLAVE_MODE BIT(4) + +static const char * const axp20x_model_names[] = { + "AXP152", + "AXP202", + "AXP209", + "AXP221", + "AXP223", + "AXP288", + "AXP313A", + "AXP803", + "AXP806", + "AXP809", + "AXP813", +}; + +static const struct regmap_config axp152_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = AXP152_PWM1_DUTY_CYCLE, +}; + +static const struct regmap_config axp20x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = AXP20X_OCV(AXP20X_OCV_MAX), +}; + +static const struct regmap_config axp22x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = AXP22X_BATLOW_THRES1, +}; + +static const struct regmap_config axp288_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = AXP288_FG_TUNE5, +}; + +static const struct regmap_config axp313a_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = AXP313A_POK_CONTROL, +}; + +static const struct regmap_config axp806_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = AXP806_REG_ADDR_EXT, +}; + +static const struct mfd_cell axp20x_cells[] = { + { + .name = "axp20x-gpio", + /* .of_compatible = "x-powers,axp209-gpio", */ + }, { + .name = "axp20x-pek", + }, { + .name = "axp20x-regulator", + }, { + .name = "axp20x-adc", + /* .of_compatible = "x-powers,axp209-adc", */ + }, { + .name = "axp20x-battery-power-supply", + /* .of_compatible = "x-powers,axp209-battery-power-supply", */ + }, { + .name = "axp20x-ac-power-supply", + /* .of_compatible = "x-powers,axp202-ac-power-supply", */ + }, { + .name = "axp20x-usb-power-supply", + /* .of_compatible = "x-powers,axp202-usb-power-supply", */ + }, +}; + +static const struct mfd_cell axp221_cells[] = { + { + .name = "axp221-pek", + }, { + .name = "axp20x-regulator", + }, { + .name = "axp22x-adc", + /* .of_compatible = "x-powers,axp221-adc", */ + }, { + .name = "axp20x-ac-power-supply", + /* .of_compatible = "x-powers,axp221-ac-power-supply", */ + }, { + .name = "axp20x-battery-power-supply", + /* .of_compatible = "x-powers,axp221-battery-power-supply", */ + }, { + .name = "axp20x-usb-power-supply", + /* .of_compatible = "x-powers,axp221-usb-power-supply", */ + }, +}; + +static const struct mfd_cell axp223_cells[] = { + { + .name = "axp221-pek", + }, { + .name = "axp22x-adc", + /* .of_compatible = "x-powers,axp221-adc", */ + }, { + .name = "axp20x-battery-power-supply", + /* .of_compatible = "x-powers,axp221-battery-power-supply", */ + }, { + .name = "axp20x-regulator", + }, { + .name = "axp20x-ac-power-supply", + /* .of_compatible = "x-powers,axp221-ac-power-supply", */ + }, { + .name = "axp20x-usb-power-supply", + /* .of_compatible = "x-powers,axp223-usb-power-supply", */ + }, +}; + +static const struct mfd_cell axp152_cells[] = { + { + .name = "axp20x-pek", + }, + { + .name = "axp20x-regulator", + }, +}; + +static const struct mfd_cell axp288_cells[] = { + { + .name = "axp288_adc", + }, { + .name = "axp288_extcon", + }, { + .name = "axp288_charger", + }, { + .name = "axp221-pek", + }, { + .name = "axp288_pmic_acpi", + }, +}; + +static const struct mfd_cell axp313a_cells[] = { + { + .name = "axp313a-regulator" + }, +}; + + +static const struct mfd_cell axp803_cells[] = { + { + .name = "axp221-pek", + }, { + .name = "axp20x-gpio", + /* .of_compatible = "x-powers,axp813-gpio", */ + }, { + .name = "axp813-adc", + /* .of_compatible = "x-powers,axp813-adc", */ + }, { + .name = "axp20x-battery-power-supply", + /* .of_compatible = "x-powers,axp813-battery-power-supply", */ + }, { + .name = "axp20x-ac-power-supply", + /* .of_compatible = "x-powers,axp813-ac-power-supply", */ + }, { + .name = "axp20x-usb-power-supply", + /* .of_compatible = "x-powers,axp813-usb-power-supply", */ + }, + { .name = "axp20x-regulator" }, +}; + +static const struct mfd_cell axp806_cells[] = { + { + .name = "axp20x-regulator", + }, +}; + +static const struct mfd_cell axp809_cells[] = { + { + .name = "axp221-pek", + }, { + .name = "axp20x-regulator", + }, +}; + +static const struct mfd_cell axp813_cells[] = { + { + .name = "axp221-pek", + }, { + .name = "axp20x-regulator", + }, { + .name = "axp20x-gpio", + /* .of_compatible = "x-powers,axp813-gpio", */ + }, { + .name = "axp813-adc", + /* .of_compatible = "x-powers,axp813-adc", */ + }, { + .name = "axp20x-battery-power-supply", + /* .of_compatible = "x-powers,axp813-battery-power-supply", */ + }, { + .name = "axp20x-ac-power-supply", + /* .of_compatible = "x-powers,axp813-ac-power-supply", */ + }, { + .name = "axp20x-usb-power-supply", + /* .of_compatible = "x-powers,axp813-usb-power-supply", */ + }, +}; + +static void axp20x_power_off(struct poweroff_handler *handler) +{ + struct axp20x_dev *axp20x = container_of(handler, struct axp20x_dev, poweroff); + + regmap_write(axp20x->regmap, AXP20X_OFF_CTRL, AXP20X_OFF); + + shutdown_barebox(); + + /* Give capacitors etc. time to drain to avoid kernel panic msg. */ + mdelay(500); + hang(); +} + +int axp20x_match_device(struct axp20x_dev *axp20x) +{ + struct device *dev = axp20x->dev; + const struct of_device_id *of_id; + + of_id = of_match_device(dev->driver->of_compatible, dev); + if (!of_id) { + dev_err(dev, "Unable to match OF ID\n"); + return -ENODEV; + } + axp20x->variant = (long)of_id->data; + + switch (axp20x->variant) { + case AXP152_ID: + axp20x->nr_cells = ARRAY_SIZE(axp152_cells); + axp20x->cells = axp152_cells; + axp20x->regmap_cfg = &axp152_regmap_config; + break; + case AXP202_ID: + case AXP209_ID: + axp20x->nr_cells = ARRAY_SIZE(axp20x_cells); + axp20x->cells = axp20x_cells; + axp20x->regmap_cfg = &axp20x_regmap_config; + break; + case AXP221_ID: + axp20x->nr_cells = ARRAY_SIZE(axp221_cells); + axp20x->cells = axp221_cells; + axp20x->regmap_cfg = &axp22x_regmap_config; + break; + case AXP223_ID: + axp20x->nr_cells = ARRAY_SIZE(axp223_cells); + axp20x->cells = axp223_cells; + axp20x->regmap_cfg = &axp22x_regmap_config; + break; + case AXP288_ID: + axp20x->cells = axp288_cells; + axp20x->nr_cells = ARRAY_SIZE(axp288_cells); + axp20x->regmap_cfg = &axp288_regmap_config; + break; + case AXP313A_ID: + axp20x->cells = axp313a_cells; + axp20x->nr_cells = ARRAY_SIZE(axp313a_cells); + axp20x->regmap_cfg = &axp313a_regmap_config; + break; + case AXP803_ID: + axp20x->nr_cells = ARRAY_SIZE(axp803_cells); + axp20x->cells = axp803_cells; + axp20x->regmap_cfg = &axp288_regmap_config; + break; + case AXP806_ID: + /* + * Don't register the power key part if in slave mode or + * if there is no interrupt line. + */ + axp20x->nr_cells = ARRAY_SIZE(axp806_cells); + axp20x->cells = axp806_cells; + axp20x->regmap_cfg = &axp806_regmap_config; + break; + case AXP809_ID: + axp20x->nr_cells = ARRAY_SIZE(axp809_cells); + axp20x->cells = axp809_cells; + axp20x->regmap_cfg = &axp22x_regmap_config; + break; + case AXP813_ID: + axp20x->nr_cells = ARRAY_SIZE(axp813_cells); + axp20x->cells = axp813_cells; + axp20x->regmap_cfg = &axp288_regmap_config; + break; + default: + dev_err(dev, "unsupported AXP20X ID %lu\n", axp20x->variant); + return -EINVAL; + } + dev_info(dev, "AXP20x variant %s found\n", + axp20x_model_names[axp20x->variant]); + + return 0; +} +EXPORT_SYMBOL(axp20x_match_device); + +int axp20x_device_probe(struct axp20x_dev *axp20x) +{ + int ret; + + /* + * The AXP806 supports either master/standalone or slave mode. + * Slave mode allows sharing the serial bus, even with multiple + * AXP806 which all have the same hardware address. + * + * This is done with extra "serial interface address extension", + * or AXP806_BUS_ADDR_EXT, and "register address extension", or + * AXP806_REG_ADDR_EXT, registers. The former is read-only, with + * 1 bit customizable at the factory, and 1 bit depending on the + * state of an external pin. The latter is writable. The device + * will only respond to operations to its other registers when + * the these device addressing bits (in the upper 4 bits of the + * registers) match. + * + * By default we support an AXP806 chained to an AXP809 in slave + * mode. Boards which use an AXP806 in master mode can set the + * property "x-powers,master-mode" to override the default. + */ + if (axp20x->variant == AXP806_ID) { + if (of_property_read_bool(axp20x->dev->of_node, + "x-powers,master-mode") || + of_property_read_bool(axp20x->dev->of_node, + "x-powers,self-working-mode")) + regmap_write(axp20x->regmap, AXP806_REG_ADDR_EXT, + AXP806_REG_ADDR_EXT_ADDR_MASTER_MODE); + else + regmap_write(axp20x->regmap, AXP806_REG_ADDR_EXT, + AXP806_REG_ADDR_EXT_ADDR_SLAVE_MODE); + } + + axp20x->dev->priv = axp20x; + + ret = mfd_add_devices(axp20x->dev, axp20x->cells, axp20x->nr_cells); + if (ret) + return dev_err_probe(axp20x->dev, ret, "failed to add MFD devices\n"); + + + axp20x->poweroff.name = "axp20x-poweroff"; + axp20x->poweroff.poweroff = axp20x_power_off; + axp20x->poweroff.priority = 200; + + if (!(axp20x->variant == AXP288_ID) || (axp20x->variant == AXP313A_ID)) + poweroff_handler_register(&axp20x->poweroff); + + return 0; +} +EXPORT_SYMBOL(axp20x_device_probe); + +MODULE_DESCRIPTION("PMIC MFD core driver for AXP20X"); +MODULE_AUTHOR("Carlo Caione <carlo@caione.org>"); +MODULE_LICENSE("GPL"); |