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 called Voltage Controller(VC). This is an specialized hardware block meant to support PMIC chips and is customized to that requirement. Even though it functions as an I2C controller, it is a write-only interface whose configurations are custom to PMICs. We have an existing implementation in mach-omap2 which has been re factored and moved out as a separate driver. This new driver is DT only and the separation was meant to get a maintainable driver which does not have to deal with legacy platform_data dependencies. The legacy driver is retained to support non-DT boot and functionality will be migrated to the DT-only version as we enable features. Currently, this implementation considers only the basic steps needed for voltage scaling and exposing voltage controller as a device whose child devices are voltage controller channel devices. We may need to do additional timing configurations to enable Low power mode voltage transitions, but this will be completed in a following series. We may need a few tweaks to hook the Adaptive Voltage Scaling(AVS) implementation for OMAP which also uses the same voltage controller to talk to PMICs. This driver is meant to interface with voltage processor when the voltage processor attempts devm_omap_vc_channel_get for the VC channel corresponding to the voltage processor. [grygorii.strashko@xxxxxx: co-developer] Signed-off-by: Grygorii Strashko <grygorii.strashko@xxxxxx> Signed-off-by: Nishanth Menon <nm@xxxxxx> --- .../devicetree/bindings/power/omap-vc.txt | 99 ++ drivers/power/avs/Kconfig | 15 + drivers/power/avs/Makefile | 20 + drivers/power/avs/omap_vc.c | 1508 ++++++++++++++++++++ drivers/power/avs/omap_vc.h | 67 + 5 files changed, 1709 insertions(+) create mode 100644 Documentation/devicetree/bindings/power/omap-vc.txt create mode 100644 drivers/power/avs/omap_vc.c create mode 100644 drivers/power/avs/omap_vc.h diff --git a/Documentation/devicetree/bindings/power/omap-vc.txt b/Documentation/devicetree/bindings/power/omap-vc.txt new file mode 100644 index 0000000..f97737c --- /dev/null +++ b/Documentation/devicetree/bindings/power/omap-vc.txt @@ -0,0 +1,99 @@ +Voltage Controller driver for Texas Instruments OMAP SoCs + +Voltage Controller Properties: +The following are the properties of the voltage controller node +Required Properties: +- compatible: Should be one of: + - "ti,omap3-vc" - for OMAP3 family of devices + - "ti,omap4-vc" - for OMAP4 family of devices + - "ti,omap5-vc" - for OMAP5 family of devices +- reg: Address and length of the register set for the device. It contains + the information of registers in the same order as described by reg-names +- reg-names: Should contain the reg names + - "base-address" - contains base address of VC module +- clocks: should point to the clock node used by VC module, usually sysclk +- ti,i2c-clk-scl-low: is mandatory if ti,i2c-pad-load is not used. contains + hex to represent timing for slow I2C phase low clock time. +- ti,i2c-clk-scl-high: is mandatory if ti,i2c-pad-load is not used. contains + hex to represent timing for slow I2C phase high clock time. +- ti,i2c-clk-hsscl-low: is mandatory if ti,i2c-pad-load is not used and + ti,i2c-high-speed is used, contains hex to represent timing for high speed I2C + phase low clock time. +- ti,i2c-clk-hsscl-high: is mandatory if ti,i2c-pad-load is not used and + ti,i2c-high-speed is used, contains hex to represent timing for high speed I2C + phase high clock time. +- Must contain VC channel nodes which belong to the Voltage controller. + +Optional Properties: +- ti,i2c-high-speed: bool to indicate if VC should operate in high speed I2C + mode. +- ti,i2c-pad-load: if ti,i2c-high-speed, then this is optional to auto load + pre-calculated I2C clock timing configuration. This is denoted in pico-farads. +- ti,i2c-pcb-length: if ti,i2c-pad-load, then this is optional to select the + pre-calculated I2C clock timing configuration. default of '63' is used. + This is denoted in millimeters. +- pinctrl: Most OMAP SoCs do not allow pinctrl option to select VC's I2C path. + it is usually hardcoded by default. Define "default" pinctrl-0 as needed. + +Voltage Controller Channel Properties: +The following are the properties of the voltage controller channel nodes +Required Properties: +- compatible: Should be one of: + - ti,omap3-vc-channel-0 - Channel 0 on OMAP3 family of devices + - ti,omap3-vc-channel-1 - Channel 1 on OMAP3 family of devices + - ti,omap4-vc-channel-mpu - Channel MPU on OMAP4 family of devices + - ti,omap4-vc-channel-iva - Channel IVA on OMAP4 family of devices + - ti,omap4-vc-channel-core - Channel CORE on OMAP4 family of devices + - ti,omap5-vc-channel-mpu - Channel MPU on OMAP5 family of devices + - ti,omap5-vc-channel-mm - Channel MM on OMAP5 family of devices + - ti,omap5-vc-channel-core - Channel CORE on OMAP5 family of devices +- ti,master-channel: bool to mark the "master channel". Only ONE channel is + to be marked as master channel depending on SoC specification. + +Optional Properties: +- ti,retention-micro-volts: retention voltage for the voltage controller in + micro-volts +- ti,off-micro-volts: OFF mode voltage for the voltage controller in micro-volts +- ti,use-master-slave-addr: available only for a "non-master" channel. This + indicates the the channel will use master channel's slave address. Usually + used when the PMIC has the same slave address for all SMPSs. +- ti,use-master-voltage-reg-addr: available only for a "non-master" channel. + channel will use voltage register of the master channel to send voltage + commands - usually used in "ganged" voltage rail configuration - NOTE: this + is useful only when doing voltage transitions alone and not using AVS. +- ti,use-master-command-reg-addr: available only for a "non-master" channel. + channel will use command register of the master channel to send command + values - usually used in "ganged" voltage rail configuration - NOTE: this + is useful only when doing voltage transitions alone and not using AVS. +- ti,use-master-command-reg-val: available only for a "non-master" channel. + channel will use command value of the master channel to send commands + - usually used in "ganged" voltage rail configuration - NOTE: this + is useful only when doing voltage transitions alone and not using AVS. + +Example: +SoC file: (example from OMAP4) +vc: vc@0x4A307B88 { + compatible = "ti,omap4-vc"; + clocks = <&sysclk_in>; + reg = <0x4A307B88 0x40>; + reg-names = "base-address"; + + vc_mpu: vc_mpu { + compatible = "ti,omap4-vc-channel-mpu"; + ti,master-channel; + ti,retention-micro-volts = <750000>; + ti,off-micro-volts = <0>; + }; + + vc_iva: vc_iva { + compatible = "ti,omap4-vc-channel-iva"; + ti,retention-micro-volts = <750000>; + ti,off-micro-volts = <0>; + }; + + vc_core: vc_core { + compatible = "ti,omap4-vc-channel-core"; + ti,retention-micro-volts = <750000>; + ti,off-micro-volts = <0>; + }; + }; diff --git a/drivers/power/avs/Kconfig b/drivers/power/avs/Kconfig index 2a1008b..68dff06 100644 --- a/drivers/power/avs/Kconfig +++ b/drivers/power/avs/Kconfig @@ -10,3 +10,18 @@ menuconfig POWER_AVS AVS is also called SmartReflex on OMAP devices. Say Y here to enable Adaptive Voltage Scaling class support. + +config POWER_TI_HARDWARE_VOLTAGE_CONTROL + tristate "TI OMAP SoC Voltage Control support" + depends on OF && ARCH_OMAP && REGULATOR_TI_OMAP_PMIC + select REGMAP_MMIO + help + Select this option to support Texas Instruments' custom Voltage + Processor + Voltage Controller data interface used in OMAP SoCs + to enable Adaptive Voltage Scaling(AVS) and Device Frequency and + Voltage Scaling(DVFS). This is a specific "write-only" interface + designed to interface with I2C based PMICs. These two subsystems + in OMAP do not exist independent of each other as they are + practically useless without each other. + + Say Y here to enable TI OMAP SoC Voltage Control support. diff --git a/drivers/power/avs/Makefile b/drivers/power/avs/Makefile index 0843386..95d5f59 100644 --- a/drivers/power/avs/Makefile +++ b/drivers/power/avs/Makefile @@ -1 +1,21 @@ obj-$(CONFIG_POWER_AVS_OMAP) += smartreflex.o + +ifneq ($(CONFIG_POWER_TI_HARDWARE_VOLTAGE_CONTROL),) + +# OMAP Common +omap-volt-common = omap_vc.o + +# OMAP SoC specific +ifneq ($(CONFIG_ARCH_OMAP3),) +obj-$(CONFIG_POWER_TI_HARDWARE_VOLTAGE_CONTROL) += $(omap-volt-common) +endif + +ifneq ($(CONFIG_ARCH_OMAP4),) +obj-$(CONFIG_POWER_TI_HARDWARE_VOLTAGE_CONTROL) += $(omap-volt-common) +endif + +ifneq ($(CONFIG_SOC_OMAP5),) +obj-$(CONFIG_POWER_TI_HARDWARE_VOLTAGE_CONTROL) += $(omap-volt-common) +endif + +endif diff --git a/drivers/power/avs/omap_vc.c b/drivers/power/avs/omap_vc.c new file mode 100644 index 0000000..4efb8ce --- /dev/null +++ b/drivers/power/avs/omap_vc.c @@ -0,0 +1,1508 @@ +/* + * OMAP Voltage Controller (VC) interface + * + * Idea based on arch/arm/mach-omap2/vc.c + * Copyright (C) 2011 Texas Instruments Incorporated. + * + * 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. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/regulator/omap-pmic-regulator.h> +#include <linux/regmap.h> +#include <linux/err.h> +#include <linux/clk.h> +#include "omap_vc.h" + +#define DRIVER_NAME "omap-vc" + +/** + * struct vc_channel_regs - Register description for VC channel + * @cfg_reg_offset: Channel configuration register offset + * @sa_reg_offset: Slave Address configuration register offset + * @voltage_reg_offset: Voltage address register offset + * @command_addr_reg_offset: Command address register offset + * @command_val_reg_offset: Command value register offset + * + * NOTE: All register offsets are from VC base, as certain flavor + * of SoCs use shared registers and other have unique register offsets + */ +struct vc_channel_regs { + u16 cfg_reg_offset; + u16 sa_reg_offset; + u16 voltage_reg_offset; + u16 command_addr_reg_offset; + u16 command_val_reg_offset; + + /* private: */ + /* cfg_reg_offset details */ + u32 racen_mask; + u32 rac_enable_mask; + u32 rav_enable_mask; + u32 cmd_sel_mask; + u32 sa_enable_mask; + + /* sa_reg_offset details */ + u32 sa_addr_mask; + + /* voltage_reg_offset details */ + u32 voltage_addr_mask; + + /* command_addr_reg_offset details */ + u32 command_addr_mask; + + /* command_val_reg_offset details */ + u32 command_val_on_mask; + u32 command_val_onlp_mask; + u32 command_val_ret_mask; + u32 command_val_off_mask; +}; + +/** + * struct omap_vc_channel - internal representation of VC channel + * @dev: device pointer for VC channel device + * @list: channel list + * @usage_count: Usage count - only 1 user at a time.(not always module) + * @is_master_channel: Is this channel the master channel? + * @use_master_channel_sa: if this channel uses master channel's slave address + * @use_master_channel_voltage_reg: if this channel uses master channel's + * voltage register address + * @use_master_channel_cmd_reg: if this channel uses master channel's command + * register address + * @use_master_channel_cmd_val: if this channel uses master channel's command + * value address + * @ch_regs: Channel register description + * @info: exported information containing PMIC hooked to this + * channel, low power state voltage information etc. + */ +struct omap_vc_channel { + struct device *dev; + struct list_head list; + int usage_count; + bool is_master_channel; + bool use_master_channel_sa; + bool use_master_channel_voltage_reg; + bool use_master_channel_cmd_reg; + bool use_master_channel_cmd_val; + const struct vc_channel_regs *ch_regs; + struct omap_vc_channel_info info; +}; + +/** + * struct omap_omap_vc_i2c_config - Voltage controller channel's I2C config + * @clk_rate: Frequency of sys_clock for the voltage controller's I2C + * @highspeed: used in I2C highspeed mode? + * @mcode: Master Code used in High speed mode + * @i2c_clk_pad_load: What is the pad load at i2c + * @i2c_clk_pcb_length: What is the pcb length to the PMIC (all inclusive) + * @i2c_clk_scll: Clock low timing config for slow speed + * @i2c_clk_sclh: Clock high timing config for slow speed + * @i2c_clk_hsscll: Clock low timing config for high speed + * @i2c_clk_hssclh: Clock high timing config for high speed + * + * Used one time at boot. + */ +struct omap_omap_vc_i2c_config { + unsigned long clk_rate; + bool highspeed; + u8 mcode; + u32 i2c_clk_pad_load; + u32 i2c_clk_pcb_length; + u8 i2c_clk_scll; + u8 i2c_clk_sclh; + u8 i2c_clk_hsscll; + u8 i2c_clk_hssclh; +}; + +/** + * struct omap_vc_common_reg - Voltage Controller Common registers + * @i2c_clk_cfg_reg: I2C clock configuration register offset + * @i2c_clk_mode_reg: I2C clock operation mode register offset + * @bypass_cmd_reg: VC_BYPASS (very low level control) register offset + * + * These may be -1 to indicate register not being present. + */ +struct omap_vc_common_reg { + s16 i2c_clk_cfg_reg; + u16 i2c_clk_mode_reg; + u16 bypass_cmd_reg; +}; + +/** + * struct omap_vc - represents the voltage controller(vc) device + * @regmap: regmap to the entire voltage controller region(includes channel) + * @regs: Voltage Controller common registers + * @master_channel_configured: Is Master channel has been configured. + */ +struct omap_vc { + struct regmap *regmap; + const struct omap_vc_common_reg *regs; + bool master_channel_configured; +}; + +/* VC Channel register configurations */ +static const struct vc_channel_regs omap3_ch_1_regs = { + .cfg_reg_offset = 0x14, + .racen_mask = 0x80000, + .rac_enable_mask = 0x40000, + .rav_enable_mask = 0x20000, + .cmd_sel_mask = 0x100000, + .sa_enable_mask = 0x10000, + + .sa_reg_offset = 0x0, + .sa_addr_mask = 0x7f0000, + + .voltage_reg_offset = 0x4, + .voltage_addr_mask = 0xFF0000, + + .command_addr_reg_offset = 0x8, + .command_addr_mask = 0xFF0000, + + .command_val_reg_offset = 0x10, + .command_val_on_mask = 0xFF000000, + .command_val_onlp_mask = 0xFF0000, + .command_val_ret_mask = 0xFF00, + .command_val_off_mask = 0xFF, +}; + +static const struct vc_channel_regs omap3_ch_0_regs = { + .cfg_reg_offset = 0x14, + .racen_mask = 0x8, + .rac_enable_mask = 0x4, + .rav_enable_mask = 0x2, + .cmd_sel_mask = 0x10, + .sa_enable_mask = 0x1, + + .sa_reg_offset = 0x0, + .sa_addr_mask = 0x7f, + + .voltage_reg_offset = 0x4, + .voltage_addr_mask = 0xFF, + + .command_addr_reg_offset = 0x8, + .command_addr_mask = 0xFF, + + .command_val_reg_offset = 0xC, + .command_val_on_mask = 0xFF000000, + .command_val_onlp_mask = 0xFF0000, + .command_val_ret_mask = 0xFF00, + .command_val_off_mask = 0xFF, +}; + +static const struct vc_channel_regs omap4_ch_mpu_regs = { + .cfg_reg_offset = 0x1c, + .racen_mask = 0x100000, + .rac_enable_mask = 0x80000, + .rav_enable_mask = 0x40000, + .cmd_sel_mask = 0x20000, + .sa_enable_mask = 0x10000, + + .sa_reg_offset = 0x0, + .sa_addr_mask = 0x7f0000, + + .voltage_reg_offset = 0x4, + .voltage_addr_mask = 0xFF0000, + + .command_addr_reg_offset = 0x8, + .command_addr_mask = 0xFF0000, + + .command_val_reg_offset = 0x10, + .command_val_on_mask = 0xFF000000, + .command_val_onlp_mask = 0xFF0000, + .command_val_ret_mask = 0xFF00, + .command_val_off_mask = 0xFF, +}; + +static const struct vc_channel_regs omap4_ch_iva_regs = { + .cfg_reg_offset = 0x1c, + .racen_mask = 0x800, + .rac_enable_mask = 0x400, + .rav_enable_mask = 0x200, + .cmd_sel_mask = 0x1000, + .sa_enable_mask = 0x100, + + .sa_reg_offset = 0x0, + .sa_addr_mask = 0x7f00, + + .voltage_reg_offset = 0x4, + .voltage_addr_mask = 0xFF00, + + .command_addr_reg_offset = 0x8, + .command_addr_mask = 0xFF00, + + .command_val_reg_offset = 0x14, + .command_val_on_mask = 0xFF000000, + .command_val_onlp_mask = 0xFF0000, + .command_val_ret_mask = 0xFF00, + .command_val_off_mask = 0xFF, +}; + +static const struct vc_channel_regs omap4_ch_core_regs = { + .cfg_reg_offset = 0x1c, + .racen_mask = 0x8, + .rac_enable_mask = 0x4, + .rav_enable_mask = 0x2, + .cmd_sel_mask = 0x10, + .sa_enable_mask = 0x1, + + .sa_reg_offset = 0x0, + .sa_addr_mask = 0x7f, + + .voltage_reg_offset = 0x4, + .voltage_addr_mask = 0xFF, + + .command_addr_reg_offset = 0x8, + .command_addr_mask = 0xFF, + + .command_val_reg_offset = 0xC, + .command_val_on_mask = 0xFF000000, + .command_val_onlp_mask = 0xFF0000, + .command_val_ret_mask = 0xFF00, + .command_val_off_mask = 0xFF, +}; + +static const struct vc_channel_regs omap5_ch_core_regs = { + .cfg_reg_offset = 0x0, + .racen_mask = 0x8000000, + .rac_enable_mask = 0x4000000, + .rav_enable_mask = 0x2000000, + .cmd_sel_mask = 0x10000000, + .sa_enable_mask = 0x1000000, + + .sa_reg_offset = 0x0, + .sa_addr_mask = 0x7f, + + .voltage_reg_offset = 0x0, + .voltage_addr_mask = 0xFF00, + + .command_addr_reg_offset = 0x0, + .command_addr_mask = 0xFF0000, + + .command_val_reg_offset = 0xC, + .command_val_on_mask = 0xFF000000, + .command_val_onlp_mask = 0xFF0000, + .command_val_ret_mask = 0xFF00, + .command_val_off_mask = 0x00, /* Reserved */ +}; + +static const struct vc_channel_regs omap5_ch_mm_regs = { + .cfg_reg_offset = 0x4, + .racen_mask = 0x8000000, + .rac_enable_mask = 0x4000000, + .rav_enable_mask = 0x2000000, + .cmd_sel_mask = 0x10000000, + .sa_enable_mask = 0x1000000, + + .sa_reg_offset = 0x4, + .sa_addr_mask = 0x7f, + + .voltage_reg_offset = 0x4, + .voltage_addr_mask = 0xFF00, + + .command_addr_reg_offset = 0x4, + .command_addr_mask = 0xFF0000, + + .command_val_reg_offset = 0x10, + .command_val_on_mask = 0xFF000000, + .command_val_onlp_mask = 0xFF0000, + .command_val_ret_mask = 0xFF00, + .command_val_off_mask = 0x00, /* Reserved */ +}; + +static const struct vc_channel_regs omap5_ch_mpu_regs = { + .cfg_reg_offset = 0x8, + .racen_mask = 0x8000000, + .rac_enable_mask = 0x4000000, + .rav_enable_mask = 0x2000000, + .cmd_sel_mask = 0x10000000, + .sa_enable_mask = 0x1000000, + + .sa_reg_offset = 0x8, + .sa_addr_mask = 0x7f, + + .voltage_reg_offset = 0x8, + .voltage_addr_mask = 0xFF00, + + .command_addr_reg_offset = 0x8, + .command_addr_mask = 0xFF0000, + + .command_val_reg_offset = 0x14, + .command_val_on_mask = 0xFF000000, + .command_val_onlp_mask = 0xFF0000, + .command_val_ret_mask = 0xFF00, + .command_val_off_mask = 0x00, /* Reserved */ +}; + +/* VC Generic register configurations */ +static const struct omap_vc_common_reg omap3_vc_regs = { + .i2c_clk_cfg_reg = -1, + /* NOTE: register is called I2C_CFG in TRM, but compatible with MODE */ + .i2c_clk_mode_reg = 0x18, + .bypass_cmd_reg = 0x1C, +}; + +static const struct omap_vc_common_reg omap4_vc_regs = { + .i2c_clk_cfg_reg = 0x24, + .i2c_clk_mode_reg = 0x20, + .bypass_cmd_reg = 0x18, +}; + +static const struct omap_vc_common_reg omap5_vc_regs = { + .i2c_clk_cfg_reg = 0x30, + .i2c_clk_mode_reg = 0x2c, + .bypass_cmd_reg = 0x18, +}; + +/* VCBYPASS register bit definitions - No variance at all */ +#define VC_BYPASS_VALID_MASK (0x01 << 24) +#define VC_BYPASS_DATA_MASK (0xff << 16) +#define VC_BYPASS_REGADDR_MASK (0xff << 8) +#define VC_BYPASS_SA_MASK (0x7f << 0) +/* I2C_CLK_MODE register bit definitions - No variance at all */ +#define VC_MODE_REPEAT_START_MASK BIT(4) +#define VC_MODE_HSENABLE_MASK BIT(3) +#define VC_MODE_MCODE_MASK (0x3 << 0) +/* I2C_CLK_CONFIG register bit definitions - No variance at all */ +#define VC_CLK_CFG_HSSCLL_MASK (0xff << 24) +#define VC_CLK_CFG_HSSCLH_MASK (0xff << 16) +#define VC_CLK_CFG_SCLL_MASK (0xff << 8) +#define VC_CLK_CFG_SCLH_MASK (0xff << 0) + +/* Stores the list containing all voltage controller channels */ +static LIST_HEAD(omap_vc_channel_list); +static DEFINE_MUTEX(omap_vc_channel_list_mutex); + +/** + * omap_vc_channel_set_on_voltage() - Update the ON transition voltage + * @info: channel info (must be valid pointer from + * devm_omap_vc_channel_get) + * @uv: ON voltage in micro volts. + * + * Updates the voltage that OMAP comes back to when resuming from a low + * power state. It is recommended to invoke this as part of voltage transition + * as the low power transitions are more performance sensitive that voltage + * transition path (part of DVFS). + * + * This needs to be invoked either as part of DVFS sequence or prior to + * attempting to enter low power state. + * + * NOTE: no extra error handling is performed to reduce overhead as much as + * sanely possible. No explicit locks are needed as regmap takes care of + * the same. + * + * Return: 0 if all operations are successful, else returns appropriate + * error value. + */ +int omap_vc_channel_set_on_voltage(struct omap_vc_channel_info *info, u32 uv) +{ + struct omap_vc *vc; + struct omap_vc_channel *vc_channel; + struct device *dev; + const struct vc_channel_regs *ch_regs; + struct omap_pmic *pmic; + struct omap_pmic_ops *ops; + struct regmap *regmap; + int ret = 0; + u32 val = 0; + u8 vsel; + + if (!info) { + pr_err("Bad parameters\n"); + ret = -EINVAL; + goto out; + } + + vc_channel = info->ch; + dev = vc_channel->dev; + ch_regs = vc_channel->ch_regs; + pmic = info->pmic; + ops = pmic->ops; + + vc = dev_get_drvdata(dev->parent); + if (!vc) { + dev_err(dev, "Unable to find parent VC data\n"); + ret = -EINVAL; + goto out; + } + + ret = ops->uv_to_vsel(pmic, uv, &vsel); + if (ret) { + dev_err(dev, "%s: Conversion onV %d to vsel fail(%d)\n", + __func__, uv, ret); + goto out; + } + regmap = vc->regmap; + + if (ch_regs->command_val_onlp_mask) { + val = vsel << __ffs(ch_regs->command_val_onlp_mask); + ret = regmap_update_bits(regmap, + ch_regs->command_val_reg_offset, + ch_regs->command_val_onlp_mask, + val); + } + if (ret) + goto fail_reg; + + if (ch_regs->command_val_on_mask) { + val = vsel << __ffs(ch_regs->command_val_on_mask); + ret = regmap_update_bits(regmap, + ch_regs->command_val_reg_offset, + ch_regs->command_val_on_mask, val); + } + +fail_reg: + if (ret) + dev_err(dev, "%s: Register operation failed with %d\n", + __func__, ret); +out: + return ret; +} +EXPORT_SYMBOL_GPL(omap_vc_channel_set_on_voltage); + +/** + * omap_vc_channel_setup_lp() - Low power voltage configuration + * @vc_channel: VC channel to configure for + * @vc: VC to which this channel belongs to. + * + * The Low power states such as OFF, RETENTION are pre-determined SoC specific + * voltage values. These can be configured at boot time and the voltages are + * achieved as needed. This also configures the boot voltage as ON voltage if + * it is available. + */ +static int omap_vc_channel_setup_lp(struct omap_vc_channel *vc_channel, + struct omap_vc *vc) +{ + struct device *dev = vc_channel->dev; + const struct vc_channel_regs *ch_regs = vc_channel->ch_regs; + struct regmap *regmap = vc->regmap; + struct omap_pmic *pmic = vc_channel->info.pmic; + struct omap_pmic_ops *ops = pmic->ops; + struct omap_vc_channel_info *info = &vc_channel->info; + int ret = 0; + u32 val; + u8 vsel; + + if (ch_regs->command_val_off_mask) { + ret = ops->uv_to_vsel(pmic, info->off_uV, &vsel); + if (ret) { + dev_err(dev, "%s: Conversion OFF %d to vsel fail(%d)\n", + __func__, info->off_uV, ret); + goto out; + } + val = vsel << __ffs(ch_regs->command_val_off_mask); + ret = regmap_update_bits(regmap, + ch_regs->command_val_reg_offset, + ch_regs->command_val_off_mask, val); + if (ret) + goto fail_reg; + } + + if (ch_regs->command_val_ret_mask) { + ret = ops->uv_to_vsel(pmic, info->retention_uV, &vsel); + if (ret) { + dev_err(dev, "%s: Conversion RET %d to vsel fail(%d)\n", + __func__, info->retention_uV, ret); + goto out; + } + val = vsel << __ffs(ch_regs->command_val_ret_mask); + ret = regmap_update_bits(regmap, + ch_regs->command_val_reg_offset, + ch_regs->command_val_ret_mask, val); + if (ret) + goto fail_reg; + } + + if (pmic->boot_voltage_uV && (ch_regs->command_val_on_mask | + ch_regs->command_val_onlp_mask)) { + ret = ops->uv_to_vsel(pmic, pmic->boot_voltage_uV, &vsel); + if (ret) { + dev_err(dev, "%s: Conversion ON %d to vsel fail(%d)\n", + __func__, pmic->boot_voltage_uV, ret); + goto out; + } + } + if (pmic->boot_voltage_uV && ch_regs->command_val_on_mask) { + val = vsel << __ffs(ch_regs->command_val_on_mask); + ret = regmap_update_bits(regmap, + ch_regs->command_val_reg_offset, + ch_regs->command_val_on_mask, val); + if (ret) + goto fail_reg; + } + + if (pmic->boot_voltage_uV && ch_regs->command_val_onlp_mask) { + val = vsel << __ffs(ch_regs->command_val_onlp_mask); + ret = regmap_update_bits(regmap, + ch_regs->command_val_reg_offset, + ch_regs->command_val_onlp_mask, + val); + } + +fail_reg: + if (ret) + dev_err(dev, "%s: Register operation failed with %d\n", + __func__, ret); + +out: + return ret; +} + +/** + * omap_vc_send_msg() - Send a VC bypass command + * @vc: Voltage controller + * @vc_channel: Voltage controller channel + * @reg_addr: Register address to write to + * @data: data to write. + * + * This bypasses all channel scheduling mechanisms inside Voltage controller + * and must be used sparingly in controlled environments. Using this to scale + * voltage is NOT a recommended procedure. + * + * The only safe usage is when Voltage Processors, SmartReflex, VFSM(PRCM) + * is in idle or known state - example at boot, this may be used for + * configuration. + */ +static int omap_vc_send_msg(struct omap_vc *vc, + struct omap_vc_channel *vc_channel, u8 reg_addr, + u8 data) +{ + struct device *dev = vc_channel->dev; + struct regmap *regmap = vc->regmap; + struct omap_pmic *pmic = vc_channel->info.pmic; + u32 loop_cnt = 0, retries_cnt = 0; + u32 vc_bypass_value; + int ret = 0; + + ret = regmap_update_bits(regmap, vc->regs->bypass_cmd_reg, + VC_BYPASS_DATA_MASK, + data << __ffs(VC_BYPASS_DATA_MASK)); + if (ret) + goto reg_fail; + ret = regmap_update_bits(regmap, vc->regs->bypass_cmd_reg, + VC_BYPASS_REGADDR_MASK, + reg_addr << __ffs(VC_BYPASS_REGADDR_MASK)); + if (ret) + goto reg_fail; + ret = regmap_update_bits(regmap, vc->regs->bypass_cmd_reg, + VC_BYPASS_SA_MASK, + pmic->slave_addr << __ffs(VC_BYPASS_SA_MASK)); + if (ret) + goto reg_fail; + /* Activate the transfer */ + ret = regmap_update_bits(regmap, vc->regs->bypass_cmd_reg, + VC_BYPASS_VALID_MASK, VC_BYPASS_VALID_MASK); + if (ret) + goto reg_fail; + + /* See if transfer complete */ + ret = regmap_read(regmap, vc->regs->bypass_cmd_reg, &vc_bypass_value); + if (ret) + goto reg_fail; + + dev_dbg(dev, + "bypass_val = 0x%08x, sa=0x%02x, reg=0x%02x, data=0x%02x\n", + vc_bypass_value, pmic->slave_addr, reg_addr, data); + + /* + * Loop which polls continously 50 times before sleeping and retry + * around for atleast pmic->i2c_timeout_us * 5. Rationale as follows: + * 1) continuous poll loops for 50 times, if it cant get it, then (2) + * 2) sleeps between 5-15 uSec. then try (1) + * if it cant get in timeout_us *5 ish (in step 1) then give up + * + * The continous loop(1) is used because the first VALID setting + * is expected to appear quiet fast. However, the delay occurs only + * when VC internally does round robin scheduling between PRCM's VFSM, + * VC channels (1-n) followed by vcbypass. Only if there is pending + * transaction will the bypass valid bit get delayed, 99.99% of the + * cases at this point in time(configuration and setup time), we do + * not expect this conflict to take place. + */ + while (vc_bypass_value & VC_BYPASS_VALID_MASK) { + loop_cnt++; + + /* Get at least 5 times pmic->i2c_timeout_us for completion */ + if (retries_cnt > pmic->i2c_timeout_us) { + dev_err(dev, "%s: Retry count exceeded\n", __func__); + ret = -ETIMEDOUT; + goto out; + } + + if (loop_cnt > 50) { + retries_cnt++; + loop_cnt = 0; + usleep_range(5, 15); + } + ret = + regmap_read(regmap, vc->regs->bypass_cmd_reg, + &vc_bypass_value); + if (ret) + goto reg_fail; + } + +reg_fail: + if (ret) + dev_err(dev, "%s: register operation failed(%d)\n", + __func__, ret); + +out: + return ret; +} + +/** + * omap_vc_channel_setup_pmic() - Setup PMIC configuration commands if any + * @vc_channel: vc channel + * @vc: vc + * + * Uses vc_bypass to send commands to the PMIC for mandatory configurations + * for the device to function. + */ +static int omap_vc_channel_setup_pmic(struct omap_vc_channel *vc_channel, + struct omap_vc *vc) +{ + struct device *dev = vc_channel->dev; + struct omap_pmic *pmic = vc_channel->info.pmic; + struct omap_pmic_setup_commands *cmd; + int i, ret = 0; + + if (!pmic->setup_num_commands) + return 0; + if (!pmic->setup_command_list) { + dev_err(dev, "Bad setup command list\n"); + return -EINVAL; + } + cmd = pmic->setup_command_list; + + for (i = 0; i < pmic->setup_num_commands; i++, cmd++) { + ret = omap_vc_send_msg(vc, vc_channel, cmd->reg, cmd->cmd_val); + if (ret) { + dev_err(dev, "Failed cmd [r=0x%02x v=0x%02x] [%d]\n", + cmd->reg, cmd->cmd_val, ret); + return ret; + } + } + + return ret; +} + +/* Quick helper to avoid having to define /bits/ 8 <0x20> for 8 bit params */ +static inline int vc_property_read_u8(const struct device_node *np, + const char *propname, u8 *out_value) +{ + u32 val; + int r; + + r = of_property_read_u32(np, propname, &val); + if (r) + return r; + + if (val > 0xFF) + return -ERANGE; + + *out_value = (u8) val; + return 0; +} + +/** + * omap_vc_channel_setup_sa() - setup Slave address + * @vc_channel: vc channel + * @vc: vc + * + * setups the slave address configuration as needed. + */ +static int omap_vc_channel_setup_sa(struct omap_vc_channel *vc_channel, + struct omap_vc *vc) +{ + struct device *dev = vc_channel->dev; + const struct vc_channel_regs *ch_regs = vc_channel->ch_regs; + struct regmap *regmap = vc->regmap; + struct omap_pmic *pmic = vc_channel->info.pmic; + int ret; + u32 enable_mask = 0; + + if (!vc_channel->use_master_channel_sa) { + u32 val; + + val = pmic->slave_addr << __ffs(ch_regs->sa_addr_mask); + ret = + regmap_update_bits(regmap, ch_regs->sa_reg_offset, + ch_regs->sa_addr_mask, val); + if (ret) + goto out; + + /* SA bit is never set for master */ + if (!vc_channel->is_master_channel) + enable_mask = ch_regs->sa_enable_mask; + } + + ret = regmap_update_bits(regmap, ch_regs->cfg_reg_offset, + ch_regs->sa_enable_mask, enable_mask); + +out: + if (ret) + dev_err(dev, "%s: update reg failed(%d)\n", __func__, ret); + + return ret; +} + +/** + * omap_vc_channel_setup_voltage() - configure channel's voltage register addr + * @vc_channel: vc channel + * @vc: voltage controller + */ +static int omap_vc_channel_setup_voltage(struct omap_vc_channel *vc_channel, + struct omap_vc *vc) +{ + struct device *dev = vc_channel->dev; + struct regmap *regmap = vc->regmap; + const struct vc_channel_regs *ch_regs = vc_channel->ch_regs; + struct omap_pmic *pmic = vc_channel->info.pmic; + int ret; + u32 enable_mask = 0; + + if (!vc_channel->use_master_channel_voltage_reg) { + u32 val; + + val = + pmic->voltage_reg_addr << __ffs(ch_regs->voltage_addr_mask); + ret = regmap_update_bits(regmap, ch_regs->voltage_reg_offset, + ch_regs->voltage_addr_mask, val); + if (ret) + goto out; + + /* RAV bit is never set for master */ + if (!vc_channel->is_master_channel) + enable_mask = ch_regs->rav_enable_mask; + } + ret = regmap_update_bits(regmap, ch_regs->cfg_reg_offset, + ch_regs->rav_enable_mask, enable_mask); +out: + if (ret) + dev_err(dev, "%s: update reg failed(%d)\n", __func__, ret); + + return ret; +} + +/** + * omap_vc_channel_setup_command() - configure channel's command register addr + * @vc_channel: vc channel + * @vc: voltage controller + */ +static int omap_vc_channel_setup_command(struct omap_vc_channel *vc_channel, + struct omap_vc *vc) +{ + struct device *dev = vc_channel->dev; + struct regmap *regmap = vc->regmap; + const struct vc_channel_regs *ch_regs = vc_channel->ch_regs; + struct omap_pmic *pmic = vc_channel->info.pmic; + int ret; + u32 cmd_sel_mask = ch_regs->cmd_sel_mask; + u32 rac_enable_mask = 0; + u32 racen_mask = 0; + + if (!vc_channel->use_master_channel_cmd_reg) { + u32 val; + + val = pmic->cmd_reg_addr << __ffs(ch_regs->command_addr_mask); + ret = + regmap_update_bits(regmap, ch_regs->command_addr_reg_offset, + ch_regs->command_addr_mask, val); + if (ret) + goto out; + + /* RAC bit is never set for master */ + if (!vc_channel->is_master_channel) + rac_enable_mask = ch_regs->rac_enable_mask; + } + + ret = regmap_update_bits(regmap, ch_regs->cfg_reg_offset, + ch_regs->rac_enable_mask, rac_enable_mask); + if (ret) + goto out; + + /* Retaining legacy logic - if needed for special case, switch to DT? */ + if (pmic->cmd_reg_addr == pmic->voltage_reg_addr) + racen_mask = ch_regs->racen_mask; + + ret = regmap_update_bits(regmap, ch_regs->cfg_reg_offset, + ch_regs->racen_mask, racen_mask); + if (ret) + goto out; + + /* CMD bit is never set for master */ + if (vc_channel->is_master_channel || + vc_channel->use_master_channel_cmd_val) + cmd_sel_mask = 0; + + ret = regmap_update_bits(regmap, ch_regs->cfg_reg_offset, + ch_regs->cmd_sel_mask, cmd_sel_mask); +out: + if (ret) + dev_err(dev, "%s: update reg failed(%d)\n", __func__, ret); + + return ret; +} + +/* Quick helper to find the OF node for a device matching to channel */ +static struct device_node *of_get_omap_vc_channel(struct device *dev) +{ + struct device_node *vc_channel_node = NULL; + char *prop_name = "ti,vc-channel"; + + dev_dbg(dev, "Looking up %s from device tree\n", prop_name); + vc_channel_node = of_parse_phandle(dev->of_node, prop_name, 0); + + if (!vc_channel_node) { + dev_err(dev, "Looking up %s property in node %s failed", + prop_name, dev->of_node->full_name); + return ERR_PTR(-ENODEV); + } + return vc_channel_node; +} + +/* Helper to cleanup when the managed device is released */ +static void devm_omap_vc_channel_release(struct device *dev, void *res) +{ + struct omap_vc_channel *vc_channel = *((struct omap_vc_channel **)res); + + mutex_lock(&omap_vc_channel_list_mutex); + + vc_channel->usage_count--; + if (!vc_channel->usage_count) + vc_channel->info.pmic = NULL; + module_put(vc_channel->dev->driver->owner); + + mutex_unlock(&omap_vc_channel_list_mutex); + + return; +} + +/** + * devm_omap_vc_channel_get() - managed request to get a VC channel + * @dev: Generic device to handle the request for + * @pmic: PMIC resource this will be assigned to + * + * Ensures that usage count is maintained. Uses managed device, + * so everything is undone on driver detach. + * + * Return: -EPROBE_DEFER if the node is present, however device is + * not yet probed. + * -EINVAL if bad pointers or node description is not found. + * -ENODEV if the property cannot be found + * -ENOMEM if allocation could not be done. + * device pointer to vp dev if all successful. error handling should be + * performed with IS_ERR + */ +struct omap_vc_channel_info *devm_omap_vc_channel_get(struct device *dev, + struct omap_pmic *pmic) +{ + struct omap_vc_channel *vc_channel, **ptr; + struct device_node *node; + struct device *vc_dev; + struct omap_vc_channel_info *info; + struct omap_vc *vc; + int ret = 0; + + if (!dev || !dev->of_node || !pmic) { + pr_err("Invalid parameters\n"); + return ERR_PTR(-EINVAL); + } + + node = of_get_omap_vc_channel(dev); + if (IS_ERR(node)) + return (void *)node; + + mutex_lock(&omap_vc_channel_list_mutex); + list_for_each_entry(vc_channel, &omap_vc_channel_list, list) + if (vc_channel->dev->of_node == node) + goto found; + + /* Node definition is present, but not probed yet.. request defer */ + info = ERR_PTR(-EPROBE_DEFER); + goto out_unlock; + +found: + vc_dev = vc_channel->dev; + if (!try_module_get(vc_dev->driver->owner)) { + dev_err(dev, "%s: Cant get device owner\n", __func__); + info = ERR_PTR(-EINVAL); + goto out_unlock; + } + + /* Allow ONLY 1 user at a time */ + if (vc_channel->usage_count) { + dev_err(dev, "%s: device %s is busy..\n", __func__, + dev_name(vc_dev)); + ret = -EBUSY; + goto out; + } + ptr = devres_alloc(devm_omap_vc_channel_release, sizeof(*ptr), + GFP_KERNEL); + if (!ptr) { + ret = -ENOMEM; + goto out; + } + info = &vc_channel->info; + + vc = dev_get_drvdata(vc_dev->parent); + if (!vc) { + dev_err(dev, "Unable to find parent VC data\n"); + ret = -EINVAL; + goto out; + } + info->pmic = pmic; + ret = omap_vc_channel_setup_sa(vc_channel, vc); + if (ret) + goto out; + + ret = omap_vc_channel_setup_voltage(vc_channel, vc); + if (ret) + goto out; + + ret = omap_vc_channel_setup_command(vc_channel, vc); + if (ret) + goto out; + + ret = omap_vc_channel_setup_pmic(vc_channel, vc); + if (ret) + goto out; + + ret = omap_vc_channel_setup_lp(vc_channel, vc); + if (ret) + goto out; + + if (vc_channel->is_master_channel) + vc->master_channel_configured = true; + + *ptr = vc_channel; + vc_channel->usage_count++; + devres_add(dev, ptr); + +out: + if (ret) { + module_put(vc_dev->driver->owner); + info = ERR_PTR(ret); + dev_err(dev, "Failed setup vc with (%d)\n", ret); + } + +out_unlock: + mutex_unlock(&omap_vc_channel_list_mutex); + + return info; +} +EXPORT_SYMBOL_GPL(devm_omap_vc_channel_get); + +static const struct of_device_id omap_vc_channel_of_match_tbl[] = { + {.compatible = "ti,omap3-vc-channel-0", .data = &omap3_ch_0_regs}, + {.compatible = "ti,omap3-vc-channel-1", .data = &omap3_ch_1_regs}, + {.compatible = "ti,omap4-vc-channel-mpu", .data = &omap4_ch_mpu_regs}, + {.compatible = "ti,omap4-vc-channel-iva", .data = &omap4_ch_iva_regs}, + {.compatible = "ti,omap4-vc-channel-core", .data = &omap4_ch_core_regs}, + {.compatible = "ti,omap5-vc-channel-mpu", .data = &omap5_ch_mpu_regs}, + {.compatible = "ti,omap5-vc-channel-mm", .data = &omap5_ch_mm_regs}, + {.compatible = "ti,omap5-vc-channel-core", .data = &omap5_ch_core_regs}, + {}, +}; +MODULE_DEVICE_TABLE(of, omap_vc_channel_of_match_tbl); + +static int omap_vc_channel_probe(struct platform_device *pdev) +{ + struct device *vc_dev, *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct omap_vc *vc; + struct omap_vc_channel *vc_channel; + struct omap_vc_channel_info *info; + const struct of_device_id *match; + + if (!node) { + dev_err(dev, "%s: no OF information?\n", __func__); + return -EINVAL; + } + + match = of_match_device(omap_vc_channel_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; + } + + if (!dev->parent) { + dev_err(dev, "%s: Unable to find parent VC device\n", __func__); + return -EINVAL; + } + vc_dev = dev->parent; + + vc = dev_get_drvdata(vc_dev); + if (!vc) { + dev_err(dev, "%s: Unable to find parent VC data\n", __func__); + return -EINVAL; + } + + vc_channel = devm_kzalloc(dev, sizeof(*vc_channel), GFP_KERNEL); + if (!vc_channel) { + dev_err(dev, "%s: Unable to allocate vc channel\n", __func__); + return -ENOMEM; + } + vc_channel->dev = dev; + vc_channel->ch_regs = match->data; + info = &vc_channel->info; + info->ch = vc_channel; + + /* Pick up optional parameters */ + of_property_read_u32(node, "ti,retention-micro-volts", + &info->retention_uV); + of_property_read_u32(node, "ti,off-micro-volts", &info->off_uV); + + vc_channel->is_master_channel = + of_property_read_bool(node, "ti,master-channel"); + if (vc_channel->is_master_channel && vc->master_channel_configured) { + dev_err(dev, "There can only be a single master channel!\n"); + return -EINVAL; + } + vc_channel->use_master_channel_sa = + of_property_read_bool(node, "ti,use-master-slave-addr"); + vc_channel->use_master_channel_voltage_reg = + of_property_read_bool(node, "ti,use-master-voltage-reg-addr"); + vc_channel->use_master_channel_cmd_reg = + of_property_read_bool(node, "ti,use-master-command-reg-addr"); + vc_channel->use_master_channel_cmd_val = + of_property_read_bool(node, "ti,use-master-command-reg-val"); + + if (vc_channel->is_master_channel && + (vc_channel->use_master_channel_sa || + vc_channel->use_master_channel_voltage_reg || + vc_channel->use_master_channel_cmd_reg || + vc_channel->use_master_channel_cmd_val)) { + dev_err(dev, "Master channel cannot route to slave channel!\n"); + return -EINVAL; + } + if (!vc->master_channel_configured && + (vc_channel->use_master_channel_sa || + vc_channel->use_master_channel_voltage_reg || + vc_channel->use_master_channel_cmd_reg || + vc_channel->use_master_channel_cmd_val)) { + dev_dbg(dev, "Deferring - Master channel not yet ready!\n"); + return -EPROBE_DEFER; + } + + platform_set_drvdata(pdev, vc_channel); + + /* Add it to VC channel list */ + mutex_lock(&omap_vc_channel_list_mutex); + list_add(&vc_channel->list, &omap_vc_channel_list); + mutex_unlock(&omap_vc_channel_list_mutex); + + return 0; +} + +/** + * omap_vc_channel_remove() - Cleanup operations for channel + * @pdev: platform device + * + * Return: -EBUSY if all users have not transitioned out, else return 0 + */ +static int omap_vc_channel_remove(struct platform_device *pdev) +{ + struct omap_vc_channel *vc_channel = platform_get_drvdata(pdev); + int ret = 0; + + mutex_lock(&omap_vc_channel_list_mutex); + list_del(&vc_channel->list); + mutex_unlock(&omap_vc_channel_list_mutex); + + return ret; +} + +static struct platform_driver omap_vc_channel_driver = { + .driver = { + .name = DRIVER_NAME "-channel", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(omap_vc_channel_of_match_tbl), + }, + .probe = omap_vc_channel_probe, + .remove = omap_vc_channel_remove, +}; + +/* Regular 32 bit registers for Voltage controller */ +static struct regmap_config omap_vc_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, +}; + +/** + * omap_vc_i2c_config() - configure the I2C clock and Mode registers + * @vc: voltage controller + * @i2c_config: i2c configuration to configure + */ +static int omap_vc_i2c_config(struct omap_vc *vc, + struct omap_omap_vc_i2c_config *i2c_config) +{ + u32 mask; + + mask = VC_MODE_HSENABLE_MASK; + regmap_update_bits(vc->regmap, vc->regs->i2c_clk_mode_reg, mask, + i2c_config->highspeed << __ffs(mask)); + mask = VC_MODE_MCODE_MASK; + regmap_update_bits(vc->regmap, vc->regs->i2c_clk_mode_reg, mask, + i2c_config->mcode << __ffs(mask)); + /* Disable repeated start */ + mask = VC_MODE_REPEAT_START_MASK; + regmap_update_bits(vc->regmap, vc->regs->i2c_clk_mode_reg, mask, 0); + + /* if there is clock config register on the SoC, skip.. */ + if (vc->regs->i2c_clk_cfg_reg < 0) + return 0; + + if (!i2c_config->i2c_clk_hsscll && + !i2c_config->i2c_clk_hssclh && + !i2c_config->i2c_clk_scll && !i2c_config->i2c_clk_sclh) + return -EINVAL; + + /* Configure up clk timings */ + mask = VC_CLK_CFG_HSSCLL_MASK; + regmap_update_bits(vc->regmap, vc->regs->i2c_clk_cfg_reg, mask, + i2c_config->i2c_clk_hsscll << __ffs(mask)); + mask = VC_CLK_CFG_HSSCLH_MASK; + regmap_update_bits(vc->regmap, vc->regs->i2c_clk_cfg_reg, mask, + i2c_config->i2c_clk_hssclh << __ffs(mask)); + mask = VC_CLK_CFG_SCLL_MASK; + regmap_update_bits(vc->regmap, vc->regs->i2c_clk_cfg_reg, mask, + i2c_config->i2c_clk_scll << __ffs(mask)); + mask = VC_CLK_CFG_SCLH_MASK; + regmap_update_bits(vc->regmap, vc->regs->i2c_clk_cfg_reg, mask, + i2c_config->i2c_clk_sclh << __ffs(mask)); + return 0; +} + +/** + * struct i2c_load_data - table mapping load to clock configs + * @load: load of the I2C bus + * @hsscll_38_4: 38.4MHz Sysclk HSSCLL configuration + * @hsscll_26: 26MHz Sysclk HSSCLL configuration + * @hsscll_19_2: 19.2MHz Sysclk HSSCLL configuration + * @hsscll_16_8: 16.8MHz Sysclk HSSCLL configuration + * @hsscll_12: 12MHz Sysclk HSSCLL configuration + * + * Instead of doing an hard multi-parameter computation, if load is provided by + * OF data then we pick the values from the table, There is always an option + * of defining the clocks from OF data itself. + */ +struct i2c_load_data { + u8 load; + u8 hsscll_38_4; + u8 hsscll_26; + u8 hsscll_19_2; + u8 hsscll_16_8; + u8 hsscll_12; +}; + +/** + * omap_vc_i2c_timing_init() - sets up board I2C timing parameters + * @dev: vc channel device + * @cfg: I2C configuration + * + * Use PMIC + board supplied settings for calculating the total I2C channel + * capacitance and set the timing parameters based on this. Pre-calculated + * values are provided in data tables, as it is not too straightforward to + * calculate these runtime. + */ +static int omap_vc_i2c_timing_init(struct device *dev, + struct omap_omap_vc_i2c_config *cfg) +{ + u32 capacitance; + u16 hsscll; + const struct i2c_load_data i2c_timing_data[] = { + { + .load = 50, + .hsscll_38_4 = 13, + .hsscll_26 = 11, + .hsscll_19_2 = 9, + .hsscll_16_8 = 9, + .hsscll_12 = 8, + }, + { + .load = 25, + .hsscll_38_4 = 13, + .hsscll_26 = 11, + .hsscll_19_2 = 9, + .hsscll_16_8 = 9, + .hsscll_12 = 8, + }, + { + .load = 12, + .hsscll_38_4 = 11, + .hsscll_26 = 10, + .hsscll_19_2 = 9, + .hsscll_16_8 = 9, + .hsscll_12 = 8, + }, + { + .load = 0, + .hsscll_38_4 = 12, + .hsscll_26 = 10, + .hsscll_19_2 = 9, + .hsscll_16_8 = 8, + .hsscll_12 = 8, + }, + }; + const struct i2c_load_data *i2c_data; + + if (!cfg->i2c_clk_pad_load && !cfg->i2c_clk_pcb_length) + return 0; + + /* PCB trace capacitance, 0.125pF / mm => mm / 8 */ + capacitance = DIV_ROUND_UP(cfg->i2c_clk_pcb_length, 8); + + /* OMAP pad capacitance */ + capacitance += 4; + + /* PMIC pad capacitance */ + capacitance += cfg->i2c_clk_pad_load; + + /* Search for capacitance match in the table */ + i2c_data = i2c_timing_data; + + while (i2c_data->load > capacitance) + i2c_data++; + + /* Select proper values based on sysclk frequency */ + switch (cfg->clk_rate) { + case 38400000: + hsscll = i2c_data->hsscll_38_4; + break; + case 26000000: + hsscll = i2c_data->hsscll_26; + break; + case 19200000: + hsscll = i2c_data->hsscll_19_2; + break; + case 16800000: + hsscll = i2c_data->hsscll_16_8; + break; + case 12000000: + hsscll = i2c_data->hsscll_12; + break; + default: + dev_err(dev, "%s: Unsupported sysclk rate: %ld!\n", __func__, + cfg->clk_rate); + return -ERANGE; + } + + /* HSSCLH can always be zero */ + cfg->i2c_clk_hssclh = 0x0; + cfg->i2c_clk_hsscll = hsscll; + /* FS timing - standard */ + cfg->i2c_clk_scll = 0x28; + cfg->i2c_clk_sclh = 0x2C; + + return 0; +} + +static const struct of_device_id omap_vc_of_match_tbl[] = { + {.compatible = "ti,omap3-vc", .data = &omap3_vc_regs,}, + {.compatible = "ti,omap4-vc", .data = &omap4_vc_regs,}, + {.compatible = "ti,omap5-vc", .data = &omap5_vc_regs,}, + {}, +}; +MODULE_DEVICE_TABLE(of, omap_vc_of_match_tbl); + +static int omap_vc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct omap_vc *vc; + struct omap_omap_vc_i2c_config i2c_config = { 0 }; + int ret; + struct resource *res; + char *pname; + struct clk *clk; + void __iomem *base; + struct regmap *regmap; + struct device_node *node = dev->of_node; + const struct of_device_id *match; + + if (!node) { + dev_err(dev, "no OF information?\n"); + return -EINVAL; + } + + match = of_match_device(omap_vc_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; + } + + /* Read mandatory parameters */ + clk = clk_get(dev, NULL); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + dev_err(dev, "%s: Unable to get clk(%d)\n", __func__, ret); + return ret; + } + i2c_config.clk_rate = clk_get_rate(clk); + /* We do not need the clock any more */ + clk_put(clk); + + pname = "base-address"; + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, pname); + base = devm_request_and_ioremap(dev, res); + if (!base) { + dev_err(dev, "Unable to map '%s'\n", pname); + return -EADDRNOTAVAIL; + } + + regmap = devm_regmap_init_mmio(dev, base, &omap_vc_regmap_config); + if (IS_ERR(regmap)) { + ret = PTR_ERR(regmap); + dev_err(dev, "regmap init failed(%d)\n", ret); + return ret; + } + + /* Read optional highspeed mode param */ + pname = "ti,i2c-high-speed"; + i2c_config.highspeed = of_property_read_bool(node, pname); + + /* Mcode if the platform likes to set it explicitly */ + if (i2c_config.highspeed) + vc_property_read_u8(node, "ti,i2c-high-speed-mcode", + &i2c_config.mcode); + + if (i2c_config.highspeed) { + pname = "ti,i2c-pad-load"; + of_property_read_u32(node, pname, &i2c_config.i2c_clk_pad_load); + pname = "ti,i2c-pcb-length"; + of_property_read_u32(node, pname, + &i2c_config.i2c_clk_pcb_length); + + if (i2c_config.i2c_clk_pad_load) { + if (!i2c_config.i2c_clk_pcb_length) + i2c_config.i2c_clk_pcb_length = 63; + + ret = omap_vc_i2c_timing_init(dev, &i2c_config); + if (ret) + goto out; + + goto skip_i2c_clk_config; + } + } + + /* So, we do not have pad load defined, expect proper timing config */ + pname = "ti,i2c-clk-scl-low"; + ret = vc_property_read_u8(node, pname, &i2c_config.i2c_clk_scll); + if (ret) + goto invalid_of_property; + + pname = "ti,i2c-clk-scl-high"; + ret = vc_property_read_u8(node, pname, &i2c_config.i2c_clk_sclh); + if (ret) + goto invalid_of_property; + + pname = "ti,i2c-clk-hsscl-low"; + ret = vc_property_read_u8(node, pname, &i2c_config.i2c_clk_hsscll); + if (ret && i2c_config.highspeed) + goto invalid_of_property; + + pname = "ti,i2c-clk-hsscl-high"; + ret = vc_property_read_u8(node, pname, &i2c_config.i2c_clk_hssclh); + if (ret && i2c_config.highspeed) + goto invalid_of_property; + +skip_i2c_clk_config: + vc = devm_kzalloc(dev, sizeof(*vc), GFP_KERNEL); + if (!vc) { + dev_err(dev, "unable to allocate vc\n"); + ret = -ENOMEM; + goto out; + } + vc->regmap = regmap; + vc->regs = match->data; + + ret = omap_vc_i2c_config(vc, &i2c_config); + if (ret) { + dev_err(dev, "Bad I2C configuration: %d\n", ret); + goto out; + } + + platform_set_drvdata(pdev, vc); + ret = of_platform_populate(dev->of_node, + omap_vc_channel_of_match_tbl, NULL, dev); + + if (ret) + dev_err(dev, "Failed to create DT children: %d\n", ret); + +out: + return ret; + +invalid_of_property: + dev_err(dev, "Missing/Invalid '%s' property - error(%d)\n", pname, ret); + return ret; +} + +static struct platform_driver omap_vc_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(omap_vc_of_match_tbl), + }, + .probe = omap_vc_probe, +}; + +static int __init omap_vc_init(void) +{ + int ret = 0; + + ret = platform_driver_register(&omap_vc_driver); + if (ret) { + pr_err("%s: platform driver register failed for VC\n", + __func__); + return ret; + } + + ret = platform_driver_register(&omap_vc_channel_driver); + if (ret) { + pr_err("%s: platform driver register failed for VC\n", + __func__); + return ret; + } + + return 0; +} +module_init(omap_vc_init); + +static void __exit omap_vc_exit(void) +{ + platform_driver_unregister(&omap_vc_channel_driver); + platform_driver_unregister(&omap_vc_driver); +} +module_exit(omap_vc_exit); + +MODULE_DESCRIPTION("OMAP Voltage Controller Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); +MODULE_AUTHOR("Texas Instruments Inc."); diff --git a/drivers/power/avs/omap_vc.h b/drivers/power/avs/omap_vc.h new file mode 100644 index 0000000..11503b0 --- /dev/null +++ b/drivers/power/avs/omap_vc.h @@ -0,0 +1,67 @@ +/* + * OMAP Voltage Controller (VC) interface exported functions + * + * Idea based on arch/arm/mach-omap2/vc.h + * Copyright (C) 2007, 2010 Texas Instruments, Inc. + * Rajendra Nayak <rnayak@xxxxxx> + * Lesly A M <x0080970@xxxxxx> + * Thara Gopinath <thara@xxxxxx> + * + * Copyright (C) 2008, 2011 Nokia Corporation + * Kalle Jokiniemi + * 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_AVS_OMAP_VC_H +#define _POWER_AVS_OMAP_VC_H + +struct omap_pmic; +/* Internal to VC */ +struct omap_vc_channel; + +/** + * struct omap_vc_channel_info - Channel information visible to users + * @pmic: PMIC pointer + * @retention_uV: retention voltage in micro volts + * @off_uV: OFF voltage in micro volts + */ +struct omap_vc_channel_info { + struct omap_pmic *pmic; + u32 retention_uV; + u32 off_uV; + /* private: */ + /* Used internally by Voltage Controller driver */ + struct omap_vc_channel *ch; +}; + +#if IS_ENABLED(CONFIG_POWER_TI_HARDWARE_VOLTAGE_CONTROL) +struct omap_vc_channel_info *devm_omap_vc_channel_get(struct device *dev, + struct omap_pmic *pmic); +int omap_vc_channel_set_on_voltage(struct omap_vc_channel_info *info, u32 uv); +#else +struct inline omap_vc_channel_info * + devm_omap_vc_channel_get(struct device *dev, struct omap_pmic *pmic) +{ + return ERR_PTR(-ENODEV); +} + +static inline int omap_vc_channel_set_on_voltage(struct omap_vc_channel_info + *info, u32 uv) +{ + return -ENODEV; +} +#endif + +#endif /* _POWER_AVS_OMAP_VC_H */ -- 1.7.9.5 -- To unsubscribe from this list: send the line "unsubscribe linux-doc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html