diff options
author | Rodrigo Vivi <rodrigo.vivi@intel.com> | 2020-02-25 20:29:58 +0300 |
---|---|---|
committer | Rodrigo Vivi <rodrigo.vivi@intel.com> | 2020-02-25 20:39:23 +0300 |
commit | ff36e78fdb251b9fa65028554689806961e011eb (patch) | |
tree | f5af925d509224e06a10936196be6c06bcbdc6ae /drivers/power | |
parent | 143d9c3e7b6aa2b785abba04266ed75f2b52e94a (diff) | |
parent | 1b245ec5b685ebf8e6e5d1e6b5bcc03b6608e8b0 (diff) | |
download | linux-ff36e78fdb251b9fa65028554689806961e011eb.tar.xz |
Merge drm/drm-next into drm-intel-next-queued
Some DSI and VBT pending patches from Hans will apply
cleanly and with less ugly conflicts if they are rebuilt
on top of other patches that recently landed on drm-next.
Reference: https://patchwork.freedesktop.org/series/70952/
Cc: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Rodrigo Vivi <rodrigo.vivi@intel.com
Diffstat (limited to 'drivers/power')
26 files changed, 2588 insertions, 211 deletions
diff --git a/drivers/power/avs/Kconfig b/drivers/power/avs/Kconfig index 089b6244b716..cdb4237bfd02 100644 --- a/drivers/power/avs/Kconfig +++ b/drivers/power/avs/Kconfig @@ -12,6 +12,22 @@ menuconfig POWER_AVS Say Y here to enable Adaptive Voltage Scaling class support. +config QCOM_CPR + tristate "QCOM Core Power Reduction (CPR) support" + depends on POWER_AVS && HAS_IOMEM + select PM_OPP + select REGMAP + help + Say Y here to enable support for the CPR hardware found on Qualcomm + SoCs like QCS404. + + This driver populates CPU OPPs tables and makes adjustments to the + tables based on feedback from the CPR hardware. If you want to do + CPUfrequency scaling say Y here. + + To compile this driver as a module, choose M here: the module will + be called qcom-cpr + config ROCKCHIP_IODOMAIN tristate "Rockchip IO domain support" depends on POWER_AVS && ARCH_ROCKCHIP && OF diff --git a/drivers/power/avs/Makefile b/drivers/power/avs/Makefile index a1b8cd453f19..9007d05853e2 100644 --- a/drivers/power/avs/Makefile +++ b/drivers/power/avs/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_POWER_AVS_OMAP) += smartreflex.o +obj-$(CONFIG_QCOM_CPR) += qcom-cpr.o obj-$(CONFIG_ROCKCHIP_IODOMAIN) += rockchip-io-domain.o diff --git a/drivers/power/avs/qcom-cpr.c b/drivers/power/avs/qcom-cpr.c new file mode 100644 index 000000000000..bd7c3e48b386 --- /dev/null +++ b/drivers/power/avs/qcom-cpr.c @@ -0,0 +1,1794 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. + * Copyright (c) 2019, Linaro Limited + */ + +#include <linux/module.h> +#include <linux/err.h> +#include <linux/debugfs.h> +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/bitops.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pm_domain.h> +#include <linux/pm_opp.h> +#include <linux/interrupt.h> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> +#include <linux/regulator/consumer.h> +#include <linux/clk.h> +#include <linux/nvmem-consumer.h> + +/* Register Offsets for RB-CPR and Bit Definitions */ + +/* RBCPR Version Register */ +#define REG_RBCPR_VERSION 0 +#define RBCPR_VER_2 0x02 +#define FLAGS_IGNORE_1ST_IRQ_STATUS BIT(0) + +/* RBCPR Gate Count and Target Registers */ +#define REG_RBCPR_GCNT_TARGET(n) (0x60 + 4 * (n)) + +#define RBCPR_GCNT_TARGET_TARGET_SHIFT 0 +#define RBCPR_GCNT_TARGET_TARGET_MASK GENMASK(11, 0) +#define RBCPR_GCNT_TARGET_GCNT_SHIFT 12 +#define RBCPR_GCNT_TARGET_GCNT_MASK GENMASK(9, 0) + +/* RBCPR Timer Control */ +#define REG_RBCPR_TIMER_INTERVAL 0x44 +#define REG_RBIF_TIMER_ADJUST 0x4c + +#define RBIF_TIMER_ADJ_CONS_UP_MASK GENMASK(3, 0) +#define RBIF_TIMER_ADJ_CONS_UP_SHIFT 0 +#define RBIF_TIMER_ADJ_CONS_DOWN_MASK GENMASK(3, 0) +#define RBIF_TIMER_ADJ_CONS_DOWN_SHIFT 4 +#define RBIF_TIMER_ADJ_CLAMP_INT_MASK GENMASK(7, 0) +#define RBIF_TIMER_ADJ_CLAMP_INT_SHIFT 8 + +/* RBCPR Config Register */ +#define REG_RBIF_LIMIT 0x48 +#define RBIF_LIMIT_CEILING_MASK GENMASK(5, 0) +#define RBIF_LIMIT_CEILING_SHIFT 6 +#define RBIF_LIMIT_FLOOR_BITS 6 +#define RBIF_LIMIT_FLOOR_MASK GENMASK(5, 0) + +#define RBIF_LIMIT_CEILING_DEFAULT RBIF_LIMIT_CEILING_MASK +#define RBIF_LIMIT_FLOOR_DEFAULT 0 + +#define REG_RBIF_SW_VLEVEL 0x94 +#define RBIF_SW_VLEVEL_DEFAULT 0x20 + +#define REG_RBCPR_STEP_QUOT 0x80 +#define RBCPR_STEP_QUOT_STEPQUOT_MASK GENMASK(7, 0) +#define RBCPR_STEP_QUOT_IDLE_CLK_MASK GENMASK(3, 0) +#define RBCPR_STEP_QUOT_IDLE_CLK_SHIFT 8 + +/* RBCPR Control Register */ +#define REG_RBCPR_CTL 0x90 + +#define RBCPR_CTL_LOOP_EN BIT(0) +#define RBCPR_CTL_TIMER_EN BIT(3) +#define RBCPR_CTL_SW_AUTO_CONT_ACK_EN BIT(5) +#define RBCPR_CTL_SW_AUTO_CONT_NACK_DN_EN BIT(6) +#define RBCPR_CTL_COUNT_MODE BIT(10) +#define RBCPR_CTL_UP_THRESHOLD_MASK GENMASK(3, 0) +#define RBCPR_CTL_UP_THRESHOLD_SHIFT 24 +#define RBCPR_CTL_DN_THRESHOLD_MASK GENMASK(3, 0) +#define RBCPR_CTL_DN_THRESHOLD_SHIFT 28 + +/* RBCPR Ack/Nack Response */ +#define REG_RBIF_CONT_ACK_CMD 0x98 +#define REG_RBIF_CONT_NACK_CMD 0x9c + +/* RBCPR Result status Register */ +#define REG_RBCPR_RESULT_0 0xa0 + +#define RBCPR_RESULT0_BUSY_SHIFT 19 +#define RBCPR_RESULT0_BUSY_MASK BIT(RBCPR_RESULT0_BUSY_SHIFT) +#define RBCPR_RESULT0_ERROR_LT0_SHIFT 18 +#define RBCPR_RESULT0_ERROR_SHIFT 6 +#define RBCPR_RESULT0_ERROR_MASK GENMASK(11, 0) +#define RBCPR_RESULT0_ERROR_STEPS_SHIFT 2 +#define RBCPR_RESULT0_ERROR_STEPS_MASK GENMASK(3, 0) +#define RBCPR_RESULT0_STEP_UP_SHIFT 1 + +/* RBCPR Interrupt Control Register */ +#define REG_RBIF_IRQ_EN(n) (0x100 + 4 * (n)) +#define REG_RBIF_IRQ_CLEAR 0x110 +#define REG_RBIF_IRQ_STATUS 0x114 + +#define CPR_INT_DONE BIT(0) +#define CPR_INT_MIN BIT(1) +#define CPR_INT_DOWN BIT(2) +#define CPR_INT_MID BIT(3) +#define CPR_INT_UP BIT(4) +#define CPR_INT_MAX BIT(5) +#define CPR_INT_CLAMP BIT(6) +#define CPR_INT_ALL (CPR_INT_DONE | CPR_INT_MIN | CPR_INT_DOWN | \ + CPR_INT_MID | CPR_INT_UP | CPR_INT_MAX | CPR_INT_CLAMP) +#define CPR_INT_DEFAULT (CPR_INT_UP | CPR_INT_DOWN) + +#define CPR_NUM_RING_OSC 8 + +/* CPR eFuse parameters */ +#define CPR_FUSE_TARGET_QUOT_BITS_MASK GENMASK(11, 0) + +#define CPR_FUSE_MIN_QUOT_DIFF 50 + +#define FUSE_REVISION_UNKNOWN (-1) + +enum voltage_change_dir { + NO_CHANGE, + DOWN, + UP, +}; + +struct cpr_fuse { + char *ring_osc; + char *init_voltage; + char *quotient; + char *quotient_offset; +}; + +struct fuse_corner_data { + int ref_uV; + int max_uV; + int min_uV; + int max_volt_scale; + int max_quot_scale; + /* fuse quot */ + int quot_offset; + int quot_scale; + int quot_adjust; + /* fuse quot_offset */ + int quot_offset_scale; + int quot_offset_adjust; +}; + +struct cpr_fuses { + int init_voltage_step; + int init_voltage_width; + struct fuse_corner_data *fuse_corner_data; +}; + +struct corner_data { + unsigned int fuse_corner; + unsigned long freq; +}; + +struct cpr_desc { + unsigned int num_fuse_corners; + int min_diff_quot; + int *step_quot; + + unsigned int timer_delay_us; + unsigned int timer_cons_up; + unsigned int timer_cons_down; + unsigned int up_threshold; + unsigned int down_threshold; + unsigned int idle_clocks; + unsigned int gcnt_us; + unsigned int vdd_apc_step_up_limit; + unsigned int vdd_apc_step_down_limit; + unsigned int clamp_timer_interval; + + struct cpr_fuses cpr_fuses; + bool reduce_to_fuse_uV; + bool reduce_to_corner_uV; +}; + +struct acc_desc { + unsigned int enable_reg; + u32 enable_mask; + + struct reg_sequence *config; + struct reg_sequence *settings; + int num_regs_per_fuse; +}; + +struct cpr_acc_desc { + const struct cpr_desc *cpr_desc; + const struct acc_desc *acc_desc; +}; + +struct fuse_corner { + int min_uV; + int max_uV; + int uV; + int quot; + int step_quot; + const struct reg_sequence *accs; + int num_accs; + unsigned long max_freq; + u8 ring_osc_idx; +}; + +struct corner { + int min_uV; + int max_uV; + int uV; + int last_uV; + int quot_adjust; + u32 save_ctl; + u32 save_irq; + unsigned long freq; + struct fuse_corner *fuse_corner; +}; + +struct cpr_drv { + unsigned int num_corners; + unsigned int ref_clk_khz; + + struct generic_pm_domain pd; + struct device *dev; + struct device *attached_cpu_dev; + struct mutex lock; + void __iomem *base; + struct corner *corner; + struct regulator *vdd_apc; + struct clk *cpu_clk; + struct regmap *tcsr; + bool loop_disabled; + u32 gcnt; + unsigned long flags; + + struct fuse_corner *fuse_corners; + struct corner *corners; + + const struct cpr_desc *desc; + const struct acc_desc *acc_desc; + const struct cpr_fuse *cpr_fuses; + + struct dentry *debugfs; +}; + +static bool cpr_is_allowed(struct cpr_drv *drv) +{ + return !drv->loop_disabled; +} + +static void cpr_write(struct cpr_drv *drv, u32 offset, u32 value) +{ + writel_relaxed(value, drv->base + offset); +} + +static u32 cpr_read(struct cpr_drv *drv, u32 offset) +{ + return readl_relaxed(drv->base + offset); +} + +static void +cpr_masked_write(struct cpr_drv *drv, u32 offset, u32 mask, u32 value) +{ + u32 val; + + val = readl_relaxed(drv->base + offset); + val &= ~mask; + val |= value & mask; + writel_relaxed(val, drv->base + offset); +} + +static void cpr_irq_clr(struct cpr_drv *drv) +{ + cpr_write(drv, REG_RBIF_IRQ_CLEAR, CPR_INT_ALL); +} + +static void cpr_irq_clr_nack(struct cpr_drv *drv) +{ + cpr_irq_clr(drv); + cpr_write(drv, REG_RBIF_CONT_NACK_CMD, 1); +} + +static void cpr_irq_clr_ack(struct cpr_drv *drv) +{ + cpr_irq_clr(drv); + cpr_write(drv, REG_RBIF_CONT_ACK_CMD, 1); +} + +static void cpr_irq_set(struct cpr_drv *drv, u32 int_bits) +{ + cpr_write(drv, REG_RBIF_IRQ_EN(0), int_bits); +} + +static void cpr_ctl_modify(struct cpr_drv *drv, u32 mask, u32 value) +{ + cpr_masked_write(drv, REG_RBCPR_CTL, mask, value); +} + +static void cpr_ctl_enable(struct cpr_drv *drv, struct corner *corner) +{ + u32 val, mask; + const struct cpr_desc *desc = drv->desc; + + /* Program Consecutive Up & Down */ + val = desc->timer_cons_down << RBIF_TIMER_ADJ_CONS_DOWN_SHIFT; + val |= desc->timer_cons_up << RBIF_TIMER_ADJ_CONS_UP_SHIFT; + mask = RBIF_TIMER_ADJ_CONS_UP_MASK | RBIF_TIMER_ADJ_CONS_DOWN_MASK; + cpr_masked_write(drv, REG_RBIF_TIMER_ADJUST, mask, val); + cpr_masked_write(drv, REG_RBCPR_CTL, + RBCPR_CTL_SW_AUTO_CONT_NACK_DN_EN | + RBCPR_CTL_SW_AUTO_CONT_ACK_EN, + corner->save_ctl); + cpr_irq_set(drv, corner->save_irq); + + if (cpr_is_allowed(drv) && corner->max_uV > corner->min_uV) + val = RBCPR_CTL_LOOP_EN; + else + val = 0; + cpr_ctl_modify(drv, RBCPR_CTL_LOOP_EN, val); +} + +static void cpr_ctl_disable(struct cpr_drv *drv) +{ + cpr_irq_set(drv, 0); + cpr_ctl_modify(drv, RBCPR_CTL_SW_AUTO_CONT_NACK_DN_EN | + RBCPR_CTL_SW_AUTO_CONT_ACK_EN, 0); + cpr_masked_write(drv, REG_RBIF_TIMER_ADJUST, + RBIF_TIMER_ADJ_CONS_UP_MASK | + RBIF_TIMER_ADJ_CONS_DOWN_MASK, 0); + cpr_irq_clr(drv); + cpr_write(drv, REG_RBIF_CONT_ACK_CMD, 1); + cpr_write(drv, REG_RBIF_CONT_NACK_CMD, 1); + cpr_ctl_modify(drv, RBCPR_CTL_LOOP_EN, 0); +} + +static bool cpr_ctl_is_enabled(struct cpr_drv *drv) +{ + u32 reg_val; + + reg_val = cpr_read(drv, REG_RBCPR_CTL); + return reg_val & RBCPR_CTL_LOOP_EN; +} + +static bool cpr_ctl_is_busy(struct cpr_drv *drv) +{ + u32 reg_val; + + reg_val = cpr_read(drv, REG_RBCPR_RESULT_0); + return reg_val & RBCPR_RESULT0_BUSY_MASK; +} + +static void cpr_corner_save(struct cpr_drv *drv, struct corner *corner) +{ + corner->save_ctl = cpr_read(drv, REG_RBCPR_CTL); + corner->save_irq = cpr_read(drv, REG_RBIF_IRQ_EN(0)); +} + +static void cpr_corner_restore(struct cpr_drv *drv, struct corner *corner) +{ + u32 gcnt, ctl, irq, ro_sel, step_quot; + struct fuse_corner *fuse = corner->fuse_corner; + const struct cpr_desc *desc = drv->desc; + int i; + + ro_sel = fuse->ring_osc_idx; + gcnt = drv->gcnt; + gcnt |= fuse->quot - corner->quot_adjust; + + /* Program the step quotient and idle clocks */ + step_quot = desc->idle_clocks << RBCPR_STEP_QUOT_IDLE_CLK_SHIFT; + step_quot |= fuse->step_quot & RBCPR_STEP_QUOT_STEPQUOT_MASK; + cpr_write(drv, REG_RBCPR_STEP_QUOT, step_quot); + + /* Clear the target quotient value and gate count of all ROs */ + for (i = 0; i < CPR_NUM_RING_OSC; i++) + cpr_write(drv, REG_RBCPR_GCNT_TARGET(i), 0); + + cpr_write(drv, REG_RBCPR_GCNT_TARGET(ro_sel), gcnt); + ctl = corner->save_ctl; + cpr_write(drv, REG_RBCPR_CTL, ctl); + irq = corner->save_irq; + cpr_irq_set(drv, irq); + dev_dbg(drv->dev, "gcnt = %#08x, ctl = %#08x, irq = %#08x\n", gcnt, + ctl, irq); +} + +static void cpr_set_acc(struct regmap *tcsr, struct fuse_corner *f, + struct fuse_corner *end) +{ + if (f == end) + return; + + if (f < end) { + for (f += 1; f <= end; f++) + regmap_multi_reg_write(tcsr, f->accs, f->num_accs); + } else { + for (f -= 1; f >= end; f--) + regmap_multi_reg_write(tcsr, f->accs, f->num_accs); + } +} + +static int cpr_pre_voltage(struct cpr_drv *drv, + struct fuse_corner *fuse_corner, + enum voltage_change_dir dir) +{ + struct fuse_corner *prev_fuse_corner = drv->corner->fuse_corner; + + if (drv->tcsr && dir == DOWN) + cpr_set_acc(drv->tcsr, prev_fuse_corner, fuse_corner); + + return 0; +} + +static int cpr_post_voltage(struct cpr_drv *drv, + struct fuse_corner *fuse_corner, + enum voltage_change_dir dir) +{ + struct fuse_corner *prev_fuse_corner = drv->corner->fuse_corner; + + if (drv->tcsr && dir == UP) + cpr_set_acc(drv->tcsr, prev_fuse_corner, fuse_corner); + + return 0; +} + +static int cpr_scale_voltage(struct cpr_drv *drv, struct corner *corner, + int new_uV, enum voltage_change_dir dir) +{ + int ret; + struct fuse_corner *fuse_corner = corner->fuse_corner; + + ret = cpr_pre_voltage(drv, fuse_corner, dir); + if (ret) + return ret; + + ret = regulator_set_voltage(drv->vdd_apc, new_uV, new_uV); + if (ret) { + dev_err_ratelimited(drv->dev, "failed to set apc voltage %d\n", + new_uV); + return ret; + } + + ret = cpr_post_voltage(drv, fuse_corner, dir); + if (ret) + return ret; + + return 0; +} + +static unsigned int cpr_get_cur_perf_state(struct cpr_drv *drv) +{ + return drv->corner ? drv->corner - drv->corners + 1 : 0; +} + +static int cpr_scale(struct cpr_drv *drv, enum voltage_change_dir dir) +{ + u32 val, error_steps, reg_mask; + int last_uV, new_uV, step_uV, ret; + struct corner *corner; + const struct cpr_desc *desc = drv->desc; + + if (dir != UP && dir != DOWN) + return 0; + + step_uV = regulator_get_linear_step(drv->vdd_apc); + if (!step_uV) + return -EINVAL; + + corner = drv->corner; + + val = cpr_read(drv, REG_RBCPR_RESULT_0); + + error_steps = val >> RBCPR_RESULT0_ERROR_STEPS_SHIFT; + error_steps &= RBCPR_RESULT0_ERROR_STEPS_MASK; + last_uV = corner->last_uV; + + if (dir == UP) { + if (desc->clamp_timer_interval && + error_steps < desc->up_threshold) { + /* + * Handle the case where another measurement started + * after the interrupt was triggered due to a core + * exiting from power collapse. + */ + error_steps = max(desc->up_threshold, + desc->vdd_apc_step_up_limit); + } + + if (last_uV >= corner->max_uV) { + cpr_irq_clr_nack(drv); + + /* Maximize the UP threshold */ + reg_mask = RBCPR_CTL_UP_THRESHOLD_MASK; + reg_mask <<= RBCPR_CTL_UP_THRESHOLD_SHIFT; + val = reg_mask; + cpr_ctl_modify(drv, reg_mask, val); + + /* Disable UP interrupt */ + cpr_irq_set(drv, CPR_INT_DEFAULT & ~CPR_INT_UP); + + return 0; + } + + if (error_steps > desc->vdd_apc_step_up_limit) + error_steps = desc->vdd_apc_step_up_limit; + + /* Calculate new voltage */ + new_uV = last_uV + error_steps * step_uV; + new_uV = min(new_uV, corner->max_uV); + + dev_dbg(drv->dev, + "UP: -> new_uV: %d last_uV: %d perf state: %u\n", + new_uV, last_uV, cpr_get_cur_perf_state(drv)); + } else { + if (desc->clamp_timer_interval && + error_steps < desc->down_threshold) { + /* + * Handle the case where another measurement started + * after the interrupt was triggered due to a core + * exiting from power collapse. + */ + error_steps = max(desc->down_threshold, + desc->vdd_apc_step_down_limit); + } + + if (last_uV <= corner->min_uV) { + cpr_irq_clr_nack(drv); + + /* Enable auto nack down */ + reg_mask = RBCPR_CTL_SW_AUTO_CONT_NACK_DN_EN; + val = RBCPR_CTL_SW_AUTO_CONT_NACK_DN_EN; + + cpr_ctl_modify(drv, reg_mask, val); + + /* Disable DOWN interrupt */ + cpr_irq_set(drv, CPR_INT_DEFAULT & ~CPR_INT_DOWN); + + return 0; + } + + if (error_steps > desc->vdd_apc_step_down_limit) + error_steps = desc->vdd_apc_step_down_limit; + + /* Calculate new voltage */ + new_uV = last_uV - error_steps * step_uV; + new_uV = max(new_uV, corner->min_uV); + + dev_dbg(drv->dev, + "DOWN: -> new_uV: %d last_uV: %d perf state: %u\n", + new_uV, last_uV, cpr_get_cur_perf_state(drv)); + } + + ret = cpr_scale_voltage(drv, corner, new_uV, dir); + if (ret) { + cpr_irq_clr_nack(drv); + return ret; + } + drv->corner->last_uV = new_uV; + + if (dir == UP) { + /* Disable auto nack down */ + reg_mask = RBCPR_CTL_SW_AUTO_CONT_NACK_DN_EN; + val = 0; + } else { + /* Restore default threshold for UP */ + reg_mask = RBCPR_CTL_UP_THRESHOLD_MASK; + reg_mask <<= RBCPR_CTL_UP_THRESHOLD_SHIFT; + val = desc->up_threshold; + val <<= RBCPR_CTL_UP_THRESHOLD_SHIFT; + } + + cpr_ctl_modify(drv, reg_mask, val); + + /* Re-enable default interrupts */ + cpr_irq_set(drv, CPR_INT_DEFAULT); + + /* Ack */ + cpr_irq_clr_ack(drv); + + return 0; +} + +static irqreturn_t cpr_irq_handler(int irq, void *dev) +{ + struct cpr_drv *drv = dev; + const struct cpr_desc *desc = drv->desc; + irqreturn_t ret = IRQ_HANDLED; + u32 val; + + mutex_lock(&drv->lock); + + val = cpr_read(drv, REG_RBIF_IRQ_STATUS); + if (drv->flags & FLAGS_IGNORE_1ST_IRQ_STATUS) + val = cpr_read(drv, REG_RBIF_IRQ_STATUS); + + dev_dbg(drv->dev, "IRQ_STATUS = %#02x\n", val); + + if (!cpr_ctl_is_enabled(drv)) { + dev_dbg(drv->dev, "CPR is disabled\n"); + ret = IRQ_NONE; + } else if (cpr_ctl_is_busy(drv) && !desc->clamp_timer_interval) { + dev_dbg(drv->dev, "CPR measurement is not ready\n"); + } else if (!cpr_is_allowed(drv)) { + val = cpr_read(drv, REG_RBCPR_CTL); + dev_err_ratelimited(drv->dev, + "Interrupt broken? RBCPR_CTL = %#02x\n", + val); + ret = IRQ_NONE; + } else { + /* + * Following sequence of handling is as per each IRQ's + * priority + */ + if (val & CPR_INT_UP) { + cpr_scale(drv, UP); + } else if (val & CPR_INT_DOWN) { + cpr_scale(drv, DOWN); + } else if (val & CPR_INT_MIN) { + cpr_irq_clr_nack(drv); + } else if (val & CPR_INT_MAX) { + cpr_irq_clr_nack(drv); + } else if (val & CPR_INT_MID) { + /* RBCPR_CTL_SW_AUTO_CONT_ACK_EN is enabled */ + dev_dbg(drv->dev, "IRQ occurred for Mid Flag\n"); + } else { + dev_dbg(drv->dev, + "IRQ occurred for unknown flag (%#08x)\n", val); + } + + /* Save register values for the corner */ + cpr_corner_save(drv, drv->corner); + } + + mutex_unlock(&drv->lock); + + return ret; +} + +static int cpr_enable(struct cpr_drv *drv) +{ + int ret; + + ret = regulator_enable(drv->vdd_apc); + if (ret) + return ret; + + mutex_lock(&drv->lock); + + if (cpr_is_allowed(drv) && drv->corner) { + cpr_irq_clr(drv); + cpr_corner_restore(drv, drv->corner); + cpr_ctl_enable(drv, drv->corner); + } + + mutex_unlock(&drv->lock); + + return 0; +} + +static int cpr_disable(struct cpr_drv *drv) +{ + int ret; + + mutex_lock(&drv->lock); + + if (cpr_is_allowed(drv)) { + cpr_ctl_disable(drv); + cpr_irq_clr(drv); + } + + mutex_unlock(&drv->lock); + + ret = regulator_disable(drv->vdd_apc); + if (ret) + return ret; + + return 0; +} + +static int cpr_config(struct cpr_drv *drv) +{ + int i; + u32 val, gcnt; + struct corner *corner; + const struct cpr_desc *desc = drv->desc; + + /* Disable interrupt and CPR */ + cpr_write(drv, REG_RBIF_IRQ_EN(0), 0); + cpr_write(drv, REG_RBCPR_CTL, 0); + + /* Program the default HW ceiling, floor and vlevel */ + val = (RBIF_LIMIT_CEILING_DEFAULT & RBIF_LIMIT_CEILING_MASK) + << RBIF_LIMIT_CEILING_SHIFT; + val |= RBIF_LIMIT_FLOOR_DEFAULT & RBIF_LIMIT_FLOOR_MASK; + cpr_write(drv, REG_RBIF_LIMIT, val); + cpr_write(drv, REG_RBIF_SW_VLEVEL, RBIF_SW_VLEVEL_DEFAULT); + + /* + * Clear the target quotient value and gate count of all + * ring oscillators + */ + for (i = 0; i < CPR_NUM_RING_OSC; i++) + cpr_write(drv, REG_RBCPR_GCNT_TARGET(i), 0); + + /* Init and save gcnt */ + gcnt = (drv->ref_clk_khz * desc->gcnt_us) / 1000; + gcnt = gcnt & RBCPR_GCNT_TARGET_GCNT_MASK; + gcnt <<= RBCPR_GCNT_TARGET_GCNT_SHIFT; + drv->gcnt = gcnt; + + /* Program the delay count for the timer */ + val = (drv->ref_clk_khz * desc->timer_delay_us) / 1000; + cpr_write(drv, REG_RBCPR_TIMER_INTERVAL, val); + dev_dbg(drv->dev, "Timer count: %#0x (for %d us)\n", val, + desc->timer_delay_us); + + /* Program Consecutive Up & Down */ + val = desc->timer_cons_down << RBIF_TIMER_ADJ_CONS_DOWN_SHIFT; + val |= desc->timer_cons_up << RBIF_TIMER_ADJ_CONS_UP_SHIFT; + val |= desc->clamp_timer_interval << RBIF_TIMER_ADJ_CLAMP_INT_SHIFT; + cpr_write(drv, REG_RBIF_TIMER_ADJUST, val); + + /* Program the control register */ + val = desc->up_threshold << RBCPR_CTL_UP_THRESHOLD_SHIFT; + val |= desc->down_threshold << RBCPR_CTL_DN_THRESHOLD_SHIFT; + val |= RBCPR_CTL_TIMER_EN | RBCPR_CTL_COUNT_MODE; + val |= RBCPR_CTL_SW_AUTO_CONT_ACK_EN; + cpr_write(drv, REG_RBCPR_CTL, val); + + for (i = 0; i < drv->num_corners; i++) { + corner = &drv->corners[i]; + corner->save_ctl = val; + corner->save_irq = CPR_INT_DEFAULT; + } + + cpr_irq_set(drv, CPR_INT_DEFAULT); + + val = cpr_read(drv, REG_RBCPR_VERSION); + if (val <= RBCPR_VER_2) + drv->flags |= FLAGS_IGNORE_1ST_IRQ_STATUS; + + return 0; +} + +static int cpr_set_performance_state(struct generic_pm_domain *domain, + unsigned int state) +{ + struct cpr_drv *drv = container_of(domain, struct cpr_drv, pd); + struct corner *corner, *end; + enum voltage_change_dir dir; + int ret = 0, new_uV; + + mutex_lock(&drv->lock); + + dev_dbg(drv->dev, "%s: setting perf state: %u (prev state: %u)\n", + __func__, state, cpr_get_cur_perf_state(drv)); + + /* + * Determine new corner we're going to. + * Remove one since lowest performance state is 1. + */ + corner = drv->corners + state - 1; + end = &drv->corners[drv->num_corners - 1]; + if (corner > end || corner < drv->corners) { + ret = -EINVAL; + goto unlock; + } + + /* Determine direction */ + if (drv->corner > corner) + dir = DOWN; + else if (drv->corner < corner) + dir = UP; + else + dir = NO_CHANGE; + + if (cpr_is_allowed(drv)) + new_uV = corner->last_uV; + else + new_uV = corner->uV; + + if (cpr_is_allowed(drv)) + cpr_ctl_disable(drv); + + ret = cpr_scale_voltage(drv, corner, new_uV, dir); + if (ret) + goto unlock; + + if (cpr_is_allowed(drv)) { + cpr_irq_clr(drv); + if (drv->corner != corner) + cpr_corner_restore(drv, corner); + cpr_ctl_enable(drv, corner); + } + + drv->corner = corner; + +unlock: + mutex_unlock(&drv->lock); + + return ret; +} + +static int cpr_read_efuse(struct device *dev, const char *cname, u32 *data) +{ + struct nvmem_cell *cell; + ssize_t len; + char *ret; + int i; + + *data = 0; + + cell = nvmem_cell_get(dev, cname); + if (IS_ERR(cell)) { + if (PTR_ERR(cell) != -EPROBE_DEFER) + dev_err(dev, "undefined cell %s\n", cname); + return PTR_ERR(cell); + } + + ret = nvmem_cell_read(cell, &len); + nvmem_cell_put(cell); + if (IS_ERR(ret)) { + dev_err(dev, "can't read cell %s\n", cname); + return PTR_ERR(ret); + } + + for (i = 0; i < len; i++) + *data |= ret[i] << (8 * i); + + kfree(ret); + dev_dbg(dev, "efuse read(%s) = %x, bytes %zd\n", cname, *data, len); + + return 0; +} + +static int +cpr_populate_ring_osc_idx(struct cpr_drv *drv) +{ + struct fuse_corner *fuse = drv->fuse_corners; + struct fuse_corner *end = fuse + drv->desc->num_fuse_corners; + const struct cpr_fuse *fuses = drv->cpr_fuses; + u32 data; + int ret; + + for (; fuse < end; fuse++, fuses++) { + ret = cpr_read_efuse(drv->dev, fuses->ring_osc, + &data); + if (ret) + return ret; + fuse->ring_osc_idx = data; + } + + return 0; +} + +static int cpr_read_fuse_uV(const struct cpr_desc *desc, + const struct fuse_corner_data *fdata, + const char *init_v_efuse, + int step_volt, + struct cpr_drv *drv) +{ + int step_size_uV, steps, uV; + u32 bits = 0; + int ret; + + ret = cpr_read_efuse(drv->dev, init_v_efuse, &bits); + if (ret) + return ret; + + steps = bits & ~BIT(desc->cpr_fuses.init_voltage_width - 1); + /* Not two's complement.. instead highest bit is sign bit */ + if (bits & BIT(desc->cpr_fuses.init_voltage_width - 1)) + steps = -steps; + + step_size_uV = desc->cpr_fuses.init_voltage_step; + + uV = fdata->ref_uV + steps * step_size_uV; + return DIV_ROUND_UP(uV, step_volt) * step_volt; +} + +static int cpr_fuse_corner_init(struct cpr_drv *drv) +{ + const struct cpr_desc *desc = drv->desc; + const struct cpr_fuse *fuses = drv->cpr_fuses; + const struct acc_desc *acc_desc = drv->acc_desc; + int i; + unsigned int step_volt; + struct fuse_corner_data *fdata; + struct fuse_corner *fuse, *end; + int uV; + const struct reg_sequence *accs; + int ret; + + accs = acc_desc->settings; + + step_volt = regulator_get_linear_step(drv->vdd_apc); + if (!step_volt) + return -EINVAL; + + /* Populate fuse_corner members */ + fuse = drv->fuse_corners; + end = &fuse[desc->num_fuse_corners - 1]; + fdata = desc->cpr_fuses.fuse_corner_data; + + for (i = 0; fuse <= end; fuse++, fuses++, i++, fdata++) { + /* + * Update SoC voltages: platforms might choose a different + * regulators than the one used to characterize the algorithms + * (ie, init_voltage_step). + */ + fdata->min_uV = roundup(fdata->min_uV, step_volt); + fdata->max_uV = roundup(fdata->max_uV, step_volt); + + /* Populate uV */ + uV = cpr_read_fuse_uV(desc, fdata, fuses->init_voltage, + step_volt, drv); + if (uV < 0) + return uV; + + fuse->min_uV = fdata->min_uV; + fuse->max_uV = fdata->max_uV; + fuse->uV = clamp(uV, fuse->min_uV, fuse->max_uV); + + if (fuse == end) { + /* + * Allow the highest fuse corner's PVS voltage to + * define the ceiling voltage for that corner in order + * to support SoC's in which variable ceiling values + * are required. + */ + end->max_uV = max(end->max_uV, end->uV); + } + + /* Populate target quotient by scaling */ + ret = cpr_read_efuse(drv->dev, fuses->quotient, &fuse->quot); + if (ret) + return ret; + + fuse->quot *= fdata->quot_scale; + fuse->quot += fdata->quot_offset; + fuse->quot += fdata->quot_adjust; + fuse->step_quot = desc->step_quot[fuse->ring_osc_idx]; + + /* Populate acc settings */ + fuse->accs = accs; + fuse->num_accs = acc_desc->num_regs_per_fuse; + accs += acc_desc->num_regs_per_fuse; + } + + /* + * Restrict all fuse corner PVS voltages based upon per corner + * ceiling and floor voltages. + */ + for (fuse = drv->fuse_corners, i = 0; fuse <= end; fuse++, i++) { + if (fuse->uV > fuse->max_uV) + fuse->uV = fuse->max_uV; + else if (fuse->uV < fuse->min_uV) + fuse->uV = fuse->min_uV; + + ret = regulator_is_supported_voltage(drv->vdd_apc, + fuse->min_uV, + fuse->min_uV); + if (!ret) { + dev_err(drv->dev, + "min uV: %d (fuse corner: %d) not supported by regulator\n", + fuse->min_uV, i); + return -EINVAL; + } + + ret = regulator_is_supported_voltage(drv->vdd_apc, + fuse->max_uV, + fuse->max_uV); + if (!ret) { + dev_err(drv->dev, + "max uV: %d (fuse corner: %d) not supported by regulator\n", + fuse->max_uV, i); + return -EINVAL; + } + + dev_dbg(drv->dev, + "fuse corner %d: [%d %d %d] RO%hhu quot %d squot %d\n", + i, fuse->min_uV, fuse->uV, fuse->max_uV, + fuse->ring_osc_idx, fuse->quot, fuse->step_quot); + } + + return 0; +} + +static int cpr_calculate_scaling(const char *quot_offset, + struct cpr_drv *drv, + const struct fuse_corner_data *fdata, + const struct corner *corner) +{ + u32 quot_diff = 0; + unsigned long freq_diff; + int scaling; + const struct fuse_corner *fuse, *prev_fuse; + int ret; + + fuse = corner->fuse_corner; + prev_fuse = fuse - 1; + + if (quot_offset) { + ret = cpr_read_efuse(drv->dev, quot_offset, "_diff); + if (ret) + return ret; + + quot_diff *= fdata->quot_offset_scale; + quot_diff += fdata->quot_offset_adjust; + } else { + quot_diff = fuse->quot - prev_fuse->quot; + } + + freq_diff = fuse->max_freq - prev_fuse->max_freq; + freq_diff /= 1000000; /* Convert to MHz */ + scaling = 1000 * quot_diff / freq_diff; + return min(scaling, fdata->max_quot_scale); +} + +static int cpr_interpolate(const struct corner *corner, int step_volt, + const struct fuse_corner_data *fdata) +{ + unsigned long f_high, f_low, f_diff; + int uV_high, uV_low, uV; + u64 temp, temp_limit; + const struct fuse_corner *fuse, *prev_fuse; + + fuse = corner->fuse_corner; + prev_fuse = fuse - 1; + + f_high = fuse->max_freq; + f_low = prev_fuse->max_freq; + uV_high = fuse->uV; + uV_low = prev_fuse->uV; + f_diff = fuse->max_freq - corner->freq; + + /* + * Don't interpolate in the wrong direction. This could happen + * if the adjusted fuse voltage overlaps with the previous fuse's + * adjusted voltage. + */ + if (f_high <= f_low || uV_high <= uV_low || f_high <= corner->freq) + return corner->uV; + + temp = f_diff * (uV_high - uV_low); + do_div(temp, f_high - f_low); + + /* + * max_volt_scale has units of uV/MHz while freq values + * have units of Hz. Divide by 1000000 to convert to. + */ + temp_limit = f_diff * fdata->max_volt_scale; + do_div(temp_limit, 1000000); + + uV = uV_high - min(temp, temp_limit); + return roundup(uV, step_volt); +} + +static unsigned int cpr_get_fuse_corner(struct dev_pm_opp *opp) +{ + struct device_node *np; + unsigned int fuse_corner = 0; + + np = dev_pm_opp_get_of_node(opp); + if (of_property_read_u32(np, "qcom,opp-fuse-level", &fuse_corner)) + pr_err("%s: missing 'qcom,opp-fuse-level' property\n", + __func__); + + of_node_put(np); + + return fuse_corner; +} + +static unsigned long cpr_get_opp_hz_for_req(struct dev_pm_opp *ref, + struct device *cpu_dev) +{ + u64 rate = 0; + struct device_node *ref_np; + struct device_node *desc_np; + struct device_node *child_np = NULL; + struct device_node *child_req_np = NULL; + + desc_np = dev_pm_opp_of_get_opp_desc_node(cpu_dev); + if (!desc_np) + return 0; + + ref_np = dev_pm_opp_get_of_node(ref); + if (!ref_np) + goto out_ref; + + do { + of_node_put(child_req_np); + child_np = of_get_next_available_child(desc_np, child_np); + child_req_np = of_parse_phandle(child_np, "required-opps", 0); + } while (child_np && child_req_np != ref_np); + + if (child_np && child_req_np == ref_np) + of_property_read_u64(child_np, "opp-hz", &rate); + + of_node_put(child_req_np); + of_node_put(child_np); + of_node_put(ref_np); +out_ref: + of_node_put(desc_np); + + return (unsigned long) rate; +} + +static int cpr_corner_init(struct cpr_drv *drv) +{ + const struct cpr_desc *desc = drv->desc; + const struct cpr_fuse *fuses = drv->cpr_fuses; + int i, level, scaling = 0; + unsigned int fnum, fc; + const char *quot_offset; + struct fuse_corner *fuse, *prev_fuse; + struct corner *corner, *end; + struct corner_data *cdata; + const struct fuse_corner_data *fdata; + bool apply_scaling; + unsigned long freq_diff, freq_diff_mhz; + unsigned long freq; + int step_volt = regulator_get_linear_step(drv->vdd_apc); + struct dev_pm_opp *opp; + + if (!step_volt) + return -EINVAL; + + corner = drv->corners; + end = &corner[drv->num_corners - 1]; + + cdata = devm_kcalloc(drv->dev, drv->num_corners, + sizeof(struct corner_data), + GFP_KERNEL); + if (!cdata) + return -ENOMEM; + + /* + * Store maximum frequency for each fuse corner based on the frequency + * plan + */ + for (level = 1; level <= drv->num_corners; level++) { + opp = dev_pm_opp_find_level_exact(&drv->pd.dev, level); + if (IS_ERR(opp)) + return -EINVAL; + fc = cpr_get_fuse_corner(opp); + if (!fc) { + dev_pm_opp_put(opp); + return -EINVAL; + } + fnum = fc - 1; + freq = cpr_get_opp_hz_for_req(opp, drv->attached_cpu_dev); + if (!freq) { + dev_pm_opp_put(opp); + return -EINVAL; + } + cdata[level - 1].fuse_corner = fnum; + cdata[level - 1].freq = freq; + + fuse = &drv->fuse_corners[fnum]; + dev_dbg(drv->dev, "freq: %lu level: %u fuse level: %u\n", + freq, dev_pm_opp_get_level(opp) - 1, fnum); + if (freq > fuse->max_freq) + fuse->max_freq = freq; + dev_pm_opp_put(opp); + } + + /* + * Get the quotient adjustment scaling factor, according to: + * + * scaling = min(1000 * (QUOT(corner_N) - QUOT(corner_N-1)) + * / (freq(corner_N) - freq(corner_N-1)), max_factor) + * + * QUOT(corner_N): quotient read from fuse for fuse corner N + * QUOT(corner_N-1): quotient read from fuse for fuse corner (N - 1) + * freq(corner_N): max frequency in MHz supported by fuse corner N + * freq(corner_N-1): max frequency in MHz supported by fuse corner + * (N - 1) + * + * Then walk through the corners mapped to each fuse corner + * and calculate the quotient adjustment for each one using the + * following formula: + * + * quot_adjust = (freq_max - freq_corner) * scaling / 1000 + * + * freq_max: max frequency in MHz supported by the fuse corner + * freq_corner: frequency in MHz corresponding to the corner + * scaling: calculated from above equation + * + * + * + + + * | v | + * q | f c o | f c + * u | c l | c + * o | f t | f + * t | c a | c + * | c f g | c f + * | e | + * +--------------- +---------------- + * 0 1 2 3 4 5 6 0 1 2 3 4 5 6 + * corner corner + * + * c = corner + * f = fuse corner + * + */ + for (apply_scaling = false, i = 0; corner <= end; corner++, i++) { + fnum = cdata[i].fuse_corner; + fdata = &desc->cpr_fuses.fuse_corner_data[fnum]; + quot_offset = fuses[fnum].quotient_offset; + fuse = &drv->fuse_corners[fnum]; + if (fnum) + prev_fuse = &drv->fuse_corners[fnum - 1]; + else + prev_fuse = NULL; + + corner->fuse_corner = fuse; + corner->freq = cdata[i].freq; + corner->uV = fuse->uV; + + if (prev_fuse && cdata[i - 1].freq == prev_fuse->max_freq) { + scaling = cpr_calculate_scaling(quot_offset, drv, + fdata, corner); + if (scaling < 0) + return scaling; + + apply_scaling = true; + } else if (corner->freq == fuse->max_freq) { + /* This is a fuse corner; don't scale anything */ + apply_scaling = false; + } + + if (apply_scaling) { + freq_diff = fuse->max_freq - corner->freq; + freq_diff_mhz = freq_diff / 1000000; + corner->quot_adjust = scaling * freq_diff_mhz / 1000; + + corner->uV = cpr_interpolate(corner, step_volt, fdata); + } + + corner->max_uV = fuse->max_uV; + corner->min_uV = fuse->min_uV; + corner->uV = clamp(corner->uV, corner->min_uV, corner->max_uV); + corner->last_uV = corner->uV; + + /* Reduce the ceiling voltage if needed */ + if (desc->reduce_to_corner_uV && corner->uV < corner->max_uV) + corner->max_uV = corner->uV; + else if (desc->reduce_to_fuse_uV && fuse->uV < corner->max_uV) + corner->max_uV = max(corner->min_uV, fuse->uV); + + dev_dbg(drv->dev, "corner %d: [%d %d %d] quot %d\n", i, + corner->min_uV, corner->uV, corner->max_uV, + fuse->quot - corner->quot_adjust); + } + + return 0; +} + +static const struct cpr_fuse *cpr_get_fuses(struct cpr_drv *drv) +{ + const struct cpr_desc *desc = drv->desc; + struct cpr_fuse *fuses; + int i; + + fuses = devm_kcalloc(drv->dev, desc->num_fuse_corners, + sizeof(struct cpr_fuse), + GFP_KERNEL); + if (!fuses) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < desc->num_fuse_corners; i++) { + char tbuf[32]; + + snprintf(tbuf, 32, "cpr_ring_osc%d", i + 1); + fuses[i].ring_osc = devm_kstrdup(drv->dev, tbuf, GFP_KERNEL); + if (!fuses[i].ring_osc) + return ERR_PTR(-ENOMEM); + + snprintf(tbuf, 32, "cpr_init_voltage%d", i + 1); + fuses[i].init_voltage = devm_kstrdup(drv->dev, tbuf, + GFP_KERNEL); + if (!fuses[i].init_voltage) + return ERR_PTR(-ENOMEM); + + snprintf(tbuf, 32, "cpr_quotient%d", i + 1); + fuses[i].quotient = devm_kstrdup(drv->dev, tbuf, GFP_KERNEL); + if (!fuses[i].quotient) + return ERR_PTR(-ENOMEM); + + snprintf(tbuf, 32, "cpr_quotient_offset%d", i + 1); + fuses[i].quotient_offset = devm_kstrdup(drv->dev, tbuf, + GFP_KERNEL); + if (!fuses[i].quotient_offset) + return ERR_PTR(-ENOMEM); + } + + return fuses; +} + +static void cpr_set_loop_allowed(struct cpr_drv *drv) +{ + drv->loop_disabled = false; +} + +static int cpr_init_parameters(struct cpr_drv *drv) +{ + const struct cpr_desc *desc = drv->desc; + struct clk *clk; + + clk = clk_get(drv->dev, "ref"); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + drv->ref_clk_khz = clk_get_rate(clk) / 1000; + clk_put(clk); + + if (desc->timer_cons_up > RBIF_TIMER_ADJ_CONS_UP_MASK || + desc->timer_cons_down > RBIF_TIMER_ADJ_CONS_DOWN_MASK || + desc->up_threshold > RBCPR_CTL_UP_THRESHOLD_MASK || + desc->down_threshold > RBCPR_CTL_DN_THRESHOLD_MASK || + desc->idle_clocks > RBCPR_STEP_QUOT_IDLE_CLK_MASK || + desc->clamp_timer_interval > RBIF_TIMER_ADJ_CLAMP_INT_MASK) + return -EINVAL; + + dev_dbg(drv->dev, "up threshold = %u, down threshold = %u\n", + desc->up_threshold, desc->down_threshold); + + return 0; +} + +static int cpr_find_initial_corner(struct cpr_drv *drv) +{ + unsigned long rate; + const struct corner *end; + struct corner *iter; + unsigned int i = 0; + + if (!drv->cpu_clk) { + dev_err(drv->dev, "cannot get rate from NULL clk\n"); + return -EINVAL; + } + + end = &drv->corners[drv->num_corners - 1]; + rate = clk_get_rate(drv->cpu_clk); + + /* + * Some bootloaders set a CPU clock frequency that is not defined + * in the OPP table. When running at an unlisted frequency, + * cpufreq_online() will change to the OPP which has the lowest + * frequency, at or above the unlisted frequency. + * Since cpufreq_online() always "rounds up" in the case of an + * unlisted frequency, this function always "rounds down" in case + * of an unlisted frequency. That way, when cpufreq_online() + * triggers the first ever call to cpr_set_performance_state(), + * it will correctly determine the direction as UP. + */ + for (iter = drv->corners; iter <= end; iter++) { + if (iter->freq > rate) + break; + i++; + if (iter->freq == rate) { + drv->corner = iter; + break; + } + if (iter->freq < rate) + drv->corner = iter; + } + + if (!drv->corner) { + dev_err(drv->dev, "boot up corner not found\n"); + return -EINVAL; + } + + dev_dbg(drv->dev, "boot up perf state: %u\n", i); + + return 0; +} + +static const struct cpr_desc qcs404_cpr_desc = { + .num_fuse_corners = 3, + .min_diff_quot = CPR_FUSE_MIN_QUOT_DIFF, + .step_quot = (int []){ 25, 25, 25, }, + .timer_delay_us = 5000, + .timer_cons_up = 0, + .timer_cons_down = 2, + .up_threshold = 1, + .down_threshold = 3, + .idle_clocks = 15, + .gcnt_us = 1, + .vdd_apc_step_up_limit = 1, + .vdd_apc_step_down_limit = 1, + .cpr_fuses = { + .init_voltage_step = 8000, + .init_voltage_width = 6, + .fuse_corner_data = (struct fuse_corner_data[]){ + /* fuse corner 0 */ + { + .ref_uV = 1224000, + .max_uV = 1224000, + .min_uV = 1048000, + .max_volt_scale = 0, + .max_quot_scale = 0, + .quot_offset = 0, + .quot_scale = 1, + .quot_adjust = 0, + .quot_offset_scale = 5, + .quot_offset_adjust = 0, + }, + /* fuse corner 1 */ + { + .ref_uV = 1288000, + .max_uV = 1288000, + .min_uV = 1048000, + .max_volt_scale = 2000, + .max_quot_scale = 1400, + .quot_offset = 0, + .quot_scale = 1, + .quot_adjust = -20, + .quot_offset_scale = 5, + .quot_offset_adjust = 0, + }, + /* fuse corner 2 */ + { + .ref_uV = 1352000, + .max_uV = 1384000, + .min_uV = 1088000, + .max_volt_scale = 2000, + .max_quot_scale = 1400, + .quot_offset = 0, + .quot_scale = 1, + .quot_adjust = 0, + .quot_offset_scale = 5, + .quot_offset_adjust = 0, + }, + }, + }, +}; + +static const struct acc_desc qcs404_acc_desc = { + .settings = (struct reg_sequence[]){ + { 0xb120, 0x1041040 }, + { 0xb124, 0x41 }, + { 0xb120, 0x0 }, + { 0xb124, 0x0 }, + { 0xb120, 0x0 }, + { 0xb124, 0x0 }, + }, + .config = (struct reg_sequence[]){ + { 0xb138, 0xff }, + { 0xb130, 0x5555 }, + }, + .num_regs_per_fuse = 2, +}; + +static const struct cpr_acc_desc qcs404_cpr_acc_desc = { + .cpr_desc = &qcs404_cpr_desc, + .acc_desc = &qcs404_acc_desc, +}; + +static unsigned int cpr_get_performance_state(struct generic_pm_domain *genpd, + struct dev_pm_opp *opp) +{ + return dev_pm_opp_get_level(opp); +} + +static int cpr_power_off(struct generic_pm_domain *domain) +{ + struct cpr_drv *drv = container_of(domain, struct cpr_drv, pd); + + return cpr_disable(drv); +} + +static int cpr_power_on(struct generic_pm_domain *domain) +{ + struct cpr_drv *drv = container_of(domain, struct cpr_drv, pd); + + return cpr_enable(drv); +} + +static int cpr_pd_attach_dev(struct generic_pm_domain *domain, + struct device *dev) +{ + struct cpr_drv *drv = container_of(domain, struct cpr_drv, pd); + const struct acc_desc *acc_desc = drv->acc_desc; + int ret = 0; + + mutex_lock(&drv->lock); + + dev_dbg(drv->dev, "attach callback for: %s\n", dev_name(dev)); + + /* + * This driver only supports scaling voltage for a CPU cluster + * where all CPUs in the cluster share a single regulator. + * Therefore, save the struct device pointer only for the first + * CPU device that gets attached. There is no need to do any + * additional initialization when further CPUs get attached. + */ + if (drv->attached_cpu_dev) + goto unlock; + + /* + * cpr_scale_voltage() requires the direction (if we are changing + * to a higher or lower OPP). The first time + * cpr_set_performance_state() is called, there is no previous + * performance state defined. Therefore, we call + * cpr_find_initial_corner() that gets the CPU clock frequency + * set by the bootloader, so that we can determine the direction + * the first time cpr_set_performance_state() is called. + */ + drv->cpu_clk = devm_clk_get(dev, NULL); + if (IS_ERR(drv->cpu_clk)) { + ret = PTR_ERR(drv->cpu_clk); + if (ret != -EPROBE_DEFER) + dev_err(drv->dev, "could not get cpu clk: %d\n", ret); + goto unlock; + } + drv->attached_cpu_dev = dev; + + dev_dbg(drv->dev, "using cpu clk from: %s\n", + dev_name(drv->attached_cpu_dev)); + + /* + * Everything related to (virtual) corners has to be initialized + * here, when attaching to the power domain, since we need to know + * the maximum frequency for each fuse corner, and this is only + * available after the cpufreq driver has attached to us. + * The reason for this is that we need to know the highest + * frequency associated with each fuse corner. + */ + ret = dev_pm_opp_get_opp_count(&drv->pd.dev); + if (ret < 0) { + dev_err(drv->dev, "could not get OPP count\n"); + goto unlock; + } + drv->num_corners = ret; + + if (drv->num_corners < 2) { + dev_err(drv->dev, "need at least 2 OPPs to use CPR\n"); + ret = -EINVAL; + goto unlock; + } + + drv->corners = devm_kcalloc(drv->dev, drv->num_corners, + sizeof(*drv->corners), + GFP_KERNEL); + if (!drv->corners) { + ret = -ENOMEM; + goto unlock; + } + + ret = cpr_corner_init(drv); + if (ret) + goto unlock; + + cpr_set_loop_allowed(drv); + + ret = cpr_init_parameters(drv); + if (ret) + goto unlock; + + /* Configure CPR HW but keep it disabled */ + ret = cpr_config(drv); + if (ret) + goto unlock; + + ret = cpr_find_initial_corner(drv); + if (ret) + goto unlock; + + if (acc_desc->config) + regmap_multi_reg_write(drv->tcsr, acc_desc->config, + acc_desc->num_regs_per_fuse); + + /* Enable ACC if required */ + if (acc_desc->enable_mask) + regmap_update_bits(drv->tcsr, acc_desc->enable_reg, + acc_desc->enable_mask, + acc_desc->enable_mask); + + dev_info(drv->dev, "driver initialized with %u OPPs\n", + drv->num_corners); + +unlock: + mutex_unlock(&drv->lock); + + return ret; +} + +static int cpr_debug_info_show(struct seq_file *s, void *unused) +{ + u32 gcnt, ro_sel, ctl, irq_status, reg, error_steps; + u32 step_dn, step_up, error, error_lt0, busy; + struct cpr_drv *drv = s->private; + struct fuse_corner *fuse_corner; + struct corner *corner; + + corner = drv->corner; + fuse_corner = corner->fuse_corner; + + seq_printf(s, "corner, current_volt = %d uV\n", + corner->last_uV); + + ro_sel = fuse_corner->ring_osc_idx; + gcnt = cpr_read(drv, REG_RBCPR_GCNT_TARGET(ro_sel)); + seq_printf(s, "rbcpr_gcnt_target (%u) = %#02X\n", ro_sel, gcnt); + + ctl = cpr_read(drv, REG_RBCPR_CTL); + seq_printf(s, "rbcpr_ctl = %#02X\n", ctl); + + irq_status = cpr_read(drv, REG_RBIF_IRQ_STATUS); + seq_printf(s, "rbcpr_irq_status = %#02X\n", irq_status); + + reg = cpr_read(drv, REG_RBCPR_RESULT_0); + seq_printf(s, "rbcpr_result_0 = %#02X\n", reg); + + step_dn = reg & 0x01; + step_up = (reg >> RBCPR_RESULT0_STEP_UP_SHIFT) & 0x01; + seq_printf(s, " [step_dn = %u", step_dn); + + seq_printf(s, ", step_up = %u", step_up); + + error_steps = (reg >> RBCPR_RESULT0_ERROR_STEPS_SHIFT) + & RBCPR_RESULT0_ERROR_STEPS_MASK; + seq_printf(s, ", error_steps = %u", error_steps); + + error = (reg >> RBCPR_RESULT0_ERROR_SHIFT) & RBCPR_RESULT0_ERROR_MASK; + seq_printf(s, ", error = %u", error); + + error_lt0 = (reg >> RBCPR_RESULT0_ERROR_LT0_SHIFT) & 0x01; + seq_printf(s, ", error_lt_0 = %u", error_lt0); + + busy = (reg >> RBCPR_RESULT0_BUSY_SHIFT) & 0x01; + seq_printf(s, ", busy = %u]\n", busy); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(cpr_debug_info); + +static void cpr_debugfs_init(struct cpr_drv *drv) +{ + drv->debugfs = debugfs_create_dir("qcom_cpr", NULL); + + debugfs_create_file("debug_info", 0444, drv->debugfs, + drv, &cpr_debug_info_fops); +} + +static int cpr_probe(struct platform_device *pdev) +{ + struct resource *res; + struct device *dev = &pdev->dev; + struct cpr_drv *drv; + int irq, ret; + const struct cpr_acc_desc *data; + struct device_node *np; + u32 cpr_rev = FUSE_REVISION_UNKNOWN; + + data = of_device_get_match_data(dev); + if (!data || !data->cpr_desc || !data->acc_desc) + return -EINVAL; + + drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL); + if (!drv) + return -ENOMEM; + drv->dev = dev; + drv->desc = data->cpr_desc; + drv->acc_desc = data->acc_desc; + + drv->fuse_corners = devm_kcalloc(dev, drv->desc->num_fuse_corners, + sizeof(*drv->fuse_corners), + GFP_KERNEL); + if (!drv->fuse_corners) + return -ENOMEM; + + np = of_parse_phandle(dev->of_node, "acc-syscon", 0); + if (!np) + return -ENODEV; + + drv->tcsr = syscon_node_to_regmap(np); + of_node_put(np); + if (IS_ERR(drv->tcsr)) + return PTR_ERR(drv->tcsr); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + drv->base = devm_ioremap_resource(dev, res); + if (IS_ERR(drv->base)) + return PTR_ERR(drv->base); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return -EINVAL; + + drv->vdd_apc = devm_regulator_get(dev, "vdd-apc"); + if (IS_ERR(drv->vdd_apc)) + return PTR_ERR(drv->vdd_apc); + + /* + * Initialize fuse corners, since it simply depends + * on data in efuses. + * Everything related to (virtual) corners has to be + * initialized after attaching to the power domain, + * since it depends on the CPU's OPP table. + */ + ret = cpr_read_efuse(dev, "cpr_fuse_revision", &cpr_rev); + if (ret) + return ret; + + drv->cpr_fuses = cpr_get_fuses(drv); + if (IS_ERR(drv->cpr_fuses)) + return PTR_ERR(drv->cpr_fuses); + + ret = cpr_populate_ring_osc_idx(drv); + if (ret) + return ret; + + ret = cpr_fuse_corner_init(drv); + if (ret) + return ret; + + mutex_init(&drv->lock); + + ret = devm_request_threaded_irq(dev, irq, NULL, + cpr_irq_handler, + IRQF_ONESHOT | IRQF_TRIGGER_RISING, + "cpr", drv); + if (ret) + return ret; + + drv->pd.name = devm_kstrdup_const(dev, dev->of_node->full_name, + GFP_KERNEL); + if (!drv->pd.name) + return -EINVAL; + + drv->pd.power_off = cpr_power_off; + drv->pd.power_on = cpr_power_on; + drv->pd.set_performance_state = cpr_set_performance_state; + drv->pd.opp_to_performance_state = cpr_get_performance_state; + drv->pd.attach_dev = cpr_pd_attach_dev; + + ret = pm_genpd_init(&drv->pd, NULL, true); + if (ret) + return ret; + + ret = of_genpd_add_provider_simple(dev->of_node, &drv->pd); + if (ret) + return ret; + + platform_set_drvdata(pdev, drv); + cpr_debugfs_init(drv); + + return 0; +} + +static int cpr_remove(struct platform_device *pdev) +{ + struct cpr_drv *drv = platform_get_drvdata(pdev); + + if (cpr_is_allowed(drv)) { + cpr_ctl_disable(drv); + cpr_irq_set(drv, 0); + } + + of_genpd_del_provider(pdev->dev.of_node); + pm_genpd_remove(&drv->pd); + + debugfs_remove_recursive(drv->debugfs); + + return 0; +} + +static const struct of_device_id cpr_match_table[] = { + { .compatible = "qcom,qcs404-cpr", .data = &qcs404_cpr_acc_desc }, + { } +}; +MODULE_DEVICE_TABLE(of, cpr_match_table); + +static struct platform_driver cpr_driver = { + .probe = cpr_probe, + .remove = cpr_remove, + .driver = { + .name = "qcom-cpr", + .of_match_table = cpr_match_table, + }, +}; +module_platform_driver(cpr_driver); + +MODULE_DESCRIPTION("Core Power Reduction (CPR) driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/avs/rockchip-io-domain.c b/drivers/power/avs/rockchip-io-domain.c index 398fc954419e..eece97f97ef8 100644 --- a/drivers/power/avs/rockchip-io-domain.c +++ b/drivers/power/avs/rockchip-io-domain.c @@ -152,18 +152,18 @@ static void px30_iodomain_init(struct rockchip_iodomain *iod) int ret; u32 val; - /* if no VCCIO0 supply we should leave things alone */ + /* if no VCCIO6 supply we should leave things alone */ if (!iod->supplies[PX30_IO_VSEL_VCCIO6_SUPPLY_NUM].reg) return; /* - * set vccio0 iodomain to also use this framework + * set vccio6 iodomain to also use this framework * instead of a special gpio. */ val = PX30_IO_VSEL_VCCIO6_SRC | (PX30_IO_VSEL_VCCIO6_SRC << 16); ret = regmap_write(iod->grf, PX30_IO_VSEL, val); if (ret < 0) - dev_warn(iod->dev, "couldn't update vccio0 ctrl\n"); + dev_warn(iod->dev, "couldn't update vccio6 ctrl\n"); } static void rk3288_iodomain_init(struct rockchip_iodomain *iod) diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig index c721939767eb..513efe8e7628 100644 --- a/drivers/power/reset/Kconfig +++ b/drivers/power/reset/Kconfig @@ -26,7 +26,7 @@ config POWER_RESET_AT91_POWEROFF config POWER_RESET_AT91_RESET tristate "Atmel AT91 reset driver" depends on ARCH_AT91 - default SOC_AT91SAM9 || SOC_SAMA5 + default SOC_AT91SAM9 || SOC_SAM9X60 || SOC_SAMA5 help This driver supports restart for Atmel AT91SAM9 and SAMA5 SoCs @@ -34,7 +34,7 @@ config POWER_RESET_AT91_RESET config POWER_RESET_AT91_SAMA5D2_SHDWC tristate "Atmel AT91 SAMA5D2-Compatible shutdown controller driver" depends on ARCH_AT91 - default SOC_SAMA5 + default SOC_SAM9X60 || SOC_SAMA5 help This driver supports the alternate shutdown controller for some Atmel SAMA5 SoCs. It is present for example on SAMA5D2 SoC. @@ -141,14 +141,14 @@ config POWER_RESET_LTC2952 down via the LTC2952. Bindings are made in the device tree. config POWER_RESET_MT6323 - bool "MediaTek MT6323 power-off driver" - depends on MFD_MT6397 - help - The power-off driver is responsible for externally shutdown down - the power of a remote MediaTek SoC MT6323 is connected to through - controlling a tiny circuit BBPU inside MT6323 RTC. - - Say Y if you have a board where MT6323 could be found. + bool "MediaTek MT6323 power-off driver" + depends on MFD_MT6397 + help + The power-off driver is responsible for externally shutdown down + the power of a remote MediaTek SoC MT6323 is connected to through + controlling a tiny circuit BBPU inside MT6323 RTC. + + Say Y if you have a board where MT6323 could be found. config POWER_RESET_QNAP bool "QNAP power-off driver" diff --git a/drivers/power/reset/at91-sama5d2_shdwc.c b/drivers/power/reset/at91-sama5d2_shdwc.c index 1c18f465a245..2fe3a627cb53 100644 --- a/drivers/power/reset/at91-sama5d2_shdwc.c +++ b/drivers/power/reset/at91-sama5d2_shdwc.c @@ -66,7 +66,7 @@ #define SHDW_CFG_NOT_USED (32) -struct shdwc_config { +struct shdwc_reg_config { u8 wkup_pin_input; u8 mr_rtcwk_shift; u8 mr_rttwk_shift; @@ -74,8 +74,17 @@ struct shdwc_config { u8 sr_rttwk_shift; }; +struct pmc_reg_config { + u8 mckr; +}; + +struct reg_config { + struct shdwc_reg_config shdwc; + struct pmc_reg_config pmc; +}; + struct shdwc { - const struct shdwc_config *cfg; + const struct reg_config *rcfg; struct clk *sclk; void __iomem *shdwc_base; void __iomem *mpddrc_base; @@ -95,6 +104,7 @@ static const unsigned long long sdwc_dbc_period[] = { static void __init at91_wakeup_status(struct platform_device *pdev) { struct shdwc *shdw = platform_get_drvdata(pdev); + const struct reg_config *rcfg = shdw->rcfg; u32 reg; char *reason = "unknown"; @@ -106,11 +116,11 @@ static void __init at91_wakeup_status(struct platform_device *pdev) if (!reg) return; - if (SHDW_WK_PIN(reg, shdw->cfg)) + if (SHDW_WK_PIN(reg, &rcfg->shdwc)) reason = "WKUP pin"; - else if (SHDW_RTCWK(reg, shdw->cfg)) + else if (SHDW_RTCWK(reg, &rcfg->shdwc)) reason = "RTC"; - else if (SHDW_RTTWK(reg, shdw->cfg)) + else if (SHDW_RTTWK(reg, &rcfg->shdwc)) reason = "RTT"; pr_info("AT91: Wake-Up source: %s\n", reason); @@ -131,9 +141,9 @@ static void at91_poweroff(void) " str %1, [%0, #" __stringify(AT91_DDRSDRC_LPR) "]\n\t" /* Switch the master clock source to slow clock. */ - "1: ldr r6, [%4, #" __stringify(AT91_PMC_MCKR) "]\n\t" + "1: ldr r6, [%4, %5]\n\t" " bic r6, r6, #" __stringify(AT91_PMC_CSS) "\n\t" - " str r6, [%4, #" __stringify(AT91_PMC_MCKR) "]\n\t" + " str r6, [%4, %5]\n\t" /* Wait for clock switch. */ "2: ldr r6, [%4, #" __stringify(AT91_PMC_SR) "]\n\t" " tst r6, #" __stringify(AT91_PMC_MCKRDY) "\n\t" @@ -148,7 +158,8 @@ static void at91_poweroff(void) "r" cpu_to_le32(AT91_DDRSDRC_LPDDR2_PWOFF), "r" (at91_shdwc->shdwc_base), "r" cpu_to_le32(AT91_SHDW_KEY | AT91_SHDW_SHDW), - "r" (at91_shdwc->pmc_base) + "r" (at91_shdwc->pmc_base), + "r" (at91_shdwc->rcfg->pmc.mckr) : "r6"); } @@ -215,6 +226,7 @@ static u32 at91_shdwc_get_wakeup_input(struct platform_device *pdev, static void at91_shdwc_dt_configure(struct platform_device *pdev) { struct shdwc *shdw = platform_get_drvdata(pdev); + const struct reg_config *rcfg = shdw->rcfg; struct device_node *np = pdev->dev.of_node; u32 mode = 0, tmp, input; @@ -227,10 +239,10 @@ static void at91_shdwc_dt_configure(struct platform_device *pdev) mode |= AT91_SHDW_WKUPDBC(at91_shdwc_debouncer_value(pdev, tmp)); if (of_property_read_bool(np, "atmel,wakeup-rtc-timer")) - mode |= SHDW_RTCWKEN(shdw->cfg); + mode |= SHDW_RTCWKEN(&rcfg->shdwc); if (of_property_read_bool(np, "atmel,wakeup-rtt-timer")) - mode |= SHDW_RTTWKEN(shdw->cfg); + mode |= SHDW_RTTWKEN(&rcfg->shdwc); dev_dbg(&pdev->dev, "%s: mode = %#x\n", __func__, mode); writel(mode, shdw->shdwc_base + AT91_SHDW_MR); @@ -239,30 +251,40 @@ static void at91_shdwc_dt_configure(struct platform_device *pdev) writel(input, shdw->shdwc_base + AT91_SHDW_WUIR); } -static const struct shdwc_config sama5d2_shdwc_config = { - .wkup_pin_input = 0, - .mr_rtcwk_shift = 17, - .mr_rttwk_shift = SHDW_CFG_NOT_USED, - .sr_rtcwk_shift = 5, - .sr_rttwk_shift = SHDW_CFG_NOT_USED, +static const struct reg_config sama5d2_reg_config = { + .shdwc = { + .wkup_pin_input = 0, + .mr_rtcwk_shift = 17, + .mr_rttwk_shift = SHDW_CFG_NOT_USED, + .sr_rtcwk_shift = 5, + .sr_rttwk_shift = SHDW_CFG_NOT_USED, + }, + .pmc = { + .mckr = 0x30, + }, }; -static const struct shdwc_config sam9x60_shdwc_config = { - .wkup_pin_input = 0, - .mr_rtcwk_shift = 17, - .mr_rttwk_shift = 16, - .sr_rtcwk_shift = 5, - .sr_rttwk_shift = 4, +static const struct reg_config sam9x60_reg_config = { + .shdwc = { + .wkup_pin_input = 0, + .mr_rtcwk_shift = 17, + .mr_rttwk_shift = 16, + .sr_rtcwk_shift = 5, + .sr_rttwk_shift = 4, + }, + .pmc = { + .mckr = 0x28, + }, }; static const struct of_device_id at91_shdwc_of_match[] = { { .compatible = "atmel,sama5d2-shdwc", - .data = &sama5d2_shdwc_config, + .data = &sama5d2_reg_config, }, { .compatible = "microchip,sam9x60-shdwc", - .data = &sam9x60_shdwc_config, + .data = &sam9x60_reg_config, }, { /*sentinel*/ } @@ -303,7 +325,7 @@ static int __init at91_shdwc_probe(struct platform_device *pdev) } match = of_match_node(at91_shdwc_of_match, pdev->dev.of_node); - at91_shdwc->cfg = match->data; + at91_shdwc->rcfg = match->data; at91_shdwc->sclk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(at91_shdwc->sclk)) diff --git a/drivers/power/reset/gpio-restart.c b/drivers/power/reset/gpio-restart.c index 308ca9d9d276..5466eeea261c 100644 --- a/drivers/power/reset/gpio-restart.c +++ b/drivers/power/reset/gpio-restart.c @@ -64,9 +64,11 @@ static int gpio_restart_probe(struct platform_device *pdev) gpio_restart->reset_gpio = devm_gpiod_get(&pdev->dev, NULL, open_source ? GPIOD_IN : GPIOD_OUT_LOW); - if (IS_ERR(gpio_restart->reset_gpio)) { - dev_err(&pdev->dev, "Could not get reset GPIO\n"); - return PTR_ERR(gpio_restart->reset_gpio); + ret = PTR_ERR_OR_ZERO(gpio_restart->reset_gpio); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "Could not get reset GPIO\n"); + return ret; } gpio_restart->restart_handler.notifier_call = gpio_restart_notify; diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 27164a1d3c7c..9a5591ab90d0 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -73,10 +73,10 @@ config WM831X_POWER provided by Wolfson Microelectronics WM831x PMICs. config WM8350_POWER - tristate "WM8350 PMU support" - depends on MFD_WM8350 - help - Say Y here to enable support for the power management unit + tristate "WM8350 PMU support" + depends on MFD_WM8350 + help + Say Y here to enable support for the power management unit provided by the Wolfson Microelectronics WM8350 PMIC. config TEST_POWER @@ -209,16 +209,16 @@ config BATTERY_WM97XX Say Y to enable support for battery measured by WM97xx aux port. config BATTERY_SBS - tristate "SBS Compliant gas gauge" - depends on I2C - help + tristate "SBS Compliant gas gauge" + depends on I2C + help Say Y to include support for SBS battery driver for SBS-compliant gas gauges. config CHARGER_SBS - tristate "SBS Compliant charger" - depends on I2C - help + tristate "SBS Compliant charger" + depends on I2C + help Say Y to include support for SBS compliant battery chargers. config MANAGER_SBS @@ -484,11 +484,11 @@ config CHARGER_MANAGER depends on REGULATOR select EXTCON help - Say Y to enable charger-manager support, which allows multiple - chargers attached to a battery and multiple batteries attached to a - system. The charger-manager also can monitor charging status in - runtime and in suspend-to-RAM by waking up the system periodically - with help of suspend_again support. + Say Y to enable charger-manager support, which allows multiple + chargers attached to a battery and multiple batteries attached to a + system. The charger-manager also can monitor charging status in + runtime and in suspend-to-RAM by waking up the system periodically + with help of suspend_again support. config CHARGER_LT3651 tristate "Analog Devices LT3651 charger" diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c index 8a0f9d769690..f69550d64f09 100644 --- a/drivers/power/supply/ab8500_charger.c +++ b/drivers/power/supply/ab8500_charger.c @@ -789,7 +789,7 @@ static int ab8500_charger_max_usb_curr(struct ab8500_charger *di, di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P05; ret = -ENXIO; break; - }; + } di->max_usb_in_curr.set_max = di->max_usb_in_curr.usb_type_max; dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", @@ -1079,7 +1079,7 @@ static int ab8500_charger_get_usb_cur(struct ab8500_charger *di) di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P05; ret = -EPERM; break; - }; + } di->max_usb_in_curr.set_max = di->max_usb_in_curr.usb_type_max; return ret; } @@ -2427,7 +2427,7 @@ static void ab8500_charger_usb_state_changed_work(struct work_struct *work) default: break; - }; + } } /** diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c index c3912ee9eb99..b96f90a82ecf 100644 --- a/drivers/power/supply/ab8500_fg.c +++ b/drivers/power/supply/ab8500_fg.c @@ -2221,10 +2221,10 @@ static int ab8500_fg_get_ext_psy_data(struct device *dev, void *data) ab8500_fg_update_cap_scalers(di); queue_work(di->fg_wq, &di->fg_work); break; - }; + } default: break; - }; + } break; case POWER_SUPPLY_PROP_TECHNOLOGY: switch (ext->desc->type) { @@ -2331,7 +2331,7 @@ static int ab8500_fg_init_hw_registers(struct ab8500_fg *di) if (ret) { dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_MAX_TIME_REG\n", __func__); goto out; - }; + } ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, AB8505_RTC_PCUT_FLAG_TIME_REG, di->bm->fg_params->pcut_flag_time); @@ -2339,7 +2339,7 @@ static int ab8500_fg_init_hw_registers(struct ab8500_fg *di) if (ret) { dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_FLAG_TIME_REG\n", __func__); goto out; - }; + } ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, AB8505_RTC_PCUT_RESTART_REG, di->bm->fg_params->pcut_max_restart); @@ -2347,7 +2347,7 @@ static int ab8500_fg_init_hw_registers(struct ab8500_fg *di) if (ret) { dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_RESTART_REG\n", __func__); goto out; - }; + } ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, AB8505_RTC_PCUT_DEBOUNCE_REG, di->bm->fg_params->pcut_debounce_time); @@ -2355,7 +2355,7 @@ static int ab8500_fg_init_hw_registers(struct ab8500_fg *di) if (ret) { dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_DEBOUNCE_REG\n", __func__); goto out; - }; + } ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, AB8505_RTC_PCUT_CTL_STATUS_REG, di->bm->fg_params->pcut_enable); @@ -2363,7 +2363,7 @@ static int ab8500_fg_init_hw_registers(struct ab8500_fg *di) if (ret) { dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_CTL_STATUS_REG\n", __func__); goto out; - }; + } } out: return ret; diff --git a/drivers/power/supply/abx500_chargalg.c b/drivers/power/supply/abx500_chargalg.c index e6e37d4f20e4..2fb33a07879a 100644 --- a/drivers/power/supply/abx500_chargalg.c +++ b/drivers/power/supply/abx500_chargalg.c @@ -1823,7 +1823,7 @@ static ssize_t abx500_chargalg_en_store(struct abx500_chargalg *di, "Enter 0. Disable AC/USB Charging\n" "1. Enable AC charging\n" "2. Enable USB Charging\n"); - }; + } return strlen(buf); } diff --git a/drivers/power/supply/axp20x_ac_power.c b/drivers/power/supply/axp20x_ac_power.c index 0d34a932b6d5..ac360016b08a 100644 --- a/drivers/power/supply/axp20x_ac_power.c +++ b/drivers/power/supply/axp20x_ac_power.c @@ -15,6 +15,7 @@ #include <linux/of.h> #include <linux/of_device.h> #include <linux/platform_device.h> +#include <linux/pm.h> #include <linux/power_supply.h> #include <linux/regmap.h> #include <linux/slab.h> @@ -23,6 +24,9 @@ #define AXP20X_PWR_STATUS_ACIN_PRESENT BIT(7) #define AXP20X_PWR_STATUS_ACIN_AVAIL BIT(6) +#define AXP813_ACIN_PATH_SEL BIT(7) +#define AXP813_ACIN_PATH_SEL_TO_BIT(x) (!!(x) << 7) + #define AXP813_VHOLD_MASK GENMASK(5, 3) #define AXP813_VHOLD_UV_TO_BIT(x) ((((x) / 100000) - 40) << 3) #define AXP813_VHOLD_REG_TO_UV(x) \ @@ -40,6 +44,9 @@ struct axp20x_ac_power { struct power_supply *supply; struct iio_channel *acin_v; struct iio_channel *acin_i; + bool has_acin_path_sel; + unsigned int num_irqs; + unsigned int irqs[]; }; static irqreturn_t axp20x_ac_power_irq(int irq, void *devid) @@ -86,6 +93,17 @@ static int axp20x_ac_power_get_property(struct power_supply *psy, return ret; val->intval = !!(reg & AXP20X_PWR_STATUS_ACIN_AVAIL); + + /* ACIN_PATH_SEL disables ACIN even if ACIN_AVAIL is set. */ + if (val->intval && power->has_acin_path_sel) { + ret = regmap_read(power->regmap, AXP813_ACIN_PATH_CTRL, + ®); + if (ret) + return ret; + + val->intval = !!(reg & AXP813_ACIN_PATH_SEL); + } + return 0; case POWER_SUPPLY_PROP_VOLTAGE_NOW: @@ -143,6 +161,11 @@ static int axp813_ac_power_set_property(struct power_supply *psy, struct axp20x_ac_power *power = power_supply_get_drvdata(psy); switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + return regmap_update_bits(power->regmap, AXP813_ACIN_PATH_CTRL, + AXP813_ACIN_PATH_SEL, + AXP813_ACIN_PATH_SEL_TO_BIT(val->intval)); + case POWER_SUPPLY_PROP_VOLTAGE_MIN: if (val->intval < 4000000 || val->intval > 4700000) return -EINVAL; @@ -169,7 +192,8 @@ static int axp813_ac_power_set_property(struct power_supply *psy, static int axp813_ac_power_prop_writeable(struct power_supply *psy, enum power_supply_property psp) { - return psp == POWER_SUPPLY_PROP_VOLTAGE_MIN || + return psp == POWER_SUPPLY_PROP_ONLINE || + psp == POWER_SUPPLY_PROP_VOLTAGE_MIN || psp == POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT; } @@ -221,34 +245,86 @@ static const struct power_supply_desc axp813_ac_power_desc = { .set_property = axp813_ac_power_set_property, }; +static const char * const axp20x_irq_names[] = { + "ACIN_PLUGIN", + "ACIN_REMOVAL", +}; + struct axp_data { const struct power_supply_desc *power_desc; + const char * const *irq_names; + unsigned int num_irq_names; bool acin_adc; + bool acin_path_sel; }; static const struct axp_data axp20x_data = { - .power_desc = &axp20x_ac_power_desc, - .acin_adc = true, + .power_desc = &axp20x_ac_power_desc, + .irq_names = axp20x_irq_names, + .num_irq_names = ARRAY_SIZE(axp20x_irq_names), + .acin_adc = true, + .acin_path_sel = false, }; static const struct axp_data axp22x_data = { - .power_desc = &axp22x_ac_power_desc, - .acin_adc = false, + .power_desc = &axp22x_ac_power_desc, + .irq_names = axp20x_irq_names, + .num_irq_names = ARRAY_SIZE(axp20x_irq_names), + .acin_adc = false, + .acin_path_sel = false, }; static const struct axp_data axp813_data = { - .power_desc = &axp813_ac_power_desc, - .acin_adc = false, + .power_desc = &axp813_ac_power_desc, + .irq_names = axp20x_irq_names, + .num_irq_names = ARRAY_SIZE(axp20x_irq_names), + .acin_adc = false, + .acin_path_sel = true, }; +#ifdef CONFIG_PM_SLEEP +static int axp20x_ac_power_suspend(struct device *dev) +{ + struct axp20x_ac_power *power = dev_get_drvdata(dev); + int i = 0; + + /* + * Allow wake via ACIN_PLUGIN only. + * + * As nested threaded IRQs are not automatically disabled during + * suspend, we must explicitly disable the remainder of the IRQs. + */ + if (device_may_wakeup(&power->supply->dev)) + enable_irq_wake(power->irqs[i++]); + while (i < power->num_irqs) + disable_irq(power->irqs[i++]); + + return 0; +} + +static int axp20x_ac_power_resume(struct device *dev) +{ + struct axp20x_ac_power *power = dev_get_drvdata(dev); + int i = 0; + + if (device_may_wakeup(&power->supply->dev)) + disable_irq_wake(power->irqs[i++]); + while (i < power->num_irqs) + enable_irq(power->irqs[i++]); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(axp20x_ac_power_pm_ops, axp20x_ac_power_suspend, + axp20x_ac_power_resume); + static int axp20x_ac_power_probe(struct platform_device *pdev) { struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); struct power_supply_config psy_cfg = {}; struct axp20x_ac_power *power; const struct axp_data *axp_data; - static const char * const irq_names[] = { "ACIN_PLUGIN", "ACIN_REMOVAL", - NULL }; int i, irq, ret; if (!of_device_is_available(pdev->dev.of_node)) @@ -259,12 +335,14 @@ static int axp20x_ac_power_probe(struct platform_device *pdev) return -EINVAL; } - power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL); + axp_data = of_device_get_match_data(&pdev->dev); + + power = devm_kzalloc(&pdev->dev, + struct_size(power, irqs, axp_data->num_irq_names), + GFP_KERNEL); if (!power) return -ENOMEM; - axp_data = of_device_get_match_data(&pdev->dev); - if (axp_data->acin_adc) { power->acin_v = devm_iio_channel_get(&pdev->dev, "acin_v"); if (IS_ERR(power->acin_v)) { @@ -282,6 +360,8 @@ static int axp20x_ac_power_probe(struct platform_device *pdev) } power->regmap = dev_get_regmap(pdev->dev.parent, NULL); + power->has_acin_path_sel = axp_data->acin_path_sel; + power->num_irqs = axp_data->num_irq_names; platform_set_drvdata(pdev, power); @@ -295,20 +375,22 @@ static int axp20x_ac_power_probe(struct platform_device *pdev) return PTR_ERR(power->supply); /* Request irqs after registering, as irqs may trigger immediately */ - for (i = 0; irq_names[i]; i++) { - irq = platform_get_irq_byname(pdev, irq_names[i]); + for (i = 0; i < axp_data->num_irq_names; i++) { + irq = platform_get_irq_byname(pdev, axp_data->irq_names[i]); if (irq < 0) { - dev_warn(&pdev->dev, "No IRQ for %s: %d\n", - irq_names[i], irq); - continue; + dev_err(&pdev->dev, "No IRQ for %s: %d\n", + axp_data->irq_names[i], irq); + return irq; } - irq = regmap_irq_get_virq(axp20x->regmap_irqc, irq); - ret = devm_request_any_context_irq(&pdev->dev, irq, + power->irqs[i] = regmap_irq_get_virq(axp20x->regmap_irqc, irq); + ret = devm_request_any_context_irq(&pdev->dev, power->irqs[i], axp20x_ac_power_irq, 0, DRVNAME, power); - if (ret < 0) - dev_warn(&pdev->dev, "Error requesting %s IRQ: %d\n", - irq_names[i], ret); + if (ret < 0) { + dev_err(&pdev->dev, "Error requesting %s IRQ: %d\n", + axp_data->irq_names[i], ret); + return ret; + } } return 0; @@ -331,8 +413,9 @@ MODULE_DEVICE_TABLE(of, axp20x_ac_power_match); static struct platform_driver axp20x_ac_power_driver = { .probe = axp20x_ac_power_probe, .driver = { - .name = DRVNAME, - .of_match_table = axp20x_ac_power_match, + .name = DRVNAME, + .of_match_table = axp20x_ac_power_match, + .pm = &axp20x_ac_power_pm_ops, }, }; diff --git a/drivers/power/supply/axp20x_usb_power.c b/drivers/power/supply/axp20x_usb_power.c index 5f0a5722b19e..4fde24b5f35a 100644 --- a/drivers/power/supply/axp20x_usb_power.c +++ b/drivers/power/supply/axp20x_usb_power.c @@ -16,6 +16,7 @@ #include <linux/of.h> #include <linux/of_device.h> #include <linux/platform_device.h> +#include <linux/pm.h> #include <linux/power_supply.h> #include <linux/regmap.h> #include <linux/slab.h> @@ -29,6 +30,9 @@ #define AXP20X_USB_STATUS_VBUS_VALID BIT(2) +#define AXP20X_VBUS_PATH_SEL BIT(7) +#define AXP20X_VBUS_PATH_SEL_OFFSET 7 + #define AXP20X_VBUS_VHOLD_uV(b) (4000000 + (((b) >> 3) & 7) * 100000) #define AXP20X_VBUS_VHOLD_MASK GENMASK(5, 3) #define AXP20X_VBUS_VHOLD_OFFSET 3 @@ -57,7 +61,6 @@ #define DEBOUNCE_TIME msecs_to_jiffies(50) struct axp20x_usb_power { - struct device_node *np; struct regmap *regmap; struct power_supply *supply; enum axp20x_variants axp20x_id; @@ -65,14 +68,32 @@ struct axp20x_usb_power { struct iio_channel *vbus_i; struct delayed_work vbus_detect; unsigned int old_status; + unsigned int online; + unsigned int num_irqs; + unsigned int irqs[]; }; +static bool axp20x_usb_vbus_needs_polling(struct axp20x_usb_power *power) +{ + /* + * Polling is only necessary while VBUS is offline. While online, a + * present->absent transition implies an online->offline transition + * and will triger the VBUS_REMOVAL IRQ. + */ + if (power->axp20x_id >= AXP221_ID && !power->online) + return true; + + return false; +} + static irqreturn_t axp20x_usb_power_irq(int irq, void *devid) { struct axp20x_usb_power *power = devid; power_supply_changed(power->supply); + mod_delayed_work(system_wq, &power->vbus_detect, DEBOUNCE_TIME); + return IRQ_HANDLED; } @@ -92,17 +113,11 @@ static void axp20x_usb_power_poll_vbus(struct work_struct *work) power_supply_changed(power->supply); power->old_status = val; + power->online = val & AXP20X_PWR_STATUS_VBUS_USED; out: - mod_delayed_work(system_wq, &power->vbus_detect, DEBOUNCE_TIME); -} - -static bool axp20x_usb_vbus_needs_polling(struct axp20x_usb_power *power) -{ - if (power->axp20x_id >= AXP221_ID) - return true; - - return false; + if (axp20x_usb_vbus_needs_polling(power)) + mod_delayed_work(system_wq, &power->vbus_detect, DEBOUNCE_TIME); } static int axp20x_get_current_max(struct axp20x_usb_power *power, int *val) @@ -264,6 +279,16 @@ static int axp20x_usb_power_get_property(struct power_supply *psy, return 0; } +static int axp813_usb_power_set_online(struct axp20x_usb_power *power, + int intval) +{ + int val = !intval << AXP20X_VBUS_PATH_SEL_OFFSET; + + return regmap_update_bits(power->regmap, + AXP20X_VBUS_IPSOUT_MGMT, + AXP20X_VBUS_PATH_SEL, val); +} + static int axp20x_usb_power_set_voltage_min(struct axp20x_usb_power *power, int intval) { @@ -345,6 +370,11 @@ static int axp20x_usb_power_set_property(struct power_supply *psy, struct axp20x_usb_power *power = power_supply_get_drvdata(psy); switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + if (power->axp20x_id != AXP813_ID) + return -EINVAL; + return axp813_usb_power_set_online(power, val->intval); + case POWER_SUPPLY_PROP_VOLTAGE_MIN: return axp20x_usb_power_set_voltage_min(power, val->intval); @@ -364,6 +394,18 @@ static int axp20x_usb_power_set_property(struct power_supply *psy, static int axp20x_usb_power_prop_writeable(struct power_supply *psy, enum power_supply_property psp) { + struct axp20x_usb_power *power = power_supply_get_drvdata(psy); + + /* + * The VBUS path select flag works differently on on AXP288 and newer: + * - On AXP20x and AXP22x, the flag enables VBUS (ignoring N_VBUSEN). + * - On AXP288 and AXP8xx, the flag disables VBUS (ignoring N_VBUSEN). + * We only expose the control on variants where it can be used to force + * the VBUS input offline. + */ + if (psp == POWER_SUPPLY_PROP_ONLINE) + return power->axp20x_id == AXP813_ID; + return psp == POWER_SUPPLY_PROP_VOLTAGE_MIN || psp == POWER_SUPPLY_PROP_CURRENT_MAX; } @@ -406,6 +448,92 @@ static const struct power_supply_desc axp22x_usb_power_desc = { .set_property = axp20x_usb_power_set_property, }; +static const char * const axp20x_irq_names[] = { + "VBUS_PLUGIN", + "VBUS_REMOVAL", + "VBUS_VALID", + "VBUS_NOT_VALID", +}; + +static const char * const axp22x_irq_names[] = { + "VBUS_PLUGIN", + "VBUS_REMOVAL", +}; + +struct axp_data { + const struct power_supply_desc *power_desc; + const char * const *irq_names; + unsigned int num_irq_names; + enum axp20x_variants axp20x_id; +}; + +static const struct axp_data axp202_data = { + .power_desc = &axp20x_usb_power_desc, + .irq_names = axp20x_irq_names, + .num_irq_names = ARRAY_SIZE(axp20x_irq_names), + .axp20x_id = AXP202_ID, +}; + +static const struct axp_data axp221_data = { + .power_desc = &axp22x_usb_power_desc, + .irq_names = axp22x_irq_names, + .num_irq_names = ARRAY_SIZE(axp22x_irq_names), + .axp20x_id = AXP221_ID, +}; + +static const struct axp_data axp223_data = { + .power_desc = &axp22x_usb_power_desc, + .irq_names = axp22x_irq_names, + .num_irq_names = ARRAY_SIZE(axp22x_irq_names), + .axp20x_id = AXP223_ID, +}; + +static const struct axp_data axp813_data = { + .power_desc = &axp22x_usb_power_desc, + .irq_names = axp22x_irq_names, + .num_irq_names = ARRAY_SIZE(axp22x_irq_names), + .axp20x_id = AXP813_ID, +}; + +#ifdef CONFIG_PM_SLEEP +static int axp20x_usb_power_suspend(struct device *dev) +{ + struct axp20x_usb_power *power = dev_get_drvdata(dev); + int i = 0; + + /* + * Allow wake via VBUS_PLUGIN only. + * + * As nested threaded IRQs are not automatically disabled during + * suspend, we must explicitly disable the remainder of the IRQs. + */ + if (device_may_wakeup(&power->supply->dev)) + enable_irq_wake(power->irqs[i++]); + while (i < power->num_irqs) + disable_irq(power->irqs[i++]); + + return 0; +} + +static int axp20x_usb_power_resume(struct device *dev) +{ + struct axp20x_usb_power *power = dev_get_drvdata(dev); + int i = 0; + + if (device_may_wakeup(&power->supply->dev)) + disable_irq_wake(power->irqs[i++]); + while (i < power->num_irqs) + enable_irq(power->irqs[i++]); + + mod_delayed_work(system_wq, &power->vbus_detect, DEBOUNCE_TIME); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(axp20x_usb_power_pm_ops, axp20x_usb_power_suspend, + axp20x_usb_power_resume); + static int configure_iio_channels(struct platform_device *pdev, struct axp20x_usb_power *power) { @@ -441,12 +569,7 @@ static int axp20x_usb_power_probe(struct platform_device *pdev) struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); struct power_supply_config psy_cfg = {}; struct axp20x_usb_power *power; - static const char * const axp20x_irq_names[] = { "VBUS_PLUGIN", - "VBUS_REMOVAL", "VBUS_VALID", "VBUS_NOT_VALID", NULL }; - static const char * const axp22x_irq_names[] = { - "VBUS_PLUGIN", "VBUS_REMOVAL", NULL }; - const char * const *irq_names; - const struct power_supply_desc *usb_power_desc; + const struct axp_data *axp_data; int i, irq, ret; if (!of_device_is_available(pdev->dev.of_node)) @@ -457,16 +580,19 @@ static int axp20x_usb_power_probe(struct platform_device *pdev) return -EINVAL; } - power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL); + axp_data = of_device_get_match_data(&pdev->dev); + + power = devm_kzalloc(&pdev->dev, + struct_size(power, irqs, axp_data->num_irq_names), + GFP_KERNEL); if (!power) return -ENOMEM; platform_set_drvdata(pdev, power); - power->axp20x_id = (enum axp20x_variants)of_device_get_match_data( - &pdev->dev); - power->np = pdev->dev.of_node; + power->axp20x_id = axp_data->axp20x_id; power->regmap = axp20x->regmap; + power->num_irqs = axp_data->num_irq_names; if (power->axp20x_id == AXP202_ID) { /* Enable vbus valid checking */ @@ -483,18 +609,6 @@ static int axp20x_usb_power_probe(struct platform_device *pdev) if (ret) return ret; - - usb_power_desc = &axp20x_usb_power_desc; - irq_names = axp20x_irq_names; - } else if (power->axp20x_id == AXP221_ID || - power->axp20x_id == AXP223_ID || - power->axp20x_id == AXP813_ID) { - usb_power_desc = &axp22x_usb_power_desc; - irq_names = axp22x_irq_names; - } else { - dev_err(&pdev->dev, "Unsupported AXP variant: %ld\n", - axp20x->variant); - return -EINVAL; } if (power->axp20x_id == AXP813_ID) { @@ -506,25 +620,29 @@ static int axp20x_usb_power_probe(struct platform_device *pdev) psy_cfg.of_node = pdev->dev.of_node; psy_cfg.drv_data = power; - power->supply = devm_power_supply_register(&pdev->dev, usb_power_desc, + power->supply = devm_power_supply_register(&pdev->dev, + axp_data->power_desc, &psy_cfg); if (IS_ERR(power->supply)) return PTR_ERR(power->supply); /* Request irqs after registering, as irqs may trigger immediately */ - for (i = 0; irq_names[i]; i++) { - irq = platform_get_irq_byname(pdev, irq_names[i]); + for (i = 0; i < axp_data->num_irq_names; i++) { + irq = platform_get_irq_byname(pdev, axp_data->irq_names[i]); if (irq < 0) { - dev_warn(&pdev->dev, "No IRQ for %s: %d\n", - irq_names[i], irq); - continue; + dev_err(&pdev->dev, "No IRQ for %s: %d\n", + axp_data->irq_names[i], irq); + return irq; + } + power->irqs[i] = regmap_irq_get_virq(axp20x->regmap_irqc, irq); + ret = devm_request_any_context_irq(&pdev->dev, power->irqs[i], + axp20x_usb_power_irq, 0, + DRVNAME, power); + if (ret < 0) { + dev_err(&pdev->dev, "Error requesting %s IRQ: %d\n", + axp_data->irq_names[i], ret); + return ret; } - irq = regmap_irq_get_virq(axp20x->regmap_irqc, irq); - ret = devm_request_any_context_irq(&pdev->dev, irq, - axp20x_usb_power_irq, 0, DRVNAME, power); - if (ret < 0) - dev_warn(&pdev->dev, "Error requesting %s IRQ: %d\n", - irq_names[i], ret); } INIT_DELAYED_WORK(&power->vbus_detect, axp20x_usb_power_poll_vbus); @@ -546,16 +664,16 @@ static int axp20x_usb_power_remove(struct platform_device *pdev) static const struct of_device_id axp20x_usb_power_match[] = { { .compatible = "x-powers,axp202-usb-power-supply", - .data = (void *)AXP202_ID, + .data = &axp202_data, }, { .compatible = "x-powers,axp221-usb-power-supply", - .data = (void *)AXP221_ID, + .data = &axp221_data, }, { .compatible = "x-powers,axp223-usb-power-supply", - .data = (void *)AXP223_ID, + .data = &axp223_data, }, { .compatible = "x-powers,axp813-usb-power-supply", - .data = (void *)AXP813_ID, + .data = &axp813_data, }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, axp20x_usb_power_match); @@ -564,8 +682,9 @@ static struct platform_driver axp20x_usb_power_driver = { .probe = axp20x_usb_power_probe, .remove = axp20x_usb_power_remove, .driver = { - .name = DRVNAME, - .of_match_table = axp20x_usb_power_match, + .name = DRVNAME, + .of_match_table = axp20x_usb_power_match, + .pm = &axp20x_usb_power_pm_ops, }, }; diff --git a/drivers/power/supply/bq25890_charger.c b/drivers/power/supply/bq25890_charger.c index 9d1ec8d677de..aebd1253dbc9 100644 --- a/drivers/power/supply/bq25890_charger.c +++ b/drivers/power/supply/bq25890_charger.c @@ -25,12 +25,20 @@ #define BQ25895_ID 7 #define BQ25896_ID 0 +enum bq25890_chip_version { + BQ25890, + BQ25892, + BQ25895, + BQ25896, +}; + enum bq25890_fields { F_EN_HIZ, F_EN_ILIM, F_IILIM, /* Reg00 */ F_BHOT, F_BCOLD, F_VINDPM_OFS, /* Reg01 */ F_CONV_START, F_CONV_RATE, F_BOOSTF, F_ICO_EN, F_HVDCP_EN, F_MAXC_EN, F_FORCE_DPM, F_AUTO_DPDM_EN, /* Reg02 */ - F_BAT_LOAD_EN, F_WD_RST, F_OTG_CFG, F_CHG_CFG, F_SYSVMIN, /* Reg03 */ + F_BAT_LOAD_EN, F_WD_RST, F_OTG_CFG, F_CHG_CFG, F_SYSVMIN, + F_MIN_VBAT_SEL, /* Reg03 */ F_PUMPX_EN, F_ICHG, /* Reg04 */ F_IPRECHG, F_ITERM, /* Reg05 */ F_VREG, F_BATLOWV, F_VRECHG, /* Reg06 */ @@ -39,8 +47,9 @@ enum bq25890_fields { F_BATCMP, F_VCLAMP, F_TREG, /* Reg08 */ F_FORCE_ICO, F_TMR2X_EN, F_BATFET_DIS, F_JEITA_VSET, F_BATFET_DLY, F_BATFET_RST_EN, F_PUMPX_UP, F_PUMPX_DN, /* Reg09 */ - F_BOOSTV, F_BOOSTI, /* Reg0A */ - F_VBUS_STAT, F_CHG_STAT, F_PG_STAT, F_SDP_STAT, F_VSYS_STAT, /* Reg0B */ + F_BOOSTV, F_PFM_OTG_DIS, F_BOOSTI, /* Reg0A */ + F_VBUS_STAT, F_CHG_STAT, F_PG_STAT, F_SDP_STAT, F_0B_RSVD, + F_VSYS_STAT, /* Reg0B */ F_WD_FAULT, F_BOOST_FAULT, F_CHG_FAULT, F_BAT_FAULT, F_NTC_FAULT, /* Reg0C */ F_FORCE_VINDPM, F_VINDPM, /* Reg0D */ @@ -91,7 +100,7 @@ struct bq25890_device { struct regmap *rmap; struct regmap_field *rmap_fields[F_MAX_FIELDS]; - int chip_id; + enum bq25890_chip_version chip_version; struct bq25890_init_data init_data; struct bq25890_state state; @@ -111,8 +120,7 @@ static const struct regmap_access_table bq25890_writeable_regs = { static const struct regmap_range bq25890_volatile_reg_ranges[] = { regmap_reg_range(0x00, 0x00), regmap_reg_range(0x09, 0x09), - regmap_reg_range(0x0b, 0x0c), - regmap_reg_range(0x0e, 0x14), + regmap_reg_range(0x0b, 0x14), }; static const struct regmap_access_table bq25890_volatile_regs = { @@ -155,7 +163,7 @@ static const struct reg_field bq25890_reg_fields[] = { [F_OTG_CFG] = REG_FIELD(0x03, 5, 5), [F_CHG_CFG] = REG_FIELD(0x03, 4, 4), [F_SYSVMIN] = REG_FIELD(0x03, 1, 3), - /* MIN_VBAT_SEL on BQ25896 */ + [F_MIN_VBAT_SEL] = REG_FIELD(0x03, 0, 0), // BQ25896 only /* REG04 */ [F_PUMPX_EN] = REG_FIELD(0x04, 7, 7), [F_ICHG] = REG_FIELD(0x04, 0, 6), @@ -188,8 +196,8 @@ static const struct reg_field bq25890_reg_fields[] = { [F_PUMPX_DN] = REG_FIELD(0x09, 0, 0), /* REG0A */ [F_BOOSTV] = REG_FIELD(0x0A, 4, 7), - /* PFM_OTG_DIS 3 on BQ25896 */ [F_BOOSTI] = REG_FIELD(0x0A, 0, 2), // reserved on BQ25895 + [F_PFM_OTG_DIS] = REG_FIELD(0x0A, 3, 3), // BQ25896 only /* REG0B */ [F_VBUS_STAT] = REG_FIELD(0x0B, 5, 7), [F_CHG_STAT] = REG_FIELD(0x0B, 3, 4), @@ -275,6 +283,7 @@ static const union { struct bq25890_lookup lt; } bq25890_tables[] = { /* range tables */ + /* TODO: BQ25896 has max ICHG 3008 mA */ [TBL_ICHG] = { .rt = {0, 5056000, 64000} }, /* uA */ [TBL_ITERM] = { .rt = {64000, 1024000, 64000} }, /* uA */ [TBL_VREG] = { .rt = {3840000, 4608000, 16000} }, /* uV */ @@ -391,11 +400,13 @@ static int bq25890_power_supply_get_property(struct power_supply *psy, break; case POWER_SUPPLY_PROP_MODEL_NAME: - if (bq->chip_id == BQ25890_ID) + if (bq->chip_version == BQ25890) val->strval = "BQ25890"; - else if (bq->chip_id == BQ25895_ID) + else if (bq->chip_version == BQ25892) + val->strval = "BQ25892"; + else if (bq->chip_version == BQ25895) val->strval = "BQ25895"; - else if (bq->chip_id == BQ25896_ID) + else if (bq->chip_version == BQ25896) val->strval = "BQ25896"; else val->strval = "UNKNOWN"; @@ -741,6 +752,56 @@ static int bq25890_usb_notifier(struct notifier_block *nb, unsigned long val, return NOTIFY_OK; } +static int bq25890_get_chip_version(struct bq25890_device *bq) +{ + int id, rev; + + id = bq25890_field_read(bq, F_PN); + if (id < 0) { + dev_err(bq->dev, "Cannot read chip ID.\n"); + return id; + } + + rev = bq25890_field_read(bq, F_DEV_REV); + if (rev < 0) { + dev_err(bq->dev, "Cannot read chip revision.\n"); + return rev; + } + + switch (id) { + case BQ25890_ID: + bq->chip_version = BQ25890; + break; + + /* BQ25892 and BQ25896 share same ID 0 */ + case BQ25896_ID: + switch (rev) { + case 2: + bq->chip_version = BQ25896; + break; + case 1: + bq->chip_version = BQ25892; + break; + default: + dev_err(bq->dev, + "Unknown device revision %d, assume BQ25892\n", + rev); + bq->chip_version = BQ25892; + } + break; + + case BQ25895_ID: + bq->chip_version = BQ25895; + break; + + default: + dev_err(bq->dev, "Unknown chip ID %d\n", id); + return -ENODEV; + } + + return 0; +} + static int bq25890_irq_probe(struct bq25890_device *bq) { struct gpio_desc *irq; @@ -859,16 +920,10 @@ static int bq25890_probe(struct i2c_client *client, i2c_set_clientdata(client, bq); - bq->chip_id = bq25890_field_read(bq, F_PN); - if (bq->chip_id < 0) { - dev_err(dev, "Cannot read chip ID.\n"); - return bq->chip_id; - } - - if ((bq->chip_id != BQ25890_ID) && (bq->chip_id != BQ25895_ID) - && (bq->chip_id != BQ25896_ID)) { - dev_err(dev, "Chip with ID=%d, not supported!\n", bq->chip_id); - return -ENODEV; + ret = bq25890_get_chip_version(bq); + if (ret) { + dev_err(dev, "Cannot read chip ID or unknown chip.\n"); + return ret; } if (!dev->platform_data) { @@ -986,12 +1041,18 @@ static const struct dev_pm_ops bq25890_pm = { static const struct i2c_device_id bq25890_i2c_ids[] = { { "bq25890", 0 }, + { "bq25892", 0 }, + { "bq25895", 0 }, + { "bq25896", 0 }, {}, }; MODULE_DEVICE_TABLE(i2c, bq25890_i2c_ids); static const struct of_device_id bq25890_of_match[] = { { .compatible = "ti,bq25890", }, + { .compatible = "ti,bq25892", }, + { .compatible = "ti,bq25895", }, + { .compatible = "ti,bq25896", }, { }, }; MODULE_DEVICE_TABLE(of, bq25890_of_match); diff --git a/drivers/power/supply/cros_usbpd-charger.c b/drivers/power/supply/cros_usbpd-charger.c index 6cc7c3910e09..30c3d37511c9 100644 --- a/drivers/power/supply/cros_usbpd-charger.c +++ b/drivers/power/supply/cros_usbpd-charger.c @@ -5,7 +5,6 @@ * Copyright (c) 2014 - 2018 Google, Inc */ -#include <linux/mfd/cros_ec.h> #include <linux/module.h> #include <linux/platform_data/cros_ec_commands.h> #include <linux/platform_data/cros_ec_proto.h> @@ -132,11 +131,8 @@ static int cros_usbpd_charger_get_num_ports(struct charger_data *charger) ret = cros_usbpd_charger_ec_command(charger, 0, EC_CMD_CHARGE_PORT_COUNT, NULL, 0, &resp, sizeof(resp)); - if (ret < 0) { - dev_err(charger->dev, - "Unable to get the number of ports (err:0x%x)\n", ret); + if (ret < 0) return ret; - } return resp.port_count; } @@ -148,11 +144,8 @@ static int cros_usbpd_charger_get_usbpd_num_ports(struct charger_data *charger) ret = cros_usbpd_charger_ec_command(charger, 0, EC_CMD_USB_PD_PORTS, NULL, 0, &resp, sizeof(resp)); - if (ret < 0) { - dev_err(charger->dev, - "Unable to get the number or ports (err:0x%x)\n", ret); + if (ret < 0) return ret; - } return resp.num_ports; } diff --git a/drivers/power/supply/ingenic-battery.c b/drivers/power/supply/ingenic-battery.c index 35816d4b3012..2748715c4c75 100644 --- a/drivers/power/supply/ingenic-battery.c +++ b/drivers/power/supply/ingenic-battery.c @@ -100,10 +100,17 @@ static int ingenic_battery_set_scale(struct ingenic_battery *bat) return -EINVAL; } - return iio_write_channel_attribute(bat->channel, - scale_raw[best_idx], - scale_raw[best_idx + 1], - IIO_CHAN_INFO_SCALE); + /* Only set scale if there is more than one (fractional) entry */ + if (scale_len > 2) { + ret = iio_write_channel_attribute(bat->channel, + scale_raw[best_idx], + scale_raw[best_idx + 1], + IIO_CHAN_INFO_SCALE); + if (ret) + return ret; + } + + return 0; } static enum power_supply_property ingenic_battery_properties[] = { diff --git a/drivers/power/supply/ipaq_micro_battery.c b/drivers/power/supply/ipaq_micro_battery.c index 03592ceaca88..192d9db0fb00 100644 --- a/drivers/power/supply/ipaq_micro_battery.c +++ b/drivers/power/supply/ipaq_micro_battery.c @@ -149,7 +149,7 @@ static int micro_batt_get_property(struct power_supply *b, default: val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; break; - }; + } break; case POWER_SUPPLY_PROP_STATUS: val->intval = get_status(b); @@ -168,7 +168,7 @@ static int micro_batt_get_property(struct power_supply *b, break; default: return -EINVAL; - }; + } return 0; } @@ -185,7 +185,7 @@ static int micro_ac_get_property(struct power_supply *b, break; default: return -EINVAL; - }; + } return 0; } diff --git a/drivers/power/supply/ltc2941-battery-gauge.c b/drivers/power/supply/ltc2941-battery-gauge.c index da49436176cd..30a9014b2f95 100644 --- a/drivers/power/supply/ltc2941-battery-gauge.c +++ b/drivers/power/supply/ltc2941-battery-gauge.c @@ -449,7 +449,7 @@ static int ltc294x_i2c_remove(struct i2c_client *client) { struct ltc294x_info *info = i2c_get_clientdata(client); - cancel_delayed_work(&info->work); + cancel_delayed_work_sync(&info->work); power_supply_unregister(info->supply); return 0; } diff --git a/drivers/power/supply/max17040_battery.c b/drivers/power/supply/max17040_battery.c index 62499018e68b..8a1f0ee493aa 100644 --- a/drivers/power/supply/max17040_battery.c +++ b/drivers/power/supply/max17040_battery.c @@ -13,6 +13,7 @@ #include <linux/err.h> #include <linux/i2c.h> #include <linux/delay.h> +#include <linux/interrupt.h> #include <linux/power_supply.h> #include <linux/max17040_battery.h> #include <linux/slab.h> @@ -28,6 +29,9 @@ #define MAX17040_DELAY 1000 #define MAX17040_BATTERY_FULL 95 +#define MAX17040_ATHD_MASK 0xFFC0 +#define MAX17040_ATHD_DEFAULT_POWER_UP 4 + struct max17040_chip { struct i2c_client *client; struct delayed_work work; @@ -42,6 +46,8 @@ struct max17040_chip { int soc; /* State Of Charge */ int status; + /* Low alert threshold from 32% to 1% of the State of Charge */ + u32 low_soc_alert; }; static int max17040_get_property(struct power_supply *psy, @@ -98,6 +104,21 @@ static void max17040_reset(struct i2c_client *client) max17040_write_reg(client, MAX17040_CMD, 0x0054); } +static int max17040_set_low_soc_alert(struct i2c_client *client, u32 level) +{ + int ret; + u16 data; + + level = 32 - level; + data = max17040_read_reg(client, MAX17040_RCOMP); + /* clear the alrt bit and set LSb 5 bits */ + data &= MAX17040_ATHD_MASK; + data |= level; + ret = max17040_write_reg(client, MAX17040_RCOMP, data); + + return ret; +} + static void max17040_get_vcell(struct i2c_client *client) { struct max17040_chip *chip = i2c_get_clientdata(client); @@ -160,21 +181,81 @@ static void max17040_get_status(struct i2c_client *client) chip->status = POWER_SUPPLY_STATUS_FULL; } +static int max17040_get_of_data(struct max17040_chip *chip) +{ + struct device *dev = &chip->client->dev; + + chip->low_soc_alert = MAX17040_ATHD_DEFAULT_POWER_UP; + device_property_read_u32(dev, + "maxim,alert-low-soc-level", + &chip->low_soc_alert); + + if (chip->low_soc_alert <= 0 || chip->low_soc_alert >= 33) + return -EINVAL; + + return 0; +} + +static void max17040_check_changes(struct i2c_client *client) +{ + max17040_get_vcell(client); + max17040_get_soc(client); + max17040_get_online(client); + max17040_get_status(client); +} + static void max17040_work(struct work_struct *work) { struct max17040_chip *chip; + int last_soc, last_status; chip = container_of(work, struct max17040_chip, work.work); - max17040_get_vcell(chip->client); - max17040_get_soc(chip->client); - max17040_get_online(chip->client); - max17040_get_status(chip->client); + /* store SOC and status to check changes */ + last_soc = chip->soc; + last_status = chip->status; + max17040_check_changes(chip->client); + + /* check changes and send uevent */ + if (last_soc != chip->soc || last_status != chip->status) + power_supply_changed(chip->battery); queue_delayed_work(system_power_efficient_wq, &chip->work, MAX17040_DELAY); } +static irqreturn_t max17040_thread_handler(int id, void *dev) +{ + struct max17040_chip *chip = dev; + struct i2c_client *client = chip->client; + + dev_warn(&client->dev, "IRQ: Alert battery low level"); + /* read registers */ + max17040_check_changes(chip->client); + + /* send uevent */ + power_supply_changed(chip->battery); + + /* reset alert bit */ + max17040_set_low_soc_alert(client, chip->low_soc_alert); + + return IRQ_HANDLED; +} + +static int max17040_enable_alert_irq(struct max17040_chip *chip) +{ + struct i2c_client *client = chip->client; + unsigned int flags; + int ret; + + flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT; + ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, + max17040_thread_handler, flags, + chip->battery->desc->name, chip); + + return ret; +} + static enum power_supply_property max17040_battery_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_ONLINE, @@ -196,6 +277,7 @@ static int max17040_probe(struct i2c_client *client, struct i2c_adapter *adapter = client->adapter; struct power_supply_config psy_cfg = {}; struct max17040_chip *chip; + int ret; if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) return -EIO; @@ -206,6 +288,12 @@ static int max17040_probe(struct i2c_client *client, chip->client = client; chip->pdata = client->dev.platform_data; + ret = max17040_get_of_data(chip); + if (ret) { + dev_err(&client->dev, + "failed: low SOC alert OF data out of bounds\n"); + return ret; + } i2c_set_clientdata(client, chip); psy_cfg.drv_data = chip; @@ -220,6 +308,24 @@ static int max17040_probe(struct i2c_client *client, max17040_reset(client); max17040_get_version(client); + /* check interrupt */ + if (client->irq && of_device_is_compatible(client->dev.of_node, + "maxim,max77836-battery")) { + ret = max17040_set_low_soc_alert(client, chip->low_soc_alert); + if (ret) { + dev_err(&client->dev, + "Failed to set low SOC alert: err %d\n", ret); + return ret; + } + + ret = max17040_enable_alert_irq(chip); + if (ret) { + client->irq = 0; + dev_warn(&client->dev, + "Failed to get IRQ err %d\n", ret); + } + } + INIT_DEFERRABLE_WORK(&chip->work, max17040_work); queue_delayed_work(system_power_efficient_wq, &chip->work, MAX17040_DELAY); @@ -244,6 +350,10 @@ static int max17040_suspend(struct device *dev) struct max17040_chip *chip = i2c_get_clientdata(client); cancel_delayed_work(&chip->work); + + if (client->irq && device_may_wakeup(dev)) + enable_irq_wake(client->irq); + return 0; } @@ -254,6 +364,10 @@ static int max17040_resume(struct device *dev) queue_delayed_work(system_power_efficient_wq, &chip->work, MAX17040_DELAY); + + if (client->irq && device_may_wakeup(dev)) + disable_irq_wake(client->irq); + return 0; } diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c index 0dfad2cf13fe..69ec4295d55d 100644 --- a/drivers/power/supply/max17042_battery.c +++ b/drivers/power/supply/max17042_battery.c @@ -282,6 +282,8 @@ static int max17042_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042) ret = regmap_read(map, MAX17042_V_empty, &data); + else if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055) + ret = regmap_read(map, MAX17055_V_empty, &data); else ret = regmap_read(map, MAX17047_V_empty, &data); if (ret < 0) @@ -627,7 +629,8 @@ static void max17042_write_config_regs(struct max17042_chip *chip) config->filter_cfg); regmap_write(map, MAX17042_RelaxCFG, config->relax_cfg); if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047 || - chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050) + chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050 || + chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055) regmap_write(map, MAX17047_FullSOCThr, config->full_soc_thresh); } @@ -758,6 +761,8 @@ static inline void max17042_override_por_values(struct max17042_chip *chip) if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042) max17042_override_por(map, MAX17042_V_empty, config->vempty); + if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055) + max17042_override_por(map, MAX17055_V_empty, config->vempty); else max17042_override_por(map, MAX17047_V_empty, config->vempty); max17042_override_por(map, MAX17042_TempNom, config->temp_nom); @@ -765,7 +770,10 @@ static inline void max17042_override_por_values(struct max17042_chip *chip) max17042_override_por(map, MAX17042_FCTC, config->fctc); max17042_override_por(map, MAX17042_RCOMP0, config->rcomp0); max17042_override_por(map, MAX17042_TempCo, config->tcompc0); - if (chip->chip_type) { + if (chip->chip_type && + ((chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042) || + (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047) || + (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050))) { max17042_override_por(map, MAX17042_EmptyTempCo, config->empty_tempco); max17042_override_por(map, MAX17042_K_empty0, @@ -929,7 +937,8 @@ max17042_get_default_pdata(struct max17042_chip *chip) if (!pdata) return pdata; - if (chip->chip_type != MAXIM_DEVICE_TYPE_MAX17042) { + if ((chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047) || + (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050)) { pdata->init_data = max17047_default_pdata_init_regs; pdata->num_init_data = ARRAY_SIZE(max17047_default_pdata_init_regs); @@ -1167,6 +1176,7 @@ static const struct of_device_id max17042_dt_match[] = { { .compatible = "maxim,max17042" }, { .compatible = "maxim,max17047" }, { .compatible = "maxim,max17050" }, + { .compatible = "maxim,max17055" }, { }, }; MODULE_DEVICE_TABLE(of, max17042_dt_match); @@ -1176,6 +1186,7 @@ static const struct i2c_device_id max17042_id[] = { { "max17042", MAXIM_DEVICE_TYPE_MAX17042 }, { "max17047", MAXIM_DEVICE_TYPE_MAX17047 }, { "max17050", MAXIM_DEVICE_TYPE_MAX17050 }, + { "max17055", MAXIM_DEVICE_TYPE_MAX17055 }, { } }; MODULE_DEVICE_TABLE(i2c, max17042_id); diff --git a/drivers/power/supply/max77650-charger.c b/drivers/power/supply/max77650-charger.c index 5f9477c5cf5a..d913428bedc0 100644 --- a/drivers/power/supply/max77650-charger.c +++ b/drivers/power/supply/max77650-charger.c @@ -354,9 +354,16 @@ static int max77650_charger_remove(struct platform_device *pdev) return max77650_charger_disable(chg); } +static const struct of_device_id max77650_charger_of_match[] = { + { .compatible = "maxim,max77650-charger" }, + { } +}; +MODULE_DEVICE_TABLE(of, max77650_charger_of_match); + static struct platform_driver max77650_charger_driver = { .driver = { .name = "max77650-charger", + .of_match_table = max77650_charger_of_match, }, .probe = max77650_charger_probe, .remove = max77650_charger_remove, diff --git a/drivers/power/supply/pda_power.c b/drivers/power/supply/pda_power.c index 3ae5707d39fa..03a37fd6be27 100644 --- a/drivers/power/supply/pda_power.c +++ b/drivers/power/supply/pda_power.c @@ -429,6 +429,10 @@ wrongid: static int pda_power_remove(struct platform_device *pdev) { +#if IS_ENABLED(CONFIG_USB_PHY) + if (!IS_ERR_OR_NULL(transceiver) && pdata->use_otg_notifier) + usb_unregister_notifier(transceiver, &otg_nb); +#endif if (pdata->is_usb_online && usb_irq) free_irq(usb_irq->start, pda_psy_usb); if (pdata->is_ac_online && ac_irq) diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 5c36c430ce8b..1a9a9fae73d3 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -565,9 +565,11 @@ EXPORT_SYMBOL_GPL(devm_power_supply_get_by_phandle); int power_supply_get_battery_info(struct power_supply *psy, struct power_supply_battery_info *info) { + struct power_supply_resistance_temp_table *resist_table; struct device_node *battery_np; const char *value; int err, len, index; + const __be32 *list; info->energy_full_design_uwh = -EINVAL; info->charge_full_design_uah = -EINVAL; @@ -578,6 +580,7 @@ int power_supply_get_battery_info(struct power_supply *psy, info->constant_charge_current_max_ua = -EINVAL; info->constant_charge_voltage_max_uv = -EINVAL; info->factory_internal_resistance_uohm = -EINVAL; + info->resist_table = NULL; for (index = 0; index < POWER_SUPPLY_OCV_TEMP_MAX; index++) { info->ocv_table[index] = NULL; @@ -644,7 +647,6 @@ int power_supply_get_battery_info(struct power_supply *psy, for (index = 0; index < len; index++) { struct power_supply_battery_ocv_table *table; char *propname; - const __be32 *list; int i, tab_len, size; propname = kasprintf(GFP_KERNEL, "ocv-capacity-table-%d", index); @@ -677,6 +679,26 @@ int power_supply_get_battery_info(struct power_supply *psy, } } + list = of_get_property(battery_np, "resistance-temp-table", &len); + if (!list || !len) + goto out_put_node; + + info->resist_table_size = len / (2 * sizeof(__be32)); + resist_table = info->resist_table = devm_kcalloc(&psy->dev, + info->resist_table_size, + sizeof(*resist_table), + GFP_KERNEL); + if (!info->resist_table) { + power_supply_put_battery_info(psy, info); + err = -ENOMEM; + goto out_put_node; + } + + for (index = 0; index < info->resist_table_size; index++) { + resist_table[index].temp = be32_to_cpu(*list++); + resist_table[index].resistance = be32_to_cpu(*list++); + } + out_put_node: of_node_put(battery_np); return err; @@ -692,10 +714,53 @@ void power_supply_put_battery_info(struct power_supply *psy, if (info->ocv_table[i]) devm_kfree(&psy->dev, info->ocv_table[i]); } + + if (info->resist_table) + devm_kfree(&psy->dev, info->resist_table); } EXPORT_SYMBOL_GPL(power_supply_put_battery_info); /** + * power_supply_temp2resist_simple() - find the battery internal resistance + * percent + * @table: Pointer to battery resistance temperature table + * @table_len: The table length + * @ocv: Current temperature + * + * This helper function is used to look up battery internal resistance percent + * according to current temperature value from the resistance temperature table, + * and the table must be ordered descending. Then the actual battery internal + * resistance = the ideal battery internal resistance * percent / 100. + * + * Return: the battery internal resistance percent + */ +int power_supply_temp2resist_simple(struct power_supply_resistance_temp_table *table, + int table_len, int temp) +{ + int i, resist; + + for (i = 0; i < table_len; i++) + if (temp > table[i].temp) + break; + + if (i > 0 && i < table_len) { + int tmp; + + tmp = (table[i - 1].resistance - table[i].resistance) * + (temp - table[i].temp); + tmp /= table[i - 1].temp - table[i].temp; + resist = tmp + table[i].resistance; + } else if (i == 0) { + resist = table[0].resistance; + } else { + resist = table[table_len - 1].resistance; + } + + return resist; +} +EXPORT_SYMBOL_GPL(power_supply_temp2resist_simple); + +/** * power_supply_ocv2cap_simple() - find the battery capacity * @table: Pointer to battery OCV lookup table * @table_len: OCV table length diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c index f8d74e9f7931..6acd242eed48 100644 --- a/drivers/power/supply/sbs-battery.c +++ b/drivers/power/supply/sbs-battery.c @@ -5,6 +5,7 @@ * Copyright (c) 2010, NVIDIA Corporation. */ +#include <linux/bits.h> #include <linux/delay.h> #include <linux/err.h> #include <linux/gpio/consumer.h> @@ -46,10 +47,10 @@ enum { /* Battery Mode defines */ #define BATTERY_MODE_OFFSET 0x03 -#define BATTERY_MODE_MASK 0x8000 -enum sbs_battery_mode { - BATTERY_MODE_AMPS = 0, - BATTERY_MODE_WATTS = 0x8000 +#define BATTERY_MODE_CAPACITY_MASK BIT(15) +enum sbs_capacity_mode { + CAPACITY_MODE_AMPS = 0, + CAPACITY_MODE_WATTS = BATTERY_MODE_CAPACITY_MASK }; /* manufacturer access defines */ @@ -518,8 +519,8 @@ static void sbs_unit_adjustment(struct i2c_client *client, } } -static enum sbs_battery_mode sbs_set_battery_mode(struct i2c_client *client, - enum sbs_battery_mode mode) +static enum sbs_capacity_mode sbs_set_capacity_mode(struct i2c_client *client, + enum sbs_capacity_mode mode) { int ret, original_val; @@ -527,13 +528,13 @@ static enum sbs_battery_mode sbs_set_battery_mode(struct i2c_client *client, if (original_val < 0) return original_val; - if ((original_val & BATTERY_MODE_MASK) == mode) + if ((original_val & BATTERY_MODE_CAPACITY_MASK) == mode) return mode; - if (mode == BATTERY_MODE_AMPS) - ret = original_val & ~BATTERY_MODE_MASK; + if (mode == CAPACITY_MODE_AMPS) + ret = original_val & ~BATTERY_MODE_CAPACITY_MASK; else - ret = original_val | BATTERY_MODE_MASK; + ret = original_val | BATTERY_MODE_CAPACITY_MASK; ret = sbs_write_word_data(client, BATTERY_MODE_OFFSET, ret); if (ret < 0) @@ -541,7 +542,7 @@ static enum sbs_battery_mode sbs_set_battery_mode(struct i2c_client *client, usleep_range(1000, 2000); - return original_val & BATTERY_MODE_MASK; + return original_val & BATTERY_MODE_CAPACITY_MASK; } static int sbs_get_battery_capacity(struct i2c_client *client, @@ -549,13 +550,13 @@ static int sbs_get_battery_capacity(struct i2c_client *client, union power_supply_propval *val) { s32 ret; - enum sbs_battery_mode mode = BATTERY_MODE_WATTS; + enum sbs_capacity_mode mode = CAPACITY_MODE_WATTS; if (power_supply_is_amp_property(psp)) - mode = BATTERY_MODE_AMPS; + mode = CAPACITY_MODE_AMPS; - mode = sbs_set_battery_mode(client, mode); - if (mode < 0) + mode = sbs_set_capacity_mode(client, mode); + if ((int)mode < 0) return mode; ret = sbs_read_word_data(client, sbs_data[reg_offset].addr); @@ -564,7 +565,7 @@ static int sbs_get_battery_capacity(struct i2c_client *client, val->intval = ret; - ret = sbs_set_battery_mode(client, mode); + ret = sbs_set_capacity_mode(client, mode); if (ret < 0) return ret; @@ -1001,6 +1002,6 @@ module_i2c_driver(sbs_battery_driver); MODULE_DESCRIPTION("SBS battery monitor driver"); MODULE_LICENSE("GPL"); -module_param(force_load, bool, S_IRUSR | S_IRGRP | S_IROTH); +module_param(force_load, bool, 0444); MODULE_PARM_DESC(force_load, "Attempt to load the driver even if no battery is connected"); diff --git a/drivers/power/supply/sc27xx_fuel_gauge.c b/drivers/power/supply/sc27xx_fuel_gauge.c index bc8f5bda5762..469c83fdaa8e 100644 --- a/drivers/power/supply/sc27xx_fuel_gauge.c +++ b/drivers/power/supply/sc27xx_fuel_gauge.c @@ -62,6 +62,8 @@ #define SC27XX_FGU_CUR_BASIC_ADC 8192 #define SC27XX_FGU_SAMPLE_HZ 2 +/* micro Ohms */ +#define SC27XX_FGU_IDEAL_RESISTANCE 20000 /* * struct sc27xx_fgu_data: describe the FGU device @@ -81,9 +83,12 @@ * @max_volt: the maximum constant input voltage in millivolt * @min_volt: the minimum drained battery voltage in microvolt * @table_len: the capacity table length + * @resist_table_len: the resistance table length * @cur_1000ma_adc: ADC value corresponding to 1000 mA * @vol_1000mv_adc: ADC value corresponding to 1000 mV + * @calib_resist: the real resistance of coulomb counter chip in uOhm * @cap_table: capacity table with corresponding ocv + * @resist_table: resistance percent table with corresponding temperature */ struct sc27xx_fgu_data { struct regmap *regmap; @@ -103,15 +108,19 @@ struct sc27xx_fgu_data { int max_volt; int min_volt; int table_len; + int resist_table_len; int cur_1000ma_adc; int vol_1000mv_adc; + int calib_resist; struct power_supply_battery_ocv_table *cap_table; + struct power_supply_resistance_temp_table *resist_table; }; static int sc27xx_fgu_cap_to_clbcnt(struct sc27xx_fgu_data *data, int capacity); static void sc27xx_fgu_capacity_calibration(struct sc27xx_fgu_data *data, int cap, bool int_mode); static void sc27xx_fgu_adjust_cap(struct sc27xx_fgu_data *data, int cap); +static int sc27xx_fgu_get_temp(struct sc27xx_fgu_data *data, int *temp); static const char * const sc27xx_charger_supply_name[] = { "sc2731_charger", @@ -434,7 +443,7 @@ static int sc27xx_fgu_get_current(struct sc27xx_fgu_data *data, int *val) static int sc27xx_fgu_get_vbat_ocv(struct sc27xx_fgu_data *data, int *val) { - int vol, cur, ret; + int vol, cur, ret, temp, resistance; ret = sc27xx_fgu_get_vbat_vol(data, &vol); if (ret) @@ -444,8 +453,19 @@ static int sc27xx_fgu_get_vbat_ocv(struct sc27xx_fgu_data *data, int *val) if (ret) return ret; + resistance = data->internal_resist; + if (data->resist_table_len > 0) { + ret = sc27xx_fgu_get_temp(data, &temp); + if (ret) + return ret; + + resistance = power_supply_temp2resist_simple(data->resist_table, + data->resist_table_len, temp); + resistance = data->internal_resist * resistance / 100; + } + /* Return the battery OCV in micro volts. */ - *val = vol * 1000 - cur * data->internal_resist; + *val = vol * 1000 - cur * resistance; return 0; } @@ -884,7 +904,9 @@ static int sc27xx_fgu_calibration(struct sc27xx_fgu_data *data) */ cal_4200mv = (calib_data & 0x1ff) + 6963 - 4096 - 256; data->vol_1000mv_adc = DIV_ROUND_CLOSEST(cal_4200mv * 10, 42); - data->cur_1000ma_adc = data->vol_1000mv_adc * 4; + data->cur_1000ma_adc = + DIV_ROUND_CLOSEST(data->vol_1000mv_adc * 4 * data->calib_resist, + SC27XX_FGU_IDEAL_RESISTANCE); kfree(buf); return 0; @@ -929,6 +951,18 @@ static int sc27xx_fgu_hw_init(struct sc27xx_fgu_data *data) if (!data->alarm_cap) data->alarm_cap += 1; + data->resist_table_len = info.resist_table_size; + if (data->resist_table_len > 0) { + data->resist_table = devm_kmemdup(data->dev, info.resist_table, + data->resist_table_len * + sizeof(struct power_supply_resistance_temp_table), + GFP_KERNEL); + if (!data->resist_table) { + power_supply_put_battery_info(data->battery, &info); + return -ENOMEM; + } + } + power_supply_put_battery_info(data->battery, &info); ret = sc27xx_fgu_calibration(data); @@ -1051,6 +1085,15 @@ static int sc27xx_fgu_probe(struct platform_device *pdev) return ret; } + ret = device_property_read_u32(&pdev->dev, + "sprd,calib-resistance-micro-ohms", + &data->calib_resist); + if (ret) { + dev_err(&pdev->dev, + "failed to get fgu calibration resistance\n"); + return ret; + } + data->channel = devm_iio_channel_get(dev, "bat-temp"); if (IS_ERR(data->channel)) { dev_err(dev, "failed to get IIO channel\n"); diff --git a/drivers/power/supply/ucs1002_power.c b/drivers/power/supply/ucs1002_power.c index 1b80ae479e7d..cdb9a23d825f 100644 --- a/drivers/power/supply/ucs1002_power.c +++ b/drivers/power/supply/ucs1002_power.c @@ -100,7 +100,9 @@ struct ucs1002_info { struct i2c_client *client; struct regmap *regmap; struct regulator_desc *regulator_descriptor; + struct regulator_dev *rdev; bool present; + bool output_disable; }; static enum power_supply_property ucs1002_props[] = { @@ -233,6 +235,11 @@ static int ucs1002_get_max_current(struct ucs1002_info *info, unsigned int reg; int ret; + if (info->output_disable) { + val->intval = 0; + return 0; + } + ret = regmap_read(info->regmap, UCS1002_REG_ILIMIT, ®); if (ret) return ret; @@ -247,6 +254,12 @@ static int ucs1002_set_max_current(struct ucs1002_info *info, u32 val) unsigned int reg; int ret, idx; + if (val == 0) { + info->output_disable = true; + regulator_disable_regmap(info->rdev); + return 0; + } + for (idx = 0; idx < ARRAY_SIZE(ucs1002_current_limit_uA); idx++) { if (val == ucs1002_current_limit_uA[idx]) break; @@ -270,6 +283,12 @@ static int ucs1002_set_max_current(struct ucs1002_info *info, u32 val) if (reg != idx) return -EINVAL; + info->output_disable = false; + + if (info->rdev && info->rdev->use_count && + !regulator_is_enabled_regmap(info->rdev)) + regulator_enable_regmap(info->rdev); + return 0; } @@ -470,9 +489,24 @@ static irqreturn_t ucs1002_alert_irq(int irq, void *data) return IRQ_HANDLED; } +static int ucs1002_regulator_enable(struct regulator_dev *rdev) +{ + struct ucs1002_info *info = rdev_get_drvdata(rdev); + + /* + * If the output is disabled due to 0 maximum current, just pretend the + * enable did work. The regulator will be enabled as soon as we get a + * a non-zero maximum current budget. + */ + if (info->output_disable) + return 0; + + return regulator_enable_regmap(rdev); +} + static const struct regulator_ops ucs1002_regulator_ops = { .is_enabled = regulator_is_enabled_regmap, - .enable = regulator_enable_regmap, + .enable = ucs1002_regulator_enable, .disable = regulator_disable_regmap, }; @@ -499,7 +533,6 @@ static int ucs1002_probe(struct i2c_client *client, }; struct regulator_config regulator_config = {}; int irq_a_det, irq_alert, ret; - struct regulator_dev *rdev; struct ucs1002_info *info; unsigned int regval; @@ -589,10 +622,11 @@ static int ucs1002_probe(struct i2c_client *client, regulator_config.dev = dev; regulator_config.of_node = dev->of_node; regulator_config.regmap = info->regmap; + regulator_config.driver_data = info; - rdev = devm_regulator_register(dev, info->regulator_descriptor, + info->rdev = devm_regulator_register(dev, info->regulator_descriptor, ®ulator_config); - ret = PTR_ERR_OR_ZERO(rdev); + ret = PTR_ERR_OR_ZERO(info->rdev); if (ret) { dev_err(dev, "Failed to register VBUS regulator: %d\n", ret); return ret; |