mxs: add support for 4.1
[openwrt/svn-archive/archive.git] / target / linux / mxs / patches-4.1 / 102-mxs-add-regulator-driver.patch
diff --git a/target/linux/mxs/patches-4.1/102-mxs-add-regulator-driver.patch b/target/linux/mxs/patches-4.1/102-mxs-add-regulator-driver.patch
new file mode 100644 (file)
index 0000000..7ca14f1
--- /dev/null
@@ -0,0 +1,577 @@
+diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
+index a6f116a..7b525f5 100644
+--- a/drivers/regulator/Kconfig
++++ b/drivers/regulator/Kconfig
+@@ -450,6 +450,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
+diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
+index 2c4da15..a3ebf23 100644
+--- a/drivers/regulator/Makefile
++++ b/drivers/regulator/Makefile
+@@ -60,6 +60,7 @@ obj-$(CONFIG_REGULATOR_MC13783) += mc13783-regulator.o
+ obj-$(CONFIG_REGULATOR_MC13892) += mc13892-regulator.o
+ obj-$(CONFIG_REGULATOR_MC13XXX_CORE) +=  mc13xxx-regulator-core.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_PALMAS) += palmas-regulator.o
+ obj-$(CONFIG_REGULATOR_PFUZE100) += pfuze100-regulator.o
+diff --git a/drivers/regulator/mxs-regulator.c b/drivers/regulator/mxs-regulator.c
+new file mode 100644
+index 0000000..e53707b
+--- /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(&reg->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(&reg->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(&reg->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");