123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570 |
- --- a/drivers/regulator/Kconfig
- +++ b/drivers/regulator/Kconfig
- @@ -462,6 +462,14 @@ config REGULATOR_MT6397
- This driver supports the control of different power rails of device
- through regulator interface.
-
- +config REGULATOR_MXS
- + tristate "Freescale MXS on-chip regulators"
- + depends on (MXS_POWER || COMPILE_TEST)
- + help
- + Say y here to support Freescale MXS on-chip regulators.
- + It is recommended that this option be enabled on i.MX23,
- + i.MX28 platform.
- +
- config REGULATOR_PALMAS
- tristate "TI Palmas PMIC Regulators"
- depends on MFD_PALMAS
- --- a/drivers/regulator/Makefile
- +++ b/drivers/regulator/Makefile
- @@ -61,6 +61,7 @@ obj-$(CONFIG_REGULATOR_MC13892) += mc138
- obj-$(CONFIG_REGULATOR_MC13XXX_CORE) += mc13xxx-regulator-core.o
- obj-$(CONFIG_REGULATOR_MT6311) += mt6311-regulator.o
- obj-$(CONFIG_REGULATOR_MT6397) += mt6397-regulator.o
- +obj-$(CONFIG_REGULATOR_MXS) += mxs-regulator.o
- obj-$(CONFIG_REGULATOR_QCOM_RPM) += qcom_rpm-regulator.o
- obj-$(CONFIG_REGULATOR_QCOM_SMD_RPM) += qcom_smd-regulator.o
- obj-$(CONFIG_REGULATOR_QCOM_SPMI) += qcom_spmi-regulator.o
- --- /dev/null
- +++ b/drivers/regulator/mxs-regulator.c
- @@ -0,0 +1,540 @@
- +/*
- + * Freescale MXS on-chip regulators
- + *
- + * Embedded Alley Solutions, Inc <source@embeddedalley.com>
- + *
- + * Copyright (C) 2014 Stefan Wahren
- + * Copyright (C) 2010 Freescale Semiconductor, Inc.
- + * Copyright (C) 2008 Embedded Alley Solutions, Inc All Rights Reserved.
- + *
- + * Inspired by imx-bootlets
- + */
- +
- +/*
- + * The code contained herein is licensed under the GNU General Public
- + * License. You may obtain a copy of the GNU General Public License
- + * Version 2 or later at the following locations:
- + *
- + * http://www.opensource.org/licenses/gpl-license.html
- + * http://www.gnu.org/copyleft/gpl.html
- + */
- +
- +#include <linux/delay.h>
- +#include <linux/device.h>
- +#include <linux/err.h>
- +#include <linux/kernel.h>
- +#include <linux/mfd/syscon.h>
- +#include <linux/module.h>
- +#include <linux/of.h>
- +#include <linux/of_device.h>
- +#include <linux/platform_device.h>
- +#include <linux/regmap.h>
- +#include <linux/regulator/driver.h>
- +#include <linux/regulator/machine.h>
- +#include <linux/regulator/of_regulator.h>
- +#include <linux/slab.h>
- +
- +/* Powered by linear regulator. DCDC output is gated off and
- + the linreg output is equal to the target. */
- +#define HW_POWER_LINREG_DCDC_OFF 1
- +
- +/* Powered by linear regulator. DCDC output is not gated off
- + and is ready for the automatic hardware transistion after a 5V
- + event. The converters are not enabled when 5V is present. LinReg output
- + is 25mV below target. */
- +#define HW_POWER_LINREG_DCDC_READY 2
- +
- +/* Powered by DCDC converter and the LinReg is on. LinReg output
- + is 25mV below target. */
- +#define HW_POWER_DCDC_LINREG_ON 3
- +
- +/* Powered by DCDC converter and the LinReg is off. LinReg output
- + is 25mV below target. */
- +#define HW_POWER_DCDC_LINREG_OFF 4
- +
- +/* Powered by DCDC converter and the LinReg is ready for the
- + automatic hardware transfer. The LinReg output is not enabled and
- + depends on the 5V presence to enable the LinRegs. LinReg offset is 25mV
- + below target. */
- +#define HW_POWER_DCDC_LINREG_READY 5
- +
- +/* Powered by an external source when 5V is present. This does not
- + necessarily mean the external source is powered by 5V,but the chip needs
- + to be aware that 5V is present. */
- +#define HW_POWER_EXTERNAL_SOURCE_5V 6
- +
- +/* Powered by an external source when 5V is not present.This doesn't
- + necessarily mean the external source is powered by the battery, but the
- + chip needs to be aware that the battery is present */
- +#define HW_POWER_EXTERNAL_SOURCE_BATTERY 7
- +
- +/* Unknown configuration. This is an error. */
- +#define HW_POWER_UNKNOWN_SOURCE 8
- +
- +/* TODO: Move power register offsets into header file */
- +#define HW_POWER_5VCTRL 0x00000010
- +#define HW_POWER_VDDDCTRL 0x00000040
- +#define HW_POWER_VDDACTRL 0x00000050
- +#define HW_POWER_VDDIOCTRL 0x00000060
- +#define HW_POWER_MISC 0x00000090
- +#define HW_POWER_STS 0x000000c0
- +
- +#define BM_POWER_STS_VBUSVALID0_STATUS BIT(15)
- +#define BM_POWER_STS_DC_OK BIT(9)
- +
- +#define BM_POWER_5VCTRL_ILIMIT_EQ_ZERO BIT(2)
- +#define BM_POWER_5VCTRL_ENABLE_DCDC BIT(0)
- +
- +#define BM_POWER_LINREG_OFFSET_DCDC_MODE BIT(1)
- +
- +#define SHIFT_FREQSEL 4
- +
- +#define BM_POWER_MISC_FREQSEL (7 << SHIFT_FREQSEL)
- +
- +/* Recommended DC-DC clock source values */
- +#define HW_POWER_MISC_FREQSEL_20000_KHZ 1
- +#define HW_POWER_MISC_FREQSEL_24000_KHZ 2
- +#define HW_POWER_MISC_FREQSEL_19200_KHZ 3
- +
- +#define HW_POWER_MISC_SEL_PLLCLK BIT(0)
- +
- +/* Regulator IDs */
- +#define MXS_DCDC 1
- +#define MXS_VDDIO 2
- +#define MXS_VDDA 3
- +#define MXS_VDDD 4
- +
- +struct mxs_reg_info {
- + /* regulator descriptor */
- + struct regulator_desc desc;
- +
- + /* regulator control register */
- + int ctrl_reg;
- +
- + /* disable DC-DC output */
- + unsigned int disable_fet_mask;
- +
- + /* steps between linreg output and DC-DC target */
- + unsigned int linreg_offset_mask;
- + u8 linreg_offset_shift;
- +
- + /* function which determine power source */
- + u8 (*get_power_source)(struct regulator_dev *);
- +};
- +
- +static inline u8 get_linreg_offset(struct mxs_reg_info *ldo, u32 regs)
- +{
- + return (regs & ldo->linreg_offset_mask) >> ldo->linreg_offset_shift;
- +}
- +
- +static u8 get_vddio_power_source(struct regulator_dev *reg)
- +{
- + struct mxs_reg_info *ldo = rdev_get_drvdata(reg);
- + u32 v5ctrl, status, base;
- + u8 offset;
- +
- + if (regmap_read(reg->regmap, HW_POWER_5VCTRL, &v5ctrl))
- + return HW_POWER_UNKNOWN_SOURCE;
- +
- + if (regmap_read(reg->regmap, HW_POWER_STS, &status))
- + return HW_POWER_UNKNOWN_SOURCE;
- +
- + if (regmap_read(reg->regmap, ldo->ctrl_reg, &base))
- + return HW_POWER_UNKNOWN_SOURCE;
- +
- + offset = get_linreg_offset(ldo, base);
- +
- + /* If VBUS valid then 5 V power supply present */
- + if (status & BM_POWER_STS_VBUSVALID0_STATUS) {
- + /* Powered by Linreg, DC-DC is off */
- + if ((base & ldo->disable_fet_mask) &&
- + !(offset & BM_POWER_LINREG_OFFSET_DCDC_MODE)) {
- + return HW_POWER_LINREG_DCDC_OFF;
- + }
- +
- + if (v5ctrl & BM_POWER_5VCTRL_ENABLE_DCDC) {
- + /* Powered by DC-DC, Linreg is on */
- + if (offset & BM_POWER_LINREG_OFFSET_DCDC_MODE)
- + return HW_POWER_DCDC_LINREG_ON;
- + } else {
- + /* Powered by Linreg, DC-DC is off */
- + if (!(offset & BM_POWER_LINREG_OFFSET_DCDC_MODE))
- + return HW_POWER_LINREG_DCDC_OFF;
- + }
- + } else {
- + /* Powered by DC-DC, Linreg is on */
- + if (offset & BM_POWER_LINREG_OFFSET_DCDC_MODE)
- + return HW_POWER_DCDC_LINREG_ON;
- + }
- +
- + return HW_POWER_UNKNOWN_SOURCE;
- +}
- +
- +static u8 get_vdda_vddd_power_source(struct regulator_dev *reg)
- +{
- + struct mxs_reg_info *ldo = rdev_get_drvdata(reg);
- + struct regulator_desc *desc = &ldo->desc;
- + u32 v5ctrl, status, base;
- + u8 offset;
- +
- + if (regmap_read(reg->regmap, HW_POWER_5VCTRL, &v5ctrl))
- + return HW_POWER_UNKNOWN_SOURCE;
- +
- + if (regmap_read(reg->regmap, HW_POWER_STS, &status))
- + return HW_POWER_UNKNOWN_SOURCE;
- +
- + if (regmap_read(reg->regmap, ldo->ctrl_reg, &base))
- + return HW_POWER_UNKNOWN_SOURCE;
- +
- + offset = get_linreg_offset(ldo, base);
- +
- + /* DC-DC output is disabled */
- + if (base & ldo->disable_fet_mask) {
- + /* Powered by 5 V supply */
- + if (status & BM_POWER_STS_VBUSVALID0_STATUS)
- + return HW_POWER_EXTERNAL_SOURCE_5V;
- +
- + /* Powered by Linreg, DC-DC is off */
- + if (!(offset & BM_POWER_LINREG_OFFSET_DCDC_MODE))
- + return HW_POWER_LINREG_DCDC_OFF;
- + }
- +
- + /* If VBUS valid then 5 V power supply present */
- + if (status & BM_POWER_STS_VBUSVALID0_STATUS) {
- + /* Powered by DC-DC, Linreg is on */
- + if (v5ctrl & BM_POWER_5VCTRL_ENABLE_DCDC)
- + return HW_POWER_DCDC_LINREG_ON;
- +
- + /* Powered by Linreg, DC-DC is off */
- + return HW_POWER_LINREG_DCDC_OFF;
- + }
- +
- + /* DC-DC is on */
- + if (offset & BM_POWER_LINREG_OFFSET_DCDC_MODE) {
- + /* Powered by DC-DC, Linreg is on */
- + if (base & desc->enable_mask)
- + return HW_POWER_DCDC_LINREG_ON;
- +
- + /* Powered by DC-DC, Linreg is off */
- + return HW_POWER_DCDC_LINREG_OFF;
- + }
- +
- + return HW_POWER_UNKNOWN_SOURCE;
- +}
- +
- +static int mxs_set_dcdc_freq(struct regulator_dev *reg, u32 hz)
- +{
- + struct mxs_reg_info *dcdc = rdev_get_drvdata(reg);
- + u32 val;
- + int ret;
- +
- + if (dcdc->desc.id != MXS_DCDC) {
- + dev_warn(®->dev, "Setting switching freq is not supported\n");
- + return -EINVAL;
- + }
- +
- + ret = regmap_read(reg->regmap, HW_POWER_MISC, &val);
- + if (ret)
- + return ret;
- +
- + val &= ~BM_POWER_MISC_FREQSEL;
- + val &= ~HW_POWER_MISC_SEL_PLLCLK;
- +
- + /*
- + * Select the PLL/PFD based frequency that the DC-DC converter uses.
- + * The actual switching frequency driving the power inductor is
- + * DCDC_CLK/16. Accept only values recommend by Freescale.
- + */
- + switch (hz) {
- + case 1200000:
- + val |= HW_POWER_MISC_FREQSEL_19200_KHZ << SHIFT_FREQSEL;
- + break;
- + case 1250000:
- + val |= HW_POWER_MISC_FREQSEL_20000_KHZ << SHIFT_FREQSEL;
- + break;
- + case 1500000:
- + val |= HW_POWER_MISC_FREQSEL_24000_KHZ << SHIFT_FREQSEL;
- + break;
- + default:
- + dev_warn(®->dev, "Switching freq: %u Hz not supported\n",
- + hz);
- + return -EINVAL;
- + }
- +
- + /* First program FREQSEL */
- + ret = regmap_write(reg->regmap, HW_POWER_MISC, val);
- + if (ret)
- + return ret;
- +
- + /* then set PLL as clock for DC-DC converter */
- + val |= HW_POWER_MISC_SEL_PLLCLK;
- +
- + return regmap_write(reg->regmap, HW_POWER_MISC, val);
- +}
- +
- +static int mxs_ldo_set_voltage_sel(struct regulator_dev *reg, unsigned sel)
- +{
- + struct mxs_reg_info *ldo = rdev_get_drvdata(reg);
- + struct regulator_desc *desc = &ldo->desc;
- + u32 status = 0;
- + int timeout;
- + int ret;
- +
- + ret = regmap_update_bits(reg->regmap, desc->vsel_reg, desc->vsel_mask,
- + sel);
- + if (ret)
- + return ret;
- +
- + if (ldo->get_power_source) {
- + switch (ldo->get_power_source(reg)) {
- + case HW_POWER_LINREG_DCDC_OFF:
- + case HW_POWER_LINREG_DCDC_READY:
- + case HW_POWER_EXTERNAL_SOURCE_5V:
- + /*
- + * Since the DC-DC converter is off we can't
- + * trigger on DC_OK. So wait at least 1 ms
- + * for stabilization.
- + */
- + usleep_range(1000, 2000);
- + return 0;
- + }
- + }
- +
- + /* Make sure DC_OK has changed */
- + usleep_range(15, 20);
- +
- + for (timeout = 0; timeout < 20; timeout++) {
- + ret = regmap_read(reg->regmap, HW_POWER_STS, &status);
- +
- + if (ret)
- + break;
- +
- + /* DC-DC converter control loop has stabilized */
- + if (status & BM_POWER_STS_DC_OK)
- + return 0;
- +
- + udelay(1);
- + }
- +
- + if (!ret)
- + dev_warn_ratelimited(®->dev, "%s: timeout status=0x%08x\n",
- + __func__, status);
- +
- + msleep(20);
- +
- + return -ETIMEDOUT;
- +}
- +
- +static int mxs_ldo_is_enabled(struct regulator_dev *reg)
- +{
- + struct mxs_reg_info *ldo = rdev_get_drvdata(reg);
- +
- + if (ldo->get_power_source) {
- + switch (ldo->get_power_source(reg)) {
- + case HW_POWER_LINREG_DCDC_OFF:
- + case HW_POWER_LINREG_DCDC_READY:
- + case HW_POWER_DCDC_LINREG_ON:
- + return 1;
- + }
- + }
- +
- + return 0;
- +}
- +
- +static struct regulator_ops mxs_dcdc_ops = {
- + .is_enabled = regulator_is_enabled_regmap,
- +};
- +
- +static struct regulator_ops mxs_ldo_ops = {
- + .list_voltage = regulator_list_voltage_linear,
- + .map_voltage = regulator_map_voltage_linear,
- + .set_voltage_sel = mxs_ldo_set_voltage_sel,
- + .get_voltage_sel = regulator_get_voltage_sel_regmap,
- + .is_enabled = mxs_ldo_is_enabled,
- +};
- +
- +static const struct mxs_reg_info mxs_info_dcdc = {
- + .desc = {
- + .name = "dcdc",
- + .id = MXS_DCDC,
- + .type = REGULATOR_VOLTAGE,
- + .owner = THIS_MODULE,
- + .ops = &mxs_dcdc_ops,
- + .enable_reg = HW_POWER_STS,
- + .enable_mask = (1 << 0),
- + },
- +};
- +
- +static const struct mxs_reg_info imx23_info_vddio = {
- + .desc = {
- + .name = "vddio",
- + .id = MXS_VDDIO,
- + .type = REGULATOR_VOLTAGE,
- + .owner = THIS_MODULE,
- + .n_voltages = 0x20,
- + .uV_step = 25000,
- + .linear_min_sel = 0,
- + .min_uV = 2800000,
- + .vsel_reg = HW_POWER_VDDIOCTRL,
- + .vsel_mask = 0x1f,
- + .ops = &mxs_ldo_ops,
- + },
- + .ctrl_reg = HW_POWER_VDDIOCTRL,
- + .disable_fet_mask = 1 << 16,
- + .linreg_offset_mask = 3 << 12,
- + .linreg_offset_shift = 12,
- + .get_power_source = get_vddio_power_source,
- +};
- +
- +static const struct mxs_reg_info imx28_info_vddio = {
- + .desc = {
- + .name = "vddio",
- + .id = MXS_VDDIO,
- + .type = REGULATOR_VOLTAGE,
- + .owner = THIS_MODULE,
- + .n_voltages = 0x11,
- + .uV_step = 50000,
- + .linear_min_sel = 0,
- + .min_uV = 2800000,
- + .vsel_reg = HW_POWER_VDDIOCTRL,
- + .vsel_mask = 0x1f,
- + .ops = &mxs_ldo_ops,
- + },
- + .ctrl_reg = HW_POWER_VDDIOCTRL,
- + .disable_fet_mask = 1 << 16,
- + .linreg_offset_mask = 3 << 12,
- + .linreg_offset_shift = 12,
- + .get_power_source = get_vddio_power_source,
- +};
- +
- +static const struct mxs_reg_info mxs_info_vdda = {
- + .desc = {
- + .name = "vdda",
- + .id = MXS_VDDA,
- + .type = REGULATOR_VOLTAGE,
- + .owner = THIS_MODULE,
- + .n_voltages = 0x20,
- + .uV_step = 25000,
- + .linear_min_sel = 0,
- + .min_uV = 1500000,
- + .vsel_reg = HW_POWER_VDDACTRL,
- + .vsel_mask = 0x1f,
- + .ops = &mxs_ldo_ops,
- + .enable_mask = (1 << 17),
- + },
- + .ctrl_reg = HW_POWER_VDDACTRL,
- + .disable_fet_mask = 1 << 16,
- + .linreg_offset_mask = 3 << 12,
- + .linreg_offset_shift = 12,
- + .get_power_source = get_vdda_vddd_power_source,
- +};
- +
- +static const struct mxs_reg_info mxs_info_vddd = {
- + .desc = {
- + .name = "vddd",
- + .id = MXS_VDDD,
- + .type = REGULATOR_VOLTAGE,
- + .owner = THIS_MODULE,
- + .n_voltages = 0x20,
- + .uV_step = 25000,
- + .linear_min_sel = 0,
- + .min_uV = 800000,
- + .vsel_reg = HW_POWER_VDDDCTRL,
- + .vsel_mask = 0x1f,
- + .ops = &mxs_ldo_ops,
- + .enable_mask = (1 << 21),
- + },
- + .ctrl_reg = HW_POWER_VDDDCTRL,
- + .disable_fet_mask = 1 << 20,
- + .linreg_offset_mask = 3 << 16,
- + .linreg_offset_shift = 16,
- + .get_power_source = get_vdda_vddd_power_source,
- +};
- +
- +static const struct of_device_id of_mxs_regulator_match[] = {
- + { .compatible = "fsl,imx23-dcdc", .data = &mxs_info_dcdc },
- + { .compatible = "fsl,imx28-dcdc", .data = &mxs_info_dcdc },
- + { .compatible = "fsl,imx23-vddio", .data = &imx23_info_vddio },
- + { .compatible = "fsl,imx23-vdda", .data = &mxs_info_vdda },
- + { .compatible = "fsl,imx23-vddd", .data = &mxs_info_vddd },
- + { .compatible = "fsl,imx28-vddio", .data = &imx28_info_vddio },
- + { .compatible = "fsl,imx28-vdda", .data = &mxs_info_vdda },
- + { .compatible = "fsl,imx28-vddd", .data = &mxs_info_vddd },
- + { /* end */ }
- +};
- +MODULE_DEVICE_TABLE(of, of_mxs_regulator_match);
- +
- +static int mxs_regulator_probe(struct platform_device *pdev)
- +{
- + struct device *dev = &pdev->dev;
- + const struct of_device_id *match;
- + struct device_node *parent_np;
- + struct regulator_dev *rdev = NULL;
- + struct mxs_reg_info *info;
- + struct regulator_init_data *initdata;
- + struct regulator_config config = { };
- + u32 switch_freq;
- +
- + match = of_match_device(of_mxs_regulator_match, dev);
- + if (!match) {
- + /* We do not expect this to happen */
- + dev_err(dev, "%s: Unable to match device\n", __func__);
- + return -ENODEV;
- + }
- +
- + info = devm_kmemdup(dev, match->data, sizeof(struct mxs_reg_info),
- + GFP_KERNEL);
- + if (!info)
- + return -ENOMEM;
- +
- + initdata = of_get_regulator_init_data(dev, dev->of_node, &info->desc);
- + if (!initdata) {
- + dev_err(dev, "missing regulator init data\n");
- + return -EINVAL;
- + }
- +
- + parent_np = of_get_parent(dev->of_node);
- + if (!parent_np)
- + return -ENODEV;
- + config.regmap = syscon_node_to_regmap(parent_np);
- + of_node_put(parent_np);
- + if (IS_ERR(config.regmap))
- + return PTR_ERR(config.regmap);
- +
- + config.dev = dev;
- + config.init_data = initdata;
- + config.driver_data = info;
- + config.of_node = dev->of_node;
- +
- + rdev = devm_regulator_register(dev, &info->desc, &config);
- + if (IS_ERR(rdev)) {
- + int ret = PTR_ERR(rdev);
- +
- + dev_err(dev, "%s: failed to register regulator(%d)\n",
- + __func__, ret);
- + return ret;
- + }
- +
- + if (!of_property_read_u32(dev->of_node, "switching-frequency",
- + &switch_freq))
- + mxs_set_dcdc_freq(rdev, switch_freq);
- +
- + platform_set_drvdata(pdev, rdev);
- +
- + return 0;
- +}
- +
- +static struct platform_driver mxs_regulator_driver = {
- + .driver = {
- + .name = "mxs_regulator",
- + .of_match_table = of_mxs_regulator_match,
- + },
- + .probe = mxs_regulator_probe,
- +};
- +
- +module_platform_driver(mxs_regulator_driver);
- +
- +MODULE_AUTHOR("Stefan Wahren <stefan.wahren@i2se.com>");
- +MODULE_DESCRIPTION("Freescale MXS regulators");
- +MODULE_LICENSE("GPL v2");
- +MODULE_ALIAS("platform:mxs_regulator");
|