From patchwork Fri Jun 21 21:25:42 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nishanth Menon X-Patchwork-Id: 2764701 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.19.201]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 6AC329F39E for ; Fri, 21 Jun 2013 21:31:33 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 4B5F5201DA for ; Fri, 21 Jun 2013 21:31:31 +0000 (UTC) Received: from casper.infradead.org (casper.infradead.org [85.118.1.10]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id C74662015D for ; Fri, 21 Jun 2013 21:31:27 +0000 (UTC) Received: from merlin.infradead.org ([2001:4978:20e::2]) by casper.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1Uq8sc-0000xw-Sh; Fri, 21 Jun 2013 21:28:29 +0000 Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1Uq8rs-000655-RJ; Fri, 21 Jun 2013 21:27:40 +0000 Received: from comal.ext.ti.com ([198.47.26.152]) by merlin.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1Uq8qh-0005xf-RZ for linux-arm-kernel@lists.infradead.org; Fri, 21 Jun 2013 21:26:51 +0000 Received: from dlelxv90.itg.ti.com ([172.17.2.17]) by comal.ext.ti.com (8.13.7/8.13.7) with ESMTP id r5LLQ31m015346; Fri, 21 Jun 2013 16:26:03 -0500 Received: from DLEE70.ent.ti.com (dlee70.ent.ti.com [157.170.170.113]) by dlelxv90.itg.ti.com (8.14.3/8.13.8) with ESMTP id r5LLQ3Yc008399; Fri, 21 Jun 2013 16:26:03 -0500 Received: from dlelxv22.itg.ti.com (172.17.1.197) by DLEE70.ent.ti.com (157.170.170.113) with Microsoft SMTP Server id 14.2.342.3; Fri, 21 Jun 2013 16:26:03 -0500 Received: from localhost (kahuna.am.dhcp.ti.com [128.247.91.59]) by dlelxv22.itg.ti.com (8.13.8/8.13.8) with ESMTP id r5LLQ3Ok015804; Fri, 21 Jun 2013 16:26:03 -0500 From: Nishanth Menon To: =?UTF-8?q?Beno=C3=AEt=20Cousson?= , Mark Brown , Tony Lindgren Subject: [RFC PATCH V2 1/8] regulator: Introduce OMAP regulator to control PMIC over VC/VP Date: Fri, 21 Jun 2013 16:25:42 -0500 Message-ID: <1371849949-12649-2-git-send-email-nm@ti.com> X-Mailer: git-send-email 1.7.9.5 In-Reply-To: <1371849949-12649-1-git-send-email-nm@ti.com> References: <1371849949-12649-1-git-send-email-nm@ti.com> MIME-Version: 1.0 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20130621_172628_372716_FD8EE5C4 X-CRM114-Status: GOOD ( 28.89 ) X-Spam-Score: -8.4 (--------) Cc: Nishanth Menon , Grygorii Strashko , linux-doc@vger.kernel.org, Kevin Hilman , devicetree-discuss@lists.ozlabs.org, linux-kernel@vger.kernel.org, Taras Kondratiuk , linux-omap@vger.kernel.org, linux-arm-kernel@lists.infradead.org X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.15 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=-5.7 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_MED, 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 Texas Instrument's OMAP SoC generations since OMAP3-5 introduced an TI custom hardware block to better facilitate and standardize integration of Power Management ICs which communicate over I2C. In fact, many custom PMICs were designed to be usable over this interface. On the other hand, generic PMICs which are compatible to the I2C protocol supported by Voltage controller may also be used. In general, the following categories of PMICs exist: a) PMICs which are completely controlled by voltage controller/voltage processor pair - this implies even configuration needs to be done over the same interface. Example: TPS62361 used on PandaBoard-ES and many OMAP4460 based platforms. Few Maxim and Fairchild PMICs used on certain products would fall in this category. - Note: in this case, there may not even exist/needed to support an "traditional Linux regulator driver" b) PMICs which provide two views over two I2C interfaces - however, voltage can only be set only on one of them. Example: TWL4030/5030: allows us to use Voltage controller once we set up a bit over regular I2C - This is used in OMAP3. TWO6030/TWL6032 - configuration *has* to be performed over regular i2c (example smps_offset) and voltage control *has* to be performed by using voltage controller/processor. - Note: in this case, there may exist "traditional Linux regulator driver" however, it may not support in any form SMPS modelling for that part of the device which is exposed to voltage controller/processor. c) PMICs which allow voltage and configurations over either i2c interfaces - TWL6035/TWL6037/Palmas family of TI processor - Note: in this case, there may exist "traditional Linux regulator driver" and, it may support in some form SMPS modelling for that part of the device which is exposed to voltage controller/processor. d) custom PMICs which are setup so that no configuration is needed to be performed and they operate with preset register offsets and minimal conferability using voltage controller/processor. - Note: in this case, there may not even exist/needed to support an "traditional Linux regulator driver" However, no matter the type of PMIC used, the OMAP view of a PMIC is generic when used over voltage controller/processor. We attempt to model this generic view of the regulator represented by OMAP SoC. Alternative to this approach would be to "hack" into the get voltage/set voltage interfaces of regulator drivers which represent the rest of the PMIC controlled over regular I2C interface and re-route the requests to voltage controller/processor. But, by doing that, we needlessly create additional code which may be abstracted out into device tree node information. Since the regulator node representing PMIC, voltage controller, processors are probed at varied points in time, probe deferral is used to sequence in the right order. It is expected by the driver to register omap_pmic_register_controller_ops providing mandatory operations at the earliest possible opportunity. Despite the current SoCs implementing the solution based on voltage controller and voltage processor (which are part of the OMAP SoC modules which enable Adaptive Voltage Scaling class support), the interfaces are generic enough to handle future equivalent modules. [grygorii.strashko@ti.com, taras@ti.com: co-developer] Signed-off-by: Grygorii Strashko Signed-off-by: Taras Kondratiuk Signed-off-by: Nishanth Menon --- .../bindings/regulator/omap-pmic-regulator.txt | 41 ++ drivers/regulator/Kconfig | 12 + drivers/regulator/Makefile | 1 + drivers/regulator/omap-pmic-regulator.c | 638 ++++++++++++++++++++ include/linux/regulator/omap-pmic-regulator.h | 162 +++++ 5 files changed, 854 insertions(+) create mode 100644 Documentation/devicetree/bindings/regulator/omap-pmic-regulator.txt create mode 100644 drivers/regulator/omap-pmic-regulator.c create mode 100644 include/linux/regulator/omap-pmic-regulator.h diff --git a/Documentation/devicetree/bindings/regulator/omap-pmic-regulator.txt b/Documentation/devicetree/bindings/regulator/omap-pmic-regulator.txt new file mode 100644 index 0000000..6991e58 --- /dev/null +++ b/Documentation/devicetree/bindings/regulator/omap-pmic-regulator.txt @@ -0,0 +1,41 @@ +Generic Power Management IC(PMIC) Regulator for Texas Instruments OMAP SoCs + +Required Properties: +- compatible: Should be one of: + - "ti,omap-twl4030-vdd1" - TWL4030/5030/TPS65950 VDD1 + - "ti,omap-twl4030-vdd2" - TWL4030/5030/TPS65950 VDD2 + - "ti,omap-twl6030-vcore1" - TWL6030 VCORE1 + - "ti,omap-twl6030-vcore2" - TWL6030 VCORE2 + - "ti,omap-twl6030-vcore3" - TWL6030 VCORE3 + - "ti,omap-tps62361" - TPS62361 VSEL1 + - "ti,omap-twl6032-smps1" - TWL6032 SMPS1 + - "ti,omap-twl6032-smps2" - TWL6032 SMPS2 + - "ti,omap-twl6032-smps5" - TWL6032 SMPS5 + - "ti,omap-twl6035-smps1" - TWL6035/6037 in SMPS1, 12, 123 configuration + - "ti,omap-twl6035-smps4" - TWL6035/6037 in SMPS4, 45 configuration + - "ti,omap-twl6035-smps8" - TWL6035/6037 in SMPS8 configuration + +Optional Properties: +- gpios: OF device-tree gpio specification - can be an array, will be setup + in the order of definition and set to the flags. +- pinctrl: OF device-tree pinctrl definitions as needed (usually for the GPIOs) +- ti,boot-voltage-micro-volts - voltage in microvolts that bootloader is leaving + over the PMIC at. This may be 'PMIC data manual configuration' if + bootloader does not set an value, or appropritate value. + + +Example: Sample PMIC description +omap_tps62361: tps62361 { + compatible = "ti,omap-tps62361"; +}; + +/* Board Specific configuration */ +&omap_tps62361 { + pinctrl-names = "default"; + pinctrl-0 = < + &tps62361_wkgpio_pins + >; + gpios = <&gpio1 7 1>; /* gpio_wk7 */ + + ti,boot-voltage-micro-volts=<1200000>; +}; diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 9296425..e14b534 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -482,6 +482,18 @@ config REGULATOR_TI_ABB on TI SoCs may be unstable without enabling this as it provides device specific optimized bias to allow/optimize functionality. +config REGULATOR_TI_OMAP_PMIC + tristate "TI Generic regulator for Voltage Processor / Controller" + depends on ARCH_OMAP + help + Select this option to support PMICs over Texas Instruments' + custom Voltage Processor + Voltage Controller data interface + used in OMAP SoCs. This is a specific "write-only" interface + designed to interface with I2C based PMICs. + + This option enables the regulator driver representing the PMIC + on the OMAP VC/VP hardware. + config REGULATOR_VEXPRESS tristate "Versatile Express regulators" depends on VEXPRESS_CONFIG diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 26e6c4a..8874e74 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -64,6 +64,7 @@ obj-$(CONFIG_REGULATOR_TPS65912) += tps65912-regulator.o obj-$(CONFIG_REGULATOR_TPS80031) += tps80031-regulator.o obj-$(CONFIG_REGULATOR_TWL4030) += twl-regulator.o obj-$(CONFIG_REGULATOR_TI_ABB) += ti-abb-regulator.o +obj-$(CONFIG_REGULATOR_TI_OMAP_PMIC) += omap-pmic-regulator.o obj-$(CONFIG_REGULATOR_VEXPRESS) += vexpress.o obj-$(CONFIG_REGULATOR_WM831X) += wm831x-dcdc.o obj-$(CONFIG_REGULATOR_WM831X) += wm831x-isink.o diff --git a/drivers/regulator/omap-pmic-regulator.c b/drivers/regulator/omap-pmic-regulator.c new file mode 100644 index 0000000..40c725f --- /dev/null +++ b/drivers/regulator/omap-pmic-regulator.c @@ -0,0 +1,638 @@ +/* + * OMAP Generic PMIC Regulator + * + * Idea based on arch/arm/mach-omap2/omap_twl.c + * Copyright (C) 2010 Texas Instruments Incorporated. + * Thara Gopinath + * Copyright (C) 2009 Texas Instruments Incorporated. + * Nishanth Menon + * Copyright (C) 2009 Nokia Corporation + * Paul Walmsley + * + * Copyright (C) 2013 Texas Instruments Incorporated + * Taras Kondratiuk + * Grygorii Strashko + * Nishanth Menon + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "omap-pmic" + +static DEFINE_MUTEX(omap_pmic_cops_mutex); +static struct omap_pmic_controller_ops *pmic_cops; + +/** + * omap_pmic_register_controller_ops() - Register voltage operations + * @cops: voltage operations + * + * It is expected that appropriate controller register it's functions + * with this driver using this interface, If this is not done, the probe + * for the corresponding device will defer till it fails. + * + * Return: -EBUSY if already registered, else returns 0 + */ +int omap_pmic_register_controller_ops(struct omap_pmic_controller_ops *cops) +{ + int ret = 0; + + mutex_lock(&omap_pmic_cops_mutex); + if (pmic_cops) { + pr_err("Controller operations already registered\n"); + ret = -EBUSY; + goto out; + } + if (!cops->devm_pmic_register || !cops->voltage_set || + !cops->voltage_get || !cops->voltage_get_range) { + pr_err("Missing operations!\n"); + ret = -EINVAL; + goto out; + } + + pmic_cops = cops; +out: + mutex_unlock(&omap_pmic_cops_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(omap_pmic_register_controller_ops); + +/** + * omap_pmic_vsel_to_uv() - Convert voltage selector(vsel) to microvolts + * @pmic: pointer to pmic struct + * @vsel: voltage selector(vsel) + * @uv: If conversion is successful, returns the voltage in micro volts + * + * Return: 0 if conversion is successful and *uv has proper value, else + * appropriate error value for failure. + */ +static int omap_pmic_vsel_to_uv(struct omap_pmic *pmic, u8 vsel, u32 *uv) +{ + u32 tmp = vsel; + const struct omap_pmic_info *info; + + if (!pmic || !uv) { + pr_err("Bad parameters pmic=%p uv=%p!\n", pmic, uv); + return -EINVAL; + } + info = pmic->info; + + if (info->voltage_selector_mask) { + tmp &= info->voltage_selector_mask; + tmp >>= __ffs(info->voltage_selector_mask); + } + + if (!tmp && info->voltage_selector_zero) + goto out; + + tmp -= info->voltage_selector_offset; + tmp *= info->step_size_uV; + tmp += info->min_uV; + + if (tmp < info->min_uV || tmp > info->max_uV) { + dev_dbg(pmic->dev, "%s: Out of range 0x%02x[%d] (%d <-> %d)\n", + __func__, vsel, tmp, info->min_uV, info->max_uV); + return -ERANGE; + } + +out: + *uv = tmp; + dev_dbg(pmic->dev, "%s: uv=%d vsel=0x%02x\n", __func__, *uv, vsel); + + return 0; +} + +/** + * omap_pmic_uv_to_vsel() - Convert microvolts to voltage selector(vsel) + * @pmic: pointer to pmic struct + * @uv: voltage in micro volts + * @vsel: If conversion is successful, voltage selector(vsel) + * + * Return: 0 if conversion is successful and *vsel has proper value, else + * appropriate error value for failure. + */ +static int omap_pmic_uv_to_vsel(struct omap_pmic *pmic, u32 uv, u8 *vsel) +{ + u32 tmp = uv; + const struct omap_pmic_info *info; + + if (!pmic || !vsel) { + pr_err("Bad parameters pmic=%p vsel=%p!\n", pmic, vsel); + return -EINVAL; + } + info = pmic->info; + + if (!tmp && info->voltage_selector_zero) + goto skip_convert; + + if (tmp > info->max_uV) + goto skip_convert; + + tmp -= info->min_uV; + tmp = DIV_ROUND_UP(tmp, info->step_size_uV); + + tmp += info->voltage_selector_offset; + +skip_convert: + if (tmp > 0xFF) { + dev_dbg(pmic->dev, "%s: Out of range 0x%04x[%d] (%d - %d)\n", + __func__, tmp, uv, info->min_uV, info->max_uV); + return -ERANGE; + } + if (info->voltage_selector_mask) { + tmp <<= __ffs(info->voltage_selector_mask); + if (tmp > 0xFF) { + dev_warn(pmic->dev, "%s: Out of range 0x%04x[%d]\n", + __func__, tmp, uv); + return -ERANGE; + } + tmp &= info->voltage_selector_mask; + } + + tmp |= info->voltage_selector_setbits; + + *vsel = tmp; + dev_dbg(pmic->dev, "%s: uv=%d vsel=0x%02x\n", __func__, uv, *vsel); + + return 0; +} + +/** + * omap_pmic_set_voltage() - regulator interface to set voltage + * @rdev: regulator device + * @min_uV: min voltage in micro-volts + * @max_uV: max voltage in micro-volts + * @unused: unused.. we dont use sel + * + * Return: -ERANGE for out of range values, appropriate error code if conversion + * fails, else returns 0. + */ +static int omap_pmic_set_voltage(struct regulator_dev *rdev, int min_uV, + int max_uV, unsigned *unused) +{ + struct omap_pmic *pmic = rdev_get_drvdata(rdev); + + return pmic_cops->voltage_set(pmic->v_dev, min_uV); +} + +/** + * omap_pmic_get_voltage() - regulator interface to get voltage + * @rdev: regulator device + * + * Return: current voltage set on PMIC OR appropriate error value + */ +static int omap_pmic_get_voltage(struct regulator_dev *rdev) +{ + struct omap_pmic *pmic = rdev_get_drvdata(rdev); + int ret; + u32 uv; + + ret = pmic_cops->voltage_get(pmic->v_dev, &uv); + if (ret) + return ret; + + return uv; +} + +static struct omap_pmic_ops omap_generic_pmic_ops = { + .vsel_to_uv = omap_pmic_vsel_to_uv, + .uv_to_vsel = omap_pmic_uv_to_vsel, +}; + +static struct regulator_ops omap_pmic_reg_ops = { + .list_voltage = regulator_list_voltage_linear, + + .set_voltage = omap_pmic_set_voltage, + .get_voltage = omap_pmic_get_voltage, +}; + +/** + * omap_pmic_of_setup_gpios() - Setup GPIO array if needed. + * @dev: device to pick up the gpios from + */ +static int omap_pmic_of_setup_gpios(struct device *dev) +{ + struct device_node *node = dev->of_node; + int num_gpios, i, ret; + + num_gpios = of_gpio_count(node); + if (num_gpios < 0) + return 0; + + for (i = 0; i < num_gpios; i++) { + int gpio, level; + enum of_gpio_flags flags; + + gpio = of_get_gpio_flags(node, i, &flags); + if (!gpio_is_valid(gpio)) { + dev_err(dev, "Invalid GPIO[%d]: %d\n", i, gpio); + return -EINVAL; + } + + ret = devm_gpio_request(dev, gpio, dev_name(dev)); + if (ret) { + dev_err(dev, "Unable to get GPIO %d (%d)\n", gpio, ret); + return ret; + } + level = (flags & OF_GPIO_ACTIVE_LOW) ? 0 : 1; + ret = gpio_direction_output(gpio, level); + if (ret) { + dev_err(dev, "Failed to set GPIO %d to %d (%d)\n", + gpio, level, ret); + return ret; + } + dev_dbg(dev, "GPIO=%d set_to=%d flags=0x%08x\n", gpio, + level, flags); + } + + return 0; +} + +/** + * omap_pmic_parse_of() - Do DT OF node parsing + * @pmic: pointer to PMIC + */ +static int omap_pmic_parse_of(struct omap_pmic *pmic) +{ + struct device *dev = pmic->dev; + struct device_node *node = dev->of_node; + u32 val = 0; + char *pname; + int ret; + + pname = "ti,boot-voltage-micro-volts"; + ret = of_property_read_u32(node, pname, &val); + if (!ret) { + if (!val) + goto invalid_of_property; + pmic->boot_voltage_uV = val; + } + + return ret; + +invalid_of_property: + if (!ret) { + dev_err(dev, "Invalid value 0x%x[%d] in '%s' property.\n", + val, val, pname); + ret = -EINVAL; + } else { + dev_err(dev, "Missing/Invalid '%s' property - error(%d)\n", + pname, ret); + } + return ret; +} + +static const struct omap_pmic_info omap_twl4030_vdd1 = { + .slave_addr = 0x12, + .voltage_reg_addr = 0x00, + .cmd_reg_addr = 0x00, + .i2c_timeout_us = 200, + .slew_rate_uV = 4000, + .step_size_uV = 12500, + .min_uV = 600000, + .max_uV = 1450000, + .voltage_selector_offset = 0, + .voltage_selector_mask = 0x7F, + .voltage_selector_setbits = 0x0, + .voltage_selector_zero = false, +}; + +static const struct omap_pmic_info omap_twl4030_vdd2 = { + .slave_addr = 0x12, + .voltage_reg_addr = 0x01, + .cmd_reg_addr = 0x01, + .i2c_timeout_us = 200, + .slew_rate_uV = 4000, + .step_size_uV = 12500, + .min_uV = 600000, + .max_uV = 1450000, + .voltage_selector_offset = 0, + .voltage_selector_mask = 0x7F, + .voltage_selector_setbits = 0x0, + .voltage_selector_zero = false, +}; + +static const struct omap_pmic_info omap_twl6030_vcore1 = { + .slave_addr = 0x12, + .voltage_reg_addr = 0x55, + .cmd_reg_addr = 0x56, + .i2c_timeout_us = 200, + .slew_rate_uV = 9000, + .step_size_uV = 12660, + .min_uV = 709000, + .max_uV = 1418000, + .voltage_selector_offset = 0x1, + .voltage_selector_mask = 0x7F, + .voltage_selector_setbits = 0x0, + .voltage_selector_zero = true, +}; + +static const struct omap_pmic_info omap_twl6030_vcore2 = { + .slave_addr = 0x12, + .voltage_reg_addr = 0x5b, + .cmd_reg_addr = 0x5c, + .i2c_timeout_us = 200, + .slew_rate_uV = 9000, + .step_size_uV = 12660, + .min_uV = 709000, + .max_uV = 1418000, + .voltage_selector_offset = 0x1, + .voltage_selector_mask = 0x7F, + .voltage_selector_setbits = 0x0, + .voltage_selector_zero = true, +}; + +static const struct omap_pmic_info omap_twl6030_vcore3 = { + .slave_addr = 0x12, + .voltage_reg_addr = 0x61, + .cmd_reg_addr = 0x62, + .i2c_timeout_us = 200, + .slew_rate_uV = 9000, + .step_size_uV = 12660, + .min_uV = 709000, + .max_uV = 1418000, + .voltage_selector_offset = 0x1, + .voltage_selector_mask = 0x7F, + .voltage_selector_setbits = 0x0, + .voltage_selector_zero = true, +}; + +static const struct omap_pmic_setup_commands omap_tps62361_cmds[] = { + {.reg = 0x06, .cmd_val = 0x06}, /* TPS6236X_RAMP_CTRL 32mV/uS */ + {.reg = 0x04, .cmd_val = 0xc0}, /* TPS6236X_CTRL VSEL0 pull down */ + {.reg = 0x05, .cmd_val = 0x00}, /* REG_TPS6236X_TEMP enable tshut */ +}; + +static const struct omap_pmic_info omap_tps62361 = { + .slave_addr = 0x60, + .voltage_reg_addr = 0x01, + .cmd_reg_addr = 0x01, + .i2c_timeout_us = 200, + .slew_rate_uV = 32000, + .step_size_uV = 10000, + .min_uV = 500000, + .max_uV = 1770000, + .voltage_selector_offset = 0x0, + .voltage_selector_mask = 0x7F, + .voltage_selector_setbits = 0x80, /* PFM mode */ + .voltage_selector_zero = false, + .setup_command_list = omap_tps62361_cmds, + .setup_num_commands = ARRAY_SIZE(omap_tps62361_cmds), +}; + +static const struct omap_pmic_info omap_twl6032_smps1 = { + .slave_addr = 0x12, + .voltage_reg_addr = 0x55, + .cmd_reg_addr = 0x56, + .i2c_timeout_us = 200, + .slew_rate_uV = 9000, + .step_size_uV = 12660, + .min_uV = 709000, + .max_uV = 1418000, + .voltage_selector_offset = 0x1, + .voltage_selector_mask = 0x7F, + .voltage_selector_setbits = 0x0, + .voltage_selector_zero = true, +}; + +static const struct omap_pmic_info omap_twl6032_smps2 = { + .slave_addr = 0x12, + .voltage_reg_addr = 0x5b, + .cmd_reg_addr = 0x5c, + .i2c_timeout_us = 200, + .slew_rate_uV = 9000, + .step_size_uV = 12660, + .min_uV = 709000, + .max_uV = 1418000, + .voltage_selector_offset = 0x1, + .voltage_selector_mask = 0x7F, + .voltage_selector_setbits = 0x0, + .voltage_selector_zero = true, +}; + +static const struct omap_pmic_info omap_twl6032_smps5 = { + .slave_addr = 0x12, + .voltage_reg_addr = 0x49, + .cmd_reg_addr = 0x4a, + .i2c_timeout_us = 200, + .slew_rate_uV = 9000, + .step_size_uV = 12660, + .min_uV = 709000, + .max_uV = 1418000, + .voltage_selector_offset = 0x1, + .voltage_selector_mask = 0x7F, + .voltage_selector_setbits = 0x0, + .voltage_selector_zero = true, +}; + +static const struct omap_pmic_info omap_twl6035_smps1 = { + .slave_addr = 0x12, + .voltage_reg_addr = 0x23, + .cmd_reg_addr = 0x22, + .i2c_timeout_us = 200, + .slew_rate_uV = 220, + .step_size_uV = 10000, + .min_uV = 500000, + .max_uV = 1650000, + .voltage_selector_offset = 0x6, + .voltage_selector_mask = 0x7F, + .voltage_selector_setbits = 0x0, + .voltage_selector_zero = true, +}; + +static const struct omap_pmic_info omap_twl6035_smps4 = { + .slave_addr = 0x12, + .voltage_reg_addr = 0x2b, + .cmd_reg_addr = 0x2a, + .i2c_timeout_us = 200, + .slew_rate_uV = 220, + .step_size_uV = 10000, + .min_uV = 500000, + .max_uV = 1650000, + .voltage_selector_offset = 0x6, + .voltage_selector_mask = 0x7F, + .voltage_selector_setbits = 0x0, + .voltage_selector_zero = true, +}; + +static const struct omap_pmic_info omap_twl6035_smps8 = { + .slave_addr = 0x12, + .voltage_reg_addr = 0x37, + .cmd_reg_addr = 0x36, + .i2c_timeout_us = 200, + .slew_rate_uV = 220, + .step_size_uV = 10000, + .min_uV = 500000, + .max_uV = 1650000, + .voltage_selector_offset = 0x6, + .voltage_selector_mask = 0x7F, + .voltage_selector_setbits = 0x0, + .voltage_selector_zero = true, +}; + +static const struct of_device_id omap_pmic_of_match_tbl[] = { + {.compatible = "ti,omap-twl4030-vdd1", .data = &omap_twl4030_vdd1,}, + {.compatible = "ti,omap-twl4030-vdd2", .data = &omap_twl4030_vdd2,}, + {.compatible = "ti,omap-twl6030-vcore1", .data = &omap_twl6030_vcore1,}, + {.compatible = "ti,omap-twl6030-vcore2", .data = &omap_twl6030_vcore2,}, + {.compatible = "ti,omap-twl6030-vcore3", .data = &omap_twl6030_vcore3,}, + {.compatible = "ti,omap-tps62361", .data = &omap_tps62361,}, + {.compatible = "ti,omap-twl6032-smps1", .data = &omap_twl6032_smps1,}, + {.compatible = "ti,omap-twl6032-smps2", .data = &omap_twl6032_smps2,}, + {.compatible = "ti,omap-twl6032-smps5", .data = &omap_twl6032_smps5,}, + {.compatible = "ti,omap-twl6035-smps1", .data = &omap_twl6035_smps1,}, + {.compatible = "ti,omap-twl6035-smps4", .data = &omap_twl6035_smps4,}, + {.compatible = "ti,omap-twl6035-smps8", .data = &omap_twl6035_smps8,}, + {}, +}; +MODULE_DEVICE_TABLE(of, omap_pmic_of_match_tbl); + +static int omap_pmic_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + const struct of_device_id *match; + struct omap_pmic *pmic; + struct regulator_desc *desc; + struct regulation_constraints *c; + struct regulator_config config = { }; + struct regulator_init_data *initdata = NULL; + struct regulator_dev *rdev = NULL; + int ret = 0; + bool ops_ready; + + if (!node) { + dev_err(dev, "%s: missing device tree nodes?\n", __func__); + return -EINVAL; + } + + mutex_lock(&omap_pmic_cops_mutex); + ops_ready = pmic_cops ? true : false; + mutex_unlock(&omap_pmic_cops_mutex); + if (!ops_ready) { + dev_dbg(dev, "Voltage Operations not ready yet..\n"); + return -EPROBE_DEFER; + } + + match = of_match_device(omap_pmic_of_match_tbl, dev); + if (!match) { + /* We do not expect this to happen */ + dev_err(dev, "%s: Unable to match device\n", __func__); + return -ENODEV; + } + if (!match->data) { + dev_err(dev, "%s: Bad data in match\n", __func__); + return -EINVAL; + } + + desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL); + if (!desc) { + dev_err(dev, "%s: unable to allocate desc\n", __func__); + return -ENOMEM; + } + + pmic = devm_kzalloc(dev, sizeof(*pmic), GFP_KERNEL); + if (!pmic) { + dev_err(dev, "%s: unable to allocate pmic\n", __func__); + return -ENOMEM; + } + + /* Read mandatory OF parameters */ + pmic->dev = dev; + pmic->ops = &omap_generic_pmic_ops; + pmic->info = match->data; + + initdata = of_get_regulator_init_data(dev, node); + if (!initdata) { + dev_err(dev, "%s: Unable to alloc regulator init data\n", + __func__); + return -ENOMEM; + } + c = &initdata->constraints; + + /* Constraint to PMIC limits */ + if (pmic->info->min_uV > c->min_uV) + c->min_uV = pmic->info->min_uV; + if (pmic->info->max_uV < c->max_uV) + c->max_uV = pmic->info->max_uV; + + ret = omap_pmic_parse_of(pmic); + if (ret) + return ret; + + ret = omap_pmic_of_setup_gpios(dev); + if (ret) + return ret; + + pmic->v_dev = pmic_cops->devm_pmic_register(dev, pmic); + if (IS_ERR(pmic->v_dev)) { + dev_dbg(dev, "Registration of pmic failed (%d)\n", ret); + ret = PTR_ERR(pmic->v_dev); + return ret; + } + desc->name = dev_name(dev); + desc->owner = THIS_MODULE; + desc->type = REGULATOR_VOLTAGE; + desc->ops = &omap_pmic_reg_ops; + desc->uV_step = pmic->info->step_size_uV; + desc->ramp_delay = pmic->info->slew_rate_uV; + + c->valid_ops_mask |= REGULATOR_CHANGE_VOLTAGE; + c->always_on = true; + ret = pmic_cops->voltage_get_range(pmic->v_dev, &c->min_uV, &c->max_uV); + if (ret) { + dev_err(dev, "Voltage Range get failed (%d)\n", ret); + return ret; + } + + config.dev = dev; + config.init_data = initdata; + config.driver_data = pmic; + config.of_node = node; + + rdev = regulator_register(desc, &config); + if (IS_ERR(rdev)) { + ret = PTR_ERR(rdev); + dev_err(dev, "%s: failed to register regulator(%d)\n", + __func__, ret); + return ret; + } + + platform_set_drvdata(pdev, rdev); + + return ret; +} + +static struct platform_driver omap_pmic_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(omap_pmic_of_match_tbl), + }, + .probe = omap_pmic_probe, +}; +module_platform_driver(omap_pmic_driver); + +MODULE_DESCRIPTION("OMAP Generic PMIC Regulator"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); +MODULE_AUTHOR("Texas Instruments Inc."); diff --git a/include/linux/regulator/omap-pmic-regulator.h b/include/linux/regulator/omap-pmic-regulator.h new file mode 100644 index 0000000..db0bab4 --- /dev/null +++ b/include/linux/regulator/omap-pmic-regulator.h @@ -0,0 +1,162 @@ +/* + * OMAP Generic PMIC Regulator interfaces + * + * Idea based on arch/arm/mach-omap2/omap_twl.c and + * arch/arm/mach-omap2/voltage.h + * Copyright (C) 2010 Texas Instruments Incorporated. + * Thara Gopinath + * Copyright (C) 2009 Texas Instruments Incorporated. + * Nishanth Menon + * Copyright (C) 2009 Nokia Corporation + * Paul Walmsley + * + * Copyright (C) 2013 Texas Instruments Incorporated. + * Grygorii Strashko + * Nishanth Menon + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __POWER_OMAP_PMIC_H +#define __POWER_OMAP_PMIC_H + +struct omap_pmic; + +/** + * struct omap_pmic_setup_commands - setup commands over voltage controller + * @reg: device's i2c register address + * @cmd_val: command to send. + */ +struct omap_pmic_setup_commands { + u8 reg; + u8 cmd_val; +}; + +/** + * struct omap_pmic_ops - Conversion routines for voltage controller/processor + * @vsel_to_uv: convert voltage selector to micro-volts + * @uv_to_vsel: convert micro-volts to voltage selector + * + * voltage controller/processor drivers SHOULD NOT do modifications on vsel or + * make any assumptions about vsel. Instead, they may operate on micro-volts + * and request vsel conversion once they are ready to do hardware operations. + * + * This is provided over the omap_pmic structure. + */ +struct omap_pmic_ops { + int (*vsel_to_uv) (struct omap_pmic *pmic, u8 vsel, u32 *uv); + int (*uv_to_vsel) (struct omap_pmic *pmic, u32 uv, u8 *vsel); +}; + +/** + * struct omap_pmic_controller_ops - regulator operations implemented + * @devm_pmic_register: managed registration of an PMIC device with a specific + * voltage processor. Voltage processor provides an device + * handle which is remaining operations. + * NOTE: + * - This will be first interface to be invoked by + * omap_pmic regulator. + * - if the underlying layers are not ready, this is + * expected to return -EPROBE_DEFER + * - if failure, appropriate error code is expected. + * - This is expected to be a managed device to avoid + * explicit cleanup operations + * - Once this succeeds, this returns the pointer to + * the controller device and all other operations are + * expected to be ready for functionality. + * @voltage_set: set the voltage - expected to be synchronous. + * @voltage_get: get the current voltage in micro-volts set on PMIC. + * @voltage_get_range: Get minimum and maxium permissible operational voltage + * range for the device - used to set initial regulator + * constraints. + * + * These voltage processor interfaces are registered by voltage processor driver + * using omap_pmic_register_controller_ops. This allows the omap_pmic driver to + * operate with a specific voltage processor driver. + */ +struct omap_pmic_controller_ops { + struct device *(*devm_pmic_register) (struct device *dev, + struct omap_pmic *pmic); + int (*voltage_set) (struct device *control_dev, u32 uv); + int (*voltage_get) (struct device *control_dev, u32 *uv); + int (*voltage_get_range) (struct device *control_dev, u32 *min_uv, + u32 *max_uv); +}; + +/** + * struct omap_pmic_info - PMIC information + * + * @slave_addr: 7 bit address representing I2C slave address. + * @voltage_reg_addr: I2C register address for setting voltage + * @cmd_reg_addr: I2C register address for low power transition commands + * @i2c_timeout_us: worst case latency for I2C operations for the device + * @slew_rate_uV: Slew rate in uV/uSeconds for voltage transitions + * @step_size_uV: Step size in uV for one vsel increment. + * @min_uV: represents the minimum step_sized incremental voltage + * @max_uV: represents the maximum step_sized incremental voltage + * @voltage_selector_setbits: what bits to set permenantly for the PMIC + * voltage selector - this may have PMIC specific meaning. + * @voltage_selector_mask: what mask to use for the vsel value - this is useful + * for PMICs where the vsel has to be applied at an offset. + * @voltage_selector_offset: what offset to apply to conversion routine when + * operating on vsel. + * @voltage_selector_zero: Special case handling if 0 value does NOT indicates + * power-off for PMIC. + * @setup_command_list: array of setup commands for PMIC to operate + * @setup_num_commands: number of setup commands for PMIC to operate + */ +struct omap_pmic_info { + u8 slave_addr; + u8 voltage_reg_addr; + u8 cmd_reg_addr; + u32 i2c_timeout_us; + + u32 slew_rate_uV; + u32 step_size_uV; + u32 min_uV; + u32 max_uV; + + u8 voltage_selector_offset; + u8 voltage_selector_mask; + u8 voltage_selector_setbits; + bool voltage_selector_zero; + + const struct omap_pmic_setup_commands *setup_command_list; + u8 setup_num_commands; +}; + +/** + * struct omap_pmic - represents the OMAP PMIC device. + * @ops: PMIC conversion operations. + * + * NOTE: the fields marked Private are meant for PMIC driver. + */ +struct omap_pmic { + const struct omap_pmic_info *info; + u32 boot_voltage_uV; + + struct omap_pmic_ops *ops; + + struct device *dev; + struct device *v_dev; +}; + +#if IS_ENABLED(CONFIG_REGULATOR_TI_OMAP_PMIC) +int omap_pmic_register_controller_ops(struct omap_pmic_controller_ops *cops); +#else +static inline int omap_pmic_register_controller_ops(struct + omap_pmic_controller_ops + *cops) +{ + return -EINVAL; +} +#endif + +#endif /* __POWER_OMAP_PMIC_H */