From patchwork Tue Apr 28 14:17:40 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Martin Fuzzey X-Patchwork-Id: 6289761 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork1.web.kernel.org (Postfix) with ESMTP id CFC289F373 for ; Tue, 28 Apr 2015 14:21:28 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 7591F2039D for ; Tue, 28 Apr 2015 14:21:26 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.9]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 151A220374 for ; Tue, 28 Apr 2015 14:21:24 +0000 (UTC) Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1Yn6Ld-0004wq-2c; Tue, 28 Apr 2015 14:18:53 +0000 Received: from ip83.parkeon.com ([213.152.31.83] helo=mta2.parkeon.com) by bombadil.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1Yn6Kp-0004iI-NJ for linux-arm-kernel@lists.infradead.org; Tue, 28 Apr 2015 14:18:08 +0000 Received: from time001.besancon.parkeon.com ([10.32.16.23] helo=mail.besancon.parkeon.com) by mta2.parkeon.com with esmtp (Exim 4.82) (envelope-from ) id 1Yn6KQ-0004Vi-BD; Tue, 28 Apr 2015 16:17:39 +0200 Received: from [10.32.51.184] (port=54649 helo=[127.0.0.1]) by mail.besancon.parkeon.com with esmtp (Exim 4.71) (envelope-from ) id 1Yn6KS-0007eA-JV; Tue, 28 Apr 2015 16:17:40 +0200 Subject: [PATCH 2/2] Regulator: add driver for Freescale MC34708 To: Mark Brown , Liam Girdwood , Rob Herring From: Martin Fuzzey Date: Tue, 28 Apr 2015 16:17:40 +0200 Message-ID: <20150428141740.16243.79036.stgit@localhost> In-Reply-To: <20150428141736.16243.57292.stgit@localhost> References: <20150428141736.16243.57292.stgit@localhost> User-Agent: StGit/0.16 MIME-Version: 1.0 X-Spam-Score: -2.0 (--) X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20150428_071804_308702_AE0574F5 X-CRM114-Status: GOOD ( 21.00 ) X-Spam-Score: -0.0 (/) Cc: devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.18-1 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Spam-Status: No, score=-4.2 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_MED, T_RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Signed-off-by: Martin Fuzzey --- drivers/regulator/Kconfig | 7 drivers/regulator/Makefile | 1 drivers/regulator/mc34708-regulator.c | 1266 +++++++++++++++++++++++++++++++++ 3 files changed, 1274 insertions(+) create mode 100644 drivers/regulator/mc34708-regulator.c diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 31ac1bf..0d03c39 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -440,6 +440,13 @@ config REGULATOR_MC13892 Say y here to support the regulators found on the Freescale MC13892 PMIC. +config REGULATOR_MC34708 + tristate "Freescale MC34708 regulator driver" + depends on MFD_MC13XXX + help + Say y here to support the regulators found on the Freescale MC34708 + and MC34709 PMICs. + config REGULATOR_PALMAS tristate "TI Palmas PMIC Regulators" depends on MFD_PALMAS diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index c1b4fba..6382f6c 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -59,6 +59,7 @@ obj-$(CONFIG_REGULATOR_MAX77802) += max77802.o 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_MC34708) += mc34708-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/mc34708-regulator.c b/drivers/regulator/mc34708-regulator.c new file mode 100644 index 0000000..b5ff727 --- /dev/null +++ b/drivers/regulator/mc34708-regulator.c @@ -0,0 +1,1266 @@ +/* + * Driver for regulators in the Fresscale MC34708/9 PMICs + * + * The hardware uses different schemes for the three types of regulators: + * + * The SWx regulators: + * A single register field multiplexes enable/disable and mode + * selection, for both normal and standby state. + * Independent voltage control in normal and standby states. + * + * The SWBST regulator: + * One register field for combined enable/disable and mode selection + * in normal state and a second field for standby state. + * Single voltage control for both normal and standby states. + * + * The LDO regulators: + * Enable bit (not multiplexed with mode) + * Single voltage control for both normal and standby states. + * Standby and Mode bits shared by standby and normal states + * + * The mc13xxx-regulator-core is not a good fit for the above so it is + * not used. + * + * Copyright 2015 Parkeon + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct mc34708_hw_mode { + s8 normal; + s8 alt_normal; + s8 standby; +}; + +struct mc34708_regulator; + +/* Common to regulators of the same type */ +struct mc34708_regulator_kind { + int (*setup)(struct mc34708_regulator *); + unsigned int (*of_map_mode)(unsigned int); + const struct mc34708_hw_mode *hw_modes; + int num_hw_modes; + const struct regulator_ops ops; +}; + +/* Specific for each regulator */ +struct mc34708_regulator_def { + const char *name; + const struct mc34708_regulator_kind *kind; + const char *supply_name; + unsigned int enable_reg; + unsigned int enable_mask; + unsigned int n_voltages; + unsigned int fixed_uV; + unsigned int min_uV; + unsigned int uV_step; + const unsigned int *volt_table; + unsigned int vsel_reg; + unsigned int vsel_mask; + unsigned int vsel_stdby_mask; + unsigned int mode_reg; + unsigned int mode_mask; + unsigned int mode_stdby_mask; +}; + +struct mc34708_regulator { + struct device *dev; + struct mc13xxx *mc13xxx; + const struct mc34708_regulator_def *def; + struct regulator_desc desc; + struct regulator_config config; + unsigned int req_mode_normal; + unsigned int req_mode_standby; + bool suspend_off; +}; + +struct mc34708_drv_data { + u32 saved_regmode0; + struct mc34708_regulator regulators[0]; +}; + +/* ********************************************************************** */ +/* Common helpers */ +/* ********************************************************************** */ + +static int mc34708_read_bits(struct mc34708_regulator *mc34708_reg, + unsigned int reg, u32 mask) +{ + int ret; + u32 val; + + ret = mc13xxx_reg_read(mc34708_reg->mc13xxx, reg, &val); + if (ret < 0) + return ret; + + val &= mask; + val >>= ffs(mask) - 1; + + return val; +} + +static int mc34708_update_bits(struct mc34708_regulator *mc34708_reg, + unsigned int reg, u32 mask, u32 val) +{ + val <<= ffs(mask) - 1; + + return mc13xxx_reg_rmw(mc34708_reg->mc13xxx, reg, mask, val); +} + +static int mc34708_get_voltage_sel(struct regulator_dev *rdev) +{ + struct mc34708_regulator *mc34708_reg = rdev->reg_data; + + return mc34708_read_bits(mc34708_reg, + rdev->desc->vsel_reg, rdev->desc->vsel_mask); +} + +static int mc34708_set_voltage_sel(struct regulator_dev *rdev, unsigned sel) +{ + struct mc34708_regulator *mc34708_reg = rdev->reg_data; + + return mc34708_update_bits(mc34708_reg, + rdev->desc->vsel_reg, + rdev->desc->vsel_mask, + sel); +} + +static int mc34708_set_suspend_voltage(struct regulator_dev *rdev, int uV) +{ + struct mc34708_regulator *mc34708_reg = rdev->reg_data; + int sel; + + sel = regulator_map_voltage_linear(rdev, uV, uV); + if (sel < 0) + return sel; + + return mc34708_update_bits(mc34708_reg, + rdev->desc->vsel_reg, + mc34708_reg->def->vsel_stdby_mask, + sel); +} + +static const struct mc34708_hw_mode *mc34708_get_hw_mode( + struct mc34708_regulator *mc34708_reg) +{ + int val; + const struct mc34708_hw_mode *hwmode; + + val = mc34708_read_bits(mc34708_reg, + mc34708_reg->def->mode_reg, + mc34708_reg->def->mode_mask); + if (val < 0) + return ERR_PTR(val); + + BUG_ON(val >= mc34708_reg->def->kind->num_hw_modes); + hwmode = &mc34708_reg->def->kind->hw_modes[val]; + + dev_dbg(mc34708_reg->dev, "%s: HwMode=0x%x => normal=%d standby=%d\n", + mc34708_reg->def->name, val, + hwmode->normal, hwmode->standby); + + return hwmode; +} + +static int mc34708_sw_find_hw_mode_sel( + const struct mc34708_regulator_kind *kind, + unsigned int normal, + unsigned int standby) +{ + const struct mc34708_hw_mode *mode; + int i; + + for (i = 0, mode = kind->hw_modes; + i < kind->num_hw_modes; i++, mode++) { + if (mode->normal == -1 || mode->standby == -1) + continue; + + if (mode->standby != standby) + continue; + + if ((mode->normal == normal) || + (normal && (mode->alt_normal == normal))) + return i; + } + + return -EINVAL; +} + +static int mc34708_update_hw_mode( + struct mc34708_regulator *mc34708_reg, + unsigned int normal, + unsigned int standby) +{ + int sel; + + sel = mc34708_sw_find_hw_mode_sel(mc34708_reg->def->kind, + normal, standby); + if (sel < 0) { + dev_err(mc34708_reg->dev, + "%s: no hardware mode for normal=%d standby=%d\n", + mc34708_reg->def->name, + normal, + standby); + + return sel; + } + + dev_dbg(mc34708_reg->dev, "%s: normal=%d standby=%d => HwMODE=0x%x\n", + mc34708_reg->def->name, + normal, + standby, + sel); + + return mc34708_update_bits(mc34708_reg, + mc34708_reg->def->mode_reg, + mc34708_reg->def->mode_mask, + sel); +} + +/* ********************************************************************** */ +/* SWx regulator support */ +/* ********************************************************************** */ + +/* + * Mapping of SWxMODE bits to mode in normal and standby state. + * The hardware has the follwoing modes (in order of increasing power): + * OFF + * PFM (Pulse Frequency Modulation) for low loads + * APS (Automatic Pulse Skip) + * PWM (Pulse Width Modulation) for high loads + * + * Not all combinations are possible. The mode in normal state cannot + * be lower power than that in standby state. + * + * We map these to Linux regulator modes as follows: + * PFM : REGULATOR_MODE_STANDBY + * PWM : REGULATOR_MODE_FAST + * APS : REGULATOR_MODE_NORMAL + * OFF : 0 + * Reserved : -1 + */ +static const struct mc34708_hw_mode mc34708_sw_modes[] = { + { .normal = 0, .standby = 0 }, + { .normal = REGULATOR_MODE_FAST, .standby = 0 }, + { .normal = -1, .standby = -1 }, + { .normal = REGULATOR_MODE_STANDBY, .standby = 0 }, + { .normal = REGULATOR_MODE_NORMAL, .standby = 0 }, + { .normal = REGULATOR_MODE_FAST, .standby = REGULATOR_MODE_FAST }, + { .normal = REGULATOR_MODE_FAST, .standby = REGULATOR_MODE_NORMAL }, + { .normal = 0, .standby = 0 }, + { .normal = REGULATOR_MODE_NORMAL, .standby = REGULATOR_MODE_NORMAL }, + { .normal = -1, .standby = -1 }, + { .normal = -1, .standby = -1 }, + { .normal = -1, .standby = -1 }, + { .normal = REGULATOR_MODE_NORMAL, .standby = REGULATOR_MODE_STANDBY }, + { .normal = REGULATOR_MODE_FAST, .standby = REGULATOR_MODE_STANDBY }, + { .normal = -1, .standby = -1 }, + { .normal = REGULATOR_MODE_STANDBY, .standby = REGULATOR_MODE_STANDBY }, +}; + +#define MC34708_SW_OPMODE_PFM 1 +#define MC34708_SW_OPMODE_APS 2 +#define MC34708_SW_OPMODE_PWM 3 + +static unsigned int mc34708_sw_of_map_mode(unsigned int mode) +{ + switch (mode) { + case MC34708_SW_OPMODE_PFM: + return REGULATOR_MODE_STANDBY; + case MC34708_SW_OPMODE_APS: + return REGULATOR_MODE_NORMAL; + case MC34708_SW_OPMODE_PWM: + return REGULATOR_MODE_FAST; + default: + return -EINVAL; + } +} + +static int mc34708_sw_setup(struct mc34708_regulator *mc34708_reg) +{ + const struct mc34708_hw_mode *hw_mode; + + hw_mode = mc34708_get_hw_mode(mc34708_reg); + if (IS_ERR(hw_mode)) + return PTR_ERR(hw_mode); + + /* + * Be safe and bail out without touching hardware if + * the initial state is "reserved" + */ + if (hw_mode->normal < 0) { + dev_err(mc34708_reg->dev, + "%s in reserved mode for normal\n", + mc34708_reg->def->name); + return -EINVAL; + } + if (hw_mode->standby < 0) { + dev_err(mc34708_reg->dev, + "%s in reserved mode for standby\n", + mc34708_reg->def->name); + return -EINVAL; + } + + if (hw_mode->normal > 0) + mc34708_reg->req_mode_normal = hw_mode->normal; + else + /* + * If regulator is intially off we don't know the mode + * but we need a mode to be able to enable it later. + */ + mc34708_reg->req_mode_normal = REGULATOR_MODE_NORMAL; + + mc34708_reg->req_mode_standby = hw_mode->standby; + if (!hw_mode->standby) + mc34708_reg->suspend_off = true; + + return 0; +} + +static int mc34708_sw_enable(struct regulator_dev *rdev) +{ + struct mc34708_regulator *mc34708_reg = rdev->reg_data; + + return mc34708_update_hw_mode(mc34708_reg, + mc34708_reg->req_mode_normal, + mc34708_reg->req_mode_standby); +} + +static int mc34708_sw_disable(struct regulator_dev *rdev) +{ + struct mc34708_regulator *mc34708_reg = rdev->reg_data; + + return mc34708_update_hw_mode(mc34708_reg, + 0, + mc34708_reg->req_mode_standby); +} + +static int mc34708_sw_is_enabled(struct regulator_dev *rdev) +{ + struct mc34708_regulator *mc34708_reg = rdev->reg_data; + const struct mc34708_hw_mode *hw_mode; + + hw_mode = mc34708_get_hw_mode(mc34708_reg); + if (IS_ERR(hw_mode)) + return PTR_ERR(hw_mode); + + return hw_mode->normal > 0; +} + +static unsigned int mc34708_sw_get_mode(struct regulator_dev *rdev) +{ + struct mc34708_regulator *mc34708_reg = rdev->reg_data; + + return mc34708_reg->req_mode_normal; +} + +static int mc34708_sw_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct mc34708_regulator *mc34708_reg = rdev->reg_data; + int enabled, ret; + + enabled = mc34708_sw_is_enabled(rdev); + if (enabled < 0) + return enabled; + + if (enabled) { + ret = mc34708_update_hw_mode(mc34708_reg, + mode, + mc34708_reg->req_mode_standby); + if (ret) + return ret; + } + + mc34708_reg->req_mode_normal = mode; + + return 0; +} + +static int mc34708_sw_set_suspend_enable(struct regulator_dev *rdev) +{ + struct mc34708_regulator *mc34708_reg = rdev->reg_data; + int ret; + + ret = mc34708_update_hw_mode(mc34708_reg, + mc34708_reg->req_mode_normal, + mc34708_reg->req_mode_standby); + if (!ret) + mc34708_reg->suspend_off = false; + + return ret; +} + +static int mc34708_sw_set_suspend_disable(struct regulator_dev *rdev) +{ + struct mc34708_regulator *mc34708_reg = rdev->reg_data; + int ret; + + ret = mc34708_update_hw_mode(mc34708_reg, + mc34708_reg->req_mode_normal, + 0); + if (!ret) + mc34708_reg->suspend_off = true; + + return ret; +} + +static int mc34708_sw_set_suspend_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct mc34708_regulator *mc34708_reg = rdev->reg_data; + int enabled, ret; + + enabled = mc34708_sw_is_enabled(rdev); + if (enabled < 0) + return enabled; + + ret = mc34708_update_hw_mode(mc34708_reg, + enabled ? mc34708_reg->req_mode_normal : 0, + mc34708_reg->suspend_off ? 0 : mode); + if (!ret) + mc34708_reg->req_mode_standby = mode; + + return ret; +} + +/* ********************************************************************** */ +/* SWBST regulator support */ +/* ********************************************************************** */ + +static int mc34708_swbst_mode_to_hwmode(unsigned int mode) +{ + switch (mode) { + case REGULATOR_MODE_IDLE: + case REGULATOR_MODE_STANDBY: + return MC34708_SW_OPMODE_PFM; + + case REGULATOR_MODE_NORMAL: + return MC34708_SW_OPMODE_APS; + + case REGULATOR_MODE_FAST: + return MC34708_SW_OPMODE_PWM; + + default: + return -EINVAL; + }; +} + +static int mc34708_swbst_hwmode_to_mode(unsigned int hwmode) +{ + return mc34708_sw_of_map_mode(hwmode); +} + +static int mc34708_swbst_setup(struct mc34708_regulator *mc34708_reg) +{ + int val, mode; + + val = mc34708_read_bits(mc34708_reg, + mc34708_reg->def->mode_reg, + mc34708_reg->def->mode_mask); + if (val < 0) + return val; + + if (val > 0) { + mode = mc34708_swbst_hwmode_to_mode(val); + if (mode < 0) + return mode; + + mc34708_reg->req_mode_normal = mode; + } else { + /* + * If regulator is intially off we don't know the mode + * but we need a mode to be able to enable it later. + */ + mc34708_reg->req_mode_normal = REGULATOR_MODE_NORMAL; + } + + dev_dbg(mc34708_reg->dev, + "%s: Initial normal mode hw=%d linux=%d\n", + mc34708_reg->def->name, val, mc34708_reg->req_mode_normal); + + val = mc34708_read_bits(mc34708_reg, + mc34708_reg->def->mode_reg, + mc34708_reg->def->mode_stdby_mask); + if (val < 0) + return val; + + if (val > 0) { + mode = mc34708_swbst_hwmode_to_mode(val); + if (mode < 0) + return mode; + + mc34708_reg->req_mode_standby = mode; + } else { + /* + * If regulator is intially off we don't know the mode + * but we need a mode to be able to enable it later. + */ + mc34708_reg->req_mode_standby = REGULATOR_MODE_STANDBY; + } + + dev_dbg(mc34708_reg->dev, "Initial standby mode hw=%d linux=%d\n", + val, + mc34708_reg->req_mode_standby); + + return 0; +} + +static int mc34708_swbst_enable(struct regulator_dev *rdev) +{ + struct mc34708_regulator *mc34708_reg = rdev->reg_data; + + return mc34708_update_bits(mc34708_reg, + mc34708_reg->def->mode_reg, + mc34708_reg->def->mode_mask, + mc34708_reg->req_mode_normal); +} + +static int mc34708_swbst_disable(struct regulator_dev *rdev) +{ + struct mc34708_regulator *mc34708_reg = rdev->reg_data; + + return mc34708_update_bits(mc34708_reg, + mc34708_reg->def->mode_reg, + mc34708_reg->def->mode_mask, + 0); +} + +static int mc34708_swbst_is_enabled(struct regulator_dev *rdev) +{ + struct mc34708_regulator *mc34708_reg = rdev->reg_data; + int val; + + val = mc34708_read_bits(mc34708_reg, + mc34708_reg->def->mode_reg, + mc34708_reg->def->mode_mask); + + if (val < 0) + return val; + + return val == 0 ? 0 : 1; +} + +static unsigned int mc34708_swbst_get_mode(struct regulator_dev *rdev) +{ + struct mc34708_regulator *mc34708_reg = rdev->reg_data; + + return mc34708_reg->req_mode_normal; +} + +static int mc34708_swbst_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct mc34708_regulator *mc34708_reg = rdev->reg_data; + int enabled, hwmode, ret; + + hwmode = mc34708_swbst_mode_to_hwmode(mode); + if (hwmode < 0) + return hwmode; + + enabled = mc34708_swbst_is_enabled(rdev); + if (enabled < 0) + return enabled; + + if (enabled) { + ret = mc34708_update_bits(mc34708_reg, + mc34708_reg->def->mode_reg, + mc34708_reg->def->mode_mask, + hwmode); + if (ret) + return ret; + } + + mc34708_reg->req_mode_normal = mode; + + return 0; +} + +static int mc34708_swbst_set_suspend_enable(struct regulator_dev *rdev) +{ + struct mc34708_regulator *mc34708_reg = rdev->reg_data; + int hwmode; + + hwmode = mc34708_swbst_mode_to_hwmode(mc34708_reg->req_mode_standby); + if (hwmode < 0) + return hwmode; + + return mc34708_update_bits(mc34708_reg, + mc34708_reg->def->mode_reg, + mc34708_reg->def->mode_stdby_mask, + hwmode); +} + +static int mc34708_swbst_set_suspend_disable(struct regulator_dev *rdev) +{ + struct mc34708_regulator *mc34708_reg = rdev->reg_data; + + return mc34708_update_bits(mc34708_reg, + mc34708_reg->def->mode_reg, + mc34708_reg->def->mode_stdby_mask, + 0); +} + +static int mc34708_swbst_set_suspend_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct mc34708_regulator *mc34708_reg = rdev->reg_data; + int ret, hwmode; + + hwmode = mc34708_swbst_mode_to_hwmode(mode); + if (hwmode < 0) + return hwmode; + + ret = mc34708_update_bits(mc34708_reg, + mc34708_reg->def->mode_reg, + mc34708_reg->def->mode_stdby_mask, + hwmode); + if (!ret) + mc34708_reg->req_mode_standby = mode; + + return ret; +} + +/* ********************************************************************** */ +/* LDO regulator support */ +/* ********************************************************************** */ + +/* + * 3 types mode / standby configuration for LDOs: + * No mode configuration + * Single bit mode cofiguration (standby bit) + * 2 bit mode configuration mode, standby + * + * LDO states (excluding enable bit) + * VxMODE VxSTBY Normal Standby + * 0 0 ON ON + * 0 1 ON OFF + * 1 0 LP LP + * 1 1 ON LP + * + * Note that it is not possible to have Normal=LP, Standby=OFF + * If this state is requested we will use Normal=ON, Standby=OFF until exit + * from suspend. + * Hence the .alt_normal below which is used for extra matching + */ +static const struct mc34708_hw_mode mc34708_ldo_modes[] = { + { + .normal = REGULATOR_MODE_NORMAL, + .standby = REGULATOR_MODE_NORMAL + }, + { + .normal = REGULATOR_MODE_NORMAL, + .alt_normal = REGULATOR_MODE_STANDBY, + .standby = 0 + }, + { + .normal = REGULATOR_MODE_STANDBY, + .standby = REGULATOR_MODE_STANDBY + }, + { + .normal = REGULATOR_MODE_NORMAL, + .standby = REGULATOR_MODE_STANDBY + }, +}; + +#define MC34708_LDO_OPMODE_LOWPOWER 1 +#define MC34708_LDO_OPMODE_NORMAL 2 + +static unsigned int mc34708_ldo_of_map_mode(unsigned int mode) +{ + switch (mode) { + case MC34708_LDO_OPMODE_NORMAL: + return REGULATOR_MODE_NORMAL; + case MC34708_LDO_OPMODE_LOWPOWER: + return REGULATOR_MODE_STANDBY; + default: + return -EINVAL; + } +} + +static bool mc34708_ldo_has_mode_bit(struct mc34708_regulator *mc34708_reg) +{ + unsigned int mask = mc34708_reg->def->mode_mask; + + if (!mask) + return false; + + mask >>= ffs(mask) - 1; + + return mask == 3; +} + +static int mc34708_ldo_setup(struct mc34708_regulator *mc34708_reg) +{ + const struct mc34708_hw_mode *hw_mode; + + hw_mode = mc34708_get_hw_mode(mc34708_reg); + if (IS_ERR(hw_mode)) + return PTR_ERR(hw_mode); + + mc34708_reg->req_mode_normal = hw_mode->normal; + if (hw_mode->standby) { + mc34708_reg->suspend_off = false; + mc34708_reg->req_mode_standby = hw_mode->standby; + } else { + /* If configured for off in standby we still need a mode to + * use when .set_suspend_enable() is called. + * That mode depends if the regulator has a mode bit. + */ + mc34708_reg->suspend_off = true; + if (mc34708_ldo_has_mode_bit(mc34708_reg)) + mc34708_reg->req_mode_standby = REGULATOR_MODE_STANDBY; + else + mc34708_reg->req_mode_standby = REGULATOR_MODE_NORMAL; + } + + return 0; +} + +static int mc34708_ldo_enable(struct regulator_dev *rdev) +{ + struct mc34708_regulator *mc34708_reg = rdev->reg_data; + + return mc34708_update_bits(mc34708_reg, + rdev->desc->enable_reg, + rdev->desc->enable_mask, + 1); +} + +static int mc34708_ldo_disable(struct regulator_dev *rdev) +{ + struct mc34708_regulator *mc34708_reg = rdev->reg_data; + + return mc34708_update_bits(mc34708_reg, + rdev->desc->enable_reg, + rdev->desc->enable_mask, + 0); +} + +static int mc34708_ldo_is_enabled(struct regulator_dev *rdev) +{ + struct mc34708_regulator *mc34708_reg = rdev->reg_data; + + return mc34708_read_bits(mc34708_reg, + rdev->desc->enable_reg, + rdev->desc->enable_mask); +} + +static int mc34708_ldo_vhalf_get_voltage(struct regulator_dev *rdev) +{ + int ret; + + if (!rdev->supply) + return -EINVAL; + + ret = regulator_get_voltage(rdev->supply); + if (ret > 0) + ret /= 2; + + return ret; +} + +static unsigned int mc34708_ldo_get_mode(struct regulator_dev *rdev) +{ + struct mc34708_regulator *mc34708_reg = rdev->reg_data; + + return mc34708_reg->req_mode_normal; +} + +static int mc34708_ldo_set_mode(struct regulator_dev *rdev, unsigned int mode) +{ + struct mc34708_regulator *mc34708_reg = rdev->reg_data; + int ret; + + if (!mc34708_ldo_has_mode_bit(mc34708_reg)) + return 0; + + ret = mc34708_update_hw_mode(mc34708_reg, + mode, + mc34708_reg->req_mode_standby); + if (!ret) + mc34708_reg->req_mode_normal = mode; + + return ret; +} + +static int mc34708_ldo_set_suspend_enable(struct regulator_dev *rdev) +{ + struct mc34708_regulator *mc34708_reg = rdev->reg_data; + int ret; + + ret = mc34708_update_hw_mode(mc34708_reg, + mc34708_reg->req_mode_normal, + mc34708_reg->req_mode_standby); + if (!ret) + mc34708_reg->suspend_off = false; + + return ret; +} + +static int mc34708_ldo_set_suspend_disable(struct regulator_dev *rdev) +{ + struct mc34708_regulator *mc34708_reg = rdev->reg_data; + int ret; + + ret = mc34708_update_hw_mode(mc34708_reg, + mc34708_reg->req_mode_normal, + 0); + if (!ret) + mc34708_reg->suspend_off = true; + + return ret; +} + +static int mc34708_ldo_set_suspend_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct mc34708_regulator *mc34708_reg = rdev->reg_data; + int ret; + + if (!mc34708_ldo_has_mode_bit(mc34708_reg)) + return 0; + + ret = mc34708_update_hw_mode(mc34708_reg, + mc34708_reg->req_mode_normal, + mc34708_reg->suspend_off ? 0 : mode); + if (!ret) + mc34708_reg->req_mode_standby = mode; + + return ret; +} + +static const struct mc34708_regulator_kind mc34708_sw_kind = { + .setup = mc34708_sw_setup, + .hw_modes = mc34708_sw_modes, + .num_hw_modes = ARRAY_SIZE(mc34708_sw_modes), + .of_map_mode = mc34708_sw_of_map_mode, + .ops = { + .enable = mc34708_sw_enable, + .disable = mc34708_sw_disable, + .is_enabled = mc34708_sw_is_enabled, + + .list_voltage = regulator_list_voltage_linear, + .get_voltage_sel = mc34708_get_voltage_sel, + .set_voltage_sel = mc34708_set_voltage_sel, + + .get_mode = mc34708_sw_get_mode, + .set_mode = mc34708_sw_set_mode, + + .set_suspend_voltage = mc34708_set_suspend_voltage, + .set_suspend_enable = mc34708_sw_set_suspend_enable, + .set_suspend_disable = mc34708_sw_set_suspend_disable, + .set_suspend_mode = mc34708_sw_set_suspend_mode, + }, +}; + +static const struct mc34708_regulator_kind mc34708_swbst_kind = { + .setup = mc34708_swbst_setup, + .of_map_mode = mc34708_sw_of_map_mode, + .ops = { + .enable = mc34708_swbst_enable, + .disable = mc34708_swbst_disable, + .is_enabled = mc34708_swbst_is_enabled, + + .list_voltage = regulator_list_voltage_linear, + .get_voltage_sel = mc34708_get_voltage_sel, + .set_voltage_sel = mc34708_set_voltage_sel, + + .get_mode = mc34708_swbst_get_mode, + .set_mode = mc34708_swbst_set_mode, + + .set_suspend_enable = mc34708_swbst_set_suspend_enable, + .set_suspend_disable = mc34708_swbst_set_suspend_disable, + .set_suspend_mode = mc34708_swbst_set_suspend_mode, + }, +}; + +static const struct mc34708_regulator_kind mc34708_ldo_kind = { + .setup = mc34708_ldo_setup, + .hw_modes = mc34708_ldo_modes, + .num_hw_modes = ARRAY_SIZE(mc34708_ldo_modes), + .of_map_mode = mc34708_ldo_of_map_mode, + .ops = { + .enable = mc34708_ldo_enable, + .disable = mc34708_ldo_disable, + .is_enabled = mc34708_ldo_is_enabled, + + .list_voltage = regulator_list_voltage_table, + .get_voltage_sel = mc34708_get_voltage_sel, + .set_voltage_sel = mc34708_set_voltage_sel, + + .get_mode = mc34708_ldo_get_mode, + .set_mode = mc34708_ldo_set_mode, + + .set_suspend_enable = mc34708_ldo_set_suspend_enable, + .set_suspend_disable = mc34708_ldo_set_suspend_disable, + .set_suspend_mode = mc34708_ldo_set_suspend_mode, + }, +}; + +static const struct mc34708_regulator_kind mc34708_ldo_fixed_kind = { + .ops = { + .enable = mc34708_ldo_enable, + .disable = mc34708_ldo_disable, + .is_enabled = mc34708_ldo_is_enabled, + }, +}; + +static const struct mc34708_regulator_kind mc34708_ldo_vhalf_kind = { + .ops = { + .enable = mc34708_ldo_enable, + .disable = mc34708_ldo_disable, + .is_enabled = mc34708_ldo_is_enabled, + .get_voltage = mc34708_ldo_vhalf_get_voltage, + }, +}; + +static const unsigned int mc34708_pll_volt_table[] = { + 1200000, 1250000, 1500000, 1800000 +}; + +static const unsigned int mc34708_vusb2_volt_table[] = { + 2500000, 2600000, 2750000, 3000000 +}; + +static const unsigned int mc34708_vdac_volt_table[] = { + 2500000, 2600000, 270000, 2775000 +}; + +static const unsigned int mc34708_vgen1_volt_table[] = { + 1200000, 1250000, 1300000, 1350000, 1400000, 1450000, 1500000, 1550000 +}; + +static const unsigned int mc34708_vgen2_volt_table[] = { + 2500000, 2700000, 2800000, 2900000, 3000000, 3100000, 3150000, 3300000 +}; + +static struct mc34708_regulator_def mc34708_regulator_defs[] = { + /* Buck regulators */ + { + .name = "sw1", + .kind = &mc34708_sw_kind, + .supply_name = "vinsw1", + .min_uV = 650000, + .uV_step = 12500, + .n_voltages = 64, + .vsel_reg = 0x18, + .vsel_mask = (0x3f << 0), + .vsel_stdby_mask = (0x3f << 6), + .mode_reg = 0x1c, + .mode_mask = (0xf << 0), + }, + { + .name = "sw2", + .kind = &mc34708_sw_kind, + .supply_name = "vinsw2", + .min_uV = 650000, + .uV_step = 12500, + .n_voltages = 64, + .vsel_reg = 0x19, + .vsel_mask = (0x3f << 0), + .vsel_stdby_mask = (0x3f << 6), + .mode_reg = 0x1c, + .mode_mask = (0xf << 14), + }, + { + .name = "sw3", + .kind = &mc34708_sw_kind, + .supply_name = "vinsw3", + .min_uV = 650000, + .uV_step = 25000, + .n_voltages = 32, + .vsel_reg = 0x19, + .vsel_mask = (0x1f << 12), + .vsel_stdby_mask = (0x1f << 18), + .mode_reg = 0x1d, + .mode_mask = (0xf << 0), + }, + { + .name = "sw4a", + .kind = &mc34708_sw_kind, + .supply_name = "vinsw4a", + .min_uV = 1200000, + .uV_step = 25000, + .n_voltages = 27, + .vsel_reg = 0x1a, + .vsel_mask = (0x1f << 0), + .vsel_stdby_mask = (0x1f << 5), + .mode_reg = 0x1d, + .mode_mask = (0xf << 6), + }, + { + .name = "sw4b", + .kind = &mc34708_sw_kind, + .supply_name = "vinsw4b", + .min_uV = 1200000, + .uV_step = 25000, + .n_voltages = 27, + .vsel_reg = 0x1a, + .vsel_mask = (0x1f << 12), + .vsel_stdby_mask = (0x1f << 17), + .mode_reg = 0x1d, + .mode_mask = (0xf << 12), + }, + { + .name = "sw5", + .kind = &mc34708_sw_kind, + .supply_name = "vinsw5", + .min_uV = 1200000, + .uV_step = 25000, + .n_voltages = 27, + .vsel_reg = 0x1b, + .vsel_mask = (0x1f << 0), + .vsel_stdby_mask = (0x1f << 10), + .mode_reg = 0x1d, + .mode_mask = (0xf << 18), + }, + + /* SWBST regulator */ + { + .name = "swbst", + .kind = &mc34708_swbst_kind, + .supply_name = "vinswbst", + .min_uV = 5000000, + .uV_step = 50000, + .n_voltages = 4, + .vsel_reg = 0x1f, + .vsel_mask = (0x3 << 0), + .mode_reg = 0x1f, + .mode_mask = (0x3 << 2), + .mode_stdby_mask = (0x3 << 5), + }, + + /* LDO regulators */ + { + .name = "vpll", + .kind = &mc34708_ldo_kind, + .enable_reg = 0x20, + .enable_mask = (1 << 15), + .volt_table = mc34708_pll_volt_table, + .n_voltages = ARRAY_SIZE(mc34708_pll_volt_table), + .mode_reg = 0x20, + .mode_mask = (1 << 16), + }, + { + .name = "vrefddr", + .kind = &mc34708_ldo_vhalf_kind, + .supply_name = "vinrefddr", + .enable_reg = 0x20, + .enable_mask = (1 << 10), + }, + { + .name = "vusb", + .kind = &mc34708_ldo_fixed_kind, + .enable_reg = 0x20, + .enable_mask = (1 << 3), + .fixed_uV = 3300000, + .n_voltages = 1, + }, + { + .name = "vusb2", + .kind = &mc34708_ldo_kind, + .enable_reg = 0x20, + .enable_mask = (1 << 18), + .volt_table = mc34708_vusb2_volt_table, + .n_voltages = ARRAY_SIZE(mc34708_vusb2_volt_table), + .mode_reg = 0x20, + .mode_mask = (3 << 19), + }, + { + .name = "vdac", + .kind = &mc34708_ldo_kind, + .enable_reg = 0x20, + .enable_mask = (1 << 4), + .volt_table = mc34708_vdac_volt_table, + .n_voltages = ARRAY_SIZE(mc34708_vdac_volt_table), + .mode_reg = 0x20, + .mode_mask = (3 << 5), + }, + { + .name = "vgen1", + .kind = &mc34708_ldo_kind, + .enable_reg = 0x20, + .enable_mask = (1 << 0), + .volt_table = mc34708_vgen1_volt_table, + .n_voltages = ARRAY_SIZE(mc34708_vgen1_volt_table), + .mode_reg = 0x20, + .mode_mask = (1 << 1), + }, + { + .name = "vgen2", + .kind = &mc34708_ldo_kind, + .enable_reg = 0x20, + .enable_mask = (1 << 12), + .volt_table = mc34708_vgen2_volt_table, + .n_voltages = ARRAY_SIZE(mc34708_vgen2_volt_table), + .mode_reg = 0x20, + .mode_mask = (3 << 13), + }, +}; + +/* + * Setting some LDO standby states also requires changing the normal state. + * Therefore save the LDO configuration register on suspend and restore it + * on resume. + * + * This works because .set_suspend_X are called by the platform suspend handler + * AFTER device suspend + */ +#define MC34708_REG_REGMODE0 0x20 + +static int mc34708_suspend(struct device *dev) +{ + struct mc34708_drv_data *mc34708_data = dev_get_drvdata(dev); + + return mc13xxx_reg_read(mc34708_data->regulators[0].mc13xxx, + MC34708_REG_REGMODE0, + &mc34708_data->saved_regmode0); +} + +static int mc34708_resume(struct device *dev) +{ + struct mc34708_drv_data *mc34708_data = dev_get_drvdata(dev); + + return mc13xxx_reg_write(mc34708_data->regulators[0].mc13xxx, + MC34708_REG_REGMODE0, + mc34708_data->saved_regmode0); +} + +static int mc34708_regulator_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int num_regs; + struct mc34708_drv_data *mc34708_data; + struct mc34708_regulator *mc34708_reg; + struct device_node *regs_np, *reg_np; + struct regulator_dev *rdev; + int ret; + int i; + + regs_np = of_get_child_by_name(dev->parent->of_node, "regulators"); + if (!regs_np) + return -ENODEV; + + dev->of_node = regs_np; + + num_regs = of_get_child_count(regs_np); + mc34708_data = devm_kzalloc(dev, sizeof(*mc34708_data) + + num_regs * sizeof(*mc34708_reg), + GFP_KERNEL); + if (!mc34708_data) { + ret = -ENOMEM; + goto out; + } + + dev_set_drvdata(dev, mc34708_data); + + mc34708_reg = mc34708_data->regulators; + for_each_child_of_node(regs_np, reg_np) { + const struct mc34708_regulator_def *rd; + bool found; + + found = false; + for (i = 0; i < ARRAY_SIZE(mc34708_regulator_defs); i++) { + rd = &mc34708_regulator_defs[i]; + + if (!of_node_cmp(reg_np->name, rd->name)) { + found = true; + break; + } + } + + if (!found) { + dev_warn(dev, "Unknown regulator '%s'\n", reg_np->name); + continue; + } + + mc34708_reg->mc13xxx = dev_get_drvdata(dev->parent); + mc34708_reg->dev = dev; + mc34708_reg->def = rd; + mc34708_reg->desc.name = rd->name; + mc34708_reg->desc.supply_name = rd->supply_name; + mc34708_reg->desc.enable_reg = rd->enable_reg; + mc34708_reg->desc.enable_mask = rd->enable_mask; + mc34708_reg->desc.n_voltages = rd->n_voltages; + mc34708_reg->desc.fixed_uV = rd->fixed_uV; + mc34708_reg->desc.min_uV = rd->min_uV; + mc34708_reg->desc.uV_step = rd->uV_step; + mc34708_reg->desc.volt_table = rd->volt_table; + mc34708_reg->desc.vsel_reg = rd->vsel_reg; + mc34708_reg->desc.vsel_mask = rd->vsel_mask; + mc34708_reg->desc.of_map_mode = rd->kind->of_map_mode; + mc34708_reg->desc.ops = &rd->kind->ops; + + mc34708_reg->config.init_data = of_get_regulator_init_data(dev, + reg_np, + &mc34708_reg->desc); + mc34708_reg->config.dev = dev; + mc34708_reg->config.of_node = reg_np; + mc34708_reg->config.driver_data = mc34708_reg; + + if (rd->kind->setup) { + ret = rd->kind->setup(mc34708_reg); + if (ret) + goto out; + } + + rdev = devm_regulator_register(dev, + &mc34708_reg->desc, + &mc34708_reg->config); + if (IS_ERR(rdev)) { + ret = PTR_ERR(rdev); + dev_err(dev, + "Failed to register regulator %s (%d)\n", + rd->name, ret); + goto out; + } + + mc34708_reg++; + } + + ret = 0; + +out: + of_node_put(regs_np); + + return ret; +} + +static SIMPLE_DEV_PM_OPS(mc34708_pm, mc34708_suspend, mc34708_resume); + +static struct platform_driver mc34708_regulator_driver = { + .driver = { + .name = "mc34708-regulator", + .pm = &mc34708_pm, + }, + .probe = mc34708_regulator_probe, +}; + +static int __init mc34708_regulator_init(void) +{ + return platform_driver_register(&mc34708_regulator_driver); +} +subsys_initcall(mc34708_regulator_init); + +static void __exit mc34708_regulator_exit(void) +{ + platform_driver_unregister(&mc34708_regulator_driver); +} +module_exit(mc34708_regulator_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Martin Fuzzey "); +MODULE_DESCRIPTION("Regulator Driver for Freescale MC34708 PMIC"); +MODULE_ALIAS("platform:mc34708-regulator");