sh-pfc: sh73a0: Add VCCQ MC0 regulator
authorLaurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
Sun, 21 Apr 2013 21:26:26 +0000 (23:26 +0200)
committerSimon Horman <horms+renesas@verge.net.au>
Wed, 5 Jun 2013 08:17:14 +0000 (17:17 +0900)
The sh73a0 has an internal power gate on the VCCQ power supply for the
SDHI0 device that is controlled (for some strange reason) by a bit in a
PFC register. This feature should be exposed as a regulator.

As the same register is also used for pin control purposes there is no
way to achieve atomic read/write sequences with a separate regulator
driver. We thus need to implement the regulator here.

Signed-off-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
Acked-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Acked-by: Linus Walleij <linus.walleij@linaro.org>
Signed-off-by: Simon Horman <horms+renesas@verge.net.au>
drivers/pinctrl/sh-pfc/Kconfig
drivers/pinctrl/sh-pfc/pfc-sh73a0.c

index 32161c4fbb1dfc7c50fe51e454d7a546504d6b47..636a882b406ecb4d404609501a524de4b20e3aab 100644 (file)
@@ -72,6 +72,7 @@ config PINCTRL_PFC_SH73A0
        def_bool y
        depends on ARCH_SH73A0
        select PINCTRL_SH_PFC
+       select REGULATOR
 
 config PINCTRL_PFC_SH7720
        def_bool y
index 587f7772abf2aa46b8608115829aad85d9cb500a..b783724801d287424f03beafb507e401e7434ddc 100644 (file)
  */
 #include <linux/io.h>
 #include <linux/kernel.h>
+#include <linux/module.h>
 #include <linux/pinctrl/pinconf-generic.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/slab.h>
 
 #include <mach/sh73a0.h>
 #include <mach/irqs.h>
@@ -3888,6 +3892,92 @@ static const struct pinmux_irq pinmux_irqs[] = {
        PINMUX_IRQ(EXT_IRQ16L(9), 308),
 };
 
+/* -----------------------------------------------------------------------------
+ * VCCQ MC0 regulator
+ */
+
+static void sh73a0_vccq_mc0_endisable(struct regulator_dev *reg, bool enable)
+{
+       struct sh_pfc *pfc = reg->reg_data;
+       void __iomem *addr = pfc->window[1].virt + 4;
+       unsigned long flags;
+       u32 value;
+
+       spin_lock_irqsave(&pfc->lock, flags);
+
+       value = ioread32(addr);
+
+       if (enable)
+               value |= BIT(28);
+       else
+               value &= ~BIT(28);
+
+       iowrite32(value, addr);
+
+       spin_unlock_irqrestore(&pfc->lock, flags);
+}
+
+static int sh73a0_vccq_mc0_enable(struct regulator_dev *reg)
+{
+       sh73a0_vccq_mc0_endisable(reg, true);
+       return 0;
+}
+
+static int sh73a0_vccq_mc0_disable(struct regulator_dev *reg)
+{
+       sh73a0_vccq_mc0_endisable(reg, false);
+       return 0;
+}
+
+static int sh73a0_vccq_mc0_is_enabled(struct regulator_dev *reg)
+{
+       struct sh_pfc *pfc = reg->reg_data;
+       void __iomem *addr = pfc->window[1].virt + 4;
+       unsigned long flags;
+       u32 value;
+
+       spin_lock_irqsave(&pfc->lock, flags);
+       value = ioread32(addr);
+       spin_unlock_irqrestore(&pfc->lock, flags);
+
+       return !!(value & BIT(28));
+}
+
+static int sh73a0_vccq_mc0_get_voltage(struct regulator_dev *reg)
+{
+       return 3300000;
+}
+
+static struct regulator_ops sh73a0_vccq_mc0_ops = {
+       .enable = sh73a0_vccq_mc0_enable,
+       .disable = sh73a0_vccq_mc0_disable,
+       .is_enabled = sh73a0_vccq_mc0_is_enabled,
+       .get_voltage = sh73a0_vccq_mc0_get_voltage,
+};
+
+static const struct regulator_desc sh73a0_vccq_mc0_desc = {
+       .owner = THIS_MODULE,
+       .name = "vccq_mc0",
+       .type = REGULATOR_VOLTAGE,
+       .ops = &sh73a0_vccq_mc0_ops,
+};
+
+static struct regulator_consumer_supply sh73a0_vccq_mc0_consumers[] = {
+       REGULATOR_SUPPLY("vqmmc", "sh_mobile_sdhi.0"),
+};
+
+static const struct regulator_init_data sh73a0_vccq_mc0_init_data = {
+       .constraints = {
+               .valid_ops_mask = REGULATOR_CHANGE_STATUS,
+       },
+       .num_consumer_supplies = ARRAY_SIZE(sh73a0_vccq_mc0_consumers),
+       .consumer_supplies = sh73a0_vccq_mc0_consumers,
+};
+
+/* -----------------------------------------------------------------------------
+ * Pin bias
+ */
+
 #define PORTnCR_PULMD_OFF      (0 << 6)
 #define PORTnCR_PULMD_DOWN     (2 << 6)
 #define PORTnCR_PULMD_UP       (3 << 6)
@@ -3934,7 +4024,51 @@ static void sh73a0_pinmux_set_bias(struct sh_pfc *pfc, unsigned int pin,
        iowrite8(value, addr);
 }
 
+/* -----------------------------------------------------------------------------
+ * SoC information
+ */
+
+struct sh73a0_pinmux_data {
+       struct regulator_dev *vccq_mc0;
+};
+
+static int sh73a0_pinmux_soc_init(struct sh_pfc *pfc)
+{
+       struct sh73a0_pinmux_data *data;
+       struct regulator_config cfg = { };
+       int ret;
+
+       data = devm_kzalloc(pfc->dev, sizeof(*data), GFP_KERNEL);
+       if (data == NULL)
+               return -ENOMEM;
+
+       cfg.dev = pfc->dev;
+       cfg.init_data = &sh73a0_vccq_mc0_init_data;
+       cfg.driver_data = pfc;
+
+       data->vccq_mc0 = regulator_register(&sh73a0_vccq_mc0_desc, &cfg);
+       if (IS_ERR(data->vccq_mc0)) {
+               ret = PTR_ERR(data->vccq_mc0);
+               dev_err(pfc->dev, "Failed to register VCCQ MC0 regulator: %d\n",
+                       ret);
+               return ret;
+       }
+
+       pfc->soc_data = data;
+
+       return 0;
+}
+
+static void sh73a0_pinmux_soc_exit(struct sh_pfc *pfc)
+{
+       struct sh73a0_pinmux_data *data = pfc->soc_data;
+
+       regulator_unregister(data->vccq_mc0);
+}
+
 static const struct sh_pfc_soc_operations sh73a0_pinmux_ops = {
+       .init = sh73a0_pinmux_soc_init,
+       .exit = sh73a0_pinmux_soc_exit,
        .get_bias = sh73a0_pinmux_get_bias,
        .set_bias = sh73a0_pinmux_set_bias,
 };