// 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 */ #include #include #include #include #include #include #include #include #include #include #include #include #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 "); MODULE_LICENSE("GPL");