diff options
Diffstat (limited to 'drivers/power')
40 files changed, 3392 insertions, 4663 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index ff0350ca3b74..696bf77a7042 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -1,4 +1,3 @@ # SPDX-License-Identifier: GPL-2.0-only -source "drivers/power/avs/Kconfig" source "drivers/power/reset/Kconfig" source "drivers/power/supply/Kconfig" diff --git a/drivers/power/Makefile b/drivers/power/Makefile index b7c2e372186b..effbf0377f32 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -1,4 +1,3 @@ # SPDX-License-Identifier: GPL-2.0-only -obj-$(CONFIG_POWER_AVS) += avs/ obj-$(CONFIG_POWER_RESET) += reset/ obj-$(CONFIG_POWER_SUPPLY) += supply/ diff --git a/drivers/power/avs/Kconfig b/drivers/power/avs/Kconfig deleted file mode 100644 index cdb4237bfd02..000000000000 --- a/drivers/power/avs/Kconfig +++ /dev/null @@ -1,37 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -menuconfig POWER_AVS - bool "Adaptive Voltage Scaling class support" - help - AVS is a power management technique which finely controls the - operating voltage of a device in order to optimize (i.e. reduce) - its power consumption. - At a given operating point the voltage is adapted depending on - static factors (chip manufacturing process) and dynamic factors - (temperature depending performance). - AVS is also called SmartReflex on OMAP devices. - - 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 - help - Say y here to enable support io domains on Rockchip SoCs. It is - necessary for the io domain setting of the SoC to match the - voltage supplied by the regulators. diff --git a/drivers/power/avs/Makefile b/drivers/power/avs/Makefile deleted file mode 100644 index 9007d05853e2..000000000000 --- a/drivers/power/avs/Makefile +++ /dev/null @@ -1,4 +0,0 @@ -# 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 deleted file mode 100644 index b24cc77d1889..000000000000 --- a/drivers/power/avs/qcom-cpr.c +++ /dev/null @@ -1,1788 +0,0 @@ -// 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) -{ - mutex_lock(&drv->lock); - - if (cpr_is_allowed(drv)) { - cpr_ctl_disable(drv); - cpr_irq_clr(drv); - } - - mutex_unlock(&drv->lock); - - return regulator_disable(drv->vdd_apc); -} - -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 deleted file mode 100644 index eece97f97ef8..000000000000 --- a/drivers/power/avs/rockchip-io-domain.c +++ /dev/null @@ -1,630 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Rockchip IO Voltage Domain driver - * - * Copyright 2014 MundoReader S.L. - * Copyright 2014 Google, Inc. - */ - -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/err.h> -#include <linux/mfd/syscon.h> -#include <linux/of.h> -#include <linux/platform_device.h> -#include <linux/regmap.h> -#include <linux/regulator/consumer.h> - -#define MAX_SUPPLIES 16 - -/* - * The max voltage for 1.8V and 3.3V come from the Rockchip datasheet under - * "Recommended Operating Conditions" for "Digital GPIO". When the typical - * is 3.3V the max is 3.6V. When the typical is 1.8V the max is 1.98V. - * - * They are used like this: - * - If the voltage on a rail is above the "1.8" voltage (1.98V) we'll tell the - * SoC we're at 3.3. - * - If the voltage on a rail is above the "3.3" voltage (3.6V) we'll consider - * that to be an error. - */ -#define MAX_VOLTAGE_1_8 1980000 -#define MAX_VOLTAGE_3_3 3600000 - -#define PX30_IO_VSEL 0x180 -#define PX30_IO_VSEL_VCCIO6_SRC BIT(0) -#define PX30_IO_VSEL_VCCIO6_SUPPLY_NUM 1 - -#define RK3288_SOC_CON2 0x24c -#define RK3288_SOC_CON2_FLASH0 BIT(7) -#define RK3288_SOC_FLASH_SUPPLY_NUM 2 - -#define RK3328_SOC_CON4 0x410 -#define RK3328_SOC_CON4_VCCIO2 BIT(7) -#define RK3328_SOC_VCCIO2_SUPPLY_NUM 1 - -#define RK3368_SOC_CON15 0x43c -#define RK3368_SOC_CON15_FLASH0 BIT(14) -#define RK3368_SOC_FLASH_SUPPLY_NUM 2 - -#define RK3399_PMUGRF_CON0 0x180 -#define RK3399_PMUGRF_CON0_VSEL BIT(8) -#define RK3399_PMUGRF_VSEL_SUPPLY_NUM 9 - -struct rockchip_iodomain; - -/** - * @supplies: voltage settings matching the register bits. - */ -struct rockchip_iodomain_soc_data { - int grf_offset; - const char *supply_names[MAX_SUPPLIES]; - void (*init)(struct rockchip_iodomain *iod); -}; - -struct rockchip_iodomain_supply { - struct rockchip_iodomain *iod; - struct regulator *reg; - struct notifier_block nb; - int idx; -}; - -struct rockchip_iodomain { - struct device *dev; - struct regmap *grf; - const struct rockchip_iodomain_soc_data *soc_data; - struct rockchip_iodomain_supply supplies[MAX_SUPPLIES]; -}; - -static int rockchip_iodomain_write(struct rockchip_iodomain_supply *supply, - int uV) -{ - struct rockchip_iodomain *iod = supply->iod; - u32 val; - int ret; - - /* set value bit */ - val = (uV > MAX_VOLTAGE_1_8) ? 0 : 1; - val <<= supply->idx; - - /* apply hiword-mask */ - val |= (BIT(supply->idx) << 16); - - ret = regmap_write(iod->grf, iod->soc_data->grf_offset, val); - if (ret) - dev_err(iod->dev, "Couldn't write to GRF\n"); - - return ret; -} - -static int rockchip_iodomain_notify(struct notifier_block *nb, - unsigned long event, - void *data) -{ - struct rockchip_iodomain_supply *supply = - container_of(nb, struct rockchip_iodomain_supply, nb); - int uV; - int ret; - - /* - * According to Rockchip it's important to keep the SoC IO domain - * higher than (or equal to) the external voltage. That means we need - * to change it before external voltage changes happen in the case - * of an increase. - * - * Note that in the "pre" change we pick the max possible voltage that - * the regulator might end up at (the client requests a range and we - * don't know for certain the exact voltage). Right now we rely on the - * slop in MAX_VOLTAGE_1_8 and MAX_VOLTAGE_3_3 to save us if clients - * request something like a max of 3.6V when they really want 3.3V. - * We could attempt to come up with better rules if this fails. - */ - if (event & REGULATOR_EVENT_PRE_VOLTAGE_CHANGE) { - struct pre_voltage_change_data *pvc_data = data; - - uV = max_t(unsigned long, pvc_data->old_uV, pvc_data->max_uV); - } else if (event & (REGULATOR_EVENT_VOLTAGE_CHANGE | - REGULATOR_EVENT_ABORT_VOLTAGE_CHANGE)) { - uV = (unsigned long)data; - } else { - return NOTIFY_OK; - } - - dev_dbg(supply->iod->dev, "Setting to %d\n", uV); - - if (uV > MAX_VOLTAGE_3_3) { - dev_err(supply->iod->dev, "Voltage too high: %d\n", uV); - - if (event == REGULATOR_EVENT_PRE_VOLTAGE_CHANGE) - return NOTIFY_BAD; - } - - ret = rockchip_iodomain_write(supply, uV); - if (ret && event == REGULATOR_EVENT_PRE_VOLTAGE_CHANGE) - return NOTIFY_BAD; - - dev_dbg(supply->iod->dev, "Setting to %d done\n", uV); - return NOTIFY_OK; -} - -static void px30_iodomain_init(struct rockchip_iodomain *iod) -{ - int ret; - u32 val; - - /* if no VCCIO6 supply we should leave things alone */ - if (!iod->supplies[PX30_IO_VSEL_VCCIO6_SUPPLY_NUM].reg) - return; - - /* - * 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 vccio6 ctrl\n"); -} - -static void rk3288_iodomain_init(struct rockchip_iodomain *iod) -{ - int ret; - u32 val; - - /* if no flash supply we should leave things alone */ - if (!iod->supplies[RK3288_SOC_FLASH_SUPPLY_NUM].reg) - return; - - /* - * set flash0 iodomain to also use this framework - * instead of a special gpio. - */ - val = RK3288_SOC_CON2_FLASH0 | (RK3288_SOC_CON2_FLASH0 << 16); - ret = regmap_write(iod->grf, RK3288_SOC_CON2, val); - if (ret < 0) - dev_warn(iod->dev, "couldn't update flash0 ctrl\n"); -} - -static void rk3328_iodomain_init(struct rockchip_iodomain *iod) -{ - int ret; - u32 val; - - /* if no vccio2 supply we should leave things alone */ - if (!iod->supplies[RK3328_SOC_VCCIO2_SUPPLY_NUM].reg) - return; - - /* - * set vccio2 iodomain to also use this framework - * instead of a special gpio. - */ - val = RK3328_SOC_CON4_VCCIO2 | (RK3328_SOC_CON4_VCCIO2 << 16); - ret = regmap_write(iod->grf, RK3328_SOC_CON4, val); - if (ret < 0) - dev_warn(iod->dev, "couldn't update vccio2 vsel ctrl\n"); -} - -static void rk3368_iodomain_init(struct rockchip_iodomain *iod) -{ - int ret; - u32 val; - - /* if no flash supply we should leave things alone */ - if (!iod->supplies[RK3368_SOC_FLASH_SUPPLY_NUM].reg) - return; - - /* - * set flash0 iodomain to also use this framework - * instead of a special gpio. - */ - val = RK3368_SOC_CON15_FLASH0 | (RK3368_SOC_CON15_FLASH0 << 16); - ret = regmap_write(iod->grf, RK3368_SOC_CON15, val); - if (ret < 0) - dev_warn(iod->dev, "couldn't update flash0 ctrl\n"); -} - -static void rk3399_pmu_iodomain_init(struct rockchip_iodomain *iod) -{ - int ret; - u32 val; - - /* if no pmu io supply we should leave things alone */ - if (!iod->supplies[RK3399_PMUGRF_VSEL_SUPPLY_NUM].reg) - return; - - /* - * set pmu io iodomain to also use this framework - * instead of a special gpio. - */ - val = RK3399_PMUGRF_CON0_VSEL | (RK3399_PMUGRF_CON0_VSEL << 16); - ret = regmap_write(iod->grf, RK3399_PMUGRF_CON0, val); - if (ret < 0) - dev_warn(iod->dev, "couldn't update pmu io iodomain ctrl\n"); -} - -static const struct rockchip_iodomain_soc_data soc_data_px30 = { - .grf_offset = 0x180, - .supply_names = { - NULL, - "vccio6", - "vccio1", - "vccio2", - "vccio3", - "vccio4", - "vccio5", - "vccio-oscgpi", - }, - .init = px30_iodomain_init, -}; - -static const struct rockchip_iodomain_soc_data soc_data_px30_pmu = { - .grf_offset = 0x100, - .supply_names = { - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - "pmuio1", - "pmuio2", - }, -}; - -/* - * On the rk3188 the io-domains are handled by a shared register with the - * lower 8 bits being still being continuing drive-strength settings. - */ -static const struct rockchip_iodomain_soc_data soc_data_rk3188 = { - .grf_offset = 0x104, - .supply_names = { - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - "ap0", - "ap1", - "cif", - "flash", - "vccio0", - "vccio1", - "lcdc0", - "lcdc1", - }, -}; - -static const struct rockchip_iodomain_soc_data soc_data_rk3228 = { - .grf_offset = 0x418, - .supply_names = { - "vccio1", - "vccio2", - "vccio3", - "vccio4", - }, -}; - -static const struct rockchip_iodomain_soc_data soc_data_rk3288 = { - .grf_offset = 0x380, - .supply_names = { - "lcdc", /* LCDC_VDD */ - "dvp", /* DVPIO_VDD */ - "flash0", /* FLASH0_VDD (emmc) */ - "flash1", /* FLASH1_VDD (sdio1) */ - "wifi", /* APIO3_VDD (sdio0) */ - "bb", /* APIO5_VDD */ - "audio", /* APIO4_VDD */ - "sdcard", /* SDMMC0_VDD (sdmmc) */ - "gpio30", /* APIO1_VDD */ - "gpio1830", /* APIO2_VDD */ - }, - .init = rk3288_iodomain_init, -}; - -static const struct rockchip_iodomain_soc_data soc_data_rk3328 = { - .grf_offset = 0x410, - .supply_names = { - "vccio1", - "vccio2", - "vccio3", - "vccio4", - "vccio5", - "vccio6", - "pmuio", - }, - .init = rk3328_iodomain_init, -}; - -static const struct rockchip_iodomain_soc_data soc_data_rk3368 = { - .grf_offset = 0x900, - .supply_names = { - NULL, /* reserved */ - "dvp", /* DVPIO_VDD */ - "flash0", /* FLASH0_VDD (emmc) */ - "wifi", /* APIO2_VDD (sdio0) */ - NULL, - "audio", /* APIO3_VDD */ - "sdcard", /* SDMMC0_VDD (sdmmc) */ - "gpio30", /* APIO1_VDD */ - "gpio1830", /* APIO4_VDD (gpujtag) */ - }, - .init = rk3368_iodomain_init, -}; - -static const struct rockchip_iodomain_soc_data soc_data_rk3368_pmu = { - .grf_offset = 0x100, - .supply_names = { - NULL, - NULL, - NULL, - NULL, - "pmu", /*PMU IO domain*/ - "vop", /*LCDC IO domain*/ - }, -}; - -static const struct rockchip_iodomain_soc_data soc_data_rk3399 = { - .grf_offset = 0xe640, - .supply_names = { - "bt656", /* APIO2_VDD */ - "audio", /* APIO5_VDD */ - "sdmmc", /* SDMMC0_VDD */ - "gpio1830", /* APIO4_VDD */ - }, -}; - -static const struct rockchip_iodomain_soc_data soc_data_rk3399_pmu = { - .grf_offset = 0x180, - .supply_names = { - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - "pmu1830", /* PMUIO2_VDD */ - }, - .init = rk3399_pmu_iodomain_init, -}; - -static const struct rockchip_iodomain_soc_data soc_data_rv1108 = { - .grf_offset = 0x404, - .supply_names = { - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - "vccio1", - "vccio2", - "vccio3", - "vccio5", - "vccio6", - }, - -}; - -static const struct rockchip_iodomain_soc_data soc_data_rv1108_pmu = { - .grf_offset = 0x104, - .supply_names = { - "pmu", - }, -}; - -static const struct of_device_id rockchip_iodomain_match[] = { - { - .compatible = "rockchip,px30-io-voltage-domain", - .data = (void *)&soc_data_px30 - }, - { - .compatible = "rockchip,px30-pmu-io-voltage-domain", - .data = (void *)&soc_data_px30_pmu - }, - { - .compatible = "rockchip,rk3188-io-voltage-domain", - .data = &soc_data_rk3188 - }, - { - .compatible = "rockchip,rk3228-io-voltage-domain", - .data = &soc_data_rk3228 - }, - { - .compatible = "rockchip,rk3288-io-voltage-domain", - .data = &soc_data_rk3288 - }, - { - .compatible = "rockchip,rk3328-io-voltage-domain", - .data = &soc_data_rk3328 - }, - { - .compatible = "rockchip,rk3368-io-voltage-domain", - .data = &soc_data_rk3368 - }, - { - .compatible = "rockchip,rk3368-pmu-io-voltage-domain", - .data = &soc_data_rk3368_pmu - }, - { - .compatible = "rockchip,rk3399-io-voltage-domain", - .data = &soc_data_rk3399 - }, - { - .compatible = "rockchip,rk3399-pmu-io-voltage-domain", - .data = &soc_data_rk3399_pmu - }, - { - .compatible = "rockchip,rv1108-io-voltage-domain", - .data = &soc_data_rv1108 - }, - { - .compatible = "rockchip,rv1108-pmu-io-voltage-domain", - .data = &soc_data_rv1108_pmu - }, - { /* sentinel */ }, -}; -MODULE_DEVICE_TABLE(of, rockchip_iodomain_match); - -static int rockchip_iodomain_probe(struct platform_device *pdev) -{ - struct device_node *np = pdev->dev.of_node; - const struct of_device_id *match; - struct rockchip_iodomain *iod; - struct device *parent; - int i, ret = 0; - - if (!np) - return -ENODEV; - - iod = devm_kzalloc(&pdev->dev, sizeof(*iod), GFP_KERNEL); - if (!iod) - return -ENOMEM; - - iod->dev = &pdev->dev; - platform_set_drvdata(pdev, iod); - - match = of_match_node(rockchip_iodomain_match, np); - iod->soc_data = match->data; - - parent = pdev->dev.parent; - if (parent && parent->of_node) { - iod->grf = syscon_node_to_regmap(parent->of_node); - } else { - dev_dbg(&pdev->dev, "falling back to old binding\n"); - iod->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf"); - } - - if (IS_ERR(iod->grf)) { - dev_err(&pdev->dev, "couldn't find grf regmap\n"); - return PTR_ERR(iod->grf); - } - - for (i = 0; i < MAX_SUPPLIES; i++) { - const char *supply_name = iod->soc_data->supply_names[i]; - struct rockchip_iodomain_supply *supply = &iod->supplies[i]; - struct regulator *reg; - int uV; - - if (!supply_name) - continue; - - reg = devm_regulator_get_optional(iod->dev, supply_name); - if (IS_ERR(reg)) { - ret = PTR_ERR(reg); - - /* If a supply wasn't specified, that's OK */ - if (ret == -ENODEV) - continue; - else if (ret != -EPROBE_DEFER) - dev_err(iod->dev, "couldn't get regulator %s\n", - supply_name); - goto unreg_notify; - } - - /* set initial correct value */ - uV = regulator_get_voltage(reg); - - /* must be a regulator we can get the voltage of */ - if (uV < 0) { - dev_err(iod->dev, "Can't determine voltage: %s\n", - supply_name); - goto unreg_notify; - } - - if (uV > MAX_VOLTAGE_3_3) { - dev_crit(iod->dev, - "%d uV is too high. May damage SoC!\n", - uV); - ret = -EINVAL; - goto unreg_notify; - } - - /* setup our supply */ - supply->idx = i; - supply->iod = iod; - supply->reg = reg; - supply->nb.notifier_call = rockchip_iodomain_notify; - - ret = rockchip_iodomain_write(supply, uV); - if (ret) { - supply->reg = NULL; - goto unreg_notify; - } - - /* register regulator notifier */ - ret = regulator_register_notifier(reg, &supply->nb); - if (ret) { - dev_err(&pdev->dev, - "regulator notifier request failed\n"); - supply->reg = NULL; - goto unreg_notify; - } - } - - if (iod->soc_data->init) - iod->soc_data->init(iod); - - return 0; - -unreg_notify: - for (i = MAX_SUPPLIES - 1; i >= 0; i--) { - struct rockchip_iodomain_supply *io_supply = &iod->supplies[i]; - - if (io_supply->reg) - regulator_unregister_notifier(io_supply->reg, - &io_supply->nb); - } - - return ret; -} - -static int rockchip_iodomain_remove(struct platform_device *pdev) -{ - struct rockchip_iodomain *iod = platform_get_drvdata(pdev); - int i; - - for (i = MAX_SUPPLIES - 1; i >= 0; i--) { - struct rockchip_iodomain_supply *io_supply = &iod->supplies[i]; - - if (io_supply->reg) - regulator_unregister_notifier(io_supply->reg, - &io_supply->nb); - } - - return 0; -} - -static struct platform_driver rockchip_iodomain_driver = { - .probe = rockchip_iodomain_probe, - .remove = rockchip_iodomain_remove, - .driver = { - .name = "rockchip-iodomain", - .of_match_table = rockchip_iodomain_match, - }, -}; - -module_platform_driver(rockchip_iodomain_driver); - -MODULE_DESCRIPTION("Rockchip IO-domain driver"); -MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>"); -MODULE_AUTHOR("Doug Anderson <dianders@chromium.org>"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/avs/smartreflex.c b/drivers/power/avs/smartreflex.c deleted file mode 100644 index 5376f3d22f31..000000000000 --- a/drivers/power/avs/smartreflex.c +++ /dev/null @@ -1,1045 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * OMAP SmartReflex Voltage Control - * - * Author: Thara Gopinath <thara@ti.com> - * - * Copyright (C) 2012 Texas Instruments, Inc. - * Thara Gopinath <thara@ti.com> - * - * Copyright (C) 2008 Nokia Corporation - * Kalle Jokiniemi - * - * Copyright (C) 2007 Texas Instruments, Inc. - * Lesly A M <x0080970@ti.com> - */ - -#include <linux/module.h> -#include <linux/mod_devicetable.h> -#include <linux/interrupt.h> -#include <linux/clk.h> -#include <linux/io.h> -#include <linux/debugfs.h> -#include <linux/delay.h> -#include <linux/slab.h> -#include <linux/pm_runtime.h> -#include <linux/power/smartreflex.h> - -#define DRIVER_NAME "smartreflex" -#define SMARTREFLEX_NAME_LEN 32 -#define NVALUE_NAME_LEN 40 -#define SR_DISABLE_TIMEOUT 200 - -/* sr_list contains all the instances of smartreflex module */ -static LIST_HEAD(sr_list); - -static struct omap_sr_class_data *sr_class; -static struct dentry *sr_dbg_dir; - -static inline void sr_write_reg(struct omap_sr *sr, unsigned offset, u32 value) -{ - __raw_writel(value, (sr->base + offset)); -} - -static inline void sr_modify_reg(struct omap_sr *sr, unsigned offset, u32 mask, - u32 value) -{ - u32 reg_val; - - /* - * Smartreflex error config register is special as it contains - * certain status bits which if written a 1 into means a clear - * of those bits. So in order to make sure no accidental write of - * 1 happens to those status bits, do a clear of them in the read - * value. This mean this API doesn't rewrite values in these bits - * if they are currently set, but does allow the caller to write - * those bits. - */ - if (sr->ip_type == SR_TYPE_V1 && offset == ERRCONFIG_V1) - mask |= ERRCONFIG_STATUS_V1_MASK; - else if (sr->ip_type == SR_TYPE_V2 && offset == ERRCONFIG_V2) - mask |= ERRCONFIG_VPBOUNDINTST_V2; - - reg_val = __raw_readl(sr->base + offset); - reg_val &= ~mask; - - value &= mask; - - reg_val |= value; - - __raw_writel(reg_val, (sr->base + offset)); -} - -static inline u32 sr_read_reg(struct omap_sr *sr, unsigned offset) -{ - return __raw_readl(sr->base + offset); -} - -static struct omap_sr *_sr_lookup(struct voltagedomain *voltdm) -{ - struct omap_sr *sr_info; - - if (!voltdm) { - pr_err("%s: Null voltage domain passed!\n", __func__); - return ERR_PTR(-EINVAL); - } - - list_for_each_entry(sr_info, &sr_list, node) { - if (voltdm == sr_info->voltdm) - return sr_info; - } - - return ERR_PTR(-ENODATA); -} - -static irqreturn_t sr_interrupt(int irq, void *data) -{ - struct omap_sr *sr_info = data; - u32 status = 0; - - switch (sr_info->ip_type) { - case SR_TYPE_V1: - /* Read the status bits */ - status = sr_read_reg(sr_info, ERRCONFIG_V1); - - /* Clear them by writing back */ - sr_write_reg(sr_info, ERRCONFIG_V1, status); - break; - case SR_TYPE_V2: - /* Read the status bits */ - status = sr_read_reg(sr_info, IRQSTATUS); - - /* Clear them by writing back */ - sr_write_reg(sr_info, IRQSTATUS, status); - break; - default: - dev_err(&sr_info->pdev->dev, "UNKNOWN IP type %d\n", - sr_info->ip_type); - return IRQ_NONE; - } - - if (sr_class->notify) - sr_class->notify(sr_info, status); - - return IRQ_HANDLED; -} - -static void sr_set_clk_length(struct omap_sr *sr) -{ - struct clk *fck; - u32 fclk_speed; - - /* Try interconnect target module fck first if it already exists */ - fck = clk_get(sr->pdev->dev.parent, "fck"); - if (IS_ERR(fck)) { - fck = clk_get(&sr->pdev->dev, "fck"); - if (IS_ERR(fck)) { - dev_err(&sr->pdev->dev, - "%s: unable to get fck for device %s\n", - __func__, dev_name(&sr->pdev->dev)); - return; - } - } - - fclk_speed = clk_get_rate(fck); - clk_put(fck); - - switch (fclk_speed) { - case 12000000: - sr->clk_length = SRCLKLENGTH_12MHZ_SYSCLK; - break; - case 13000000: - sr->clk_length = SRCLKLENGTH_13MHZ_SYSCLK; - break; - case 19200000: - sr->clk_length = SRCLKLENGTH_19MHZ_SYSCLK; - break; - case 26000000: - sr->clk_length = SRCLKLENGTH_26MHZ_SYSCLK; - break; - case 38400000: - sr->clk_length = SRCLKLENGTH_38MHZ_SYSCLK; - break; - default: - dev_err(&sr->pdev->dev, "%s: Invalid fclk rate: %d\n", - __func__, fclk_speed); - break; - } -} - -static void sr_start_vddautocomp(struct omap_sr *sr) -{ - if (!sr_class || !(sr_class->enable) || !(sr_class->configure)) { - dev_warn(&sr->pdev->dev, - "%s: smartreflex class driver not registered\n", - __func__); - return; - } - - if (!sr_class->enable(sr)) - sr->autocomp_active = true; -} - -static void sr_stop_vddautocomp(struct omap_sr *sr) -{ - if (!sr_class || !(sr_class->disable)) { - dev_warn(&sr->pdev->dev, - "%s: smartreflex class driver not registered\n", - __func__); - return; - } - - if (sr->autocomp_active) { - sr_class->disable(sr, 1); - sr->autocomp_active = false; - } -} - -/* - * This function handles the initializations which have to be done - * only when both sr device and class driver regiter has - * completed. This will be attempted to be called from both sr class - * driver register and sr device intializtion API's. Only one call - * will ultimately succeed. - * - * Currently this function registers interrupt handler for a particular SR - * if smartreflex class driver is already registered and has - * requested for interrupts and the SR interrupt line in present. - */ -static int sr_late_init(struct omap_sr *sr_info) -{ - struct omap_sr_data *pdata = sr_info->pdev->dev.platform_data; - int ret = 0; - - if (sr_class->notify && sr_class->notify_flags && sr_info->irq) { - ret = devm_request_irq(&sr_info->pdev->dev, sr_info->irq, - sr_interrupt, 0, sr_info->name, sr_info); - if (ret) - goto error; - disable_irq(sr_info->irq); - } - - if (pdata && pdata->enable_on_init) - sr_start_vddautocomp(sr_info); - - return ret; - -error: - list_del(&sr_info->node); - dev_err(&sr_info->pdev->dev, "%s: ERROR in registering interrupt handler. Smartreflex will not function as desired\n", - __func__); - - return ret; -} - -static void sr_v1_disable(struct omap_sr *sr) -{ - int timeout = 0; - int errconf_val = ERRCONFIG_MCUACCUMINTST | ERRCONFIG_MCUVALIDINTST | - ERRCONFIG_MCUBOUNDINTST; - - /* Enable MCUDisableAcknowledge interrupt */ - sr_modify_reg(sr, ERRCONFIG_V1, - ERRCONFIG_MCUDISACKINTEN, ERRCONFIG_MCUDISACKINTEN); - - /* SRCONFIG - disable SR */ - sr_modify_reg(sr, SRCONFIG, SRCONFIG_SRENABLE, 0x0); - - /* Disable all other SR interrupts and clear the status as needed */ - if (sr_read_reg(sr, ERRCONFIG_V1) & ERRCONFIG_VPBOUNDINTST_V1) - errconf_val |= ERRCONFIG_VPBOUNDINTST_V1; - sr_modify_reg(sr, ERRCONFIG_V1, - (ERRCONFIG_MCUACCUMINTEN | ERRCONFIG_MCUVALIDINTEN | - ERRCONFIG_MCUBOUNDINTEN | ERRCONFIG_VPBOUNDINTEN_V1), - errconf_val); - - /* - * Wait for SR to be disabled. - * wait until ERRCONFIG.MCUDISACKINTST = 1. Typical latency is 1us. - */ - sr_test_cond_timeout((sr_read_reg(sr, ERRCONFIG_V1) & - ERRCONFIG_MCUDISACKINTST), SR_DISABLE_TIMEOUT, - timeout); - - if (timeout >= SR_DISABLE_TIMEOUT) - dev_warn(&sr->pdev->dev, "%s: Smartreflex disable timedout\n", - __func__); - - /* Disable MCUDisableAcknowledge interrupt & clear pending interrupt */ - sr_modify_reg(sr, ERRCONFIG_V1, ERRCONFIG_MCUDISACKINTEN, - ERRCONFIG_MCUDISACKINTST); -} - -static void sr_v2_disable(struct omap_sr *sr) -{ - int timeout = 0; - - /* Enable MCUDisableAcknowledge interrupt */ - sr_write_reg(sr, IRQENABLE_SET, IRQENABLE_MCUDISABLEACKINT); - - /* SRCONFIG - disable SR */ - sr_modify_reg(sr, SRCONFIG, SRCONFIG_SRENABLE, 0x0); - - /* - * Disable all other SR interrupts and clear the status - * write to status register ONLY on need basis - only if status - * is set. - */ - if (sr_read_reg(sr, ERRCONFIG_V2) & ERRCONFIG_VPBOUNDINTST_V2) - sr_modify_reg(sr, ERRCONFIG_V2, ERRCONFIG_VPBOUNDINTEN_V2, - ERRCONFIG_VPBOUNDINTST_V2); - else - sr_modify_reg(sr, ERRCONFIG_V2, ERRCONFIG_VPBOUNDINTEN_V2, - 0x0); - sr_write_reg(sr, IRQENABLE_CLR, (IRQENABLE_MCUACCUMINT | - IRQENABLE_MCUVALIDINT | - IRQENABLE_MCUBOUNDSINT)); - sr_write_reg(sr, IRQSTATUS, (IRQSTATUS_MCUACCUMINT | - IRQSTATUS_MCVALIDINT | - IRQSTATUS_MCBOUNDSINT)); - - /* - * Wait for SR to be disabled. - * wait until IRQSTATUS.MCUDISACKINTST = 1. Typical latency is 1us. - */ - sr_test_cond_timeout((sr_read_reg(sr, IRQSTATUS) & - IRQSTATUS_MCUDISABLEACKINT), SR_DISABLE_TIMEOUT, - timeout); - - if (timeout >= SR_DISABLE_TIMEOUT) - dev_warn(&sr->pdev->dev, "%s: Smartreflex disable timedout\n", - __func__); - - /* Disable MCUDisableAcknowledge interrupt & clear pending interrupt */ - sr_write_reg(sr, IRQENABLE_CLR, IRQENABLE_MCUDISABLEACKINT); - sr_write_reg(sr, IRQSTATUS, IRQSTATUS_MCUDISABLEACKINT); -} - -static struct omap_sr_nvalue_table *sr_retrieve_nvalue_row( - struct omap_sr *sr, u32 efuse_offs) -{ - int i; - - if (!sr->nvalue_table) { - dev_warn(&sr->pdev->dev, "%s: Missing ntarget value table\n", - __func__); - return NULL; - } - - for (i = 0; i < sr->nvalue_count; i++) { - if (sr->nvalue_table[i].efuse_offs == efuse_offs) - return &sr->nvalue_table[i]; - } - - return NULL; -} - -/* Public Functions */ - -/** - * sr_configure_errgen() - Configures the SmartReflex to perform AVS using the - * error generator module. - * @sr: SR module to be configured. - * - * This API is to be called from the smartreflex class driver to - * configure the error generator module inside the smartreflex module. - * SR settings if using the ERROR module inside Smartreflex. - * SR CLASS 3 by default uses only the ERROR module where as - * SR CLASS 2 can choose between ERROR module and MINMAXAVG - * module. Returns 0 on success and error value in case of failure. - */ -int sr_configure_errgen(struct omap_sr *sr) -{ - u32 sr_config, sr_errconfig, errconfig_offs; - u32 vpboundint_en, vpboundint_st; - u32 senp_en = 0, senn_en = 0; - u8 senp_shift, senn_shift; - - if (!sr) { - pr_warn("%s: NULL omap_sr from %pS\n", - __func__, (void *)_RET_IP_); - return -EINVAL; - } - - if (!sr->clk_length) - sr_set_clk_length(sr); - - senp_en = sr->senp_mod; - senn_en = sr->senn_mod; - - sr_config = (sr->clk_length << SRCONFIG_SRCLKLENGTH_SHIFT) | - SRCONFIG_SENENABLE | SRCONFIG_ERRGEN_EN; - - switch (sr->ip_type) { - case SR_TYPE_V1: - sr_config |= SRCONFIG_DELAYCTRL; - senn_shift = SRCONFIG_SENNENABLE_V1_SHIFT; - senp_shift = SRCONFIG_SENPENABLE_V1_SHIFT; - errconfig_offs = ERRCONFIG_V1; - vpboundint_en = ERRCONFIG_VPBOUNDINTEN_V1; - vpboundint_st = ERRCONFIG_VPBOUNDINTST_V1; - break; - case SR_TYPE_V2: - senn_shift = SRCONFIG_SENNENABLE_V2_SHIFT; - senp_shift = SRCONFIG_SENPENABLE_V2_SHIFT; - errconfig_offs = ERRCONFIG_V2; - vpboundint_en = ERRCONFIG_VPBOUNDINTEN_V2; - vpboundint_st = ERRCONFIG_VPBOUNDINTST_V2; - break; - default: - dev_err(&sr->pdev->dev, "%s: Trying to Configure smartreflex module without specifying the ip\n", - __func__); - return -EINVAL; - } - - sr_config |= ((senn_en << senn_shift) | (senp_en << senp_shift)); - sr_write_reg(sr, SRCONFIG, sr_config); - sr_errconfig = (sr->err_weight << ERRCONFIG_ERRWEIGHT_SHIFT) | - (sr->err_maxlimit << ERRCONFIG_ERRMAXLIMIT_SHIFT) | - (sr->err_minlimit << ERRCONFIG_ERRMINLIMIT_SHIFT); - sr_modify_reg(sr, errconfig_offs, (SR_ERRWEIGHT_MASK | - SR_ERRMAXLIMIT_MASK | SR_ERRMINLIMIT_MASK), - sr_errconfig); - - /* Enabling the interrupts if the ERROR module is used */ - sr_modify_reg(sr, errconfig_offs, (vpboundint_en | vpboundint_st), - vpboundint_en); - - return 0; -} - -/** - * sr_disable_errgen() - Disables SmartReflex AVS module's errgen component - * @sr: SR module to be configured. - * - * This API is to be called from the smartreflex class driver to - * disable the error generator module inside the smartreflex module. - * - * Returns 0 on success and error value in case of failure. - */ -int sr_disable_errgen(struct omap_sr *sr) -{ - u32 errconfig_offs; - u32 vpboundint_en, vpboundint_st; - - if (!sr) { - pr_warn("%s: NULL omap_sr from %pS\n", - __func__, (void *)_RET_IP_); - return -EINVAL; - } - - switch (sr->ip_type) { - case SR_TYPE_V1: - errconfig_offs = ERRCONFIG_V1; - vpboundint_en = ERRCONFIG_VPBOUNDINTEN_V1; - vpboundint_st = ERRCONFIG_VPBOUNDINTST_V1; - break; - case SR_TYPE_V2: - errconfig_offs = ERRCONFIG_V2; - vpboundint_en = ERRCONFIG_VPBOUNDINTEN_V2; - vpboundint_st = ERRCONFIG_VPBOUNDINTST_V2; - break; - default: - dev_err(&sr->pdev->dev, "%s: Trying to Configure smartreflex module without specifying the ip\n", - __func__); - return -EINVAL; - } - - /* Disable the Sensor and errorgen */ - sr_modify_reg(sr, SRCONFIG, SRCONFIG_SENENABLE | SRCONFIG_ERRGEN_EN, 0); - - /* - * Disable the interrupts of ERROR module - * NOTE: modify is a read, modify,write - an implicit OCP barrier - * which is required is present here - sequencing is critical - * at this point (after errgen is disabled, vpboundint disable) - */ - sr_modify_reg(sr, errconfig_offs, vpboundint_en | vpboundint_st, 0); - - return 0; -} - -/** - * sr_configure_minmax() - Configures the SmartReflex to perform AVS using the - * minmaxavg module. - * @sr: SR module to be configured. - * - * This API is to be called from the smartreflex class driver to - * configure the minmaxavg module inside the smartreflex module. - * SR settings if using the ERROR module inside Smartreflex. - * SR CLASS 3 by default uses only the ERROR module where as - * SR CLASS 2 can choose between ERROR module and MINMAXAVG - * module. Returns 0 on success and error value in case of failure. - */ -int sr_configure_minmax(struct omap_sr *sr) -{ - u32 sr_config, sr_avgwt; - u32 senp_en = 0, senn_en = 0; - u8 senp_shift, senn_shift; - - if (!sr) { - pr_warn("%s: NULL omap_sr from %pS\n", - __func__, (void *)_RET_IP_); - return -EINVAL; - } - - if (!sr->clk_length) - sr_set_clk_length(sr); - - senp_en = sr->senp_mod; - senn_en = sr->senn_mod; - - sr_config = (sr->clk_length << SRCONFIG_SRCLKLENGTH_SHIFT) | - SRCONFIG_SENENABLE | - (sr->accum_data << SRCONFIG_ACCUMDATA_SHIFT); - - switch (sr->ip_type) { - case SR_TYPE_V1: - sr_config |= SRCONFIG_DELAYCTRL; - senn_shift = SRCONFIG_SENNENABLE_V1_SHIFT; - senp_shift = SRCONFIG_SENPENABLE_V1_SHIFT; - break; - case SR_TYPE_V2: - senn_shift = SRCONFIG_SENNENABLE_V2_SHIFT; - senp_shift = SRCONFIG_SENPENABLE_V2_SHIFT; - break; - default: - dev_err(&sr->pdev->dev, "%s: Trying to Configure smartreflex module without specifying the ip\n", - __func__); - return -EINVAL; - } - - sr_config |= ((senn_en << senn_shift) | (senp_en << senp_shift)); - sr_write_reg(sr, SRCONFIG, sr_config); - sr_avgwt = (sr->senp_avgweight << AVGWEIGHT_SENPAVGWEIGHT_SHIFT) | - (sr->senn_avgweight << AVGWEIGHT_SENNAVGWEIGHT_SHIFT); - sr_write_reg(sr, AVGWEIGHT, sr_avgwt); - - /* - * Enabling the interrupts if MINMAXAVG module is used. - * TODO: check if all the interrupts are mandatory - */ - switch (sr->ip_type) { - case SR_TYPE_V1: - sr_modify_reg(sr, ERRCONFIG_V1, - (ERRCONFIG_MCUACCUMINTEN | ERRCONFIG_MCUVALIDINTEN | - ERRCONFIG_MCUBOUNDINTEN), - (ERRCONFIG_MCUACCUMINTEN | ERRCONFIG_MCUACCUMINTST | - ERRCONFIG_MCUVALIDINTEN | ERRCONFIG_MCUVALIDINTST | - ERRCONFIG_MCUBOUNDINTEN | ERRCONFIG_MCUBOUNDINTST)); - break; - case SR_TYPE_V2: - sr_write_reg(sr, IRQSTATUS, - IRQSTATUS_MCUACCUMINT | IRQSTATUS_MCVALIDINT | - IRQSTATUS_MCBOUNDSINT | IRQSTATUS_MCUDISABLEACKINT); - sr_write_reg(sr, IRQENABLE_SET, - IRQENABLE_MCUACCUMINT | IRQENABLE_MCUVALIDINT | - IRQENABLE_MCUBOUNDSINT | IRQENABLE_MCUDISABLEACKINT); - break; - default: - dev_err(&sr->pdev->dev, "%s: Trying to Configure smartreflex module without specifying the ip\n", - __func__); - return -EINVAL; - } - - return 0; -} - -/** - * sr_enable() - Enables the smartreflex module. - * @sr: pointer to which the SR module to be configured belongs to. - * @volt: The voltage at which the Voltage domain associated with - * the smartreflex module is operating at. - * This is required only to program the correct Ntarget value. - * - * This API is to be called from the smartreflex class driver to - * enable a smartreflex module. Returns 0 on success. Returns error - * value if the voltage passed is wrong or if ntarget value is wrong. - */ -int sr_enable(struct omap_sr *sr, unsigned long volt) -{ - struct omap_volt_data *volt_data; - struct omap_sr_nvalue_table *nvalue_row; - int ret; - - if (!sr) { - pr_warn("%s: NULL omap_sr from %pS\n", - __func__, (void *)_RET_IP_); - return -EINVAL; - } - - volt_data = omap_voltage_get_voltdata(sr->voltdm, volt); - - if (IS_ERR(volt_data)) { - dev_warn(&sr->pdev->dev, "%s: Unable to get voltage table for nominal voltage %ld\n", - __func__, volt); - return PTR_ERR(volt_data); - } - - nvalue_row = sr_retrieve_nvalue_row(sr, volt_data->sr_efuse_offs); - - if (!nvalue_row) { - dev_warn(&sr->pdev->dev, "%s: failure getting SR data for this voltage %ld\n", - __func__, volt); - return -ENODATA; - } - - /* errminlimit is opp dependent and hence linked to voltage */ - sr->err_minlimit = nvalue_row->errminlimit; - - pm_runtime_get_sync(&sr->pdev->dev); - - /* Check if SR is already enabled. If yes do nothing */ - if (sr_read_reg(sr, SRCONFIG) & SRCONFIG_SRENABLE) - return 0; - - /* Configure SR */ - ret = sr_class->configure(sr); - if (ret) - return ret; - - sr_write_reg(sr, NVALUERECIPROCAL, nvalue_row->nvalue); - - /* SRCONFIG - enable SR */ - sr_modify_reg(sr, SRCONFIG, SRCONFIG_SRENABLE, SRCONFIG_SRENABLE); - return 0; -} - -/** - * sr_disable() - Disables the smartreflex module. - * @sr: pointer to which the SR module to be configured belongs to. - * - * This API is to be called from the smartreflex class driver to - * disable a smartreflex module. - */ -void sr_disable(struct omap_sr *sr) -{ - if (!sr) { - pr_warn("%s: NULL omap_sr from %pS\n", - __func__, (void *)_RET_IP_); - return; - } - - /* Check if SR clocks are already disabled. If yes do nothing */ - if (pm_runtime_suspended(&sr->pdev->dev)) - return; - - /* - * Disable SR if only it is indeed enabled. Else just - * disable the clocks. - */ - if (sr_read_reg(sr, SRCONFIG) & SRCONFIG_SRENABLE) { - switch (sr->ip_type) { - case SR_TYPE_V1: - sr_v1_disable(sr); - break; - case SR_TYPE_V2: - sr_v2_disable(sr); - break; - default: - dev_err(&sr->pdev->dev, "UNKNOWN IP type %d\n", - sr->ip_type); - } - } - - pm_runtime_put_sync_suspend(&sr->pdev->dev); -} - -/** - * sr_register_class() - API to register a smartreflex class parameters. - * @class_data: The structure containing various sr class specific data. - * - * This API is to be called by the smartreflex class driver to register itself - * with the smartreflex driver during init. Returns 0 on success else the - * error value. - */ -int sr_register_class(struct omap_sr_class_data *class_data) -{ - struct omap_sr *sr_info; - - if (!class_data) { - pr_warn("%s:, Smartreflex class data passed is NULL\n", - __func__); - return -EINVAL; - } - - if (sr_class) { - pr_warn("%s: Smartreflex class driver already registered\n", - __func__); - return -EBUSY; - } - - sr_class = class_data; - - /* - * Call into late init to do initializations that require - * both sr driver and sr class driver to be initiallized. - */ - list_for_each_entry(sr_info, &sr_list, node) - sr_late_init(sr_info); - - return 0; -} - -/** - * omap_sr_enable() - API to enable SR clocks and to call into the - * registered smartreflex class enable API. - * @voltdm: VDD pointer to which the SR module to be configured belongs to. - * - * This API is to be called from the kernel in order to enable - * a particular smartreflex module. This API will do the initial - * configurations to turn on the smartreflex module and in turn call - * into the registered smartreflex class enable API. - */ -void omap_sr_enable(struct voltagedomain *voltdm) -{ - struct omap_sr *sr = _sr_lookup(voltdm); - - if (IS_ERR(sr)) { - pr_warn("%s: omap_sr struct for voltdm not found\n", __func__); - return; - } - - if (!sr->autocomp_active) - return; - - if (!sr_class || !(sr_class->enable) || !(sr_class->configure)) { - dev_warn(&sr->pdev->dev, "%s: smartreflex class driver not registered\n", - __func__); - return; - } - - sr_class->enable(sr); -} - -/** - * omap_sr_disable() - API to disable SR without resetting the voltage - * processor voltage - * @voltdm: VDD pointer to which the SR module to be configured belongs to. - * - * This API is to be called from the kernel in order to disable - * a particular smartreflex module. This API will in turn call - * into the registered smartreflex class disable API. This API will tell - * the smartreflex class disable not to reset the VP voltage after - * disabling smartreflex. - */ -void omap_sr_disable(struct voltagedomain *voltdm) -{ - struct omap_sr *sr = _sr_lookup(voltdm); - - if (IS_ERR(sr)) { - pr_warn("%s: omap_sr struct for voltdm not found\n", __func__); - return; - } - - if (!sr->autocomp_active) - return; - - if (!sr_class || !(sr_class->disable)) { - dev_warn(&sr->pdev->dev, "%s: smartreflex class driver not registered\n", - __func__); - return; - } - - sr_class->disable(sr, 0); -} - -/** - * omap_sr_disable_reset_volt() - API to disable SR and reset the - * voltage processor voltage - * @voltdm: VDD pointer to which the SR module to be configured belongs to. - * - * This API is to be called from the kernel in order to disable - * a particular smartreflex module. This API will in turn call - * into the registered smartreflex class disable API. This API will tell - * the smartreflex class disable to reset the VP voltage after - * disabling smartreflex. - */ -void omap_sr_disable_reset_volt(struct voltagedomain *voltdm) -{ - struct omap_sr *sr = _sr_lookup(voltdm); - - if (IS_ERR(sr)) { - pr_warn("%s: omap_sr struct for voltdm not found\n", __func__); - return; - } - - if (!sr->autocomp_active) - return; - - if (!sr_class || !(sr_class->disable)) { - dev_warn(&sr->pdev->dev, "%s: smartreflex class driver not registered\n", - __func__); - return; - } - - sr_class->disable(sr, 1); -} - -/* PM Debug FS entries to enable and disable smartreflex. */ -static int omap_sr_autocomp_show(void *data, u64 *val) -{ - struct omap_sr *sr_info = data; - - if (!sr_info) { - pr_warn("%s: omap_sr struct not found\n", __func__); - return -EINVAL; - } - - *val = sr_info->autocomp_active; - - return 0; -} - -static int omap_sr_autocomp_store(void *data, u64 val) -{ - struct omap_sr *sr_info = data; - - if (!sr_info) { - pr_warn("%s: omap_sr struct not found\n", __func__); - return -EINVAL; - } - - /* Sanity check */ - if (val > 1) { - pr_warn("%s: Invalid argument %lld\n", __func__, val); - return -EINVAL; - } - - /* control enable/disable only if there is a delta in value */ - if (sr_info->autocomp_active != val) { - if (!val) - sr_stop_vddautocomp(sr_info); - else - sr_start_vddautocomp(sr_info); - } - - return 0; -} - -DEFINE_SIMPLE_ATTRIBUTE(pm_sr_fops, omap_sr_autocomp_show, - omap_sr_autocomp_store, "%llu\n"); - -static int omap_sr_probe(struct platform_device *pdev) -{ - struct omap_sr *sr_info; - struct omap_sr_data *pdata = pdev->dev.platform_data; - struct resource *mem, *irq; - struct dentry *nvalue_dir; - int i, ret = 0; - - sr_info = devm_kzalloc(&pdev->dev, sizeof(struct omap_sr), GFP_KERNEL); - if (!sr_info) - return -ENOMEM; - - sr_info->name = devm_kzalloc(&pdev->dev, - SMARTREFLEX_NAME_LEN, GFP_KERNEL); - if (!sr_info->name) - return -ENOMEM; - - platform_set_drvdata(pdev, sr_info); - - if (!pdata) { - dev_err(&pdev->dev, "%s: platform data missing\n", __func__); - return -EINVAL; - } - - mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - sr_info->base = devm_ioremap_resource(&pdev->dev, mem); - if (IS_ERR(sr_info->base)) { - dev_err(&pdev->dev, "%s: ioremap fail\n", __func__); - return PTR_ERR(sr_info->base); - } - - irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); - - pm_runtime_enable(&pdev->dev); - pm_runtime_irq_safe(&pdev->dev); - - snprintf(sr_info->name, SMARTREFLEX_NAME_LEN, "%s", pdata->name); - - sr_info->pdev = pdev; - sr_info->srid = pdev->id; - sr_info->voltdm = pdata->voltdm; - sr_info->nvalue_table = pdata->nvalue_table; - sr_info->nvalue_count = pdata->nvalue_count; - sr_info->senn_mod = pdata->senn_mod; - sr_info->senp_mod = pdata->senp_mod; - sr_info->err_weight = pdata->err_weight; - sr_info->err_maxlimit = pdata->err_maxlimit; - sr_info->accum_data = pdata->accum_data; - sr_info->senn_avgweight = pdata->senn_avgweight; - sr_info->senp_avgweight = pdata->senp_avgweight; - sr_info->autocomp_active = false; - sr_info->ip_type = pdata->ip_type; - - if (irq) - sr_info->irq = irq->start; - - sr_set_clk_length(sr_info); - - list_add(&sr_info->node, &sr_list); - - ret = pm_runtime_get_sync(&pdev->dev); - if (ret < 0) { - pm_runtime_put_noidle(&pdev->dev); - goto err_list_del; - } - - /* - * Call into late init to do initializations that require - * both sr driver and sr class driver to be initiallized. - */ - if (sr_class) { - ret = sr_late_init(sr_info); - if (ret) { - pr_warn("%s: Error in SR late init\n", __func__); - goto err_list_del; - } - } - - dev_info(&pdev->dev, "%s: SmartReflex driver initialized\n", __func__); - if (!sr_dbg_dir) - sr_dbg_dir = debugfs_create_dir("smartreflex", NULL); - - sr_info->dbg_dir = debugfs_create_dir(sr_info->name, sr_dbg_dir); - - debugfs_create_file("autocomp", S_IRUGO | S_IWUSR, sr_info->dbg_dir, - sr_info, &pm_sr_fops); - debugfs_create_x32("errweight", S_IRUGO, sr_info->dbg_dir, - &sr_info->err_weight); - debugfs_create_x32("errmaxlimit", S_IRUGO, sr_info->dbg_dir, - &sr_info->err_maxlimit); - - nvalue_dir = debugfs_create_dir("nvalue", sr_info->dbg_dir); - - if (sr_info->nvalue_count == 0 || !sr_info->nvalue_table) { - dev_warn(&pdev->dev, "%s: %s: No Voltage table for the corresponding vdd. Cannot create debugfs entries for n-values\n", - __func__, sr_info->name); - - ret = -ENODATA; - goto err_debugfs; - } - - for (i = 0; i < sr_info->nvalue_count; i++) { - char name[NVALUE_NAME_LEN + 1]; - - snprintf(name, sizeof(name), "volt_%lu", - sr_info->nvalue_table[i].volt_nominal); - debugfs_create_x32(name, S_IRUGO | S_IWUSR, nvalue_dir, - &(sr_info->nvalue_table[i].nvalue)); - snprintf(name, sizeof(name), "errminlimit_%lu", - sr_info->nvalue_table[i].volt_nominal); - debugfs_create_x32(name, S_IRUGO | S_IWUSR, nvalue_dir, - &(sr_info->nvalue_table[i].errminlimit)); - - } - - pm_runtime_put_sync(&pdev->dev); - - return ret; - -err_debugfs: - debugfs_remove_recursive(sr_info->dbg_dir); -err_list_del: - list_del(&sr_info->node); - - pm_runtime_put_sync(&pdev->dev); - - return ret; -} - -static int omap_sr_remove(struct platform_device *pdev) -{ - struct omap_sr_data *pdata = pdev->dev.platform_data; - struct omap_sr *sr_info; - - if (!pdata) { - dev_err(&pdev->dev, "%s: platform data missing\n", __func__); - return -EINVAL; - } - - sr_info = _sr_lookup(pdata->voltdm); - if (IS_ERR(sr_info)) { - dev_warn(&pdev->dev, "%s: omap_sr struct not found\n", - __func__); - return PTR_ERR(sr_info); - } - - if (sr_info->autocomp_active) - sr_stop_vddautocomp(sr_info); - debugfs_remove_recursive(sr_info->dbg_dir); - - pm_runtime_disable(&pdev->dev); - list_del(&sr_info->node); - return 0; -} - -static void omap_sr_shutdown(struct platform_device *pdev) -{ - struct omap_sr_data *pdata = pdev->dev.platform_data; - struct omap_sr *sr_info; - - if (!pdata) { - dev_err(&pdev->dev, "%s: platform data missing\n", __func__); - return; - } - - sr_info = _sr_lookup(pdata->voltdm); - if (IS_ERR(sr_info)) { - dev_warn(&pdev->dev, "%s: omap_sr struct not found\n", - __func__); - return; - } - - if (sr_info->autocomp_active) - sr_stop_vddautocomp(sr_info); - - return; -} - -static const struct of_device_id omap_sr_match[] = { - { .compatible = "ti,omap3-smartreflex-core", }, - { .compatible = "ti,omap3-smartreflex-mpu-iva", }, - { .compatible = "ti,omap4-smartreflex-core", }, - { .compatible = "ti,omap4-smartreflex-mpu", }, - { .compatible = "ti,omap4-smartreflex-iva", }, - { }, -}; -MODULE_DEVICE_TABLE(of, omap_sr_match); - -static struct platform_driver smartreflex_driver = { - .probe = omap_sr_probe, - .remove = omap_sr_remove, - .shutdown = omap_sr_shutdown, - .driver = { - .name = DRIVER_NAME, - .of_match_table = omap_sr_match, - }, -}; - -static int __init sr_init(void) -{ - int ret = 0; - - ret = platform_driver_register(&smartreflex_driver); - if (ret) { - pr_err("%s: platform driver register failed for SR\n", - __func__); - return ret; - } - - return 0; -} -late_initcall(sr_init); - -static void __exit sr_exit(void) -{ - platform_driver_unregister(&smartreflex_driver); -} -module_exit(sr_exit); - -MODULE_DESCRIPTION("OMAP Smartreflex Driver"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:" DRIVER_NAME); -MODULE_AUTHOR("Texas Instruments Inc"); diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig index 0a1fb5c74f83..d55b3727e00e 100644 --- a/drivers/power/reset/Kconfig +++ b/drivers/power/reset/Kconfig @@ -129,10 +129,10 @@ config POWER_RESET_QCOM_PON config POWER_RESET_OCELOT_RESET bool "Microsemi Ocelot reset driver" - depends on MSCC_OCELOT || COMPILE_TEST + depends on MSCC_OCELOT || ARCH_SPARX5 || COMPILE_TEST select MFD_SYSCON help - This driver supports restart for Microsemi Ocelot SoC. + This driver supports restart for Microsemi Ocelot SoC and similar. config POWER_RESET_OXNAS bool "OXNAS SoC restart driver" diff --git a/drivers/power/reset/ocelot-reset.c b/drivers/power/reset/ocelot-reset.c index 419952c61fd0..f74e1dbb4ba3 100644 --- a/drivers/power/reset/ocelot-reset.c +++ b/drivers/power/reset/ocelot-reset.c @@ -15,15 +15,20 @@ #include <linux/reboot.h> #include <linux/regmap.h> +struct reset_props { + const char *syscon; + u32 protect_reg; + u32 vcore_protect; + u32 if_si_owner_bit; +}; + struct ocelot_reset_context { void __iomem *base; struct regmap *cpu_ctrl; + const struct reset_props *props; struct notifier_block restart_handler; }; -#define ICPU_CFG_CPU_SYSTEM_CTRL_RESET 0x20 -#define CORE_RST_PROTECT BIT(2) - #define SOFT_CHIP_RST BIT(0) #define ICPU_CFG_CPU_SYSTEM_CTRL_GENERAL_CTRL 0x24 @@ -31,7 +36,6 @@ struct ocelot_reset_context { #define IF_SI_OWNER_SISL 0 #define IF_SI_OWNER_SIBM 1 #define IF_SI_OWNER_SIMC 2 -#define IF_SI_OWNER_OFFSET 4 static int ocelot_restart_handle(struct notifier_block *this, unsigned long mode, void *cmd) @@ -39,15 +43,18 @@ static int ocelot_restart_handle(struct notifier_block *this, struct ocelot_reset_context *ctx = container_of(this, struct ocelot_reset_context, restart_handler); + u32 if_si_owner_bit = ctx->props->if_si_owner_bit; /* Make sure the core is not protected from reset */ - regmap_update_bits(ctx->cpu_ctrl, ICPU_CFG_CPU_SYSTEM_CTRL_RESET, - CORE_RST_PROTECT, 0); + regmap_update_bits(ctx->cpu_ctrl, ctx->props->protect_reg, + ctx->props->vcore_protect, 0); /* Make the SI back to boot mode */ regmap_update_bits(ctx->cpu_ctrl, ICPU_CFG_CPU_SYSTEM_CTRL_GENERAL_CTRL, - IF_SI_OWNER_MASK << IF_SI_OWNER_OFFSET, - IF_SI_OWNER_SIBM << IF_SI_OWNER_OFFSET); + IF_SI_OWNER_MASK << if_si_owner_bit, + IF_SI_OWNER_SIBM << if_si_owner_bit); + + pr_emerg("Resetting SoC\n"); writel(SOFT_CHIP_RST, ctx->base); @@ -72,9 +79,13 @@ static int ocelot_reset_probe(struct platform_device *pdev) if (IS_ERR(ctx->base)) return PTR_ERR(ctx->base); - ctx->cpu_ctrl = syscon_regmap_lookup_by_compatible("mscc,ocelot-cpu-syscon"); - if (IS_ERR(ctx->cpu_ctrl)) + ctx->props = device_get_match_data(dev); + + ctx->cpu_ctrl = syscon_regmap_lookup_by_compatible(ctx->props->syscon); + if (IS_ERR(ctx->cpu_ctrl)) { + dev_err(dev, "No syscon map: %s\n", ctx->props->syscon); return PTR_ERR(ctx->cpu_ctrl); + } ctx->restart_handler.notifier_call = ocelot_restart_handle; ctx->restart_handler.priority = 192; @@ -85,9 +96,29 @@ static int ocelot_reset_probe(struct platform_device *pdev) return err; } +static const struct reset_props reset_props_ocelot = { + .syscon = "mscc,ocelot-cpu-syscon", + .protect_reg = 0x20, + .vcore_protect = BIT(2), + .if_si_owner_bit = 4, +}; + +static const struct reset_props reset_props_sparx5 = { + .syscon = "microchip,sparx5-cpu-syscon", + .protect_reg = 0x84, + .vcore_protect = BIT(10), + .if_si_owner_bit = 6, +}; + static const struct of_device_id ocelot_reset_of_match[] = { - { .compatible = "mscc,ocelot-chip-reset" }, - {} + { + .compatible = "mscc,ocelot-chip-reset", + .data = &reset_props_ocelot + }, { + .compatible = "microchip,sparx5-chip-reset", + .data = &reset_props_sparx5 + }, + { /*sentinel*/ } }; static struct platform_driver ocelot_reset_driver = { diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index faf2830aa152..eec646c568b7 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -164,7 +164,7 @@ config BATTERY_DS2782 config BATTERY_LEGO_EV3 tristate "LEGO MINDSTORMS EV3 battery" - depends on OF && IIO && GPIOLIB + depends on OF && IIO && GPIOLIB && (ARCH_DAVINCI_DA850 || COMPILE_TEST) help Say Y here to enable support for the LEGO MINDSTORMS EV3 battery. @@ -367,10 +367,15 @@ config AXP288_FUEL_GAUGE config BATTERY_MAX17040 tristate "Maxim MAX17040 Fuel Gauge" depends on I2C + select REGMAP_I2C help - MAX17040 is fuel-gauge systems for lithium-ion (Li+) batteries - in handheld and portable equipment. The MAX17040 is configured - to operate with a single lithium cell + Maxim models with ModelGauge are fuel-gauge systems for lithium-ion + (Li+) batteries in handheld and portable equipment, including + max17040, max17041, max17043, max17044, max17048, max17049, max17058, + max17059. It is also included in some batteries like max77836. + + Driver supports reporting SOC (State of Charge, i.e capacity), + voltage and configurable low-SOC wakeup interrupt. config BATTERY_MAX17042 tristate "Maxim MAX17042/17047/17050/8997/8966 Fuel Gauge" @@ -631,13 +636,22 @@ config CHARGER_BQ25890 help Say Y to enable support for the TI BQ25890 battery charger. +config CHARGER_BQ25980 + tristate "TI BQ25980 battery charger driver" + depends on I2C + depends on GPIOLIB || COMPILE_TEST + select REGMAP_I2C + help + Say Y to enable support for the TI BQ25980, BQ25975 and BQ25960 + series of fast battery chargers. + config CHARGER_SMB347 - tristate "Summit Microelectronics SMB347 Battery Charger" + tristate "Summit Microelectronics SMB3XX Battery Charger" depends on I2C select REGMAP_I2C help - Say Y to include support for Summit Microelectronics SMB347 - Battery Charger. + Say Y to include support for Summit Microelectronics SMB345, + SMB347 or SMB358 Battery Charger. config CHARGER_TPS65090 tristate "TPS65090 battery charger driver" @@ -752,4 +766,12 @@ config CHARGER_WILCO information can be found in Documentation/ABI/testing/sysfs-class-power-wilco +config RN5T618_POWER + tristate "RN5T618 charger/fuel gauge support" + depends on MFD_RN5T618 + help + Say Y here to have support for RN5T618 PMIC family fuel gauge and charger. + This driver can also be built as a module. If so, the module will be + called rn5t618_power. + endif # POWER_SUPPLY diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index b3c694a65114..dd4b86318cd9 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -84,6 +84,7 @@ obj-$(CONFIG_CHARGER_BQ24257) += bq24257_charger.o obj-$(CONFIG_CHARGER_BQ24735) += bq24735-charger.o obj-$(CONFIG_CHARGER_BQ2515X) += bq2515x_charger.o obj-$(CONFIG_CHARGER_BQ25890) += bq25890_charger.o +obj-$(CONFIG_CHARGER_BQ25980) += bq25980_charger.o obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o obj-$(CONFIG_CHARGER_TPS65217) += tps65217_charger.o @@ -96,3 +97,4 @@ obj-$(CONFIG_CHARGER_UCS1002) += ucs1002_power.o obj-$(CONFIG_CHARGER_BD70528) += bd70528-charger.o obj-$(CONFIG_CHARGER_BD99954) += bd99954-charger.o obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o +obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c index 7eec415c82a3..592a73d4dde6 100644 --- a/drivers/power/supply/ab8500_fg.c +++ b/drivers/power/supply/ab8500_fg.c @@ -653,7 +653,7 @@ int ab8500_fg_inst_curr_finalize(struct ab8500_fg *di, int *res) /* * negative value for Discharging - * convert 2's compliment into decimal + * convert 2's complement into decimal */ if (high & 0x10) val = (low | (high << 8) | 0xFFFFE000); @@ -781,7 +781,7 @@ static void ab8500_fg_acc_cur_work(struct work_struct *work) if (ret < 0) goto exit; - /* Check for sign bit in case of negative value, 2's compliment */ + /* Check for sign bit in case of negative value, 2's complement */ if (high & 0x10) val = (low | (med << 8) | (high << 16) | 0xFFE00000); else diff --git a/drivers/power/supply/bq24257_charger.c b/drivers/power/supply/bq24257_charger.c index 8e60cb0f3c3f..96cb3290bcaa 100644 --- a/drivers/power/supply/bq24257_charger.c +++ b/drivers/power/supply/bq24257_charger.c @@ -1152,6 +1152,7 @@ static const struct of_device_id bq24257_of_match[] = { }; MODULE_DEVICE_TABLE(of, bq24257_of_match); +#ifdef CONFIG_ACPI static const struct acpi_device_id bq24257_acpi_match[] = { { "BQ242500", BQ24250 }, { "BQ242510", BQ24251 }, @@ -1159,6 +1160,7 @@ static const struct acpi_device_id bq24257_acpi_match[] = { {}, }; MODULE_DEVICE_TABLE(acpi, bq24257_acpi_match); +#endif static struct i2c_driver bq24257_driver = { .driver = { diff --git a/drivers/power/supply/bq2515x_charger.c b/drivers/power/supply/bq2515x_charger.c index 36b0c8c98d40..374b112f712a 100644 --- a/drivers/power/supply/bq2515x_charger.c +++ b/drivers/power/supply/bq2515x_charger.c @@ -168,7 +168,7 @@ enum bq2515x_id { * @device_id: value of device_id * @mains_online: boolean value indicating power supply online * - * @bq2515x_init_data init_data: charger initialization data structure + * @init_data: charger initialization data structure */ struct bq2515x_device { struct power_supply *mains; @@ -188,7 +188,7 @@ struct bq2515x_device { struct bq2515x_init_data init_data; }; -static struct reg_default bq25150_reg_defaults[] = { +static const struct reg_default bq25150_reg_defaults[] = { {BQ2515X_FLAG0, 0x0}, {BQ2515X_FLAG1, 0x0}, {BQ2515X_FLAG2, 0x0}, @@ -227,7 +227,7 @@ static struct reg_default bq25150_reg_defaults[] = { {BQ2515X_DEVICE_ID, 0x20}, }; -static struct reg_default bq25155_reg_defaults[] = { +static const struct reg_default bq25155_reg_defaults[] = { {BQ2515X_FLAG0, 0x0}, {BQ2515X_FLAG1, 0x0}, {BQ2515X_FLAG2, 0x0}, @@ -886,14 +886,14 @@ static int bq2515x_battery_get_property(struct power_supply *psy, return 0; } -static enum power_supply_property bq2515x_battery_properties[] = { +static const enum power_supply_property bq2515x_battery_properties[] = { POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, }; -static enum power_supply_property bq2515x_mains_properties[] = { +static const enum power_supply_property bq2515x_mains_properties[] = { POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_HEALTH, @@ -905,7 +905,7 @@ static enum power_supply_property bq2515x_mains_properties[] = { POWER_SUPPLY_PROP_PRECHARGE_CURRENT, }; -static struct power_supply_desc bq2515x_mains_desc = { +static const struct power_supply_desc bq2515x_mains_desc = { .name = "bq2515x-mains", .type = POWER_SUPPLY_TYPE_MAINS, .get_property = bq2515x_mains_get_property, @@ -915,7 +915,7 @@ static struct power_supply_desc bq2515x_mains_desc = { .property_is_writeable = bq2515x_power_supply_property_is_writeable, }; -static struct power_supply_desc bq2515x_battery_desc = { +static const struct power_supply_desc bq2515x_battery_desc = { .name = "bq2515x-battery", .type = POWER_SUPPLY_TYPE_BATTERY, .get_property = bq2515x_battery_get_property, diff --git a/drivers/power/supply/bq25890_charger.c b/drivers/power/supply/bq25890_charger.c index 77150667e36b..34c21c51bac1 100644 --- a/drivers/power/supply/bq25890_charger.c +++ b/drivers/power/supply/bq25890_charger.c @@ -83,6 +83,8 @@ struct bq25890_init_data { u8 boostf; /* boost frequency */ u8 ilim_en; /* enable ILIM pin */ u8 treg; /* thermal regulation threshold */ + u8 rbatcomp; /* IBAT sense resistor value */ + u8 vclamp; /* IBAT compensation voltage limit */ }; struct bq25890_state { @@ -258,6 +260,8 @@ enum bq25890_table_ids { TBL_VREG, TBL_BOOSTV, TBL_SYSVMIN, + TBL_VBATCOMP, + TBL_RBATCOMP, /* lookup tables */ TBL_TREG, @@ -299,6 +303,8 @@ static const union { [TBL_VREG] = { .rt = {3840000, 4608000, 16000} }, /* uV */ [TBL_BOOSTV] = { .rt = {4550000, 5510000, 64000} }, /* uV */ [TBL_SYSVMIN] = { .rt = {3000000, 3700000, 100000} }, /* uV */ + [TBL_VBATCOMP] ={ .rt = {0, 224000, 32000} }, /* uV */ + [TBL_RBATCOMP] ={ .rt = {0, 140000, 20000} }, /* uOhm */ /* lookup tables */ [TBL_TREG] = { .lt = {bq25890_treg_tbl, BQ25890_TREG_TBL_SIZE} }, @@ -648,7 +654,9 @@ static int bq25890_hw_init(struct bq25890_device *bq) {F_BOOSTI, bq->init_data.boosti}, {F_BOOSTF, bq->init_data.boostf}, {F_EN_ILIM, bq->init_data.ilim_en}, - {F_TREG, bq->init_data.treg} + {F_TREG, bq->init_data.treg}, + {F_BATCMP, bq->init_data.rbatcomp}, + {F_VCLAMP, bq->init_data.vclamp}, }; ret = bq25890_chip_reset(bq); @@ -859,11 +867,14 @@ static int bq25890_fw_read_u32_props(struct bq25890_device *bq) {"ti,boost-max-current", false, TBL_BOOSTI, &init->boosti}, /* optional properties */ - {"ti,thermal-regulation-threshold", true, TBL_TREG, &init->treg} + {"ti,thermal-regulation-threshold", true, TBL_TREG, &init->treg}, + {"ti,ibatcomp-micro-ohms", true, TBL_RBATCOMP, &init->rbatcomp}, + {"ti,ibatcomp-clamp-microvolt", true, TBL_VBATCOMP, &init->vclamp}, }; /* initialize data for optional properties */ init->treg = 3; /* 120 degrees Celsius */ + init->rbatcomp = init->vclamp = 0; /* IBAT compensation disabled */ for (i = 0; i < ARRAY_SIZE(props); i++) { ret = device_property_read_u32(bq->dev, props[i].name, @@ -1073,11 +1084,13 @@ static const struct of_device_id bq25890_of_match[] = { }; MODULE_DEVICE_TABLE(of, bq25890_of_match); +#ifdef CONFIG_ACPI static const struct acpi_device_id bq25890_acpi_match[] = { {"BQ258900", 0}, {}, }; MODULE_DEVICE_TABLE(acpi, bq25890_acpi_match); +#endif static struct i2c_driver bq25890_driver = { .driver = { diff --git a/drivers/power/supply/bq25980_charger.c b/drivers/power/supply/bq25980_charger.c new file mode 100644 index 000000000000..c936f311eb4f --- /dev/null +++ b/drivers/power/supply/bq25980_charger.c @@ -0,0 +1,1314 @@ +// SPDX-License-Identifier: GPL-2.0 +// BQ25980 Battery Charger Driver +// Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com/ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/gpio/consumer.h> +#include <linux/power_supply.h> +#include <linux/regmap.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/moduleparam.h> +#include <linux/slab.h> + +#include "bq25980_charger.h" + +struct bq25980_state { + bool dischg; + bool ovp; + bool ocp; + bool wdt; + bool tflt; + bool online; + bool ce; + bool hiz; + bool bypass; + + u32 vbat_adc; + u32 vsys_adc; + u32 ibat_adc; +}; + +enum bq25980_id { + BQ25980, + BQ25975, + BQ25960, +}; + +struct bq25980_chip_info { + + int model_id; + + const struct regmap_config *regmap_config; + + int busocp_def; + int busocp_sc_max; + int busocp_byp_max; + int busocp_sc_min; + int busocp_byp_min; + + int busovp_sc_def; + int busovp_byp_def; + int busovp_sc_step; + + int busovp_sc_offset; + int busovp_byp_step; + int busovp_byp_offset; + int busovp_sc_min; + int busovp_sc_max; + int busovp_byp_min; + int busovp_byp_max; + + int batovp_def; + int batovp_max; + int batovp_min; + int batovp_step; + int batovp_offset; + + int batocp_def; + int batocp_max; +}; + +struct bq25980_init_data { + u32 ichg; + u32 bypass_ilim; + u32 sc_ilim; + u32 vreg; + u32 iterm; + u32 iprechg; + u32 bypass_vlim; + u32 sc_vlim; + u32 ichg_max; + u32 vreg_max; +}; + +struct bq25980_device { + struct i2c_client *client; + struct device *dev; + struct power_supply *charger; + struct power_supply *battery; + struct mutex lock; + struct regmap *regmap; + + char model_name[I2C_NAME_SIZE]; + + struct bq25980_init_data init_data; + const struct bq25980_chip_info *chip_info; + struct bq25980_state state; + int watchdog_timer; +}; + +static struct reg_default bq25980_reg_defs[] = { + {BQ25980_BATOVP, 0x5A}, + {BQ25980_BATOVP_ALM, 0x46}, + {BQ25980_BATOCP, 0x51}, + {BQ25980_BATOCP_ALM, 0x50}, + {BQ25980_BATUCP_ALM, 0x28}, + {BQ25980_CHRGR_CTRL_1, 0x0}, + {BQ25980_BUSOVP, 0x26}, + {BQ25980_BUSOVP_ALM, 0x22}, + {BQ25980_BUSOCP, 0xD}, + {BQ25980_BUSOCP_ALM, 0xC}, + {BQ25980_TEMP_CONTROL, 0x30}, + {BQ25980_TDIE_ALM, 0xC8}, + {BQ25980_TSBUS_FLT, 0x15}, + {BQ25980_TSBAT_FLG, 0x15}, + {BQ25980_VAC_CONTROL, 0x0}, + {BQ25980_CHRGR_CTRL_2, 0x0}, + {BQ25980_CHRGR_CTRL_3, 0x20}, + {BQ25980_CHRGR_CTRL_4, 0x1D}, + {BQ25980_CHRGR_CTRL_5, 0x18}, + {BQ25980_STAT1, 0x0}, + {BQ25980_STAT2, 0x0}, + {BQ25980_STAT3, 0x0}, + {BQ25980_STAT4, 0x0}, + {BQ25980_STAT5, 0x0}, + {BQ25980_FLAG1, 0x0}, + {BQ25980_FLAG2, 0x0}, + {BQ25980_FLAG3, 0x0}, + {BQ25980_FLAG4, 0x0}, + {BQ25980_FLAG5, 0x0}, + {BQ25980_MASK1, 0x0}, + {BQ25980_MASK2, 0x0}, + {BQ25980_MASK3, 0x0}, + {BQ25980_MASK4, 0x0}, + {BQ25980_MASK5, 0x0}, + {BQ25980_DEVICE_INFO, 0x8}, + {BQ25980_ADC_CONTROL1, 0x0}, + {BQ25980_ADC_CONTROL2, 0x0}, + {BQ25980_IBUS_ADC_LSB, 0x0}, + {BQ25980_IBUS_ADC_MSB, 0x0}, + {BQ25980_VBUS_ADC_LSB, 0x0}, + {BQ25980_VBUS_ADC_MSB, 0x0}, + {BQ25980_VAC1_ADC_LSB, 0x0}, + {BQ25980_VAC2_ADC_LSB, 0x0}, + {BQ25980_VOUT_ADC_LSB, 0x0}, + {BQ25980_VBAT_ADC_LSB, 0x0}, + {BQ25980_IBAT_ADC_MSB, 0x0}, + {BQ25980_IBAT_ADC_LSB, 0x0}, + {BQ25980_TSBUS_ADC_LSB, 0x0}, + {BQ25980_TSBAT_ADC_LSB, 0x0}, + {BQ25980_TDIE_ADC_LSB, 0x0}, + {BQ25980_DEGLITCH_TIME, 0x0}, + {BQ25980_CHRGR_CTRL_6, 0x0}, +}; + +static struct reg_default bq25975_reg_defs[] = { + {BQ25980_BATOVP, 0x5A}, + {BQ25980_BATOVP_ALM, 0x46}, + {BQ25980_BATOCP, 0x51}, + {BQ25980_BATOCP_ALM, 0x50}, + {BQ25980_BATUCP_ALM, 0x28}, + {BQ25980_CHRGR_CTRL_1, 0x0}, + {BQ25980_BUSOVP, 0x26}, + {BQ25980_BUSOVP_ALM, 0x22}, + {BQ25980_BUSOCP, 0xD}, + {BQ25980_BUSOCP_ALM, 0xC}, + {BQ25980_TEMP_CONTROL, 0x30}, + {BQ25980_TDIE_ALM, 0xC8}, + {BQ25980_TSBUS_FLT, 0x15}, + {BQ25980_TSBAT_FLG, 0x15}, + {BQ25980_VAC_CONTROL, 0x0}, + {BQ25980_CHRGR_CTRL_2, 0x0}, + {BQ25980_CHRGR_CTRL_3, 0x20}, + {BQ25980_CHRGR_CTRL_4, 0x1D}, + {BQ25980_CHRGR_CTRL_5, 0x18}, + {BQ25980_STAT1, 0x0}, + {BQ25980_STAT2, 0x0}, + {BQ25980_STAT3, 0x0}, + {BQ25980_STAT4, 0x0}, + {BQ25980_STAT5, 0x0}, + {BQ25980_FLAG1, 0x0}, + {BQ25980_FLAG2, 0x0}, + {BQ25980_FLAG3, 0x0}, + {BQ25980_FLAG4, 0x0}, + {BQ25980_FLAG5, 0x0}, + {BQ25980_MASK1, 0x0}, + {BQ25980_MASK2, 0x0}, + {BQ25980_MASK3, 0x0}, + {BQ25980_MASK4, 0x0}, + {BQ25980_MASK5, 0x0}, + {BQ25980_DEVICE_INFO, 0x8}, + {BQ25980_ADC_CONTROL1, 0x0}, + {BQ25980_ADC_CONTROL2, 0x0}, + {BQ25980_IBUS_ADC_LSB, 0x0}, + {BQ25980_IBUS_ADC_MSB, 0x0}, + {BQ25980_VBUS_ADC_LSB, 0x0}, + {BQ25980_VBUS_ADC_MSB, 0x0}, + {BQ25980_VAC1_ADC_LSB, 0x0}, + {BQ25980_VAC2_ADC_LSB, 0x0}, + {BQ25980_VOUT_ADC_LSB, 0x0}, + {BQ25980_VBAT_ADC_LSB, 0x0}, + {BQ25980_IBAT_ADC_MSB, 0x0}, + {BQ25980_IBAT_ADC_LSB, 0x0}, + {BQ25980_TSBUS_ADC_LSB, 0x0}, + {BQ25980_TSBAT_ADC_LSB, 0x0}, + {BQ25980_TDIE_ADC_LSB, 0x0}, + {BQ25980_DEGLITCH_TIME, 0x0}, + {BQ25980_CHRGR_CTRL_6, 0x0}, +}; + +static struct reg_default bq25960_reg_defs[] = { + {BQ25980_BATOVP, 0x5A}, + {BQ25980_BATOVP_ALM, 0x46}, + {BQ25980_BATOCP, 0x51}, + {BQ25980_BATOCP_ALM, 0x50}, + {BQ25980_BATUCP_ALM, 0x28}, + {BQ25980_CHRGR_CTRL_1, 0x0}, + {BQ25980_BUSOVP, 0x26}, + {BQ25980_BUSOVP_ALM, 0x22}, + {BQ25980_BUSOCP, 0xD}, + {BQ25980_BUSOCP_ALM, 0xC}, + {BQ25980_TEMP_CONTROL, 0x30}, + {BQ25980_TDIE_ALM, 0xC8}, + {BQ25980_TSBUS_FLT, 0x15}, + {BQ25980_TSBAT_FLG, 0x15}, + {BQ25980_VAC_CONTROL, 0x0}, + {BQ25980_CHRGR_CTRL_2, 0x0}, + {BQ25980_CHRGR_CTRL_3, 0x20}, + {BQ25980_CHRGR_CTRL_4, 0x1D}, + {BQ25980_CHRGR_CTRL_5, 0x18}, + {BQ25980_STAT1, 0x0}, + {BQ25980_STAT2, 0x0}, + {BQ25980_STAT3, 0x0}, + {BQ25980_STAT4, 0x0}, + {BQ25980_STAT5, 0x0}, + {BQ25980_FLAG1, 0x0}, + {BQ25980_FLAG2, 0x0}, + {BQ25980_FLAG3, 0x0}, + {BQ25980_FLAG4, 0x0}, + {BQ25980_FLAG5, 0x0}, + {BQ25980_MASK1, 0x0}, + {BQ25980_MASK2, 0x0}, + {BQ25980_MASK3, 0x0}, + {BQ25980_MASK4, 0x0}, + {BQ25980_MASK5, 0x0}, + {BQ25980_DEVICE_INFO, 0x8}, + {BQ25980_ADC_CONTROL1, 0x0}, + {BQ25980_ADC_CONTROL2, 0x0}, + {BQ25980_IBUS_ADC_LSB, 0x0}, + {BQ25980_IBUS_ADC_MSB, 0x0}, + {BQ25980_VBUS_ADC_LSB, 0x0}, + {BQ25980_VBUS_ADC_MSB, 0x0}, + {BQ25980_VAC1_ADC_LSB, 0x0}, + {BQ25980_VAC2_ADC_LSB, 0x0}, + {BQ25980_VOUT_ADC_LSB, 0x0}, + {BQ25980_VBAT_ADC_LSB, 0x0}, + {BQ25980_IBAT_ADC_MSB, 0x0}, + {BQ25980_IBAT_ADC_LSB, 0x0}, + {BQ25980_TSBUS_ADC_LSB, 0x0}, + {BQ25980_TSBAT_ADC_LSB, 0x0}, + {BQ25980_TDIE_ADC_LSB, 0x0}, + {BQ25980_DEGLITCH_TIME, 0x0}, + {BQ25980_CHRGR_CTRL_6, 0x0}, +}; + +static int bq25980_watchdog_time[BQ25980_NUM_WD_VAL] = {5000, 10000, 50000, + 300000}; + +static int bq25980_get_input_curr_lim(struct bq25980_device *bq) +{ + unsigned int busocp_reg_code; + int ret; + + ret = regmap_read(bq->regmap, BQ25980_BUSOCP, &busocp_reg_code); + if (ret) + return ret; + + return (busocp_reg_code * BQ25980_BUSOCP_STEP_uA) + BQ25980_BUSOCP_OFFSET_uA; +} + +static int bq25980_set_hiz(struct bq25980_device *bq, int setting) +{ + return regmap_update_bits(bq->regmap, BQ25980_CHRGR_CTRL_2, + BQ25980_EN_HIZ, setting); +} + +static int bq25980_set_input_curr_lim(struct bq25980_device *bq, int busocp) +{ + unsigned int busocp_reg_code; + int ret; + + if (!busocp) + return bq25980_set_hiz(bq, BQ25980_ENABLE_HIZ); + + bq25980_set_hiz(bq, BQ25980_DISABLE_HIZ); + + if (busocp < BQ25980_BUSOCP_MIN_uA) + busocp = BQ25980_BUSOCP_MIN_uA; + + if (bq->state.bypass) + busocp = min(busocp, bq->chip_info->busocp_sc_max); + else + busocp = min(busocp, bq->chip_info->busocp_byp_max); + + busocp_reg_code = (busocp - BQ25980_BUSOCP_OFFSET_uA) + / BQ25980_BUSOCP_STEP_uA; + + ret = regmap_write(bq->regmap, BQ25980_BUSOCP, busocp_reg_code); + if (ret) + return ret; + + return regmap_write(bq->regmap, BQ25980_BUSOCP_ALM, busocp_reg_code); +} + +static int bq25980_get_input_volt_lim(struct bq25980_device *bq) +{ + unsigned int busovp_reg_code; + unsigned int busovp_offset; + unsigned int busovp_step; + int ret; + + if (bq->state.bypass) { + busovp_step = bq->chip_info->busovp_byp_step; + busovp_offset = bq->chip_info->busovp_byp_offset; + } else { + busovp_step = bq->chip_info->busovp_sc_step; + busovp_offset = bq->chip_info->busovp_sc_offset; + } + + ret = regmap_read(bq->regmap, BQ25980_BUSOVP, &busovp_reg_code); + if (ret) + return ret; + + return (busovp_reg_code * busovp_step) + busovp_offset; +} + +static int bq25980_set_input_volt_lim(struct bq25980_device *bq, int busovp) +{ + unsigned int busovp_reg_code; + unsigned int busovp_step; + unsigned int busovp_offset; + int ret; + + if (bq->state.bypass) { + busovp_step = bq->chip_info->busovp_byp_step; + busovp_offset = bq->chip_info->busovp_byp_offset; + if (busovp > bq->chip_info->busovp_byp_max) + busovp = bq->chip_info->busovp_byp_max; + else if (busovp < bq->chip_info->busovp_byp_min) + busovp = bq->chip_info->busovp_byp_min; + } else { + busovp_step = bq->chip_info->busovp_sc_step; + busovp_offset = bq->chip_info->busovp_sc_offset; + if (busovp > bq->chip_info->busovp_sc_max) + busovp = bq->chip_info->busovp_sc_max; + else if (busovp < bq->chip_info->busovp_sc_min) + busovp = bq->chip_info->busovp_sc_min; + } + + busovp_reg_code = (busovp - busovp_offset) / busovp_step; + + ret = regmap_write(bq->regmap, BQ25980_BUSOVP, busovp_reg_code); + if (ret) + return ret; + + return regmap_write(bq->regmap, BQ25980_BUSOVP_ALM, busovp_reg_code); +} + +static int bq25980_get_const_charge_curr(struct bq25980_device *bq) +{ + unsigned int batocp_reg_code; + int ret; + + ret = regmap_read(bq->regmap, BQ25980_BATOCP, &batocp_reg_code); + if (ret) + return ret; + + return (batocp_reg_code & BQ25980_BATOCP_MASK) * + BQ25980_BATOCP_STEP_uA; +} + +static int bq25980_set_const_charge_curr(struct bq25980_device *bq, int batocp) +{ + unsigned int batocp_reg_code; + int ret; + + batocp = max(batocp, BQ25980_BATOCP_MIN_uA); + batocp = min(batocp, bq->chip_info->batocp_max); + + batocp_reg_code = batocp / BQ25980_BATOCP_STEP_uA; + + ret = regmap_update_bits(bq->regmap, BQ25980_BATOCP, + BQ25980_BATOCP_MASK, batocp_reg_code); + if (ret) + return ret; + + return regmap_update_bits(bq->regmap, BQ25980_BATOCP_ALM, + BQ25980_BATOCP_MASK, batocp_reg_code); +} + +static int bq25980_get_const_charge_volt(struct bq25980_device *bq) +{ + unsigned int batovp_reg_code; + int ret; + + ret = regmap_read(bq->regmap, BQ25980_BATOVP, &batovp_reg_code); + if (ret) + return ret; + + return ((batovp_reg_code * bq->chip_info->batovp_step) + + bq->chip_info->batovp_offset); +} + +static int bq25980_set_const_charge_volt(struct bq25980_device *bq, int batovp) +{ + unsigned int batovp_reg_code; + int ret; + + if (batovp < bq->chip_info->batovp_min) + batovp = bq->chip_info->batovp_min; + + if (batovp > bq->chip_info->batovp_max) + batovp = bq->chip_info->batovp_max; + + batovp_reg_code = (batovp - bq->chip_info->batovp_offset) / + bq->chip_info->batovp_step; + + ret = regmap_write(bq->regmap, BQ25980_BATOVP, batovp_reg_code); + if (ret) + return ret; + + return regmap_write(bq->regmap, BQ25980_BATOVP_ALM, batovp_reg_code); +} + +static int bq25980_set_bypass(struct bq25980_device *bq, bool en_bypass) +{ + int ret; + + if (en_bypass) + ret = regmap_update_bits(bq->regmap, BQ25980_CHRGR_CTRL_2, + BQ25980_EN_BYPASS, BQ25980_EN_BYPASS); + else + ret = regmap_update_bits(bq->regmap, BQ25980_CHRGR_CTRL_2, + BQ25980_EN_BYPASS, en_bypass); + if (ret) + return ret; + + bq->state.bypass = en_bypass; + + return bq->state.bypass; +} + +static int bq25980_set_chg_en(struct bq25980_device *bq, bool en_chg) +{ + int ret; + + if (en_chg) + ret = regmap_update_bits(bq->regmap, BQ25980_CHRGR_CTRL_2, + BQ25980_CHG_EN, BQ25980_CHG_EN); + else + ret = regmap_update_bits(bq->regmap, BQ25980_CHRGR_CTRL_2, + BQ25980_CHG_EN, en_chg); + if (ret) + return ret; + + bq->state.ce = en_chg; + + return 0; +} + +static int bq25980_get_adc_ibus(struct bq25980_device *bq) +{ + int ibus_adc_lsb, ibus_adc_msb; + u16 ibus_adc; + int ret; + + ret = regmap_read(bq->regmap, BQ25980_IBUS_ADC_MSB, &ibus_adc_msb); + if (ret) + return ret; + + ret = regmap_read(bq->regmap, BQ25980_IBUS_ADC_LSB, &ibus_adc_lsb); + if (ret) + return ret; + + ibus_adc = (ibus_adc_msb << 8) | ibus_adc_lsb; + + if (ibus_adc_msb & BQ25980_ADC_POLARITY_BIT) + return ((ibus_adc ^ 0xffff) + 1) * BQ25980_ADC_CURR_STEP_uA; + + return ibus_adc * BQ25980_ADC_CURR_STEP_uA; +} + +static int bq25980_get_adc_vbus(struct bq25980_device *bq) +{ + int vbus_adc_lsb, vbus_adc_msb; + u16 vbus_adc; + int ret; + + ret = regmap_read(bq->regmap, BQ25980_VBUS_ADC_MSB, &vbus_adc_msb); + if (ret) + return ret; + + ret = regmap_read(bq->regmap, BQ25980_VBUS_ADC_LSB, &vbus_adc_lsb); + if (ret) + return ret; + + vbus_adc = (vbus_adc_msb << 8) | vbus_adc_lsb; + + return vbus_adc * BQ25980_ADC_VOLT_STEP_uV; +} + +static int bq25980_get_ibat_adc(struct bq25980_device *bq) +{ + int ret; + int ibat_adc_lsb, ibat_adc_msb; + int ibat_adc; + + ret = regmap_read(bq->regmap, BQ25980_IBAT_ADC_MSB, &ibat_adc_msb); + if (ret) + return ret; + + ret = regmap_read(bq->regmap, BQ25980_IBAT_ADC_LSB, &ibat_adc_lsb); + if (ret) + return ret; + + ibat_adc = (ibat_adc_msb << 8) | ibat_adc_lsb; + + if (ibat_adc_msb & BQ25980_ADC_POLARITY_BIT) + return ((ibat_adc ^ 0xffff) + 1) * BQ25980_ADC_CURR_STEP_uA; + + return ibat_adc * BQ25980_ADC_CURR_STEP_uA; +} + +static int bq25980_get_adc_vbat(struct bq25980_device *bq) +{ + int vsys_adc_lsb, vsys_adc_msb; + u16 vsys_adc; + int ret; + + ret = regmap_read(bq->regmap, BQ25980_VBAT_ADC_MSB, &vsys_adc_msb); + if (ret) + return ret; + + ret = regmap_read(bq->regmap, BQ25980_VBAT_ADC_LSB, &vsys_adc_lsb); + if (ret) + return ret; + + vsys_adc = (vsys_adc_msb << 8) | vsys_adc_lsb; + + return vsys_adc * BQ25980_ADC_VOLT_STEP_uV; +} + +static int bq25980_get_state(struct bq25980_device *bq, + struct bq25980_state *state) +{ + unsigned int chg_ctrl_2; + unsigned int stat1; + unsigned int stat2; + unsigned int stat3; + unsigned int stat4; + unsigned int ibat_adc_msb; + int ret; + + ret = regmap_read(bq->regmap, BQ25980_STAT1, &stat1); + if (ret) + return ret; + + ret = regmap_read(bq->regmap, BQ25980_STAT2, &stat2); + if (ret) + return ret; + + ret = regmap_read(bq->regmap, BQ25980_STAT3, &stat3); + if (ret) + return ret; + + ret = regmap_read(bq->regmap, BQ25980_STAT4, &stat4); + if (ret) + return ret; + + ret = regmap_read(bq->regmap, BQ25980_CHRGR_CTRL_2, &chg_ctrl_2); + if (ret) + return ret; + + ret = regmap_read(bq->regmap, BQ25980_IBAT_ADC_MSB, &ibat_adc_msb); + if (ret) + return ret; + + state->dischg = ibat_adc_msb & BQ25980_ADC_POLARITY_BIT; + state->ovp = (stat1 & BQ25980_STAT1_OVP_MASK) | + (stat3 & BQ25980_STAT3_OVP_MASK); + state->ocp = (stat1 & BQ25980_STAT1_OCP_MASK) | + (stat2 & BQ25980_STAT2_OCP_MASK); + state->tflt = stat4 & BQ25980_STAT4_TFLT_MASK; + state->wdt = stat4 & BQ25980_WD_STAT; + state->online = stat3 & BQ25980_PRESENT_MASK; + state->ce = chg_ctrl_2 & BQ25980_CHG_EN; + state->hiz = chg_ctrl_2 & BQ25980_EN_HIZ; + state->bypass = chg_ctrl_2 & BQ25980_EN_BYPASS; + + return 0; +} + +static int bq25980_set_battery_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct bq25980_device *bq = power_supply_get_drvdata(psy); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + ret = bq25980_set_const_charge_curr(bq, val->intval); + if (ret) + return ret; + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + ret = bq25980_set_const_charge_volt(bq, val->intval); + if (ret) + return ret; + break; + + default: + return -EINVAL; + } + + return ret; +} + +static int bq25980_get_battery_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct bq25980_device *bq = power_supply_get_drvdata(psy); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + val->intval = bq->init_data.ichg_max; + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + val->intval = bq->init_data.vreg_max; + break; + + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = bq25980_get_ibat_adc(bq); + val->intval = ret; + break; + + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = bq25980_get_adc_vbat(bq); + if (ret < 0) + return ret; + + val->intval = ret; + break; + + default: + return -EINVAL; + } + + return ret; +} + +static int bq25980_set_charger_property(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + struct bq25980_device *bq = power_supply_get_drvdata(psy); + int ret = -EINVAL; + + switch (prop) { + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + ret = bq25980_set_input_curr_lim(bq, val->intval); + if (ret) + return ret; + break; + + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: + ret = bq25980_set_input_volt_lim(bq, val->intval); + if (ret) + return ret; + break; + + case POWER_SUPPLY_PROP_CHARGE_TYPE: + ret = bq25980_set_bypass(bq, val->intval); + if (ret) + return ret; + break; + + case POWER_SUPPLY_PROP_STATUS: + ret = bq25980_set_chg_en(bq, val->intval); + if (ret) + return ret; + break; + + default: + return -EINVAL; + } + + return ret; +} + +static int bq25980_get_charger_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct bq25980_device *bq = power_supply_get_drvdata(psy); + struct bq25980_state state; + int ret = 0; + + mutex_lock(&bq->lock); + ret = bq25980_get_state(bq, &state); + mutex_unlock(&bq->lock); + if (ret) + return ret; + + switch (psp) { + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = BQ25980_MANUFACTURER; + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = bq->model_name; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = state.online; + break; + + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: + ret = bq25980_get_input_volt_lim(bq); + if (ret < 0) + return ret; + val->intval = ret; + break; + + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + ret = bq25980_get_input_curr_lim(bq); + if (ret < 0) + return ret; + + val->intval = ret; + break; + + case POWER_SUPPLY_PROP_HEALTH: + val->intval = POWER_SUPPLY_HEALTH_GOOD; + + if (state.tflt) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (state.ovp) + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + else if (state.ocp) + val->intval = POWER_SUPPLY_HEALTH_OVERCURRENT; + else if (state.wdt) + val->intval = + POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE; + break; + + case POWER_SUPPLY_PROP_STATUS: + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + + if ((state.ce) && (!state.hiz)) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if (state.dischg) + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else if (!state.ce) + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + + if (!state.ce) + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + else if (state.bypass) + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + else if (!state.bypass) + val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD; + break; + + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = bq25980_get_adc_ibus(bq); + if (ret < 0) + return ret; + + val->intval = ret; + break; + + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = bq25980_get_adc_vbus(bq); + if (ret < 0) + return ret; + + val->intval = ret; + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + ret = bq25980_get_const_charge_curr(bq); + if (ret < 0) + return ret; + + val->intval = ret; + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + ret = bq25980_get_const_charge_volt(bq); + if (ret < 0) + return ret; + + val->intval = ret; + break; + + default: + return -EINVAL; + } + + return ret; +} + +static bool bq25980_state_changed(struct bq25980_device *bq, + struct bq25980_state *new_state) +{ + struct bq25980_state old_state; + + mutex_lock(&bq->lock); + old_state = bq->state; + mutex_unlock(&bq->lock); + + return (old_state.dischg != new_state->dischg || + old_state.ovp != new_state->ovp || + old_state.ocp != new_state->ocp || + old_state.online != new_state->online || + old_state.wdt != new_state->wdt || + old_state.tflt != new_state->tflt || + old_state.ce != new_state->ce || + old_state.hiz != new_state->hiz || + old_state.bypass != new_state->bypass); +} + +static irqreturn_t bq25980_irq_handler_thread(int irq, void *private) +{ + struct bq25980_device *bq = private; + struct bq25980_state state; + int ret; + + ret = bq25980_get_state(bq, &state); + if (ret < 0) + goto irq_out; + + if (!bq25980_state_changed(bq, &state)) + goto irq_out; + + mutex_lock(&bq->lock); + bq->state = state; + mutex_unlock(&bq->lock); + + power_supply_changed(bq->charger); + +irq_out: + return IRQ_HANDLED; +} + +static enum power_supply_property bq25980_power_supply_props[] = { + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +static enum power_supply_property bq25980_battery_props[] = { + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +static char *bq25980_charger_supplied_to[] = { + "main-battery", +}; + +static int bq25980_property_is_writeable(struct power_supply *psy, + enum power_supply_property prop) +{ + switch (prop) { + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + case POWER_SUPPLY_PROP_CHARGE_TYPE: + case POWER_SUPPLY_PROP_STATUS: + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: + return true; + default: + return false; + } +} + +static const struct power_supply_desc bq25980_power_supply_desc = { + .name = "bq25980-charger", + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = bq25980_power_supply_props, + .num_properties = ARRAY_SIZE(bq25980_power_supply_props), + .get_property = bq25980_get_charger_property, + .set_property = bq25980_set_charger_property, + .property_is_writeable = bq25980_property_is_writeable, +}; + +static struct power_supply_desc bq25980_battery_desc = { + .name = "bq25980-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .get_property = bq25980_get_battery_property, + .set_property = bq25980_set_battery_property, + .properties = bq25980_battery_props, + .num_properties = ARRAY_SIZE(bq25980_battery_props), + .property_is_writeable = bq25980_property_is_writeable, +}; + + +static bool bq25980_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BQ25980_CHRGR_CTRL_2: + case BQ25980_STAT1...BQ25980_FLAG5: + case BQ25980_ADC_CONTROL1...BQ25980_TDIE_ADC_LSB: + return true; + default: + return false; + } +} + +static const struct regmap_config bq25980_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = BQ25980_CHRGR_CTRL_6, + .reg_defaults = bq25980_reg_defs, + .num_reg_defaults = ARRAY_SIZE(bq25980_reg_defs), + .cache_type = REGCACHE_RBTREE, + .volatile_reg = bq25980_is_volatile_reg, +}; + +static const struct regmap_config bq25975_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = BQ25980_CHRGR_CTRL_6, + .reg_defaults = bq25975_reg_defs, + .num_reg_defaults = ARRAY_SIZE(bq25975_reg_defs), + .cache_type = REGCACHE_RBTREE, + .volatile_reg = bq25980_is_volatile_reg, +}; + +static const struct regmap_config bq25960_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = BQ25980_CHRGR_CTRL_6, + .reg_defaults = bq25960_reg_defs, + .num_reg_defaults = ARRAY_SIZE(bq25960_reg_defs), + .cache_type = REGCACHE_RBTREE, + .volatile_reg = bq25980_is_volatile_reg, +}; + +static const struct bq25980_chip_info bq25980_chip_info_tbl[] = { + [BQ25980] = { + .model_id = BQ25980, + .regmap_config = &bq25980_regmap_config, + + .busocp_def = BQ25980_BUSOCP_DFLT_uA, + .busocp_sc_min = BQ25960_BUSOCP_SC_MAX_uA, + .busocp_sc_max = BQ25980_BUSOCP_SC_MAX_uA, + .busocp_byp_max = BQ25980_BUSOCP_BYP_MAX_uA, + .busocp_byp_min = BQ25980_BUSOCP_MIN_uA, + + .busovp_sc_def = BQ25980_BUSOVP_DFLT_uV, + .busovp_byp_def = BQ25980_BUSOVP_BYPASS_DFLT_uV, + .busovp_sc_step = BQ25980_BUSOVP_SC_STEP_uV, + .busovp_sc_offset = BQ25980_BUSOVP_SC_OFFSET_uV, + .busovp_byp_step = BQ25980_BUSOVP_BYP_STEP_uV, + .busovp_byp_offset = BQ25980_BUSOVP_BYP_OFFSET_uV, + .busovp_sc_min = BQ25980_BUSOVP_SC_MIN_uV, + .busovp_sc_max = BQ25980_BUSOVP_SC_MAX_uV, + .busovp_byp_min = BQ25980_BUSOVP_BYP_MIN_uV, + .busovp_byp_max = BQ25980_BUSOVP_BYP_MAX_uV, + + .batovp_def = BQ25980_BATOVP_DFLT_uV, + .batovp_max = BQ25980_BATOVP_MAX_uV, + .batovp_min = BQ25980_BATOVP_MIN_uV, + .batovp_step = BQ25980_BATOVP_STEP_uV, + .batovp_offset = BQ25980_BATOVP_OFFSET_uV, + + .batocp_def = BQ25980_BATOCP_DFLT_uA, + .batocp_max = BQ25980_BATOCP_MAX_uA, + }, + + [BQ25975] = { + .model_id = BQ25975, + .regmap_config = &bq25975_regmap_config, + + .busocp_def = BQ25975_BUSOCP_DFLT_uA, + .busocp_sc_min = BQ25975_BUSOCP_SC_MAX_uA, + .busocp_sc_max = BQ25975_BUSOCP_SC_MAX_uA, + .busocp_byp_min = BQ25980_BUSOCP_MIN_uA, + .busocp_byp_max = BQ25975_BUSOCP_BYP_MAX_uA, + + .busovp_sc_def = BQ25975_BUSOVP_DFLT_uV, + .busovp_byp_def = BQ25975_BUSOVP_BYPASS_DFLT_uV, + .busovp_sc_step = BQ25975_BUSOVP_SC_STEP_uV, + .busovp_sc_offset = BQ25975_BUSOVP_SC_OFFSET_uV, + .busovp_byp_step = BQ25975_BUSOVP_BYP_STEP_uV, + .busovp_byp_offset = BQ25975_BUSOVP_BYP_OFFSET_uV, + .busovp_sc_min = BQ25975_BUSOVP_SC_MIN_uV, + .busovp_sc_max = BQ25975_BUSOVP_SC_MAX_uV, + .busovp_byp_min = BQ25975_BUSOVP_BYP_MIN_uV, + .busovp_byp_max = BQ25975_BUSOVP_BYP_MAX_uV, + + .batovp_def = BQ25975_BATOVP_DFLT_uV, + .batovp_max = BQ25975_BATOVP_MAX_uV, + .batovp_min = BQ25975_BATOVP_MIN_uV, + .batovp_step = BQ25975_BATOVP_STEP_uV, + .batovp_offset = BQ25975_BATOVP_OFFSET_uV, + + .batocp_def = BQ25980_BATOCP_DFLT_uA, + .batocp_max = BQ25980_BATOCP_MAX_uA, + }, + + [BQ25960] = { + .model_id = BQ25960, + .regmap_config = &bq25960_regmap_config, + + .busocp_def = BQ25960_BUSOCP_DFLT_uA, + .busocp_sc_min = BQ25960_BUSOCP_SC_MAX_uA, + .busocp_sc_max = BQ25960_BUSOCP_SC_MAX_uA, + .busocp_byp_min = BQ25960_BUSOCP_SC_MAX_uA, + .busocp_byp_max = BQ25960_BUSOCP_BYP_MAX_uA, + + .busovp_sc_def = BQ25975_BUSOVP_DFLT_uV, + .busovp_byp_def = BQ25975_BUSOVP_BYPASS_DFLT_uV, + .busovp_sc_step = BQ25960_BUSOVP_SC_STEP_uV, + .busovp_sc_offset = BQ25960_BUSOVP_SC_OFFSET_uV, + .busovp_byp_step = BQ25960_BUSOVP_BYP_STEP_uV, + .busovp_byp_offset = BQ25960_BUSOVP_BYP_OFFSET_uV, + .busovp_sc_min = BQ25960_BUSOVP_SC_MIN_uV, + .busovp_sc_max = BQ25960_BUSOVP_SC_MAX_uV, + .busovp_byp_min = BQ25960_BUSOVP_BYP_MIN_uV, + .busovp_byp_max = BQ25960_BUSOVP_BYP_MAX_uV, + + .batovp_def = BQ25960_BATOVP_DFLT_uV, + .batovp_max = BQ25960_BATOVP_MAX_uV, + .batovp_min = BQ25960_BATOVP_MIN_uV, + .batovp_step = BQ25960_BATOVP_STEP_uV, + .batovp_offset = BQ25960_BATOVP_OFFSET_uV, + + .batocp_def = BQ25960_BATOCP_DFLT_uA, + .batocp_max = BQ25960_BATOCP_MAX_uA, + }, +}; + +static int bq25980_power_supply_init(struct bq25980_device *bq, + struct device *dev) +{ + struct power_supply_config psy_cfg = { .drv_data = bq, + .of_node = dev->of_node, }; + + psy_cfg.supplied_to = bq25980_charger_supplied_to; + psy_cfg.num_supplicants = ARRAY_SIZE(bq25980_charger_supplied_to); + + bq->charger = devm_power_supply_register(bq->dev, + &bq25980_power_supply_desc, + &psy_cfg); + if (IS_ERR(bq->charger)) + return -EINVAL; + + bq->battery = devm_power_supply_register(bq->dev, + &bq25980_battery_desc, + &psy_cfg); + if (IS_ERR(bq->battery)) + return -EINVAL; + + return 0; +} + +static int bq25980_hw_init(struct bq25980_device *bq) +{ + struct power_supply_battery_info bat_info = { }; + int wd_reg_val = BQ25980_WATCHDOG_DIS; + int wd_max_val = BQ25980_NUM_WD_VAL - 1; + int ret = 0; + int curr_val; + int volt_val; + int i; + + if (bq->watchdog_timer) { + if (bq->watchdog_timer >= bq25980_watchdog_time[wd_max_val]) + wd_reg_val = wd_max_val; + else { + for (i = 0; i < wd_max_val; i++) { + if (bq->watchdog_timer > bq25980_watchdog_time[i] && + bq->watchdog_timer < bq25980_watchdog_time[i + 1]) { + wd_reg_val = i; + break; + } + } + } + } + + ret = regmap_update_bits(bq->regmap, BQ25980_CHRGR_CTRL_3, + BQ25980_WATCHDOG_MASK, wd_reg_val); + if (ret) + return ret; + + ret = power_supply_get_battery_info(bq->charger, &bat_info); + if (ret) { + dev_warn(bq->dev, "battery info missing\n"); + return -EINVAL; + } + + bq->init_data.ichg_max = bat_info.constant_charge_current_max_ua; + bq->init_data.vreg_max = bat_info.constant_charge_voltage_max_uv; + + if (bq->state.bypass) { + ret = regmap_update_bits(bq->regmap, BQ25980_CHRGR_CTRL_2, + BQ25980_EN_BYPASS, BQ25980_EN_BYPASS); + if (ret) + return ret; + + curr_val = bq->init_data.bypass_ilim; + volt_val = bq->init_data.bypass_vlim; + } else { + curr_val = bq->init_data.sc_ilim; + volt_val = bq->init_data.sc_vlim; + } + + ret = bq25980_set_input_curr_lim(bq, curr_val); + if (ret) + return ret; + + ret = bq25980_set_input_volt_lim(bq, volt_val); + if (ret) + return ret; + + return regmap_update_bits(bq->regmap, BQ25980_ADC_CONTROL1, + BQ25980_ADC_EN, BQ25980_ADC_EN); +} + +static int bq25980_parse_dt(struct bq25980_device *bq) +{ + int ret; + + ret = device_property_read_u32(bq->dev, "ti,watchdog-timeout-ms", + &bq->watchdog_timer); + if (ret) + bq->watchdog_timer = BQ25980_WATCHDOG_MIN; + + if (bq->watchdog_timer > BQ25980_WATCHDOG_MAX || + bq->watchdog_timer < BQ25980_WATCHDOG_MIN) + return -EINVAL; + + ret = device_property_read_u32(bq->dev, + "ti,sc-ovp-limit-microvolt", + &bq->init_data.sc_vlim); + if (ret) + bq->init_data.sc_vlim = bq->chip_info->busovp_sc_def; + + if (bq->init_data.sc_vlim > bq->chip_info->busovp_sc_max || + bq->init_data.sc_vlim < bq->chip_info->busovp_sc_min) { + dev_err(bq->dev, "SC ovp limit is out of range\n"); + return -EINVAL; + } + + ret = device_property_read_u32(bq->dev, + "ti,sc-ocp-limit-microamp", + &bq->init_data.sc_ilim); + if (ret) + bq->init_data.sc_ilim = bq->chip_info->busocp_def; + + if (bq->init_data.sc_ilim > bq->chip_info->busocp_sc_max || + bq->init_data.sc_ilim < bq->chip_info->busocp_sc_min) { + dev_err(bq->dev, "SC ocp limit is out of range\n"); + return -EINVAL; + } + + ret = device_property_read_u32(bq->dev, + "ti,bypass-ovp-limit-microvolt", + &bq->init_data.bypass_vlim); + if (ret) + bq->init_data.bypass_vlim = bq->chip_info->busovp_byp_def; + + if (bq->init_data.bypass_vlim > bq->chip_info->busovp_byp_max || + bq->init_data.bypass_vlim < bq->chip_info->busovp_byp_min) { + dev_err(bq->dev, "Bypass ovp limit is out of range\n"); + return -EINVAL; + } + + ret = device_property_read_u32(bq->dev, + "ti,bypass-ocp-limit-microamp", + &bq->init_data.bypass_ilim); + if (ret) + bq->init_data.bypass_ilim = bq->chip_info->busocp_def; + + if (bq->init_data.bypass_ilim > bq->chip_info->busocp_byp_max || + bq->init_data.bypass_ilim < bq->chip_info->busocp_byp_min) { + dev_err(bq->dev, "Bypass ocp limit is out of range\n"); + return -EINVAL; + } + + + bq->state.bypass = device_property_read_bool(bq->dev, + "ti,bypass-enable"); + return 0; +} + +static int bq25980_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct bq25980_device *bq; + int ret; + + bq = devm_kzalloc(dev, sizeof(*bq), GFP_KERNEL); + if (!bq) + return -ENOMEM; + + bq->client = client; + bq->dev = dev; + + mutex_init(&bq->lock); + + strncpy(bq->model_name, id->name, I2C_NAME_SIZE); + bq->chip_info = &bq25980_chip_info_tbl[id->driver_data]; + + bq->regmap = devm_regmap_init_i2c(client, + bq->chip_info->regmap_config); + if (IS_ERR(bq->regmap)) { + dev_err(dev, "Failed to allocate register map\n"); + return PTR_ERR(bq->regmap); + } + + i2c_set_clientdata(client, bq); + + ret = bq25980_parse_dt(bq); + if (ret) { + dev_err(dev, "Failed to read device tree properties%d\n", ret); + return ret; + } + + if (client->irq) { + ret = devm_request_threaded_irq(dev, client->irq, NULL, + bq25980_irq_handler_thread, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + dev_name(&client->dev), bq); + if (ret) + return ret; + } + + ret = bq25980_power_supply_init(bq, dev); + if (ret) { + dev_err(dev, "Failed to register power supply\n"); + return ret; + } + + ret = bq25980_hw_init(bq); + if (ret) { + dev_err(dev, "Cannot initialize the chip.\n"); + return ret; + } + + return 0; +} + +static const struct i2c_device_id bq25980_i2c_ids[] = { + { "bq25980", BQ25980 }, + { "bq25975", BQ25975 }, + { "bq25975", BQ25975 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, bq25980_i2c_ids); + +static const struct of_device_id bq25980_of_match[] = { + { .compatible = "ti,bq25980", .data = (void *)BQ25980 }, + { .compatible = "ti,bq25975", .data = (void *)BQ25975 }, + { .compatible = "ti,bq25960", .data = (void *)BQ25960 }, + { }, +}; +MODULE_DEVICE_TABLE(of, bq25980_of_match); + +static struct i2c_driver bq25980_driver = { + .driver = { + .name = "bq25980-charger", + .of_match_table = bq25980_of_match, + }, + .probe = bq25980_probe, + .id_table = bq25980_i2c_ids, +}; +module_i2c_driver(bq25980_driver); + +MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>"); +MODULE_AUTHOR("Ricardo Rivera-Matos <r-rivera-matos@ti.com>"); +MODULE_DESCRIPTION("bq25980 charger driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/supply/bq25980_charger.h b/drivers/power/supply/bq25980_charger.h new file mode 100644 index 000000000000..39f94eba5f6c --- /dev/null +++ b/drivers/power/supply/bq25980_charger.h @@ -0,0 +1,178 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com/ */ + +#ifndef BQ25980_CHARGER_H +#define BQ25980_CHARGER_H + +#define BQ25980_MANUFACTURER "Texas Instruments" + +#define BQ25980_BATOVP 0x0 +#define BQ25980_BATOVP_ALM 0x1 +#define BQ25980_BATOCP 0x2 +#define BQ25980_BATOCP_ALM 0x3 +#define BQ25980_BATUCP_ALM 0x4 +#define BQ25980_CHRGR_CTRL_1 0x5 +#define BQ25980_BUSOVP 0x6 +#define BQ25980_BUSOVP_ALM 0x7 +#define BQ25980_BUSOCP 0x8 +#define BQ25980_BUSOCP_ALM 0x9 +#define BQ25980_TEMP_CONTROL 0xA +#define BQ25980_TDIE_ALM 0xB +#define BQ25980_TSBUS_FLT 0xC +#define BQ25980_TSBAT_FLG 0xD +#define BQ25980_VAC_CONTROL 0xE +#define BQ25980_CHRGR_CTRL_2 0xF +#define BQ25980_CHRGR_CTRL_3 0x10 +#define BQ25980_CHRGR_CTRL_4 0x11 +#define BQ25980_CHRGR_CTRL_5 0x12 +#define BQ25980_STAT1 0x13 +#define BQ25980_STAT2 0x14 +#define BQ25980_STAT3 0x15 +#define BQ25980_STAT4 0x16 +#define BQ25980_STAT5 0x17 +#define BQ25980_FLAG1 0x18 +#define BQ25980_FLAG2 0x19 +#define BQ25980_FLAG3 0x1A +#define BQ25980_FLAG4 0x1B +#define BQ25980_FLAG5 0x1C +#define BQ25980_MASK1 0x1D +#define BQ25980_MASK2 0x1E +#define BQ25980_MASK3 0x1F +#define BQ25980_MASK4 0x20 +#define BQ25980_MASK5 0x21 +#define BQ25980_DEVICE_INFO 0x22 +#define BQ25980_ADC_CONTROL1 0x23 +#define BQ25980_ADC_CONTROL2 0x24 +#define BQ25980_IBUS_ADC_MSB 0x25 +#define BQ25980_IBUS_ADC_LSB 0x26 +#define BQ25980_VBUS_ADC_MSB 0x27 +#define BQ25980_VBUS_ADC_LSB 0x28 +#define BQ25980_VAC1_ADC_MSB 0x29 +#define BQ25980_VAC1_ADC_LSB 0x2A +#define BQ25980_VAC2_ADC_MSB 0x2B +#define BQ25980_VAC2_ADC_LSB 0x2C +#define BQ25980_VOUT_ADC_MSB 0x2D +#define BQ25980_VOUT_ADC_LSB 0x2E +#define BQ25980_VBAT_ADC_MSB 0x2F +#define BQ25980_VBAT_ADC_LSB 0x30 +#define BQ25980_IBAT_ADC_MSB 0x31 +#define BQ25980_IBAT_ADC_LSB 0x32 +#define BQ25980_TSBUS_ADC_MSB 0x33 +#define BQ25980_TSBUS_ADC_LSB 0x34 +#define BQ25980_TSBAT_ADC_MSB 0x35 +#define BQ25980_TSBAT_ADC_LSB 0x36 +#define BQ25980_TDIE_ADC_MSB 0x37 +#define BQ25980_TDIE_ADC_LSB 0x38 +#define BQ25980_DEGLITCH_TIME 0x39 +#define BQ25980_CHRGR_CTRL_6 0x3A + +#define BQ25980_BUSOCP_STEP_uA 250000 +#define BQ25980_BUSOCP_OFFSET_uA 1000000 + +#define BQ25980_BUSOCP_DFLT_uA 4250000 +#define BQ25975_BUSOCP_DFLT_uA 4250000 +#define BQ25960_BUSOCP_DFLT_uA 3250000 + +#define BQ25980_BUSOCP_MIN_uA 1000000 + +#define BQ25980_BUSOCP_SC_MAX_uA 5750000 +#define BQ25975_BUSOCP_SC_MAX_uA 5750000 +#define BQ25960_BUSOCP_SC_MAX_uA 3750000 + +#define BQ25980_BUSOCP_BYP_MAX_uA 8500000 +#define BQ25975_BUSOCP_BYP_MAX_uA 8500000 +#define BQ25960_BUSOCP_BYP_MAX_uA 5750000 + +#define BQ25980_BUSOVP_SC_STEP_uV 100000 +#define BQ25975_BUSOVP_SC_STEP_uV 50000 +#define BQ25960_BUSOVP_SC_STEP_uV 50000 +#define BQ25980_BUSOVP_SC_OFFSET_uV 14000000 +#define BQ25975_BUSOVP_SC_OFFSET_uV 7000000 +#define BQ25960_BUSOVP_SC_OFFSET_uV 7000000 + +#define BQ25980_BUSOVP_BYP_STEP_uV 50000 +#define BQ25975_BUSOVP_BYP_STEP_uV 25000 +#define BQ25960_BUSOVP_BYP_STEP_uV 25000 +#define BQ25980_BUSOVP_BYP_OFFSET_uV 7000000 +#define BQ25975_BUSOVP_BYP_OFFSET_uV 3500000 +#define BQ25960_BUSOVP_BYP_OFFSET_uV 3500000 + +#define BQ25980_BUSOVP_DFLT_uV 17800000 +#define BQ25980_BUSOVP_BYPASS_DFLT_uV 8900000 +#define BQ25975_BUSOVP_DFLT_uV 8900000 +#define BQ25975_BUSOVP_BYPASS_DFLT_uV 4450000 +#define BQ25960_BUSOVP_DFLT_uV 8900000 + +#define BQ25980_BUSOVP_SC_MIN_uV 14000000 +#define BQ25975_BUSOVP_SC_MIN_uV 7000000 +#define BQ25960_BUSOVP_SC_MIN_uV 7000000 +#define BQ25980_BUSOVP_BYP_MIN_uV 7000000 +#define BQ25975_BUSOVP_BYP_MIN_uV 3500000 +#define BQ25960_BUSOVP_BYP_MIN_uV 3500000 + +#define BQ25980_BUSOVP_SC_MAX_uV 22000000 +#define BQ25975_BUSOVP_SC_MAX_uV 12750000 +#define BQ25960_BUSOVP_SC_MAX_uV 12750000 + +#define BQ25980_BUSOVP_BYP_MAX_uV 12750000 +#define BQ25975_BUSOVP_BYP_MAX_uV 6500000 +#define BQ25960_BUSOVP_BYP_MAX_uV 6500000 + +#define BQ25980_BATOVP_STEP_uV 20000 +#define BQ25975_BATOVP_STEP_uV 10000 +#define BQ25960_BATOVP_STEP_uV 10000 + +#define BQ25980_BATOVP_OFFSET_uV 7000000 +#define BQ25975_BATOVP_OFFSET_uV 3500000 +#define BQ25960_BATOVP_OFFSET_uV 3500000 + +#define BQ25980_BATOVP_DFLT_uV 14000000 +#define BQ25975_BATOVP_DFLT_uV 8900000 +#define BQ25960_BATOVP_DFLT_uV 8900000 + +#define BQ25980_BATOVP_MIN_uV 7000000 +#define BQ25975_BATOVP_MIN_uV 3500000 +#define BQ25960_BATOVP_MIN_uV 3500000 + +#define BQ25980_BATOVP_MAX_uV 9540000 +#define BQ25975_BATOVP_MAX_uV 4770000 +#define BQ25960_BATOVP_MAX_uV 4770000 + +#define BQ25980_BATOCP_STEP_uA 100000 + +#define BQ25980_BATOCP_MASK GENMASK(6, 0) + +#define BQ25980_BATOCP_DFLT_uA 8100000 +#define BQ25960_BATOCP_DFLT_uA 6100000 + +#define BQ25980_BATOCP_MIN_uA 2000000 + +#define BQ25980_BATOCP_MAX_uA 11000000 +#define BQ25975_BATOCP_MAX_uA 11000000 +#define BQ25960_BATOCP_MAX_uA 7000000 + +#define BQ25980_ENABLE_HIZ 0xff +#define BQ25980_DISABLE_HIZ 0x0 +#define BQ25980_EN_BYPASS BIT(3) +#define BQ25980_STAT1_OVP_MASK (BIT(6) | BIT(5) | BIT(0)) +#define BQ25980_STAT3_OVP_MASK (BIT(7) | BIT(6)) +#define BQ25980_STAT1_OCP_MASK BIT(3) +#define BQ25980_STAT2_OCP_MASK (BIT(6) | BIT(1)) +#define BQ25980_STAT4_TFLT_MASK GENMASK(5, 1) +#define BQ25980_WD_STAT BIT(0) +#define BQ25980_PRESENT_MASK GENMASK(4, 2) +#define BQ25980_CHG_EN BIT(4) +#define BQ25980_EN_HIZ BIT(6) +#define BQ25980_ADC_EN BIT(7) + +#define BQ25980_ADC_VOLT_STEP_uV 1000 +#define BQ25980_ADC_CURR_STEP_uA 1000 +#define BQ25980_ADC_POLARITY_BIT BIT(7) + +#define BQ25980_WATCHDOG_MASK GENMASK(4, 3) +#define BQ25980_WATCHDOG_DIS BIT(2) +#define BQ25980_WATCHDOG_MAX 300000 +#define BQ25980_WATCHDOG_MIN 0 +#define BQ25980_NUM_WD_VAL 4 + +#endif /* BQ25980_CHARGER_H */ diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c index a123f6e21f08..315e0909e6a4 100644 --- a/drivers/power/supply/bq27xxx_battery.c +++ b/drivers/power/supply/bq27xxx_battery.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * BQ27xxx battery driver * @@ -9,14 +10,6 @@ * * Based on a previous work by Copyright (C) 2008 Texas Instruments, Inc. * - * This package is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED - * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. - * * Datasheets: * https://www.ti.com/product/bq27000 * https://www.ti.com/product/bq27200 @@ -45,6 +38,7 @@ * https://www.ti.com/product/bq27621-g1 * https://www.ti.com/product/bq27z561 * https://www.ti.com/product/bq28z610 + * https://www.ti.com/product/bq34z100-g1 */ #include <linux/device.h> @@ -83,7 +77,7 @@ /* BQ27Z561 has different layout for Flags register */ #define BQ27Z561_FLAG_FDC BIT(4) /* Battery fully discharged */ -#define BQ27Z561_FLAG_FC BIT(5) /* Battery fully charged */ +#define BQ27Z561_FLAG_FC BIT(5) /* Battery fully charged */ #define BQ27Z561_FLAG_DIS_CH BIT(6) /* Battery is discharging */ /* control register params */ @@ -483,6 +477,26 @@ static u8 [BQ27XXX_REG_DCAP] = 0x3c, [BQ27XXX_REG_AP] = 0x22, BQ27XXX_DM_REG_ROWS, + }, + bq34z100_regs[BQ27XXX_REG_MAX] = { + [BQ27XXX_REG_CTRL] = 0x00, + [BQ27XXX_REG_TEMP] = 0x0c, + [BQ27XXX_REG_INT_TEMP] = 0x2a, + [BQ27XXX_REG_VOLT] = 0x08, + [BQ27XXX_REG_AI] = 0x0a, + [BQ27XXX_REG_FLAGS] = 0x0e, + [BQ27XXX_REG_TTE] = 0x18, + [BQ27XXX_REG_TTF] = 0x1a, + [BQ27XXX_REG_TTES] = 0x1e, + [BQ27XXX_REG_TTECP] = INVALID_REG_ADDR, + [BQ27XXX_REG_NAC] = INVALID_REG_ADDR, + [BQ27XXX_REG_FCC] = 0x06, + [BQ27XXX_REG_CYCT] = 0x2c, + [BQ27XXX_REG_AE] = 0x24, + [BQ27XXX_REG_SOC] = 0x02, + [BQ27XXX_REG_DCAP] = 0x3c, + [BQ27XXX_REG_AP] = 0x22, + BQ27XXX_DM_REG_ROWS, }; static enum power_supply_property bq27000_props[] = { @@ -757,6 +771,27 @@ static enum power_supply_property bq28z610_props[] = { POWER_SUPPLY_PROP_MANUFACTURER, }; +static enum power_supply_property bq34z100_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, + POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_ENERGY_NOW, + POWER_SUPPLY_PROP_POWER_AVG, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + struct bq27xxx_dm_reg { u8 subclass_id; u8 offset; @@ -854,13 +889,17 @@ static struct bq27xxx_dm_reg bq27621_dm_regs[] = { #define bq27z561_dm_regs 0 #define bq28z610_dm_regs 0 - -#define BQ27XXX_O_ZERO 0x00000001 -#define BQ27XXX_O_OTDC 0x00000002 /* has OTC/OTD overtemperature flags */ -#define BQ27XXX_O_UTOT 0x00000004 /* has OT overtemperature flag */ -#define BQ27XXX_O_CFGUP 0x00000008 -#define BQ27XXX_O_RAM 0x00000010 -#define BQ27Z561_O_BITS 0x00000020 +#define bq34z100_dm_regs 0 + +#define BQ27XXX_O_ZERO BIT(0) +#define BQ27XXX_O_OTDC BIT(1) /* has OTC/OTD overtemperature flags */ +#define BQ27XXX_O_UTOT BIT(2) /* has OT overtemperature flag */ +#define BQ27XXX_O_CFGUP BIT(3) +#define BQ27XXX_O_RAM BIT(4) +#define BQ27Z561_O_BITS BIT(5) +#define BQ27XXX_O_SOC_SI BIT(6) /* SoC is single register */ +#define BQ27XXX_O_HAS_CI BIT(7) /* has Capacity Inaccurate flag */ +#define BQ27XXX_O_MUL_CHEM BIT(8) /* multiple chemistries supported */ #define BQ27XXX_DATA(ref, key, opt) { \ .opts = (opt), \ @@ -878,8 +917,8 @@ static struct { enum power_supply_property *props; size_t props_size; } bq27xxx_chip_data[] = { - [BQ27000] = BQ27XXX_DATA(bq27000, 0 , BQ27XXX_O_ZERO), - [BQ27010] = BQ27XXX_DATA(bq27010, 0 , BQ27XXX_O_ZERO), + [BQ27000] = BQ27XXX_DATA(bq27000, 0 , BQ27XXX_O_ZERO | BQ27XXX_O_SOC_SI | BQ27XXX_O_HAS_CI), + [BQ27010] = BQ27XXX_DATA(bq27010, 0 , BQ27XXX_O_ZERO | BQ27XXX_O_SOC_SI | BQ27XXX_O_HAS_CI), [BQ2750X] = BQ27XXX_DATA(bq2750x, 0 , BQ27XXX_O_OTDC), [BQ2751X] = BQ27XXX_DATA(bq2751x, 0 , BQ27XXX_O_OTDC), [BQ2752X] = BQ27XXX_DATA(bq2752x, 0 , BQ27XXX_O_OTDC), @@ -907,6 +946,8 @@ static struct { [BQ27621] = BQ27XXX_DATA(bq27621, 0x80008000, BQ27XXX_O_UTOT | BQ27XXX_O_CFGUP | BQ27XXX_O_RAM), [BQ27Z561] = BQ27XXX_DATA(bq27z561, 0 , BQ27Z561_O_BITS), [BQ28Z610] = BQ27XXX_DATA(bq28z610, 0 , BQ27Z561_O_BITS), + [BQ34Z100] = BQ27XXX_DATA(bq34z100, 0 , BQ27XXX_O_OTDC | BQ27XXX_O_SOC_SI | \ + BQ27XXX_O_HAS_CI | BQ27XXX_O_MUL_CHEM), }; static DEFINE_MUTEX(bq27xxx_list_lock); @@ -1426,7 +1467,7 @@ static int bq27xxx_battery_read_soc(struct bq27xxx_device_info *di) { int soc; - if (di->opts & BQ27XXX_O_ZERO) + if (di->opts & BQ27XXX_O_SOC_SI) soc = bq27xxx_read(di, BQ27XXX_REG_SOC, true); else soc = bq27xxx_read(di, BQ27XXX_REG_SOC, false); @@ -1664,7 +1705,7 @@ static int bq27xxx_battery_read_health(struct bq27xxx_device_info *di) void bq27xxx_battery_update(struct bq27xxx_device_info *di) { struct bq27xxx_reg_cache cache = {0, }; - bool has_ci_flag = di->opts & BQ27XXX_O_ZERO; + bool has_ci_flag = di->opts & BQ27XXX_O_HAS_CI; bool has_singe_flag = di->opts & BQ27XXX_O_ZERO; cache.flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, has_singe_flag); @@ -1772,8 +1813,6 @@ static int bq27xxx_battery_status(struct bq27xxx_device_info *di, status = POWER_SUPPLY_STATUS_FULL; else if (di->cache.flags & BQ27000_FLAG_CHGS) status = POWER_SUPPLY_STATUS_CHARGING; - else if (power_supply_am_i_supplied(di->bat) > 0) - status = POWER_SUPPLY_STATUS_NOT_CHARGING; else status = POWER_SUPPLY_STATUS_DISCHARGING; } else if (di->opts & BQ27Z561_O_BITS) { @@ -1792,6 +1831,10 @@ static int bq27xxx_battery_status(struct bq27xxx_device_info *di, status = POWER_SUPPLY_STATUS_CHARGING; } + if ((status == POWER_SUPPLY_STATUS_DISCHARGING) && + (power_supply_am_i_supplied(di->bat) > 0)) + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + val->intval = status; return 0; @@ -1916,7 +1959,10 @@ static int bq27xxx_battery_get_property(struct power_supply *psy, ret = bq27xxx_simple_value(di->cache.time_to_full, val); break; case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + if (di->opts & BQ27XXX_O_MUL_CHEM) + val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; + else + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; break; case POWER_SUPPLY_PROP_CHARGE_NOW: ret = bq27xxx_simple_value(bq27xxx_battery_read_nac(di), val); @@ -1992,13 +2038,9 @@ int bq27xxx_battery_setup(struct bq27xxx_device_info *di) psy_desc->external_power_changed = bq27xxx_external_power_changed; di->bat = power_supply_register_no_ws(di->dev, psy_desc, &psy_cfg); - if (IS_ERR(di->bat)) { - if (PTR_ERR(di->bat) == -EPROBE_DEFER) - dev_dbg(di->dev, "failed to register battery, deferring probe\n"); - else - dev_err(di->dev, "failed to register battery\n"); - return PTR_ERR(di->bat); - } + if (IS_ERR(di->bat)) + return dev_err_probe(di->dev, PTR_ERR(di->bat), + "failed to register battery\n"); bq27xxx_battery_settings(di); bq27xxx_battery_update(di); diff --git a/drivers/power/supply/bq27xxx_battery_hdq.c b/drivers/power/supply/bq27xxx_battery_hdq.c index d56b3e19e996..922759ab2e04 100644 --- a/drivers/power/supply/bq27xxx_battery_hdq.c +++ b/drivers/power/supply/bq27xxx_battery_hdq.c @@ -1,16 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 /* * BQ27xxx battery monitor HDQ/1-wire driver * * Copyright (C) 2007-2017 Texas Instruments Incorporated - https://www.ti.com/ * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed "as is" WITHOUT ANY WARRANTY of any - * kind, whether express or implied; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #include <linux/kernel.h> diff --git a/drivers/power/supply/bq27xxx_battery_i2c.c b/drivers/power/supply/bq27xxx_battery_i2c.c index ab02456d69e5..eb4f4284982f 100644 --- a/drivers/power/supply/bq27xxx_battery_i2c.c +++ b/drivers/power/supply/bq27xxx_battery_i2c.c @@ -1,17 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 /* * BQ27xxx battery monitor I2C driver * * Copyright (C) 2015 Texas Instruments Incorporated - https://www.ti.com/ * Andrew F. Davis <afd@ti.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed "as is" WITHOUT ANY WARRANTY of any - * kind, whether express or implied; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #include <linux/i2c.h> @@ -255,6 +247,7 @@ static const struct i2c_device_id bq27xxx_i2c_id_table[] = { { "bq27621", BQ27621 }, { "bq27z561", BQ27Z561 }, { "bq28z610", BQ28Z610 }, + { "bq34z100", BQ34Z100 }, {}, }; MODULE_DEVICE_TABLE(i2c, bq27xxx_i2c_id_table); @@ -290,6 +283,7 @@ static const struct of_device_id bq27xxx_battery_i2c_of_match_table[] = { { .compatible = "ti,bq27621" }, { .compatible = "ti,bq27z561" }, { .compatible = "ti,bq28z610" }, + { .compatible = "ti,bq34z100" }, {}, }; MODULE_DEVICE_TABLE(of, bq27xxx_battery_i2c_of_match_table); diff --git a/drivers/power/supply/charger-manager.c b/drivers/power/supply/charger-manager.c index 2ef53dc1f2fb..6fcebe441552 100644 --- a/drivers/power/supply/charger-manager.c +++ b/drivers/power/supply/charger-manager.c @@ -26,6 +26,29 @@ #include <linux/of.h> #include <linux/thermal.h> +static struct { + const char *name; + u64 extcon_type; +} extcon_mapping[] = { + /* Current textual representations */ + { "USB", EXTCON_USB }, + { "USB-HOST", EXTCON_USB_HOST }, + { "SDP", EXTCON_CHG_USB_SDP }, + { "DCP", EXTCON_CHG_USB_DCP }, + { "CDP", EXTCON_CHG_USB_CDP }, + { "ACA", EXTCON_CHG_USB_ACA }, + { "FAST-CHARGER", EXTCON_CHG_USB_FAST }, + { "SLOW-CHARGER", EXTCON_CHG_USB_SLOW }, + { "WPT", EXTCON_CHG_WPT }, + { "PD", EXTCON_CHG_USB_PD }, + { "DOCK", EXTCON_DOCK }, + { "JIG", EXTCON_JIG }, + { "MECHANICAL", EXTCON_MECHANICAL }, + /* Deprecated textual representations */ + { "TA", EXTCON_CHG_USB_SDP }, + { "CHARGE-DOWNSTREAM", EXTCON_CHG_USB_CDP }, +}; + /* * Default temperature threshold for charging. * Every temperature units are in tenth of centigrade. @@ -33,18 +56,6 @@ #define CM_DEFAULT_RECHARGE_TEMP_DIFF 50 #define CM_DEFAULT_CHARGE_TEMP_MAX 500 -static const char * const default_event_names[] = { - [CM_EVENT_UNKNOWN] = "Unknown", - [CM_EVENT_BATT_FULL] = "Battery Full", - [CM_EVENT_BATT_IN] = "Battery Inserted", - [CM_EVENT_BATT_OUT] = "Battery Pulled Out", - [CM_EVENT_BATT_OVERHEAT] = "Battery Overheat", - [CM_EVENT_BATT_COLD] = "Battery Cold", - [CM_EVENT_EXT_PWR_IN_OUT] = "External Power Attach/Detach", - [CM_EVENT_CHG_START_STOP] = "Charging Start/Stop", - [CM_EVENT_OTHERS] = "Other battery events" -}; - /* * Regard CM_JIFFIES_SMALL jiffies is small enough to ignore for * delayed works so that we can run delayed works with CM_JIFFIES_SMALL @@ -61,8 +72,6 @@ static const char * const default_event_names[] = { */ #define CM_RTC_SMALL (2) -#define UEVENT_BUF_SIZE 32 - static LIST_HEAD(cm_list); static DEFINE_MUTEX(cm_list_mtx); @@ -285,6 +294,19 @@ static bool is_full_charged(struct charger_manager *cm) if (!fuel_gauge) return false; + /* Full, if it's over the fullbatt voltage */ + if (desc->fullbatt_uV > 0) { + ret = get_batt_uV(cm, &uV); + if (!ret) { + /* Battery is already full, checks voltage drop. */ + if (cm->battery_status == POWER_SUPPLY_STATUS_FULL + && desc->fullbatt_vchkdrop_uV) + uV += desc->fullbatt_vchkdrop_uV; + if (uV >= desc->fullbatt_uV) + return true; + } + } + if (desc->fullbatt_full_capacity > 0) { val.intval = 0; @@ -297,15 +319,6 @@ static bool is_full_charged(struct charger_manager *cm) } } - /* Full, if it's over the fullbatt voltage */ - if (desc->fullbatt_uV > 0) { - ret = get_batt_uV(cm, &uV); - if (!ret && uV >= desc->fullbatt_uV) { - is_full = true; - goto out; - } - } - /* Full, if the capacity is more than fullbatt_soc */ if (desc->fullbatt_soc > 0) { val.intval = 0; @@ -427,122 +440,6 @@ static int try_charger_enable(struct charger_manager *cm, bool enable) } /** - * try_charger_restart - Restart charging. - * @cm: the Charger Manager representing the battery. - * - * Restart charging by turning off and on the charger. - */ -static int try_charger_restart(struct charger_manager *cm) -{ - int err; - - if (cm->emergency_stop) - return -EAGAIN; - - err = try_charger_enable(cm, false); - if (err) - return err; - - return try_charger_enable(cm, true); -} - -/** - * uevent_notify - Let users know something has changed. - * @cm: the Charger Manager representing the battery. - * @event: the event string. - * - * If @event is null, it implies that uevent_notify is called - * by resume function. When called in the resume function, cm_suspended - * should be already reset to false in order to let uevent_notify - * notify the recent event during the suspend to users. While - * suspended, uevent_notify does not notify users, but tracks - * events so that uevent_notify can notify users later after resumed. - */ -static void uevent_notify(struct charger_manager *cm, const char *event) -{ - static char env_str[UEVENT_BUF_SIZE + 1] = ""; - static char env_str_save[UEVENT_BUF_SIZE + 1] = ""; - - if (cm_suspended) { - /* Nothing in suspended-event buffer */ - if (env_str_save[0] == 0) { - if (!strncmp(env_str, event, UEVENT_BUF_SIZE)) - return; /* status not changed */ - strncpy(env_str_save, event, UEVENT_BUF_SIZE); - return; - } - - if (!strncmp(env_str_save, event, UEVENT_BUF_SIZE)) - return; /* Duplicated. */ - strncpy(env_str_save, event, UEVENT_BUF_SIZE); - return; - } - - if (event == NULL) { - /* No messages pending */ - if (!env_str_save[0]) - return; - - strncpy(env_str, env_str_save, UEVENT_BUF_SIZE); - kobject_uevent(&cm->dev->kobj, KOBJ_CHANGE); - env_str_save[0] = 0; - - return; - } - - /* status not changed */ - if (!strncmp(env_str, event, UEVENT_BUF_SIZE)) - return; - - /* save the status and notify the update */ - strncpy(env_str, event, UEVENT_BUF_SIZE); - kobject_uevent(&cm->dev->kobj, KOBJ_CHANGE); - - dev_info(cm->dev, "%s\n", event); -} - -/** - * fullbatt_vchk - Check voltage drop some times after "FULL" event. - * @work: the work_struct appointing the function - * - * If a user has designated "fullbatt_vchkdrop_ms/uV" values with - * charger_desc, Charger Manager checks voltage drop after the battery - * "FULL" event. It checks whether the voltage has dropped more than - * fullbatt_vchkdrop_uV by calling this function after fullbatt_vchkrop_ms. - */ -static void fullbatt_vchk(struct work_struct *work) -{ - struct delayed_work *dwork = to_delayed_work(work); - struct charger_manager *cm = container_of(dwork, - struct charger_manager, fullbatt_vchk_work); - struct charger_desc *desc = cm->desc; - int batt_uV, err, diff; - - /* remove the appointment for fullbatt_vchk */ - cm->fullbatt_vchk_jiffies_at = 0; - - if (!desc->fullbatt_vchkdrop_uV || !desc->fullbatt_vchkdrop_ms) - return; - - err = get_batt_uV(cm, &batt_uV); - if (err) { - dev_err(cm->dev, "%s: get_batt_uV error(%d)\n", __func__, err); - return; - } - - diff = desc->fullbatt_uV - batt_uV; - if (diff < 0) - return; - - dev_info(cm->dev, "VBATT dropped %duV after full-batt\n", diff); - - if (diff > desc->fullbatt_vchkdrop_uV) { - try_charger_restart(cm); - uevent_notify(cm, "Recharging"); - } -} - -/** * check_charging_duration - Monitor charging/discharging duration * @cm: the Charger Manager representing the battery. * @@ -569,19 +466,14 @@ static int check_charging_duration(struct charger_manager *cm) if (duration > desc->charging_max_duration_ms) { dev_info(cm->dev, "Charging duration exceed %ums\n", desc->charging_max_duration_ms); - uevent_notify(cm, "Discharging"); - try_charger_enable(cm, false); ret = true; } - } else if (is_ext_pwr_online(cm) && !cm->charger_enabled) { + } else if (cm->battery_status == POWER_SUPPLY_STATUS_NOT_CHARGING) { duration = curr - cm->charging_end_time; - if (duration > desc->discharging_max_duration_ms && - is_ext_pwr_online(cm)) { + if (duration > desc->discharging_max_duration_ms) { dev_info(cm->dev, "Discharging duration exceed %ums\n", desc->discharging_max_duration_ms); - uevent_notify(cm, "Recharging"); - try_charger_enable(cm, true); ret = true; } } @@ -657,14 +549,53 @@ static int cm_check_thermal_status(struct charger_manager *cm) } if (temp > upper_limit) - ret = CM_EVENT_BATT_OVERHEAT; + ret = CM_BATT_OVERHEAT; else if (temp < lower_limit) - ret = CM_EVENT_BATT_COLD; + ret = CM_BATT_COLD; + else + ret = CM_BATT_OK; + + cm->emergency_stop = ret; return ret; } /** + * cm_get_target_status - Check current status and get next target status. + * @cm: the Charger Manager representing the battery. + */ +static int cm_get_target_status(struct charger_manager *cm) +{ + if (!is_ext_pwr_online(cm)) + return POWER_SUPPLY_STATUS_DISCHARGING; + + if (cm_check_thermal_status(cm)) { + /* Check if discharging duration exeeds limit. */ + if (check_charging_duration(cm)) + goto charging_ok; + return POWER_SUPPLY_STATUS_NOT_CHARGING; + } + + switch (cm->battery_status) { + case POWER_SUPPLY_STATUS_CHARGING: + /* Check if charging duration exeeds limit. */ + if (check_charging_duration(cm)) + return POWER_SUPPLY_STATUS_FULL; + fallthrough; + case POWER_SUPPLY_STATUS_FULL: + if (is_full_charged(cm)) + return POWER_SUPPLY_STATUS_FULL; + fallthrough; + default: + break; + } + +charging_ok: + /* Charging is allowed. */ + return POWER_SUPPLY_STATUS_CHARGING; +} + +/** * _cm_monitor - Monitor the temperature and return true for exceptions. * @cm: the Charger Manager representing the battery. * @@ -673,60 +604,18 @@ static int cm_check_thermal_status(struct charger_manager *cm) */ static bool _cm_monitor(struct charger_manager *cm) { - int temp_alrt; - - temp_alrt = cm_check_thermal_status(cm); - - /* It has been stopped already */ - if (temp_alrt && cm->emergency_stop) - return false; - - /* - * Check temperature whether overheat or cold. - * If temperature is out of range normal state, stop charging. - */ - if (temp_alrt) { - cm->emergency_stop = temp_alrt; - if (!try_charger_enable(cm, false)) - uevent_notify(cm, default_event_names[temp_alrt]); - - /* - * Check whole charging duration and discharging duration - * after full-batt. - */ - } else if (!cm->emergency_stop && check_charging_duration(cm)) { - dev_dbg(cm->dev, - "Charging/Discharging duration is out of range\n"); - /* - * Check dropped voltage of battery. If battery voltage is more - * dropped than fullbatt_vchkdrop_uV after fully charged state, - * charger-manager have to recharge battery. - */ - } else if (!cm->emergency_stop && is_ext_pwr_online(cm) && - !cm->charger_enabled) { - fullbatt_vchk(&cm->fullbatt_vchk_work.work); + int target; - /* - * Check whether fully charged state to protect overcharge - * if charger-manager is charging for battery. - */ - } else if (!cm->emergency_stop && is_full_charged(cm) && - cm->charger_enabled) { - dev_info(cm->dev, "EVENT_HANDLE: Battery Fully Charged\n"); - uevent_notify(cm, default_event_names[CM_EVENT_BATT_FULL]); + target = cm_get_target_status(cm); - try_charger_enable(cm, false); + try_charger_enable(cm, (target == POWER_SUPPLY_STATUS_CHARGING)); - fullbatt_vchk(&cm->fullbatt_vchk_work.work); - } else { - cm->emergency_stop = 0; - if (is_ext_pwr_online(cm)) { - if (!try_charger_enable(cm, true)) - uevent_notify(cm, "CHARGING"); - } + if (cm->battery_status != target) { + cm->battery_status = target; + power_supply_changed(cm->charger_psy); } - return true; + return (cm->battery_status == POWER_SUPPLY_STATUS_NOT_CHARGING); } /** @@ -819,66 +708,6 @@ static void cm_monitor_poller(struct work_struct *work) schedule_work(&setup_polling); } -/** - * fullbatt_handler - Event handler for CM_EVENT_BATT_FULL - * @cm: the Charger Manager representing the battery. - */ -static void fullbatt_handler(struct charger_manager *cm) -{ - struct charger_desc *desc = cm->desc; - - if (!desc->fullbatt_vchkdrop_uV || !desc->fullbatt_vchkdrop_ms) - goto out; - - if (cm_suspended) - device_set_wakeup_capable(cm->dev, true); - - mod_delayed_work(cm_wq, &cm->fullbatt_vchk_work, - msecs_to_jiffies(desc->fullbatt_vchkdrop_ms)); - cm->fullbatt_vchk_jiffies_at = jiffies + msecs_to_jiffies( - desc->fullbatt_vchkdrop_ms); - - if (cm->fullbatt_vchk_jiffies_at == 0) - cm->fullbatt_vchk_jiffies_at = 1; - -out: - dev_info(cm->dev, "EVENT_HANDLE: Battery Fully Charged\n"); - uevent_notify(cm, default_event_names[CM_EVENT_BATT_FULL]); -} - -/** - * battout_handler - Event handler for CM_EVENT_BATT_OUT - * @cm: the Charger Manager representing the battery. - */ -static void battout_handler(struct charger_manager *cm) -{ - if (cm_suspended) - device_set_wakeup_capable(cm->dev, true); - - if (!is_batt_present(cm)) { - dev_emerg(cm->dev, "Battery Pulled Out!\n"); - uevent_notify(cm, default_event_names[CM_EVENT_BATT_OUT]); - } else { - uevent_notify(cm, "Battery Reinserted?"); - } -} - -/** - * misc_event_handler - Handler for other events - * @cm: the Charger Manager representing the battery. - * @type: the Charger Manager representing the battery. - */ -static void misc_event_handler(struct charger_manager *cm, - enum cm_event_types type) -{ - if (cm_suspended) - device_set_wakeup_capable(cm->dev, true); - - if (is_polling_required(cm) && cm->desc->polling_interval_ms) - schedule_work(&setup_polling); - uevent_notify(cm, default_event_names[type]); -} - static int charger_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) @@ -891,12 +720,7 @@ static int charger_get_property(struct power_supply *psy, switch (psp) { case POWER_SUPPLY_PROP_STATUS: - if (is_charging(cm)) - val->intval = POWER_SUPPLY_STATUS_CHARGING; - else if (is_ext_pwr_online(cm)) - val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; - else - val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + val->intval = cm->battery_status; break; case POWER_SUPPLY_PROP_HEALTH: if (cm->emergency_stop > 0) @@ -925,7 +749,6 @@ static int charger_get_property(struct power_supply *psy, POWER_SUPPLY_PROP_CURRENT_NOW, val); break; case POWER_SUPPLY_PROP_TEMP: - case POWER_SUPPLY_PROP_TEMP_AMBIENT: return cm_get_battery_temperature(cm, &val->intval); case POWER_SUPPLY_PROP_CAPACITY: if (!is_batt_present(cm)) { @@ -981,35 +804,13 @@ static int charger_get_property(struct power_supply *psy, val->intval = 0; break; case POWER_SUPPLY_PROP_CHARGE_FULL: - if (is_full_charged(cm)) - val->intval = 1; - else - val->intval = 0; - ret = 0; - break; case POWER_SUPPLY_PROP_CHARGE_NOW: - if (is_charging(cm)) { - fuel_gauge = power_supply_get_by_name( - cm->desc->psy_fuel_gauge); - if (!fuel_gauge) { - ret = -ENODEV; - break; - } - - ret = power_supply_get_property(fuel_gauge, - POWER_SUPPLY_PROP_CHARGE_NOW, - val); - if (ret) { - val->intval = 1; - ret = 0; - } else { - /* If CHARGE_NOW is supplied, use it */ - val->intval = (val->intval > 0) ? - val->intval : 1; - } - } else { - val->intval = 0; + fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge); + if (!fuel_gauge) { + ret = -ENODEV; + break; } + ret = power_supply_get_property(fuel_gauge, psp, val); break; default: return -EINVAL; @@ -1028,13 +829,12 @@ static enum power_supply_property default_charger_props[] = { POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_CAPACITY, POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_CHARGE_FULL, /* * Optional properties are: + * POWER_SUPPLY_PROP_CHARGE_FULL, * POWER_SUPPLY_PROP_CHARGE_NOW, * POWER_SUPPLY_PROP_CURRENT_NOW, - * POWER_SUPPLY_PROP_TEMP, and - * POWER_SUPPLY_PROP_TEMP_AMBIENT, + * POWER_SUPPLY_PROP_TEMP, */ }; @@ -1069,21 +869,6 @@ static bool cm_setup_timer(void) mutex_lock(&cm_list_mtx); list_for_each_entry(cm, &cm_list, entry) { - unsigned int fbchk_ms = 0; - - /* fullbatt_vchk is required. setup timer for that */ - if (cm->fullbatt_vchk_jiffies_at) { - fbchk_ms = jiffies_to_msecs(cm->fullbatt_vchk_jiffies_at - - jiffies); - if (time_is_before_eq_jiffies( - cm->fullbatt_vchk_jiffies_at) || - msecs_to_jiffies(fbchk_ms) < CM_JIFFIES_SMALL) { - fullbatt_vchk(&cm->fullbatt_vchk_work.work); - fbchk_ms = 0; - } - } - CM_MIN_VALID(wakeup_ms, fbchk_ms); - /* Skip if polling is not required for this CM */ if (!is_polling_required(cm) && !cm->emergency_stop) continue; @@ -1145,7 +930,8 @@ static void charger_extcon_work(struct work_struct *work) cable->min_uA, cable->max_uA); } - try_charger_enable(cable->cm, cable->attached); + cancel_delayed_work(&cm_monitor_work); + queue_delayed_work(cm_wq, &cm_monitor_work, 0); } /** @@ -1169,15 +955,6 @@ static int charger_extcon_notifier(struct notifier_block *self, cable->attached = event; /* - * Setup monitoring to check battery state - * when charger cable is attached. - */ - if (cable->attached && is_polling_required(cable->cm)) { - cancel_work_sync(&setup_polling); - schedule_work(&setup_polling); - } - - /* * Setup work for controlling charger(regulator) * according to charger cable. */ @@ -1196,7 +973,8 @@ static int charger_extcon_notifier(struct notifier_block *self, static int charger_extcon_init(struct charger_manager *cm, struct charger_cable *cable) { - int ret; + int ret, i; + u64 extcon_type = EXTCON_NONE; /* * Charger manager use Extcon framework to identify @@ -1205,14 +983,39 @@ static int charger_extcon_init(struct charger_manager *cm, */ INIT_WORK(&cable->wq, charger_extcon_work); cable->nb.notifier_call = charger_extcon_notifier; - ret = extcon_register_interest(&cable->extcon_dev, - cable->extcon_name, cable->name, &cable->nb); + + cable->extcon_dev = extcon_get_extcon_dev(cable->extcon_name); + if (IS_ERR_OR_NULL(cable->extcon_dev)) { + pr_err("Cannot find extcon_dev for %s (cable: %s)\n", + cable->extcon_name, cable->name); + if (cable->extcon_dev == NULL) + return -EPROBE_DEFER; + else + return PTR_ERR(cable->extcon_dev); + } + + for (i = 0; i < ARRAY_SIZE(extcon_mapping); i++) { + if (!strcmp(cable->name, extcon_mapping[i].name)) { + extcon_type = extcon_mapping[i].extcon_type; + break; + } + } + if (extcon_type == EXTCON_NONE) { + pr_err("Cannot find cable for type %s", cable->name); + return -EINVAL; + } + + cable->extcon_type = extcon_type; + + ret = devm_extcon_register_notifier(cm->dev, cable->extcon_dev, + cable->extcon_type, &cable->nb); if (ret < 0) { - pr_info("Cannot register extcon_dev for %s(cable: %s)\n", + pr_err("Cannot register extcon_dev for %s (cable: %s)\n", cable->extcon_name, cable->name); + return ret; } - return ret; + return 0; } /** @@ -1229,6 +1032,7 @@ static int charger_manager_register_extcon(struct charger_manager *cm) { struct charger_desc *desc = cm->desc; struct charger_regulator *charger; + unsigned long event; int ret; int i; int j; @@ -1256,6 +1060,11 @@ static int charger_manager_register_extcon(struct charger_manager *cm) } cable->charger = charger; cable->cm = cm; + + event = extcon_get_state(cable->extcon_dev, + cable->extcon_type); + charger_extcon_notifier(&cable->nb, + event, NULL); } } @@ -1447,7 +1256,7 @@ static int cm_init_thermal_data(struct charger_manager *cm, return PTR_ERR(cm->tzd_batt); /* Use external thermometer */ - properties[*num_properties] = POWER_SUPPLY_PROP_TEMP_AMBIENT; + properties[*num_properties] = POWER_SUPPLY_PROP_TEMP; (*num_properties)++; cm->desc->measure_battery_temp = true; ret = 0; @@ -1491,8 +1300,6 @@ static struct charger_desc *of_cm_parse_desc(struct device *dev) of_property_read_u32(np, "cm-poll-interval", &desc->polling_interval_ms); - of_property_read_u32(np, "cm-fullbatt-vchkdrop-ms", - &desc->fullbatt_vchkdrop_ms); of_property_read_u32(np, "cm-fullbatt-vchkdrop-volt", &desc->fullbatt_vchkdrop_uV); of_property_read_u32(np, "cm-fullbatt-voltage", &desc->fullbatt_uV); @@ -1504,8 +1311,8 @@ static struct charger_desc *of_cm_parse_desc(struct device *dev) desc->battery_present = battery_stat; /* chargers */ - of_property_read_u32(np, "cm-num-chargers", &num_chgs); - if (num_chgs) { + num_chgs = of_property_count_strings(np, "cm-chargers"); + if (num_chgs > 0) { int i; /* Allocate empty bin at the tail of array */ @@ -1618,7 +1425,6 @@ static int charger_manager_probe(struct platform_device *pdev) struct charger_desc *desc = cm_get_drv_data(pdev); struct charger_manager *cm; int ret, i = 0; - int j = 0; union power_supply_propval val; struct power_supply *fuel_gauge; enum power_supply_property *properties; @@ -1654,9 +1460,8 @@ static int charger_manager_probe(struct platform_device *pdev) if (desc->fullbatt_uV == 0) { dev_info(&pdev->dev, "Ignoring full-battery voltage threshold as it is not supplied\n"); } - if (!desc->fullbatt_vchkdrop_ms || !desc->fullbatt_vchkdrop_uV) { + if (!desc->fullbatt_vchkdrop_uV) { dev_info(&pdev->dev, "Disabling full-battery voltage drop checking mechanism as it is not supplied\n"); - desc->fullbatt_vchkdrop_ms = 0; desc->fullbatt_vchkdrop_uV = 0; } if (desc->fullbatt_soc == 0) { @@ -1739,6 +1544,12 @@ static int charger_manager_probe(struct platform_device *pdev) return -ENODEV; } if (!power_supply_get_property(fuel_gauge, + POWER_SUPPLY_PROP_CHARGE_FULL, &val)) { + properties[num_properties] = + POWER_SUPPLY_PROP_CHARGE_FULL; + num_properties++; + } + if (!power_supply_get_property(fuel_gauge, POWER_SUPPLY_PROP_CHARGE_NOW, &val)) { properties[num_properties] = POWER_SUPPLY_PROP_CHARGE_NOW; @@ -1762,8 +1573,6 @@ static int charger_manager_probe(struct platform_device *pdev) cm->charger_psy_desc.properties = properties; cm->charger_psy_desc.num_properties = num_properties; - INIT_DELAYED_WORK(&cm->fullbatt_vchk_work, fullbatt_vchk); - /* Register sysfs entry for charger(regulator) */ ret = charger_manager_prepare_sysfs(cm); if (ret < 0) { @@ -1813,19 +1622,8 @@ static int charger_manager_probe(struct platform_device *pdev) return 0; err_reg_extcon: - for (i = 0; i < desc->num_charger_regulators; i++) { - struct charger_regulator *charger; - - charger = &desc->charger_regulators[i]; - for (j = 0; j < charger->num_cables; j++) { - struct charger_cable *cable = &charger->cables[j]; - /* Remove notifier block if only edev exists */ - if (cable->extcon_dev.edev) - extcon_unregister_interest(&cable->extcon_dev); - } - + for (i = 0; i < desc->num_charger_regulators; i++) regulator_put(desc->charger_regulators[i].consumer); - } power_supply_unregister(cm->charger_psy); @@ -1837,7 +1635,6 @@ static int charger_manager_remove(struct platform_device *pdev) struct charger_manager *cm = platform_get_drvdata(pdev); struct charger_desc *desc = cm->desc; int i = 0; - int j = 0; /* Remove from the list */ mutex_lock(&cm_list_mtx); @@ -1847,15 +1644,6 @@ static int charger_manager_remove(struct platform_device *pdev) cancel_work_sync(&setup_polling); cancel_delayed_work_sync(&cm_monitor_work); - for (i = 0 ; i < desc->num_charger_regulators ; i++) { - struct charger_regulator *charger - = &desc->charger_regulators[i]; - for (j = 0 ; j < charger->num_cables ; j++) { - struct charger_cable *cable = &charger->cables[j]; - extcon_unregister_interest(&cable->extcon_dev); - } - } - for (i = 0 ; i < desc->num_charger_regulators ; i++) regulator_put(desc->charger_regulators[i].consumer); @@ -1903,8 +1691,6 @@ static bool cm_need_to_awake(void) static int cm_suspend_prepare(struct device *dev) { - struct charger_manager *cm = dev_get_drvdata(dev); - if (cm_need_to_awake()) return -EBUSY; @@ -1916,7 +1702,6 @@ static int cm_suspend_prepare(struct device *dev) if (cm_timer_set) { cancel_work_sync(&setup_polling); cancel_delayed_work_sync(&cm_monitor_work); - cancel_delayed_work(&cm->fullbatt_vchk_work); } return 0; @@ -1941,31 +1726,6 @@ static void cm_suspend_complete(struct device *dev) _cm_monitor(cm); - /* Re-enqueue delayed work (fullbatt_vchk_work) */ - if (cm->fullbatt_vchk_jiffies_at) { - unsigned long delay = 0; - unsigned long now = jiffies + CM_JIFFIES_SMALL; - - if (time_after_eq(now, cm->fullbatt_vchk_jiffies_at)) { - delay = (unsigned long)((long)now - - (long)(cm->fullbatt_vchk_jiffies_at)); - delay = jiffies_to_msecs(delay); - } else { - delay = 0; - } - - /* - * Account for cm_suspend_duration_ms with assuming that - * timer stops in suspend. - */ - if (delay > cm_suspend_duration_ms) - delay -= cm_suspend_duration_ms; - else - delay = 0; - - queue_delayed_work(cm_wq, &cm->fullbatt_vchk_work, - msecs_to_jiffies(delay)); - } device_set_wakeup_capable(cm->dev, false); } @@ -2007,56 +1767,6 @@ static void __exit charger_manager_cleanup(void) } module_exit(charger_manager_cleanup); -/** - * cm_notify_event - charger driver notify Charger Manager of charger event - * @psy: pointer to instance of charger's power_supply - * @type: type of charger event - * @msg: optional message passed to uevent_notify function - */ -void cm_notify_event(struct power_supply *psy, enum cm_event_types type, - char *msg) -{ - struct charger_manager *cm; - bool found_power_supply = false; - - if (psy == NULL) - return; - - mutex_lock(&cm_list_mtx); - list_for_each_entry(cm, &cm_list, entry) { - if (match_string(cm->desc->psy_charger_stat, -1, - psy->desc->name) >= 0) { - found_power_supply = true; - break; - } - } - mutex_unlock(&cm_list_mtx); - - if (!found_power_supply) - return; - - switch (type) { - case CM_EVENT_BATT_FULL: - fullbatt_handler(cm); - break; - case CM_EVENT_BATT_OUT: - battout_handler(cm); - break; - case CM_EVENT_BATT_IN: - case CM_EVENT_EXT_PWR_IN_OUT ... CM_EVENT_CHG_START_STOP: - misc_event_handler(cm, type); - break; - case CM_EVENT_UNKNOWN: - case CM_EVENT_OTHERS: - uevent_notify(cm, msg ? msg : default_event_names[type]); - break; - default: - dev_err(cm->dev, "%s: type not specified\n", __func__); - break; - } -} -EXPORT_SYMBOL_GPL(cm_notify_event); - MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); MODULE_DESCRIPTION("Charger Manager"); MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/cpcap-battery.c b/drivers/power/supply/cpcap-battery.c index 90eba364664b..295611b3b15e 100644 --- a/drivers/power/supply/cpcap-battery.c +++ b/drivers/power/supply/cpcap-battery.c @@ -747,11 +747,8 @@ static int cpcap_battery_init_iio(struct cpcap_battery_ddata *ddata) return 0; out_err: - if (error != -EPROBE_DEFER) - dev_err(ddata->dev, "could not initialize VBUS or ID IIO: %i\n", - error); - - return error; + return dev_err_probe(ddata->dev, error, + "could not initialize VBUS or ID IIO\n"); } /* Calibrate coulomb counter */ diff --git a/drivers/power/supply/ds2780_battery.c b/drivers/power/supply/ds2780_battery.c index db3a25404c9f..dd57a472e878 100644 --- a/drivers/power/supply/ds2780_battery.c +++ b/drivers/power/supply/ds2780_battery.c @@ -160,7 +160,7 @@ static int ds2780_get_voltage(struct ds2780_device_info *dev_info, /* * The voltage value is located in 10 bits across the voltage MSB - * and LSB registers in two's compliment form + * and LSB registers in two's complement form * Sign bit of the voltage value is in bit 7 of the voltage MSB register * Bits 9 - 3 of the voltage value are in bits 6 - 0 of the * voltage MSB register @@ -188,7 +188,7 @@ static int ds2780_get_temperature(struct ds2780_device_info *dev_info, /* * The temperature value is located in 10 bits across the temperature - * MSB and LSB registers in two's compliment form + * MSB and LSB registers in two's complement form * Sign bit of the temperature value is in bit 7 of the temperature * MSB register * Bits 9 - 3 of the temperature value are in bits 6 - 0 of the @@ -241,7 +241,7 @@ static int ds2780_get_current(struct ds2780_device_info *dev_info, /* * The current value is located in 16 bits across the current MSB - * and LSB registers in two's compliment form + * and LSB registers in two's complement form * Sign bit of the current value is in bit 7 of the current MSB register * Bits 14 - 8 of the current value are in bits 6 - 0 of the current * MSB register diff --git a/drivers/power/supply/ds2781_battery.c b/drivers/power/supply/ds2781_battery.c index 130cbdfc14eb..3df3c820b38c 100644 --- a/drivers/power/supply/ds2781_battery.c +++ b/drivers/power/supply/ds2781_battery.c @@ -168,7 +168,7 @@ static int ds2781_get_voltage(struct ds2781_device_info *dev_info, return ret; /* * The voltage value is located in 10 bits across the voltage MSB - * and LSB registers in two's compliment form + * and LSB registers in two's complement form * Sign bit of the voltage value is in bit 7 of the voltage MSB register * Bits 9 - 3 of the voltage value are in bits 6 - 0 of the * voltage MSB register @@ -197,7 +197,7 @@ static int ds2781_get_temperature(struct ds2781_device_info *dev_info, return ret; /* * The temperature value is located in 10 bits across the temperature - * MSB and LSB registers in two's compliment form + * MSB and LSB registers in two's complement form * Sign bit of the temperature value is in bit 7 of the temperature * MSB register * Bits 9 - 3 of the temperature value are in bits 6 - 0 of the @@ -242,7 +242,7 @@ static int ds2781_get_current(struct ds2781_device_info *dev_info, /* * The current value is located in 16 bits across the current MSB - * and LSB registers in two's compliment form + * and LSB registers in two's complement form * Sign bit of the current value is in bit 7 of the current MSB register * Bits 14 - 8 of the current value are in bits 6 - 0 of the current * MSB register diff --git a/drivers/power/supply/goldfish_battery.c b/drivers/power/supply/goldfish_battery.c index c2644a9fe80f..bf1754355c9f 100644 --- a/drivers/power/supply/goldfish_battery.c +++ b/drivers/power/supply/goldfish_battery.c @@ -266,11 +266,13 @@ static const struct of_device_id goldfish_battery_of_match[] = { }; MODULE_DEVICE_TABLE(of, goldfish_battery_of_match); +#ifdef CONFIG_ACPI static const struct acpi_device_id goldfish_battery_acpi_match[] = { { "GFSH0001", 0 }, { }, }; MODULE_DEVICE_TABLE(acpi, goldfish_battery_acpi_match); +#endif static struct platform_driver goldfish_battery_device = { .probe = goldfish_battery_probe, diff --git a/drivers/power/supply/gpio-charger.c b/drivers/power/supply/gpio-charger.c index 875735d50716..68212b39785b 100644 --- a/drivers/power/supply/gpio-charger.c +++ b/drivers/power/supply/gpio-charger.c @@ -5,7 +5,6 @@ */ #include <linux/device.h> -#include <linux/gpio.h> /* For legacy platform data */ #include <linux/init.h> #include <linux/interrupt.h> #include <linux/kernel.h> @@ -18,7 +17,13 @@ #include <linux/power/gpio-charger.h> +struct gpio_mapping { + u32 limit_ua; + u32 gpiodata; +} __packed; + struct gpio_charger { + struct device *dev; unsigned int irq; unsigned int charge_status_irq; bool wakeup_enabled; @@ -27,6 +32,11 @@ struct gpio_charger { struct power_supply_desc charger_desc; struct gpio_desc *gpiod; struct gpio_desc *charge_status; + + struct gpio_descs *current_limit_gpios; + struct gpio_mapping *current_limit_map; + u32 current_limit_map_size; + u32 charge_current_limit; }; static irqreturn_t gpio_charger_irq(int irq, void *devid) @@ -43,6 +53,35 @@ static inline struct gpio_charger *psy_to_gpio_charger(struct power_supply *psy) return power_supply_get_drvdata(psy); } +static int set_charge_current_limit(struct gpio_charger *gpio_charger, int val) +{ + struct gpio_mapping mapping; + int ndescs = gpio_charger->current_limit_gpios->ndescs; + struct gpio_desc **gpios = gpio_charger->current_limit_gpios->desc; + int i; + + if (!gpio_charger->current_limit_map_size) + return -EINVAL; + + for (i = 0; i < gpio_charger->current_limit_map_size; i++) { + if (gpio_charger->current_limit_map[i].limit_ua <= val) + break; + } + mapping = gpio_charger->current_limit_map[i]; + + for (i = 0; i < ndescs; i++) { + bool val = (mapping.gpiodata >> i) & 1; + gpiod_set_value_cansleep(gpios[ndescs-i-1], val); + } + + gpio_charger->charge_current_limit = mapping.limit_ua; + + dev_dbg(gpio_charger->dev, "set charge current limit to %d (requested: %d)\n", + gpio_charger->charge_current_limit, val); + + return 0; +} + static int gpio_charger_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { @@ -58,6 +97,24 @@ static int gpio_charger_get_property(struct power_supply *psy, else val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + val->intval = gpio_charger->charge_current_limit; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int gpio_charger_set_property(struct power_supply *psy, + enum power_supply_property psp, const union power_supply_propval *val) +{ + struct gpio_charger *gpio_charger = psy_to_gpio_charger(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + return set_charge_current_limit(gpio_charger, val->intval); default: return -EINVAL; } @@ -65,6 +122,19 @@ static int gpio_charger_get_property(struct power_supply *psy, return 0; } +static int gpio_charger_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + return 1; + default: + break; + } + + return 0; +} + static enum power_supply_type gpio_charger_get_type(struct device *dev) { const char *chargetype; @@ -112,6 +182,61 @@ static int gpio_charger_get_irq(struct device *dev, void *dev_id, return irq; } +static int init_charge_current_limit(struct device *dev, + struct gpio_charger *gpio_charger) +{ + int i, len; + u32 cur_limit = U32_MAX; + + gpio_charger->current_limit_gpios = devm_gpiod_get_array_optional(dev, + "charge-current-limit", GPIOD_OUT_LOW); + if (IS_ERR(gpio_charger->current_limit_gpios)) { + dev_err(dev, "error getting current-limit GPIOs\n"); + return PTR_ERR(gpio_charger->current_limit_gpios); + } + + if (!gpio_charger->current_limit_gpios) + return 0; + + len = device_property_read_u32_array(dev, "charge-current-limit-mapping", + NULL, 0); + if (len < 0) + return len; + + if (len == 0 || len % 2) { + dev_err(dev, "invalid charge-current-limit-mapping length\n"); + return -EINVAL; + } + + gpio_charger->current_limit_map = devm_kmalloc_array(dev, + len / 2, sizeof(*gpio_charger->current_limit_map), GFP_KERNEL); + if (!gpio_charger->current_limit_map) + return -ENOMEM; + + gpio_charger->current_limit_map_size = len / 2; + + len = device_property_read_u32_array(dev, "charge-current-limit-mapping", + (u32*) gpio_charger->current_limit_map, len); + if (len < 0) + return len; + + for (i=0; i < gpio_charger->current_limit_map_size; i++) { + if (gpio_charger->current_limit_map[i].limit_ua > cur_limit) { + dev_err(dev, "charge-current-limit-mapping not sorted by current in descending order\n"); + return -EINVAL; + } + + cur_limit = gpio_charger->current_limit_map[i].limit_ua; + } + + /* default to smallest current limitation for safety reasons */ + len = gpio_charger->current_limit_map_size - 1; + set_charge_current_limit(gpio_charger, + gpio_charger->current_limit_map[len].limit_ua); + + return 0; +} + /* * The entries will be overwritten by driver's probe routine depending * on the available features. This list ensures, that the array is big @@ -120,6 +245,7 @@ static int gpio_charger_get_irq(struct device *dev, void *dev_id, static enum power_supply_property gpio_charger_properties[] = { POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, }; static int gpio_charger_probe(struct platform_device *pdev) @@ -131,7 +257,6 @@ static int gpio_charger_probe(struct platform_device *pdev) struct power_supply_desc *charger_desc; struct gpio_desc *charge_status; int charge_status_irq; - unsigned long flags; int ret; int num_props = 0; @@ -143,40 +268,17 @@ static int gpio_charger_probe(struct platform_device *pdev) gpio_charger = devm_kzalloc(dev, sizeof(*gpio_charger), GFP_KERNEL); if (!gpio_charger) return -ENOMEM; + gpio_charger->dev = dev; /* * This will fetch a GPIO descriptor from device tree, ACPI or * boardfile descriptor tables. It's good to try this first. */ gpio_charger->gpiod = devm_gpiod_get_optional(dev, NULL, GPIOD_IN); - - /* - * Fallback to legacy platform data method, if no GPIO is specified - * using boardfile descriptor tables. - */ - if (!gpio_charger->gpiod && pdata) { - /* Non-DT: use legacy GPIO numbers */ - if (!gpio_is_valid(pdata->gpio)) { - dev_err(dev, "Invalid gpio pin in pdata\n"); - return -EINVAL; - } - flags = GPIOF_IN; - if (pdata->gpio_active_low) - flags |= GPIOF_ACTIVE_LOW; - ret = devm_gpio_request_one(dev, pdata->gpio, flags, - dev_name(dev)); - if (ret) { - dev_err(dev, "Failed to request gpio pin: %d\n", ret); - return ret; - } - /* Then convert this to gpiod for now */ - gpio_charger->gpiod = gpio_to_desc(pdata->gpio); - } else if (IS_ERR(gpio_charger->gpiod)) { + if (IS_ERR(gpio_charger->gpiod)) { /* Just try again if this happens */ - if (PTR_ERR(gpio_charger->gpiod) == -EPROBE_DEFER) - return -EPROBE_DEFER; - dev_err(dev, "error getting GPIO descriptor\n"); - return PTR_ERR(gpio_charger->gpiod); + return dev_err_probe(dev, PTR_ERR(gpio_charger->gpiod), + "error getting GPIO descriptor\n"); } if (gpio_charger->gpiod) { @@ -193,10 +295,22 @@ static int gpio_charger_probe(struct platform_device *pdev) num_props++; } + ret = init_charge_current_limit(dev, gpio_charger); + if (ret < 0) + return ret; + if (gpio_charger->current_limit_map) { + gpio_charger_properties[num_props] = + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX; + num_props++; + } + charger_desc = &gpio_charger->charger_desc; charger_desc->properties = gpio_charger_properties; charger_desc->num_properties = num_props; charger_desc->get_property = gpio_charger_get_property; + charger_desc->set_property = gpio_charger_set_property; + charger_desc->property_is_writeable = + gpio_charger_property_is_writeable; psy_cfg.of_node = dev->of_node; psy_cfg.drv_data = gpio_charger; diff --git a/drivers/power/supply/ingenic-battery.c b/drivers/power/supply/ingenic-battery.c index dd3d93dfe3eb..32dc77fd9a95 100644 --- a/drivers/power/supply/ingenic-battery.c +++ b/drivers/power/supply/ingenic-battery.c @@ -147,11 +147,9 @@ static int ingenic_battery_probe(struct platform_device *pdev) psy_cfg.of_node = dev->of_node; bat->battery = devm_power_supply_register(dev, desc, &psy_cfg); - if (IS_ERR(bat->battery)) { - if (PTR_ERR(bat->battery) != -EPROBE_DEFER) - dev_err(dev, "Unable to register battery\n"); - return PTR_ERR(bat->battery); - } + if (IS_ERR(bat->battery)) + return dev_err_probe(dev, PTR_ERR(bat->battery), + "Unable to register battery\n"); ret = power_supply_get_battery_info(bat->battery, &bat->info); if (ret) { diff --git a/drivers/power/supply/lego_ev3_battery.c b/drivers/power/supply/lego_ev3_battery.c index 1ae3710909b7..ccb00be38e2c 100644 --- a/drivers/power/supply/lego_ev3_battery.c +++ b/drivers/power/supply/lego_ev3_battery.c @@ -166,27 +166,21 @@ static int lego_ev3_battery_probe(struct platform_device *pdev) batt->iio_v = devm_iio_channel_get(dev, "voltage"); err = PTR_ERR_OR_ZERO(batt->iio_v); - if (err) { - if (err != -EPROBE_DEFER) - dev_err(dev, "Failed to get voltage iio channel\n"); - return err; - } + if (err) + return dev_err_probe(dev, err, + "Failed to get voltage iio channel\n"); batt->iio_i = devm_iio_channel_get(dev, "current"); err = PTR_ERR_OR_ZERO(batt->iio_i); - if (err) { - if (err != -EPROBE_DEFER) - dev_err(dev, "Failed to get current iio channel\n"); - return err; - } + if (err) + return dev_err_probe(dev, err, + "Failed to get current iio channel\n"); batt->rechargeable_gpio = devm_gpiod_get(dev, "rechargeable", GPIOD_IN); err = PTR_ERR_OR_ZERO(batt->rechargeable_gpio); - if (err) { - if (err != -EPROBE_DEFER) - dev_err(dev, "Failed to get rechargeable gpio\n"); - return err; - } + if (err) + return dev_err_probe(dev, err, + "Failed to get rechargeable gpio\n"); /* * The rechargeable battery indication switch cannot be changed without diff --git a/drivers/power/supply/ltc2941-battery-gauge.c b/drivers/power/supply/ltc2941-battery-gauge.c index 30a9014b2f95..10cd617516ec 100644 --- a/drivers/power/supply/ltc2941-battery-gauge.c +++ b/drivers/power/supply/ltc2941-battery-gauge.c @@ -473,7 +473,8 @@ static int ltc294x_i2c_probe(struct i2c_client *client, np = of_node_get(client->dev.of_node); - info->id = (enum ltc294x_id)of_device_get_match_data(&client->dev); + info->id = (enum ltc294x_id) (uintptr_t) of_device_get_match_data( + &client->dev); info->supply_desc.name = np->name; /* r_sense can be negative, when sense+ is connected to the battery diff --git a/drivers/power/supply/max17040_battery.c b/drivers/power/supply/max17040_battery.c index 6cb31b9a958d..d956c67d5155 100644 --- a/drivers/power/supply/max17040_battery.c +++ b/drivers/power/supply/max17040_battery.c @@ -15,196 +15,289 @@ #include <linux/delay.h> #include <linux/interrupt.h> #include <linux/power_supply.h> +#include <linux/of_device.h> #include <linux/max17040_battery.h> +#include <linux/regmap.h> #include <linux/slab.h> #define MAX17040_VCELL 0x02 #define MAX17040_SOC 0x04 #define MAX17040_MODE 0x06 #define MAX17040_VER 0x08 -#define MAX17040_RCOMP 0x0C +#define MAX17040_CONFIG 0x0C +#define MAX17040_STATUS 0x1A #define MAX17040_CMD 0xFE #define MAX17040_DELAY 1000 #define MAX17040_BATTERY_FULL 95 +#define MAX17040_RCOMP_DEFAULT 0x9700 -#define MAX17040_ATHD_MASK 0xFFC0 +#define MAX17040_ATHD_MASK 0x3f +#define MAX17040_ALSC_MASK 0x40 #define MAX17040_ATHD_DEFAULT_POWER_UP 4 +#define MAX17040_STATUS_HD_MASK 0x1000 +#define MAX17040_STATUS_SC_MASK 0x2000 +#define MAX17040_CFG_RCOMP_MASK 0xff00 + +enum chip_id { + ID_MAX17040, + ID_MAX17041, + ID_MAX17043, + ID_MAX17044, + ID_MAX17048, + ID_MAX17049, + ID_MAX17058, + ID_MAX17059, +}; + +/* values that differ by chip_id */ +struct chip_data { + u16 reset_val; + u16 vcell_shift; + u16 vcell_mul; + u16 vcell_div; + u8 has_low_soc_alert; + u8 rcomp_bytes; + u8 has_soc_alert; +}; + +static struct chip_data max17040_family[] = { + [ID_MAX17040] = { + .reset_val = 0x0054, + .vcell_shift = 4, + .vcell_mul = 1250, + .vcell_div = 1, + .has_low_soc_alert = 0, + .rcomp_bytes = 2, + .has_soc_alert = 0, + }, + [ID_MAX17041] = { + .reset_val = 0x0054, + .vcell_shift = 4, + .vcell_mul = 2500, + .vcell_div = 1, + .has_low_soc_alert = 0, + .rcomp_bytes = 2, + .has_soc_alert = 0, + }, + [ID_MAX17043] = { + .reset_val = 0x0054, + .vcell_shift = 4, + .vcell_mul = 1250, + .vcell_div = 1, + .has_low_soc_alert = 1, + .rcomp_bytes = 1, + .has_soc_alert = 0, + }, + [ID_MAX17044] = { + .reset_val = 0x0054, + .vcell_shift = 4, + .vcell_mul = 2500, + .vcell_div = 1, + .has_low_soc_alert = 1, + .rcomp_bytes = 1, + .has_soc_alert = 0, + }, + [ID_MAX17048] = { + .reset_val = 0x5400, + .vcell_shift = 0, + .vcell_mul = 625, + .vcell_div = 8, + .has_low_soc_alert = 1, + .rcomp_bytes = 1, + .has_soc_alert = 1, + }, + [ID_MAX17049] = { + .reset_val = 0x5400, + .vcell_shift = 0, + .vcell_mul = 625, + .vcell_div = 4, + .has_low_soc_alert = 1, + .rcomp_bytes = 1, + .has_soc_alert = 1, + }, + [ID_MAX17058] = { + .reset_val = 0x5400, + .vcell_shift = 0, + .vcell_mul = 625, + .vcell_div = 8, + .has_low_soc_alert = 1, + .rcomp_bytes = 1, + .has_soc_alert = 0, + }, + [ID_MAX17059] = { + .reset_val = 0x5400, + .vcell_shift = 0, + .vcell_mul = 625, + .vcell_div = 4, + .has_low_soc_alert = 1, + .rcomp_bytes = 1, + .has_soc_alert = 0, + }, +}; struct max17040_chip { struct i2c_client *client; + struct regmap *regmap; struct delayed_work work; struct power_supply *battery; struct max17040_platform_data *pdata; + struct chip_data data; - /* State Of Connect */ - int online; - /* battery voltage */ - int vcell; /* battery capacity */ int soc; /* State Of Charge */ int status; /* Low alert threshold from 32% to 1% of the State of Charge */ u32 low_soc_alert; + /* some devices return twice the capacity */ + bool quirk_double_soc; + /* higher 8 bits for 17043+, 16 bits for 17040,41 */ + u16 rcomp; }; -static int max17040_get_property(struct power_supply *psy, - enum power_supply_property psp, - union power_supply_propval *val) +static int max17040_reset(struct max17040_chip *chip) { - struct max17040_chip *chip = power_supply_get_drvdata(psy); - - switch (psp) { - case POWER_SUPPLY_PROP_STATUS: - val->intval = chip->status; - break; - case POWER_SUPPLY_PROP_ONLINE: - val->intval = chip->online; - break; - case POWER_SUPPLY_PROP_VOLTAGE_NOW: - val->intval = chip->vcell; - break; - case POWER_SUPPLY_PROP_CAPACITY: - val->intval = chip->soc; - break; - case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN: - val->intval = chip->low_soc_alert; - break; - default: - return -EINVAL; - } - return 0; + return regmap_write(chip->regmap, MAX17040_CMD, chip->data.reset_val); } -static int max17040_write_reg(struct i2c_client *client, int reg, u16 value) +static int max17040_set_low_soc_alert(struct max17040_chip *chip, u32 level) { - int ret; - - ret = i2c_smbus_write_word_swapped(client, reg, value); - - if (ret < 0) - dev_err(&client->dev, "%s: err %d\n", __func__, ret); - - return ret; + level = 32 - level * (chip->quirk_double_soc ? 2 : 1); + return regmap_update_bits(chip->regmap, MAX17040_CONFIG, + MAX17040_ATHD_MASK, level); } -static int max17040_read_reg(struct i2c_client *client, int reg) +static int max17040_set_soc_alert(struct max17040_chip *chip, bool enable) { - int ret; - - ret = i2c_smbus_read_word_swapped(client, reg); - - if (ret < 0) - dev_err(&client->dev, "%s: err %d\n", __func__, ret); - - return ret; + return regmap_update_bits(chip->regmap, MAX17040_CONFIG, + MAX17040_ALSC_MASK, enable ? MAX17040_ALSC_MASK : 0); } -static void max17040_reset(struct i2c_client *client) +static int max17040_set_rcomp(struct max17040_chip *chip, u16 rcomp) { - max17040_write_reg(client, MAX17040_CMD, 0x0054); + u16 mask = chip->data.rcomp_bytes == 2 ? + 0xffff : MAX17040_CFG_RCOMP_MASK; + + return regmap_update_bits(chip->regmap, MAX17040_CONFIG, mask, rcomp); } -static int max17040_set_low_soc_alert(struct i2c_client *client, u32 level) +static int max17040_raw_vcell_to_uvolts(struct max17040_chip *chip, u16 vcell) { - int ret; - u16 data; + struct chip_data *d = &chip->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; + return (vcell >> d->vcell_shift) * d->vcell_mul / d->vcell_div; } -static void max17040_get_vcell(struct i2c_client *client) + +static int max17040_get_vcell(struct max17040_chip *chip) { - struct max17040_chip *chip = i2c_get_clientdata(client); - u16 vcell; + u32 vcell; - vcell = max17040_read_reg(client, MAX17040_VCELL); + regmap_read(chip->regmap, MAX17040_VCELL, &vcell); - chip->vcell = (vcell >> 4) * 1250; + return max17040_raw_vcell_to_uvolts(chip, vcell); } -static void max17040_get_soc(struct i2c_client *client) +static int max17040_get_soc(struct max17040_chip *chip) { - struct max17040_chip *chip = i2c_get_clientdata(client); - u16 soc; + u32 soc; - soc = max17040_read_reg(client, MAX17040_SOC); + regmap_read(chip->regmap, MAX17040_SOC, &soc); - chip->soc = (soc >> 8); + return soc >> (chip->quirk_double_soc ? 9 : 8); } -static void max17040_get_version(struct i2c_client *client) +static int max17040_get_version(struct max17040_chip *chip) { - u16 version; + int ret; + u32 version; - version = max17040_read_reg(client, MAX17040_VER); + ret = regmap_read(chip->regmap, MAX17040_VER, &version); - dev_info(&client->dev, "MAX17040 Fuel-Gauge Ver 0x%x\n", version); + return ret ? ret : version; } -static void max17040_get_online(struct i2c_client *client) +static int max17040_get_online(struct max17040_chip *chip) { - struct max17040_chip *chip = i2c_get_clientdata(client); - - if (chip->pdata && chip->pdata->battery_online) - chip->online = chip->pdata->battery_online(); - else - chip->online = 1; + return chip->pdata && chip->pdata->battery_online ? + chip->pdata->battery_online() : 1; } -static void max17040_get_status(struct i2c_client *client) +static int max17040_get_status(struct max17040_chip *chip) { - struct max17040_chip *chip = i2c_get_clientdata(client); - if (!chip->pdata || !chip->pdata->charger_online - || !chip->pdata->charger_enable) { - chip->status = POWER_SUPPLY_STATUS_UNKNOWN; - return; - } + || !chip->pdata->charger_enable) + return POWER_SUPPLY_STATUS_UNKNOWN; - if (chip->pdata->charger_online()) { + if (max17040_get_soc(chip) > MAX17040_BATTERY_FULL) + return POWER_SUPPLY_STATUS_FULL; + + if (chip->pdata->charger_online()) if (chip->pdata->charger_enable()) - chip->status = POWER_SUPPLY_STATUS_CHARGING; + return POWER_SUPPLY_STATUS_CHARGING; else - chip->status = POWER_SUPPLY_STATUS_NOT_CHARGING; - } else { - chip->status = POWER_SUPPLY_STATUS_DISCHARGING; - } - - if (chip->soc > MAX17040_BATTERY_FULL) - chip->status = POWER_SUPPLY_STATUS_FULL; + return POWER_SUPPLY_STATUS_NOT_CHARGING; + else + return POWER_SUPPLY_STATUS_DISCHARGING; } static int max17040_get_of_data(struct max17040_chip *chip) { struct device *dev = &chip->client->dev; + struct chip_data *data = &max17040_family[ + (uintptr_t) of_device_get_match_data(dev)]; + int rcomp_len; + u8 rcomp[2]; + + chip->quirk_double_soc = device_property_read_bool(dev, + "maxim,double-soc"); 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) + if (chip->low_soc_alert <= 0 || + chip->low_soc_alert > (chip->quirk_double_soc ? 16 : 32)) { + dev_err(dev, "maxim,alert-low-soc-level out of bounds\n"); return -EINVAL; + } + + rcomp_len = device_property_count_u8(dev, "maxim,rcomp"); + chip->rcomp = MAX17040_RCOMP_DEFAULT; + if (rcomp_len == data->rcomp_bytes) { + device_property_read_u8_array(dev, "maxim,rcomp", + rcomp, rcomp_len); + chip->rcomp = rcomp_len == 2 ? + rcomp[0] << 8 | rcomp[1] : + rcomp[0] << 8; + } else if (rcomp_len > 0) { + dev_err(dev, "maxim,rcomp has incorrect length\n"); + return -EINVAL; + } return 0; } -static void max17040_check_changes(struct i2c_client *client) +static void max17040_check_changes(struct max17040_chip *chip) { - max17040_get_vcell(client); - max17040_get_soc(client); - max17040_get_online(client); - max17040_get_status(client); + chip->soc = max17040_get_soc(chip); + chip->status = max17040_get_status(chip); +} + +static void max17040_queue_work(struct max17040_chip *chip) +{ + queue_delayed_work(system_power_efficient_wq, &chip->work, + MAX17040_DELAY); +} + +static void max17040_stop_work(void *data) +{ + struct max17040_chip *chip = data; + + cancel_delayed_work_sync(&chip->work); } static void max17040_work(struct work_struct *work) @@ -217,30 +310,51 @@ static void max17040_work(struct work_struct *work) /* store SOC and status to check changes */ last_soc = chip->soc; last_status = chip->status; - max17040_check_changes(chip->client); + max17040_check_changes(chip); /* 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); + max17040_queue_work(chip); +} + +/* Returns true if alert cause was SOC change, not low SOC */ +static bool max17040_handle_soc_alert(struct max17040_chip *chip) +{ + bool ret = true; + u32 data; + + regmap_read(chip->regmap, MAX17040_STATUS, &data); + + if (data & MAX17040_STATUS_HD_MASK) { + // this alert was caused by low soc + ret = false; + } + if (data & MAX17040_STATUS_SC_MASK) { + // soc change bit -- deassert to mark as handled + regmap_write(chip->regmap, MAX17040_STATUS, + data & ~MAX17040_STATUS_SC_MASK); + } + + return ret; } 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"); + if (!(chip->data.has_soc_alert && max17040_handle_soc_alert(chip))) + dev_warn(&chip->client->dev, "IRQ: Alert battery low level\n"); + /* read registers */ - max17040_check_changes(chip->client); + max17040_check_changes(chip); /* send uevent */ power_supply_changed(chip->battery); /* reset alert bit */ - max17040_set_low_soc_alert(client, chip->low_soc_alert); + max17040_set_low_soc_alert(chip, chip->low_soc_alert); return IRQ_HANDLED; } @@ -279,12 +393,13 @@ static int max17040_set_property(struct power_supply *psy, switch (psp) { case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN: - /* alert threshold can be programmed from 1% up to 32% */ - if ((val->intval < 1) || (val->intval > 32)) { + /* alert threshold can be programmed from 1% up to 16/32% */ + if ((val->intval < 1) || + (val->intval > (chip->quirk_double_soc ? 16 : 32))) { ret = -EINVAL; break; } - ret = max17040_set_low_soc_alert(chip->client, val->intval); + ret = max17040_set_low_soc_alert(chip, val->intval); chip->low_soc_alert = val->intval; break; default: @@ -294,6 +409,41 @@ static int max17040_set_property(struct power_supply *psy, return ret; } +static int max17040_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct max17040_chip *chip = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = max17040_get_status(chip); + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = max17040_get_online(chip); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = max17040_get_vcell(chip); + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = max17040_get_soc(chip); + break; + case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN: + val->intval = chip->low_soc_alert; + break; + default: + return -EINVAL; + } + return 0; +} + +static const struct regmap_config max17040_regmap = { + .reg_bits = 8, + .reg_stride = 2, + .val_bits = 16, + .val_format_endian = REGMAP_ENDIAN_BIG, +}; + static enum power_supply_property max17040_battery_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_ONLINE, @@ -318,6 +468,8 @@ static int max17040_probe(struct i2c_client *client, struct i2c_adapter *adapter = client->adapter; struct power_supply_config psy_cfg = {}; struct max17040_chip *chip; + enum chip_id chip_id; + bool enable_irq = false; int ret; if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) @@ -328,37 +480,68 @@ static int max17040_probe(struct i2c_client *client, return -ENOMEM; chip->client = client; + chip->regmap = devm_regmap_init_i2c(client, &max17040_regmap); 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; + chip_id = (enum chip_id) id->driver_data; + if (client->dev.of_node) { + ret = max17040_get_of_data(chip); + if (ret) + return ret; + chip_id = (enum chip_id) (uintptr_t) + of_device_get_match_data(&client->dev); } + chip->data = max17040_family[chip_id]; i2c_set_clientdata(client, chip); psy_cfg.drv_data = chip; - chip->battery = power_supply_register(&client->dev, + chip->battery = devm_power_supply_register(&client->dev, &max17040_battery_desc, &psy_cfg); if (IS_ERR(chip->battery)) { dev_err(&client->dev, "failed: power supply register\n"); return PTR_ERR(chip->battery); } - max17040_reset(client); - max17040_get_version(client); + ret = max17040_get_version(chip); + if (ret < 0) + return ret; + dev_dbg(&chip->client->dev, "MAX17040 Fuel-Gauge Ver 0x%x\n", ret); + + if (chip_id == ID_MAX17040 || chip_id == ID_MAX17041) + max17040_reset(chip); + + max17040_set_rcomp(chip, chip->rcomp); /* 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 (client->irq && chip->data.has_low_soc_alert) { + ret = max17040_set_low_soc_alert(chip, chip->low_soc_alert); if (ret) { dev_err(&client->dev, "Failed to set low SOC alert: err %d\n", ret); return ret; } + enable_irq = true; + } + + if (client->irq && chip->data.has_soc_alert) { + ret = max17040_set_soc_alert(chip, 1); + if (ret) { + dev_err(&client->dev, + "Failed to set SOC alert: err %d\n", ret); + return ret; + } + enable_irq = true; + } else { + /* soc alerts negate the need for polling */ + INIT_DEFERRABLE_WORK(&chip->work, max17040_work); + ret = devm_add_action(&client->dev, max17040_stop_work, chip); + if (ret) + return ret; + max17040_queue_work(chip); + } + + if (enable_irq) { ret = max17040_enable_alert_irq(chip); if (ret) { client->irq = 0; @@ -367,19 +550,6 @@ static int max17040_probe(struct i2c_client *client, } } - INIT_DEFERRABLE_WORK(&chip->work, max17040_work); - queue_delayed_work(system_power_efficient_wq, &chip->work, - MAX17040_DELAY); - - return 0; -} - -static int max17040_remove(struct i2c_client *client) -{ - struct max17040_chip *chip = i2c_get_clientdata(client); - - power_supply_unregister(chip->battery); - cancel_delayed_work(&chip->work); return 0; } @@ -390,7 +560,11 @@ static int max17040_suspend(struct device *dev) struct i2c_client *client = to_i2c_client(dev); struct max17040_chip *chip = i2c_get_clientdata(client); - cancel_delayed_work(&chip->work); + if (client->irq && chip->data.has_soc_alert) + // disable soc alert to prevent wakeup + max17040_set_soc_alert(chip, 0); + else + cancel_delayed_work(&chip->work); if (client->irq && device_may_wakeup(dev)) enable_irq_wake(client->irq); @@ -403,12 +577,14 @@ static int max17040_resume(struct device *dev) struct i2c_client *client = to_i2c_client(dev); struct max17040_chip *chip = i2c_get_clientdata(client); - queue_delayed_work(system_power_efficient_wq, &chip->work, - MAX17040_DELAY); - if (client->irq && device_may_wakeup(dev)) disable_irq_wake(client->irq); + if (client->irq && chip->data.has_soc_alert) + max17040_set_soc_alert(chip, 1); + else + max17040_queue_work(chip); + return 0; } @@ -422,16 +598,30 @@ static SIMPLE_DEV_PM_OPS(max17040_pm_ops, max17040_suspend, max17040_resume); #endif /* CONFIG_PM_SLEEP */ static const struct i2c_device_id max17040_id[] = { - { "max17040" }, - { "max77836-battery" }, - { } + { "max17040", ID_MAX17040 }, + { "max17041", ID_MAX17041 }, + { "max17043", ID_MAX17043 }, + { "max77836-battery", ID_MAX17043 }, + { "max17044", ID_MAX17044 }, + { "max17048", ID_MAX17048 }, + { "max17049", ID_MAX17049 }, + { "max17058", ID_MAX17058 }, + { "max17059", ID_MAX17059 }, + { /* sentinel */ } }; MODULE_DEVICE_TABLE(i2c, max17040_id); static const struct of_device_id max17040_of_match[] = { - { .compatible = "maxim,max17040" }, - { .compatible = "maxim,max77836-battery" }, - { }, + { .compatible = "maxim,max17040", .data = (void *) ID_MAX17040 }, + { .compatible = "maxim,max17041", .data = (void *) ID_MAX17041 }, + { .compatible = "maxim,max17043", .data = (void *) ID_MAX17043 }, + { .compatible = "maxim,max77836-battery", .data = (void *) ID_MAX17043 }, + { .compatible = "maxim,max17044", .data = (void *) ID_MAX17044 }, + { .compatible = "maxim,max17048", .data = (void *) ID_MAX17048 }, + { .compatible = "maxim,max17049", .data = (void *) ID_MAX17049 }, + { .compatible = "maxim,max17058", .data = (void *) ID_MAX17058 }, + { .compatible = "maxim,max17059", .data = (void *) ID_MAX17059 }, + { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, max17040_of_match); @@ -442,7 +632,6 @@ static struct i2c_driver max17040_i2c_driver = { .pm = MAX17040_PM_OPS, }, .probe = max17040_probe, - .remove = max17040_remove, .id_table = max17040_id, }; module_i2c_driver(max17040_i2c_driver); diff --git a/drivers/power/supply/pm2301_charger.c b/drivers/power/supply/pm2301_charger.c index 17749fc90e16..2df6a2459d1f 100644 --- a/drivers/power/supply/pm2301_charger.c +++ b/drivers/power/supply/pm2301_charger.c @@ -104,11 +104,6 @@ static int pm2xxx_charger_current_map[] = { 3000, }; -static const struct i2c_device_id pm2xxx_ident[] = { - { "pm2301", 0 }, - { } -}; - static void set_lpn_pin(struct pm2xxx_charger *pm2) { if (!pm2->ac.charger_connected && gpio_is_valid(pm2->lpn_pin)) { @@ -396,7 +391,7 @@ static int pm2_int_reg3(void *pm2_data, int val) if (val & (PM2XXX_INT4_ITCHARGINGON)) { dev_dbg(pm2->dev , - "chargind operation has started\n"); + "charging operation has started\n"); } if (val & (PM2XXX_INT4_ITVRESUME)) { diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index ccbad435ed12..38e3aa642131 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -579,6 +579,12 @@ int power_supply_get_battery_info(struct power_supply *psy, info->charge_term_current_ua = -EINVAL; info->constant_charge_current_max_ua = -EINVAL; info->constant_charge_voltage_max_uv = -EINVAL; + info->temp_ambient_alert_min = INT_MIN; + info->temp_ambient_alert_max = INT_MAX; + info->temp_alert_min = INT_MIN; + info->temp_alert_max = INT_MAX; + info->temp_min = INT_MIN; + info->temp_max = INT_MAX; info->factory_internal_resistance_uohm = -EINVAL; info->resist_table = NULL; @@ -639,6 +645,19 @@ int power_supply_get_battery_info(struct power_supply *psy, of_property_read_u32(battery_np, "factory-internal-resistance-micro-ohms", &info->factory_internal_resistance_uohm); + of_property_read_u32_index(battery_np, "ambient-celsius", + 0, &info->temp_ambient_alert_min); + of_property_read_u32_index(battery_np, "ambient-celsius", + 1, &info->temp_ambient_alert_max); + of_property_read_u32_index(battery_np, "alert-celsius", + 0, &info->temp_alert_min); + of_property_read_u32_index(battery_np, "alert-celsius", + 1, &info->temp_alert_max); + of_property_read_u32_index(battery_np, "operating-range-celsius", + 0, &info->temp_min); + of_property_read_u32_index(battery_np, "operating-range-celsius", + 1, &info->temp_max); + len = of_property_count_u32_elems(battery_np, "ocv-capacity-celsius"); if (len < 0 && len != -EINVAL) { err = len; diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index 3d383086018c..a616b9d8f43c 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -56,6 +56,7 @@ static const char * const POWER_SUPPLY_TYPE_TEXT[] = { [POWER_SUPPLY_TYPE_USB_PD] = "USB_PD", [POWER_SUPPLY_TYPE_USB_PD_DRP] = "USB_PD_DRP", [POWER_SUPPLY_TYPE_APPLE_BRICK_ID] = "BrickID", + [POWER_SUPPLY_TYPE_WIRELESS] = "Wireless", }; static const char * const POWER_SUPPLY_USB_TYPE_TEXT[] = { diff --git a/drivers/power/supply/rn5t618_power.c b/drivers/power/supply/rn5t618_power.c new file mode 100644 index 000000000000..dee520f0fdf5 --- /dev/null +++ b/drivers/power/supply/rn5t618_power.c @@ -0,0 +1,556 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Power supply driver for the RICOH RN5T618 power management chip family + * + * Copyright (C) 2020 Andreas Kemnade + */ + +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/bitops.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/mfd/rn5t618.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#define CHG_STATE_ADP_INPUT 0x40 +#define CHG_STATE_USB_INPUT 0x80 +#define CHG_STATE_MASK 0x1f +#define CHG_STATE_CHG_OFF 0 +#define CHG_STATE_CHG_READY_VADP 1 +#define CHG_STATE_CHG_TRICKLE 2 +#define CHG_STATE_CHG_RAPID 3 +#define CHG_STATE_CHG_COMPLETE 4 +#define CHG_STATE_SUSPEND 5 +#define CHG_STATE_VCHG_OVER_VOL 6 +#define CHG_STATE_BAT_ERROR 7 +#define CHG_STATE_NO_BAT 8 +#define CHG_STATE_BAT_OVER_VOL 9 +#define CHG_STATE_BAT_TEMP_ERR 10 +#define CHG_STATE_DIE_ERR 11 +#define CHG_STATE_DIE_SHUTDOWN 12 +#define CHG_STATE_NO_BAT2 13 +#define CHG_STATE_CHG_READY_VUSB 14 + +#define FG_ENABLE 1 + +struct rn5t618_power_info { + struct rn5t618 *rn5t618; + struct platform_device *pdev; + struct power_supply *battery; + struct power_supply *usb; + struct power_supply *adp; + int irq; +}; + +static enum power_supply_property rn5t618_usb_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, +}; + +static enum power_supply_property rn5t618_adp_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, +}; + + +static enum power_supply_property rn5t618_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, + POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, +}; + +static int rn5t618_battery_read_doublereg(struct rn5t618_power_info *info, + u8 reg, u16 *result) +{ + int ret, i; + u8 data[2]; + u16 old, new; + + old = 0; + /* Prevent races when registers are changing. */ + for (i = 0; i < 3; i++) { + ret = regmap_bulk_read(info->rn5t618->regmap, + reg, data, sizeof(data)); + if (ret) + return ret; + + new = data[0] << 8; + new |= data[1]; + if (new == old) + break; + + old = new; + } + + *result = new; + + return 0; +} + +static int rn5t618_decode_status(unsigned int status) +{ + switch (status & CHG_STATE_MASK) { + case CHG_STATE_CHG_OFF: + case CHG_STATE_SUSPEND: + case CHG_STATE_VCHG_OVER_VOL: + case CHG_STATE_DIE_SHUTDOWN: + return POWER_SUPPLY_STATUS_DISCHARGING; + + case CHG_STATE_CHG_TRICKLE: + case CHG_STATE_CHG_RAPID: + return POWER_SUPPLY_STATUS_CHARGING; + + case CHG_STATE_CHG_COMPLETE: + return POWER_SUPPLY_STATUS_FULL; + + default: + return POWER_SUPPLY_STATUS_NOT_CHARGING; + } +} + +static int rn5t618_battery_status(struct rn5t618_power_info *info, + union power_supply_propval *val) +{ + unsigned int v; + int ret; + + ret = regmap_read(info->rn5t618->regmap, RN5T618_CHGSTATE, &v); + if (ret) + return ret; + + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + + if (v & 0xc0) { /* USB or ADP plugged */ + val->intval = rn5t618_decode_status(v); + } else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + + return ret; +} + +static int rn5t618_battery_present(struct rn5t618_power_info *info, + union power_supply_propval *val) +{ + unsigned int v; + int ret; + + ret = regmap_read(info->rn5t618->regmap, RN5T618_CHGSTATE, &v); + if (ret) + return ret; + + v &= CHG_STATE_MASK; + if ((v == CHG_STATE_NO_BAT) || (v == CHG_STATE_NO_BAT2)) + val->intval = 0; + else + val->intval = 1; + + return ret; +} + +static int rn5t618_battery_voltage_now(struct rn5t618_power_info *info, + union power_supply_propval *val) +{ + u16 res; + int ret; + + ret = rn5t618_battery_read_doublereg(info, RN5T618_VOLTAGE_1, &res); + if (ret) + return ret; + + val->intval = res * 2 * 2500 / 4095 * 1000; + + return 0; +} + +static int rn5t618_battery_current_now(struct rn5t618_power_info *info, + union power_supply_propval *val) +{ + u16 res; + int ret; + + ret = rn5t618_battery_read_doublereg(info, RN5T618_CC_AVEREG1, &res); + if (ret) + return ret; + + /* current is negative when discharging */ + val->intval = sign_extend32(res, 13) * 1000; + + return 0; +} + +static int rn5t618_battery_capacity(struct rn5t618_power_info *info, + union power_supply_propval *val) +{ + unsigned int v; + int ret; + + ret = regmap_read(info->rn5t618->regmap, RN5T618_SOC, &v); + if (ret) + return ret; + + val->intval = v; + + return 0; +} + +static int rn5t618_battery_temp(struct rn5t618_power_info *info, + union power_supply_propval *val) +{ + u16 res; + int ret; + + ret = rn5t618_battery_read_doublereg(info, RN5T618_TEMP_1, &res); + if (ret) + return ret; + + val->intval = sign_extend32(res, 11) * 10 / 16; + + return 0; +} + +static int rn5t618_battery_tte(struct rn5t618_power_info *info, + union power_supply_propval *val) +{ + u16 res; + int ret; + + ret = rn5t618_battery_read_doublereg(info, RN5T618_TT_EMPTY_H, &res); + if (ret) + return ret; + + if (res == 65535) + return -ENODATA; + + val->intval = res * 60; + + return 0; +} + +static int rn5t618_battery_ttf(struct rn5t618_power_info *info, + union power_supply_propval *val) +{ + u16 res; + int ret; + + ret = rn5t618_battery_read_doublereg(info, RN5T618_TT_FULL_H, &res); + if (ret) + return ret; + + if (res == 65535) + return -ENODATA; + + val->intval = res * 60; + + return 0; +} + +static int rn5t618_battery_charge_full(struct rn5t618_power_info *info, + union power_supply_propval *val) +{ + u16 res; + int ret; + + ret = rn5t618_battery_read_doublereg(info, RN5T618_FA_CAP_H, &res); + if (ret) + return ret; + + val->intval = res * 1000; + + return 0; +} + +static int rn5t618_battery_charge_now(struct rn5t618_power_info *info, + union power_supply_propval *val) +{ + u16 res; + int ret; + + ret = rn5t618_battery_read_doublereg(info, RN5T618_RE_CAP_H, &res); + if (ret) + return ret; + + val->intval = res * 1000; + + return 0; +} + +static int rn5t618_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + struct rn5t618_power_info *info = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + ret = rn5t618_battery_status(info, val); + break; + case POWER_SUPPLY_PROP_PRESENT: + ret = rn5t618_battery_present(info, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + ret = rn5t618_battery_voltage_now(info, val); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + ret = rn5t618_battery_current_now(info, val); + break; + case POWER_SUPPLY_PROP_CAPACITY: + ret = rn5t618_battery_capacity(info, val); + break; + case POWER_SUPPLY_PROP_TEMP: + ret = rn5t618_battery_temp(info, val); + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: + ret = rn5t618_battery_tte(info, val); + break; + case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: + ret = rn5t618_battery_ttf(info, val); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + ret = rn5t618_battery_charge_full(info, val); + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + ret = rn5t618_battery_charge_now(info, val); + break; + default: + return -EINVAL; + } + + return ret; +} + +static int rn5t618_adp_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct rn5t618_power_info *info = power_supply_get_drvdata(psy); + unsigned int chgstate; + bool online; + int ret; + + ret = regmap_read(info->rn5t618->regmap, RN5T618_CHGSTATE, &chgstate); + if (ret) + return ret; + + online = !!(chgstate & CHG_STATE_ADP_INPUT); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = online; + break; + case POWER_SUPPLY_PROP_STATUS: + if (!online) { + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + } + val->intval = rn5t618_decode_status(chgstate); + if (val->intval != POWER_SUPPLY_STATUS_CHARGING) + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + + break; + default: + return -EINVAL; + } + + return 0; +} + +static int rn5t618_usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct rn5t618_power_info *info = power_supply_get_drvdata(psy); + unsigned int chgstate; + bool online; + int ret; + + ret = regmap_read(info->rn5t618->regmap, RN5T618_CHGSTATE, &chgstate); + if (ret) + return ret; + + online = !!(chgstate & CHG_STATE_USB_INPUT); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = online; + break; + case POWER_SUPPLY_PROP_STATUS: + if (!online) { + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + } + val->intval = rn5t618_decode_status(chgstate); + if (val->intval != POWER_SUPPLY_STATUS_CHARGING) + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct power_supply_desc rn5t618_battery_desc = { + .name = "rn5t618-battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = rn5t618_battery_props, + .num_properties = ARRAY_SIZE(rn5t618_battery_props), + .get_property = rn5t618_battery_get_property, +}; + +static const struct power_supply_desc rn5t618_adp_desc = { + .name = "rn5t618-adp", + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = rn5t618_adp_props, + .num_properties = ARRAY_SIZE(rn5t618_adp_props), + .get_property = rn5t618_adp_get_property, +}; + +static const struct power_supply_desc rn5t618_usb_desc = { + .name = "rn5t618-usb", + .type = POWER_SUPPLY_TYPE_USB, + .properties = rn5t618_usb_props, + .num_properties = ARRAY_SIZE(rn5t618_usb_props), + .get_property = rn5t618_usb_get_property, +}; + +static irqreturn_t rn5t618_charger_irq(int irq, void *data) +{ + struct device *dev = data; + struct rn5t618_power_info *info = dev_get_drvdata(dev); + + unsigned int ctrl, stat1, stat2, err; + + regmap_read(info->rn5t618->regmap, RN5T618_CHGERR_IRR, &err); + regmap_read(info->rn5t618->regmap, RN5T618_CHGCTRL_IRR, &ctrl); + regmap_read(info->rn5t618->regmap, RN5T618_CHGSTAT_IRR1, &stat1); + regmap_read(info->rn5t618->regmap, RN5T618_CHGSTAT_IRR2, &stat2); + + regmap_write(info->rn5t618->regmap, RN5T618_CHGERR_IRR, 0); + regmap_write(info->rn5t618->regmap, RN5T618_CHGCTRL_IRR, 0); + regmap_write(info->rn5t618->regmap, RN5T618_CHGSTAT_IRR1, 0); + regmap_write(info->rn5t618->regmap, RN5T618_CHGSTAT_IRR2, 0); + + dev_dbg(dev, "chgerr: %x chgctrl: %x chgstat: %x chgstat2: %x\n", + err, ctrl, stat1, stat2); + + power_supply_changed(info->usb); + power_supply_changed(info->adp); + power_supply_changed(info->battery); + + return IRQ_HANDLED; +} + +static int rn5t618_power_probe(struct platform_device *pdev) +{ + int ret = 0; + unsigned int v; + struct power_supply_config psy_cfg = {}; + struct rn5t618_power_info *info; + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->pdev = pdev; + info->rn5t618 = dev_get_drvdata(pdev->dev.parent); + info->irq = -1; + + platform_set_drvdata(pdev, info); + + ret = regmap_read(info->rn5t618->regmap, RN5T618_CONTROL, &v); + if (ret) + return ret; + + if (!(v & FG_ENABLE)) { + /* E.g. the vendor kernels of various Kobo and Tolino Ebook + * readers disable the fuel gauge on shutdown. If a kernel + * without fuel gauge support is booted after that, the fuel + * gauge will get decalibrated. + */ + dev_info(&pdev->dev, "Fuel gauge not enabled, enabling now\n"); + dev_info(&pdev->dev, "Expect imprecise results\n"); + regmap_update_bits(info->rn5t618->regmap, RN5T618_CONTROL, + FG_ENABLE, FG_ENABLE); + } + + psy_cfg.drv_data = info; + info->battery = devm_power_supply_register(&pdev->dev, + &rn5t618_battery_desc, + &psy_cfg); + if (IS_ERR(info->battery)) { + ret = PTR_ERR(info->battery); + dev_err(&pdev->dev, "failed to register battery: %d\n", ret); + return ret; + } + + info->adp = devm_power_supply_register(&pdev->dev, + &rn5t618_adp_desc, + &psy_cfg); + if (IS_ERR(info->adp)) { + ret = PTR_ERR(info->adp); + dev_err(&pdev->dev, "failed to register adp: %d\n", ret); + return ret; + } + + info->usb = devm_power_supply_register(&pdev->dev, + &rn5t618_usb_desc, + &psy_cfg); + if (IS_ERR(info->usb)) { + ret = PTR_ERR(info->usb); + dev_err(&pdev->dev, "failed to register usb: %d\n", ret); + return ret; + } + + if (info->rn5t618->irq_data) + info->irq = regmap_irq_get_virq(info->rn5t618->irq_data, + RN5T618_IRQ_CHG); + + if (info->irq < 0) + info->irq = -1; + else { + ret = devm_request_threaded_irq(&pdev->dev, info->irq, NULL, + rn5t618_charger_irq, + IRQF_ONESHOT, + "rn5t618_power", + &pdev->dev); + + if (ret < 0) { + dev_err(&pdev->dev, "request IRQ:%d fail\n", + info->irq); + info->irq = -1; + } + } + + return 0; +} + +static struct platform_driver rn5t618_power_driver = { + .driver = { + .name = "rn5t618-power", + }, + .probe = rn5t618_power_probe, +}; + +module_platform_driver(rn5t618_power_driver); +MODULE_ALIAS("platform:rn5t618-power"); +MODULE_DESCRIPTION("Power supply driver for RICOH RN5T618"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/rt9455_charger.c b/drivers/power/supply/rt9455_charger.c index 29161ae90245..594bb3b8a4d1 100644 --- a/drivers/power/supply/rt9455_charger.c +++ b/drivers/power/supply/rt9455_charger.c @@ -1731,11 +1731,13 @@ static const struct of_device_id rt9455_of_match[] = { }; MODULE_DEVICE_TABLE(of, rt9455_of_match); +#ifdef CONFIG_ACPI static const struct acpi_device_id rt9455_i2c_acpi_match[] = { { "RT945500", 0 }, { } }; MODULE_DEVICE_TABLE(acpi, rt9455_i2c_acpi_match); +#endif static struct i2c_driver rt9455_driver = { .probe = rt9455_probe, diff --git a/drivers/power/supply/s3c_adc_battery.c b/drivers/power/supply/s3c_adc_battery.c index 3d00b35cafc9..60b7f41ab063 100644 --- a/drivers/power/supply/s3c_adc_battery.c +++ b/drivers/power/supply/s3c_adc_battery.c @@ -22,7 +22,7 @@ #include <linux/init.h> #include <linux/module.h> -#include <plat/adc.h> +#include <linux/soc/samsung/s3c-adc.h> #define BAT_POLL_INTERVAL 10000 /* ms */ #define JITTER_DELAY 500 /* ms */ diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c index 7439753fac87..b6a538ebb378 100644 --- a/drivers/power/supply/sbs-battery.c +++ b/drivers/power/supply/sbs-battery.c @@ -193,7 +193,6 @@ struct sbs_info { struct power_supply *power_supply; bool is_present; struct gpio_desc *gpio_detect; - bool enable_detection; bool charger_broadcasts; int last_state; int poll_time; @@ -480,37 +479,6 @@ static bool sbs_bat_needs_calibration(struct i2c_client *client) return !!(ret & BIT(7)); } -static int sbs_get_battery_presence_and_health( - struct i2c_client *client, enum power_supply_property psp, - union power_supply_propval *val) -{ - int ret; - - /* Dummy command; if it succeeds, battery is present. */ - ret = sbs_read_word_data(client, sbs_data[REG_STATUS].addr); - - if (ret < 0) { /* battery not present*/ - if (psp == POWER_SUPPLY_PROP_PRESENT) { - val->intval = 0; - return 0; - } - return ret; - } - - if (psp == POWER_SUPPLY_PROP_PRESENT) - val->intval = 1; /* battery present */ - else { /* POWER_SUPPLY_PROP_HEALTH */ - if (sbs_bat_needs_calibration(client)) { - val->intval = POWER_SUPPLY_HEALTH_CALIBRATION_REQUIRED; - } else { - /* SBS spec doesn't have a general health command. */ - val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; - } - } - - return 0; -} - static int sbs_get_ti_battery_presence_and_health( struct i2c_client *client, enum power_supply_property psp, union power_supply_propval *val) @@ -569,6 +537,41 @@ static int sbs_get_ti_battery_presence_and_health( return 0; } +static int sbs_get_battery_presence_and_health( + struct i2c_client *client, enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sbs_info *chip = i2c_get_clientdata(client); + int ret; + + if (chip->flags & SBS_FLAGS_TI_BQ20ZX5) + return sbs_get_ti_battery_presence_and_health(client, psp, val); + + /* Dummy command; if it succeeds, battery is present. */ + ret = sbs_read_word_data(client, sbs_data[REG_STATUS].addr); + + if (ret < 0) { /* battery not present*/ + if (psp == POWER_SUPPLY_PROP_PRESENT) { + val->intval = 0; + return 0; + } + return ret; + } + + if (psp == POWER_SUPPLY_PROP_PRESENT) + val->intval = 1; /* battery present */ + else { /* POWER_SUPPLY_PROP_HEALTH */ + if (sbs_bat_needs_calibration(client)) { + val->intval = POWER_SUPPLY_HEALTH_CALIBRATION_REQUIRED; + } else { + /* SBS spec doesn't have a general health command. */ + val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; + } + } + + return 0; +} + static int sbs_get_battery_property(struct i2c_client *client, int reg_offset, enum power_supply_property psp, union power_supply_propval *val) @@ -871,12 +874,7 @@ static int sbs_get_property(struct power_supply *psy, switch (psp) { case POWER_SUPPLY_PROP_PRESENT: case POWER_SUPPLY_PROP_HEALTH: - if (chip->flags & SBS_FLAGS_TI_BQ20ZX5) - ret = sbs_get_ti_battery_presence_and_health(client, - psp, val); - else - ret = sbs_get_battery_presence_and_health(client, psp, - val); + ret = sbs_get_battery_presence_and_health(client, psp, val); /* this can only be true if no gpio is used */ if (psp == POWER_SUPPLY_PROP_PRESENT) @@ -967,32 +965,30 @@ static int sbs_get_property(struct power_supply *psy, return -EINVAL; } - if (!chip->enable_detection) - goto done; + if (!chip->gpio_detect && chip->is_present != (ret >= 0)) { + bool old_present = chip->is_present; + union power_supply_propval val; + int err = sbs_get_battery_presence_and_health( + client, POWER_SUPPLY_PROP_PRESENT, &val); - if (!chip->gpio_detect && - chip->is_present != (ret >= 0)) { - sbs_update_presence(chip, (ret >= 0)); - power_supply_changed(chip->power_supply); + sbs_update_presence(chip, !err && val.intval); + + if (old_present != chip->is_present) + power_supply_changed(chip->power_supply); } done: if (!ret) { /* Convert units to match requirements for power supply class */ sbs_unit_adjustment(client, psp, val); + dev_dbg(&client->dev, + "%s: property = %d, value = %x\n", __func__, + psp, val->intval); + } else if (!chip->is_present) { + /* battery not present, so return NODATA for properties */ + ret = -ENODATA; } - - dev_dbg(&client->dev, - "%s: property = %d, value = %x\n", __func__, psp, val->intval); - - if (ret && chip->is_present) - return ret; - - /* battery not present, so return NODATA for properties */ - if (ret) - return -ENODATA; - - return 0; + return ret; } static void sbs_supply_changed(struct sbs_info *chip) @@ -1098,7 +1094,6 @@ static int sbs_probe(struct i2c_client *client) chip->flags = (u32)(uintptr_t)device_get_match_data(&client->dev); chip->client = client; - chip->enable_detection = false; psy_cfg.of_node = client->dev.of_node; psy_cfg.drv_data = chip; chip->last_state = POWER_SUPPLY_STATUS_UNKNOWN; @@ -1159,15 +1154,19 @@ skip_gpio: * to the battery. */ if (!(force_load || chip->gpio_detect)) { - rc = sbs_read_word_data(client, sbs_data[REG_STATUS].addr); + union power_supply_propval val; - if (rc < 0) { - dev_err(&client->dev, "%s: Failed to get device status\n", - __func__); + rc = sbs_get_battery_presence_and_health( + client, POWER_SUPPLY_PROP_PRESENT, &val); + if (rc < 0 || !val.intval) { + dev_err(&client->dev, "Failed to get present status\n"); + rc = -ENODEV; goto exit_psupply; } } + INIT_DELAYED_WORK(&chip->work, sbs_delayed_work); + chip->power_supply = devm_power_supply_register(&client->dev, sbs_desc, &psy_cfg); if (IS_ERR(chip->power_supply)) { @@ -1180,10 +1179,6 @@ skip_gpio: dev_info(&client->dev, "%s: battery gas gauge device registered\n", client->name); - INIT_DELAYED_WORK(&chip->work, sbs_delayed_work); - - chip->enable_detection = true; - return 0; exit_psupply: diff --git a/drivers/power/supply/smb347-charger.c b/drivers/power/supply/smb347-charger.c index f99026d81f2a..d3bf35ed12ce 100644 --- a/drivers/power/supply/smb347-charger.c +++ b/drivers/power/supply/smb347-charger.c @@ -16,11 +16,18 @@ #include <linux/init.h> #include <linux/interrupt.h> #include <linux/i2c.h> -#include <linux/mutex.h> #include <linux/power_supply.h> -#include <linux/power/smb347-charger.h> +#include <linux/property.h> #include <linux/regmap.h> +#include <dt-bindings/power/summit,smb347-charger.h> + +/* Use the default compensation method */ +#define SMB3XX_SOFT_TEMP_COMPENSATE_DEFAULT -1 + +/* Use default factory programmed value for hard/soft temperature limit */ +#define SMB3XX_TEMP_USE_DEFAULT -273 + /* * Configuration registers. These are mirrored to volatile RAM and can be * written once %CMD_A_ALLOW_WRITE is set in %CMD_A register. They will be @@ -122,82 +129,140 @@ /** * struct smb347_charger - smb347 charger instance - * @lock: protects concurrent access to online variables * @dev: pointer to device * @regmap: pointer to driver regmap * @mains: power_supply instance for AC/DC power * @usb: power_supply instance for USB power - * @battery: power_supply instance for battery + * @id: SMB charger ID * @mains_online: is AC/DC input connected * @usb_online: is USB input connected * @charging_enabled: is charging enabled - * @pdata: pointer to platform data + * @max_charge_current: maximum current (in uA) the battery can be charged + * @max_charge_voltage: maximum voltage (in uV) the battery can be charged + * @pre_charge_current: current (in uA) to use in pre-charging phase + * @termination_current: current (in uA) used to determine when the + * charging cycle terminates + * @pre_to_fast_voltage: voltage (in uV) treshold used for transitioning to + * pre-charge to fast charge mode + * @mains_current_limit: maximum input current drawn from AC/DC input (in uA) + * @usb_hc_current_limit: maximum input high current (in uA) drawn from USB + * input + * @chip_temp_threshold: die temperature where device starts limiting charge + * current [%100 - %130] (in degree C) + * @soft_cold_temp_limit: soft cold temperature limit [%0 - %15] (in degree C), + * granularity is 5 deg C. + * @soft_hot_temp_limit: soft hot temperature limit [%40 - %55] (in degree C), + * granularity is 5 deg C. + * @hard_cold_temp_limit: hard cold temperature limit [%-5 - %10] (in degree C), + * granularity is 5 deg C. + * @hard_hot_temp_limit: hard hot temperature limit [%50 - %65] (in degree C), + * granularity is 5 deg C. + * @suspend_on_hard_temp_limit: suspend charging when hard limit is hit + * @soft_temp_limit_compensation: compensation method when soft temperature + * limit is hit + * @charge_current_compensation: current (in uA) for charging compensation + * current when temperature hits soft limits + * @use_mains: AC/DC input can be used + * @use_usb: USB input can be used + * @use_usb_otg: USB OTG output can be used (not implemented yet) + * @enable_control: how charging enable/disable is controlled + * (driver/pin controls) + * + * @use_main, @use_usb, and @use_usb_otg are means to enable/disable + * hardware support for these. This is useful when we want to have for + * example OTG charging controlled via OTG transceiver driver and not by + * the SMB347 hardware. + * + * Hard and soft temperature limit values are given as described in the + * device data sheet and assuming NTC beta value is %3750. Even if this is + * not the case, these values should be used. They can be mapped to the + * corresponding NTC beta values with the help of table %2 in the data + * sheet. So for example if NTC beta is %3375 and we want to program hard + * hot limit to be %53 deg C, @hard_hot_temp_limit should be set to %50. + * + * If zero value is given in any of the current and voltage values, the + * factory programmed default will be used. For soft/hard temperature + * values, pass in %SMB3XX_TEMP_USE_DEFAULT instead. */ struct smb347_charger { - struct mutex lock; struct device *dev; struct regmap *regmap; struct power_supply *mains; struct power_supply *usb; - struct power_supply *battery; + unsigned int id; bool mains_online; bool usb_online; bool charging_enabled; - const struct smb347_charger_platform_data *pdata; + + unsigned int max_charge_current; + unsigned int max_charge_voltage; + unsigned int pre_charge_current; + unsigned int termination_current; + unsigned int pre_to_fast_voltage; + unsigned int mains_current_limit; + unsigned int usb_hc_current_limit; + unsigned int chip_temp_threshold; + int soft_cold_temp_limit; + int soft_hot_temp_limit; + int hard_cold_temp_limit; + int hard_hot_temp_limit; + bool suspend_on_hard_temp_limit; + unsigned int soft_temp_limit_compensation; + unsigned int charge_current_compensation; + bool use_mains; + bool use_usb; + bool use_usb_otg; + unsigned int enable_control; }; -/* Fast charge current in uA */ -static const unsigned int fcc_tbl[] = { - 700000, - 900000, - 1200000, - 1500000, - 1800000, - 2000000, - 2200000, - 2500000, +enum smb_charger_chipid { + SMB345, + SMB347, + SMB358, + NUM_CHIP_TYPES, }; +/* Fast charge current in uA */ +static const unsigned int fcc_tbl[NUM_CHIP_TYPES][8] = { + [SMB345] = { 200000, 450000, 600000, 900000, + 1300000, 1500000, 1800000, 2000000 }, + [SMB347] = { 700000, 900000, 1200000, 1500000, + 1800000, 2000000, 2200000, 2500000 }, + [SMB358] = { 200000, 450000, 600000, 900000, + 1300000, 1500000, 1800000, 2000000 }, +}; /* Pre-charge current in uA */ -static const unsigned int pcc_tbl[] = { - 100000, - 150000, - 200000, - 250000, +static const unsigned int pcc_tbl[NUM_CHIP_TYPES][4] = { + [SMB345] = { 150000, 250000, 350000, 450000 }, + [SMB347] = { 100000, 150000, 200000, 250000 }, + [SMB358] = { 150000, 250000, 350000, 450000 }, }; /* Termination current in uA */ -static const unsigned int tc_tbl[] = { - 37500, - 50000, - 100000, - 150000, - 200000, - 250000, - 500000, - 600000, +static const unsigned int tc_tbl[NUM_CHIP_TYPES][8] = { + [SMB345] = { 30000, 40000, 60000, 80000, + 100000, 125000, 150000, 200000 }, + [SMB347] = { 37500, 50000, 100000, 150000, + 200000, 250000, 500000, 600000 }, + [SMB358] = { 30000, 40000, 60000, 80000, + 100000, 125000, 150000, 200000 }, }; /* Input current limit in uA */ -static const unsigned int icl_tbl[] = { - 300000, - 500000, - 700000, - 900000, - 1200000, - 1500000, - 1800000, - 2000000, - 2200000, - 2500000, +static const unsigned int icl_tbl[NUM_CHIP_TYPES][10] = { + [SMB345] = { 300000, 500000, 700000, 1000000, 1500000, + 1800000, 2000000, 2000000, 2000000, 2000000 }, + [SMB347] = { 300000, 500000, 700000, 900000, 1200000, + 1500000, 1800000, 2000000, 2200000, 2500000 }, + [SMB358] = { 300000, 500000, 700000, 1000000, 1500000, + 1800000, 2000000, 2000000, 2000000, 2000000 }, }; /* Charge current compensation in uA */ -static const unsigned int ccc_tbl[] = { - 250000, - 700000, - 900000, - 1200000, +static const unsigned int ccc_tbl[NUM_CHIP_TYPES][4] = { + [SMB345] = { 200000, 450000, 600000, 900000 }, + [SMB347] = { 250000, 700000, 900000, 1200000 }, + [SMB358] = { 200000, 450000, 600000, 900000 }, }; /* Convert register value to current using lookup table */ @@ -242,16 +307,14 @@ static int smb347_update_ps_status(struct smb347_charger *smb) * Dc and usb are set depending on whether they are enabled in * platform data _and_ whether corresponding undervoltage is set. */ - if (smb->pdata->use_mains) + if (smb->use_mains) dc = !(val & IRQSTAT_E_DCIN_UV_STAT); - if (smb->pdata->use_usb) + if (smb->use_usb) usb = !(val & IRQSTAT_E_USBIN_UV_STAT); - mutex_lock(&smb->lock); ret = smb->mains_online != dc || smb->usb_online != usb; smb->mains_online = dc; smb->usb_online = usb; - mutex_unlock(&smb->lock); return ret; } @@ -267,13 +330,7 @@ static int smb347_update_ps_status(struct smb347_charger *smb) */ static bool smb347_is_ps_online(struct smb347_charger *smb) { - bool ret; - - mutex_lock(&smb->lock); - ret = smb->usb_online || smb->mains_online; - mutex_unlock(&smb->lock); - - return ret; + return smb->usb_online || smb->mains_online; } /** @@ -302,19 +359,18 @@ static int smb347_charging_set(struct smb347_charger *smb, bool enable) { int ret = 0; - if (smb->pdata->enable_control != SMB347_CHG_ENABLE_SW) { + if (smb->enable_control != SMB3XX_CHG_ENABLE_SW) { dev_dbg(smb->dev, "charging enable/disable in SW disabled\n"); return 0; } - mutex_lock(&smb->lock); if (smb->charging_enabled != enable) { ret = regmap_update_bits(smb->regmap, CMD_A, CMD_A_CHG_ENABLED, enable ? CMD_A_CHG_ENABLED : 0); if (!ret) smb->charging_enabled = enable; } - mutex_unlock(&smb->lock); + return ret; } @@ -352,11 +408,12 @@ static int smb347_start_stop_charging(struct smb347_charger *smb) static int smb347_set_charge_current(struct smb347_charger *smb) { + unsigned int id = smb->id; int ret; - if (smb->pdata->max_charge_current) { - ret = current_to_hw(fcc_tbl, ARRAY_SIZE(fcc_tbl), - smb->pdata->max_charge_current); + if (smb->max_charge_current) { + ret = current_to_hw(fcc_tbl[id], ARRAY_SIZE(fcc_tbl[id]), + smb->max_charge_current); if (ret < 0) return ret; @@ -367,9 +424,9 @@ static int smb347_set_charge_current(struct smb347_charger *smb) return ret; } - if (smb->pdata->pre_charge_current) { - ret = current_to_hw(pcc_tbl, ARRAY_SIZE(pcc_tbl), - smb->pdata->pre_charge_current); + if (smb->pre_charge_current) { + ret = current_to_hw(pcc_tbl[id], ARRAY_SIZE(pcc_tbl[id]), + smb->pre_charge_current); if (ret < 0) return ret; @@ -380,9 +437,9 @@ static int smb347_set_charge_current(struct smb347_charger *smb) return ret; } - if (smb->pdata->termination_current) { - ret = current_to_hw(tc_tbl, ARRAY_SIZE(tc_tbl), - smb->pdata->termination_current); + if (smb->termination_current) { + ret = current_to_hw(tc_tbl[id], ARRAY_SIZE(tc_tbl[id]), + smb->termination_current); if (ret < 0) return ret; @@ -397,11 +454,12 @@ static int smb347_set_charge_current(struct smb347_charger *smb) static int smb347_set_current_limits(struct smb347_charger *smb) { + unsigned int id = smb->id; int ret; - if (smb->pdata->mains_current_limit) { - ret = current_to_hw(icl_tbl, ARRAY_SIZE(icl_tbl), - smb->pdata->mains_current_limit); + if (smb->mains_current_limit) { + ret = current_to_hw(icl_tbl[id], ARRAY_SIZE(icl_tbl[id]), + smb->mains_current_limit); if (ret < 0) return ret; @@ -412,9 +470,9 @@ static int smb347_set_current_limits(struct smb347_charger *smb) return ret; } - if (smb->pdata->usb_hc_current_limit) { - ret = current_to_hw(icl_tbl, ARRAY_SIZE(icl_tbl), - smb->pdata->usb_hc_current_limit); + if (smb->usb_hc_current_limit) { + ret = current_to_hw(icl_tbl[id], ARRAY_SIZE(icl_tbl[id]), + smb->usb_hc_current_limit); if (ret < 0) return ret; @@ -431,8 +489,8 @@ static int smb347_set_voltage_limits(struct smb347_charger *smb) { int ret; - if (smb->pdata->pre_to_fast_voltage) { - ret = smb->pdata->pre_to_fast_voltage; + if (smb->pre_to_fast_voltage) { + ret = smb->pre_to_fast_voltage; /* uV */ ret = clamp_val(ret, 2400000, 3000000) - 2400000; @@ -445,8 +503,8 @@ static int smb347_set_voltage_limits(struct smb347_charger *smb) return ret; } - if (smb->pdata->max_charge_voltage) { - ret = smb->pdata->max_charge_voltage; + if (smb->max_charge_voltage) { + ret = smb->max_charge_voltage; /* uV */ ret = clamp_val(ret, 3500000, 4500000) - 3500000; @@ -463,12 +521,13 @@ static int smb347_set_voltage_limits(struct smb347_charger *smb) static int smb347_set_temp_limits(struct smb347_charger *smb) { + unsigned int id = smb->id; bool enable_therm_monitor = false; int ret = 0; int val; - if (smb->pdata->chip_temp_threshold) { - val = smb->pdata->chip_temp_threshold; + if (smb->chip_temp_threshold) { + val = smb->chip_temp_threshold; /* degree C */ val = clamp_val(val, 100, 130) - 100; @@ -481,8 +540,8 @@ static int smb347_set_temp_limits(struct smb347_charger *smb) return ret; } - if (smb->pdata->soft_cold_temp_limit != SMB347_TEMP_USE_DEFAULT) { - val = smb->pdata->soft_cold_temp_limit; + if (smb->soft_cold_temp_limit != SMB3XX_TEMP_USE_DEFAULT) { + val = smb->soft_cold_temp_limit; val = clamp_val(val, 0, 15); val /= 5; @@ -498,8 +557,8 @@ static int smb347_set_temp_limits(struct smb347_charger *smb) enable_therm_monitor = true; } - if (smb->pdata->soft_hot_temp_limit != SMB347_TEMP_USE_DEFAULT) { - val = smb->pdata->soft_hot_temp_limit; + if (smb->soft_hot_temp_limit != SMB3XX_TEMP_USE_DEFAULT) { + val = smb->soft_hot_temp_limit; val = clamp_val(val, 40, 55) - 40; val /= 5; @@ -513,8 +572,8 @@ static int smb347_set_temp_limits(struct smb347_charger *smb) enable_therm_monitor = true; } - if (smb->pdata->hard_cold_temp_limit != SMB347_TEMP_USE_DEFAULT) { - val = smb->pdata->hard_cold_temp_limit; + if (smb->hard_cold_temp_limit != SMB3XX_TEMP_USE_DEFAULT) { + val = smb->hard_cold_temp_limit; val = clamp_val(val, -5, 10) + 5; val /= 5; @@ -530,8 +589,8 @@ static int smb347_set_temp_limits(struct smb347_charger *smb) enable_therm_monitor = true; } - if (smb->pdata->hard_hot_temp_limit != SMB347_TEMP_USE_DEFAULT) { - val = smb->pdata->hard_hot_temp_limit; + if (smb->hard_hot_temp_limit != SMB3XX_TEMP_USE_DEFAULT) { + val = smb->hard_hot_temp_limit; val = clamp_val(val, 50, 65) - 50; val /= 5; @@ -562,16 +621,16 @@ static int smb347_set_temp_limits(struct smb347_charger *smb) return ret; } - if (smb->pdata->suspend_on_hard_temp_limit) { + if (smb->suspend_on_hard_temp_limit) { ret = regmap_update_bits(smb->regmap, CFG_SYSOK, CFG_SYSOK_SUSPEND_HARD_LIMIT_DISABLED, 0); if (ret < 0) return ret; } - if (smb->pdata->soft_temp_limit_compensation != - SMB347_SOFT_TEMP_COMPENSATE_DEFAULT) { - val = smb->pdata->soft_temp_limit_compensation & 0x3; + if (smb->soft_temp_limit_compensation != + SMB3XX_SOFT_TEMP_COMPENSATE_DEFAULT) { + val = smb->soft_temp_limit_compensation & 0x3; ret = regmap_update_bits(smb->regmap, CFG_THERM, CFG_THERM_SOFT_HOT_COMPENSATION_MASK, @@ -586,9 +645,9 @@ static int smb347_set_temp_limits(struct smb347_charger *smb) return ret; } - if (smb->pdata->charge_current_compensation) { - val = current_to_hw(ccc_tbl, ARRAY_SIZE(ccc_tbl), - smb->pdata->charge_current_compensation); + if (smb->charge_current_compensation) { + val = current_to_hw(ccc_tbl[id], ARRAY_SIZE(ccc_tbl[id]), + smb->charge_current_compensation); if (val < 0) return val; @@ -647,7 +706,7 @@ static int smb347_hw_init(struct smb347_charger *smb) goto fail; /* If USB charging is disabled we put the USB in suspend mode */ - if (!smb->pdata->use_usb) { + if (!smb->use_usb) { ret = regmap_update_bits(smb->regmap, CMD_A, CMD_A_SUSPEND_ENABLED, CMD_A_SUSPEND_ENABLED); @@ -660,7 +719,7 @@ static int smb347_hw_init(struct smb347_charger *smb) * support for driving VBUS. Otherwise we disable it. */ ret = regmap_update_bits(smb->regmap, CFG_OTHER, CFG_OTHER_RID_MASK, - smb->pdata->use_usb_otg ? CFG_OTHER_RID_ENABLED_AUTO_OTG : 0); + smb->use_usb_otg ? CFG_OTHER_RID_ENABLED_AUTO_OTG : 0); if (ret < 0) goto fail; @@ -669,11 +728,11 @@ static int smb347_hw_init(struct smb347_charger *smb) * command register unless pin control is specified in the platform * data. */ - switch (smb->pdata->enable_control) { - case SMB347_CHG_ENABLE_PIN_ACTIVE_LOW: + switch (smb->enable_control) { + case SMB3XX_CHG_ENABLE_PIN_ACTIVE_LOW: val = CFG_PIN_EN_CTRL_ACTIVE_LOW; break; - case SMB347_CHG_ENABLE_PIN_ACTIVE_HIGH: + case SMB3XX_CHG_ENABLE_PIN_ACTIVE_HIGH: val = CFG_PIN_EN_CTRL_ACTIVE_HIGH; break; default: @@ -742,7 +801,10 @@ static irqreturn_t smb347_interrupt(int irq, void *data) */ if (stat_c & STAT_C_CHARGER_ERROR) { dev_err(smb->dev, "charging stopped due to charger error\n"); - power_supply_changed(smb->battery); + if (smb->use_mains) + power_supply_changed(smb->mains); + if (smb->use_usb) + power_supply_changed(smb->usb); handled = true; } @@ -752,8 +814,12 @@ static irqreturn_t smb347_interrupt(int irq, void *data) * disabled by the hardware. */ if (irqstat_c & (IRQSTAT_C_TERMINATION_IRQ | IRQSTAT_C_TAPER_IRQ)) { - if (irqstat_c & IRQSTAT_C_TERMINATION_STAT) - power_supply_changed(smb->battery); + if (irqstat_c & IRQSTAT_C_TERMINATION_STAT) { + if (smb->use_mains) + power_supply_changed(smb->mains); + if (smb->use_usb) + power_supply_changed(smb->usb); + } dev_dbg(smb->dev, "going to HW maintenance mode\n"); handled = true; } @@ -767,7 +833,10 @@ static irqreturn_t smb347_interrupt(int irq, void *data) if (irqstat_d & IRQSTAT_D_CHARGE_TIMEOUT_STAT) dev_warn(smb->dev, "charging stopped due to timeout\n"); - power_supply_changed(smb->battery); + if (smb->use_mains) + power_supply_changed(smb->mains); + if (smb->use_usb) + power_supply_changed(smb->usb); handled = true; } @@ -778,9 +847,9 @@ static irqreturn_t smb347_interrupt(int irq, void *data) if (irqstat_e & (IRQSTAT_E_USBIN_UV_IRQ | IRQSTAT_E_DCIN_UV_IRQ)) { if (smb347_update_ps_status(smb) > 0) { smb347_start_stop_charging(smb); - if (smb->pdata->use_mains) + if (smb->use_mains) power_supply_changed(smb->mains); - if (smb->pdata->use_usb) + if (smb->use_usb) power_supply_changed(smb->usb); } handled = true; @@ -835,22 +904,17 @@ static inline int smb347_irq_disable(struct smb347_charger *smb) static int smb347_irq_init(struct smb347_charger *smb, struct i2c_client *client) { - const struct smb347_charger_platform_data *pdata = smb->pdata; - int ret, irq = gpio_to_irq(pdata->irq_gpio); - - ret = gpio_request_one(pdata->irq_gpio, GPIOF_IN, client->name); - if (ret < 0) - goto fail; + int ret; - ret = request_threaded_irq(irq, NULL, smb347_interrupt, - IRQF_TRIGGER_FALLING | IRQF_ONESHOT, - client->name, smb); + ret = devm_request_threaded_irq(smb->dev, client->irq, NULL, + smb347_interrupt, IRQF_ONESHOT, + client->name, smb); if (ret < 0) - goto fail_gpio; + return ret; ret = smb347_set_writable(smb, true); if (ret < 0) - goto fail_irq; + return ret; /* * Configure the STAT output to be suitable for interrupts: disable @@ -860,20 +924,10 @@ static int smb347_irq_init(struct smb347_charger *smb, CFG_STAT_ACTIVE_HIGH | CFG_STAT_DISABLED, CFG_STAT_DISABLED); if (ret < 0) - goto fail_readonly; + client->irq = 0; smb347_set_writable(smb, false); - client->irq = irq; - return 0; -fail_readonly: - smb347_set_writable(smb, false); -fail_irq: - free_irq(irq, smb); -fail_gpio: - gpio_free(pdata->irq_gpio); -fail: - client->irq = 0; return ret; } @@ -883,6 +937,7 @@ fail: */ static int get_const_charge_current(struct smb347_charger *smb) { + unsigned int id = smb->id; int ret, intval; unsigned int v; @@ -898,10 +953,12 @@ static int get_const_charge_current(struct smb347_charger *smb) * and we can detect which table to use from bit 5. */ if (v & 0x20) { - intval = hw_to_current(fcc_tbl, ARRAY_SIZE(fcc_tbl), v & 7); + intval = hw_to_current(fcc_tbl[id], + ARRAY_SIZE(fcc_tbl[id]), v & 7); } else { v >>= 3; - intval = hw_to_current(pcc_tbl, ARRAY_SIZE(pcc_tbl), v & 7); + intval = hw_to_current(pcc_tbl[id], + ARRAY_SIZE(pcc_tbl[id]), v & 7); } return intval; @@ -932,95 +989,19 @@ static int get_const_charge_voltage(struct smb347_charger *smb) return intval; } -static int smb347_mains_get_property(struct power_supply *psy, - enum power_supply_property prop, - union power_supply_propval *val) -{ - struct smb347_charger *smb = power_supply_get_drvdata(psy); - int ret; - - switch (prop) { - case POWER_SUPPLY_PROP_ONLINE: - val->intval = smb->mains_online; - break; - - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: - ret = get_const_charge_voltage(smb); - if (ret < 0) - return ret; - else - val->intval = ret; - break; - - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: - ret = get_const_charge_current(smb); - if (ret < 0) - return ret; - else - val->intval = ret; - break; - - default: - return -EINVAL; - } - - return 0; -} - -static enum power_supply_property smb347_mains_properties[] = { - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, - POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, -}; - -static int smb347_usb_get_property(struct power_supply *psy, - enum power_supply_property prop, - union power_supply_propval *val) -{ - struct smb347_charger *smb = power_supply_get_drvdata(psy); - int ret; - - switch (prop) { - case POWER_SUPPLY_PROP_ONLINE: - val->intval = smb->usb_online; - break; - - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: - ret = get_const_charge_voltage(smb); - if (ret < 0) - return ret; - else - val->intval = ret; - break; - - case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: - ret = get_const_charge_current(smb); - if (ret < 0) - return ret; - else - val->intval = ret; - break; - - default: - return -EINVAL; - } - - return 0; -} - -static enum power_supply_property smb347_usb_properties[] = { - POWER_SUPPLY_PROP_ONLINE, - POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, - POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, -}; - -static int smb347_get_charging_status(struct smb347_charger *smb) +static int smb347_get_charging_status(struct smb347_charger *smb, + struct power_supply *psy) { int ret, status; unsigned int val; - if (!smb347_is_ps_online(smb)) - return POWER_SUPPLY_STATUS_DISCHARGING; + if (psy->desc->type == POWER_SUPPLY_TYPE_USB) { + if (!smb->usb_online) + return POWER_SUPPLY_STATUS_DISCHARGING; + } else { + if (!smb->mains_online) + return POWER_SUPPLY_STATUS_DISCHARGING; + } ret = regmap_read(smb->regmap, STAT_C, &val); if (ret < 0) @@ -1059,29 +1040,29 @@ static int smb347_get_charging_status(struct smb347_charger *smb) return status; } -static int smb347_battery_get_property(struct power_supply *psy, - enum power_supply_property prop, - union power_supply_propval *val) +static int smb347_get_property_locked(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) { struct smb347_charger *smb = power_supply_get_drvdata(psy); - const struct smb347_charger_platform_data *pdata = smb->pdata; int ret; - ret = smb347_update_ps_status(smb); - if (ret < 0) - return ret; - switch (prop) { case POWER_SUPPLY_PROP_STATUS: - ret = smb347_get_charging_status(smb); + ret = smb347_get_charging_status(smb, psy); if (ret < 0) return ret; val->intval = ret; break; case POWER_SUPPLY_PROP_CHARGE_TYPE: - if (!smb347_is_ps_online(smb)) - return -ENODATA; + if (psy->desc->type == POWER_SUPPLY_TYPE_USB) { + if (!smb->usb_online) + return -ENODATA; + } else { + if (!smb->mains_online) + return -ENODATA; + } /* * We handle trickle and pre-charging the same, and taper @@ -1100,24 +1081,25 @@ static int smb347_battery_get_property(struct power_supply *psy, } break; - case POWER_SUPPLY_PROP_TECHNOLOGY: - val->intval = pdata->battery_info.technology; - break; - - case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: - val->intval = pdata->battery_info.voltage_min_design; - break; - - case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: - val->intval = pdata->battery_info.voltage_max_design; + case POWER_SUPPLY_PROP_ONLINE: + if (psy->desc->type == POWER_SUPPLY_TYPE_USB) + val->intval = smb->usb_online; + else + val->intval = smb->mains_online; break; - case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: - val->intval = pdata->battery_info.charge_full_design; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + ret = get_const_charge_voltage(smb); + if (ret < 0) + return ret; + val->intval = ret; break; - case POWER_SUPPLY_PROP_MODEL_NAME: - val->strval = pdata->battery_info.name; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + ret = get_const_charge_current(smb); + if (ret < 0) + return ret; + val->intval = ret; break; default: @@ -1127,14 +1109,27 @@ static int smb347_battery_get_property(struct power_supply *psy, return 0; } -static enum power_supply_property smb347_battery_properties[] = { +static int smb347_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct smb347_charger *smb = power_supply_get_drvdata(psy); + struct i2c_client *client = to_i2c_client(smb->dev); + int ret; + + disable_irq(client->irq); + ret = smb347_get_property_locked(psy, prop, val); + enable_irq(client->irq); + + return ret; +} + +static enum power_supply_property smb347_properties[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_CHARGE_TYPE, - POWER_SUPPLY_PROP_TECHNOLOGY, - POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, - POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, - POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, - POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, }; static bool smb347_volatile_reg(struct device *dev, unsigned int reg) @@ -1180,6 +1175,96 @@ static bool smb347_readable_reg(struct device *dev, unsigned int reg) return smb347_volatile_reg(dev, reg); } +static void smb347_dt_parse_dev_info(struct smb347_charger *smb) +{ + struct device *dev = smb->dev; + + smb->soft_temp_limit_compensation = + SMB3XX_SOFT_TEMP_COMPENSATE_DEFAULT; + /* + * These properties come from the battery info, still we need to + * pre-initialize the values. See smb347_get_battery_info() below. + */ + smb->soft_cold_temp_limit = SMB3XX_TEMP_USE_DEFAULT; + smb->hard_cold_temp_limit = SMB3XX_TEMP_USE_DEFAULT; + smb->soft_hot_temp_limit = SMB3XX_TEMP_USE_DEFAULT; + smb->hard_hot_temp_limit = SMB3XX_TEMP_USE_DEFAULT; + + /* Charging constraints */ + device_property_read_u32(dev, "summit,fast-voltage-threshold-microvolt", + &smb->pre_to_fast_voltage); + device_property_read_u32(dev, "summit,mains-current-limit-microamp", + &smb->mains_current_limit); + device_property_read_u32(dev, "summit,usb-current-limit-microamp", + &smb->usb_hc_current_limit); + + /* For thermometer monitoring */ + device_property_read_u32(dev, "summit,chip-temperature-threshold-celsius", + &smb->chip_temp_threshold); + device_property_read_u32(dev, "summit,soft-compensation-method", + &smb->soft_temp_limit_compensation); + device_property_read_u32(dev, "summit,charge-current-compensation-microamp", + &smb->charge_current_compensation); + + /* Supported charging mode */ + smb->use_mains = device_property_read_bool(dev, "summit,enable-mains-charging"); + smb->use_usb = device_property_read_bool(dev, "summit,enable-usb-charging"); + smb->use_usb_otg = device_property_read_bool(dev, "summit,enable-otg-charging"); + + /* Select charging control */ + device_property_read_u32(dev, "summit,enable-charge-control", + &smb->enable_control); +} + +static int smb347_get_battery_info(struct smb347_charger *smb) +{ + struct power_supply_battery_info info = {}; + struct power_supply *supply; + int err; + + if (smb->mains) + supply = smb->mains; + else + supply = smb->usb; + + err = power_supply_get_battery_info(supply, &info); + if (err == -ENXIO || err == -ENODEV) + return 0; + if (err) + return err; + + if (info.constant_charge_current_max_ua != -EINVAL) + smb->max_charge_current = info.constant_charge_current_max_ua; + + if (info.constant_charge_voltage_max_uv != -EINVAL) + smb->max_charge_voltage = info.constant_charge_voltage_max_uv; + + if (info.precharge_current_ua != -EINVAL) + smb->pre_charge_current = info.precharge_current_ua; + + if (info.charge_term_current_ua != -EINVAL) + smb->termination_current = info.charge_term_current_ua; + + if (info.temp_alert_min != INT_MIN) + smb->soft_cold_temp_limit = info.temp_alert_min; + + if (info.temp_alert_max != INT_MAX) + smb->soft_hot_temp_limit = info.temp_alert_max; + + if (info.temp_min != INT_MIN) + smb->hard_cold_temp_limit = info.temp_min; + + if (info.temp_max != INT_MAX) + smb->hard_hot_temp_limit = info.temp_max; + + /* Suspend when battery temperature is outside hard limits */ + if (smb->hard_cold_temp_limit != SMB3XX_TEMP_USE_DEFAULT || + smb->hard_hot_temp_limit != SMB3XX_TEMP_USE_DEFAULT) + smb->suspend_on_hard_temp_limit = true; + + return 0; +} + static const struct regmap_config smb347_regmap = { .reg_bits = 8, .val_bits = 8, @@ -1191,98 +1276,71 @@ static const struct regmap_config smb347_regmap = { static const struct power_supply_desc smb347_mains_desc = { .name = "smb347-mains", .type = POWER_SUPPLY_TYPE_MAINS, - .get_property = smb347_mains_get_property, - .properties = smb347_mains_properties, - .num_properties = ARRAY_SIZE(smb347_mains_properties), + .get_property = smb347_get_property, + .properties = smb347_properties, + .num_properties = ARRAY_SIZE(smb347_properties), }; static const struct power_supply_desc smb347_usb_desc = { .name = "smb347-usb", .type = POWER_SUPPLY_TYPE_USB, - .get_property = smb347_usb_get_property, - .properties = smb347_usb_properties, - .num_properties = ARRAY_SIZE(smb347_usb_properties), -}; - -static const struct power_supply_desc smb347_battery_desc = { - .name = "smb347-battery", - .type = POWER_SUPPLY_TYPE_BATTERY, - .get_property = smb347_battery_get_property, - .properties = smb347_battery_properties, - .num_properties = ARRAY_SIZE(smb347_battery_properties), + .get_property = smb347_get_property, + .properties = smb347_properties, + .num_properties = ARRAY_SIZE(smb347_properties), }; static int smb347_probe(struct i2c_client *client, const struct i2c_device_id *id) { - static char *battery[] = { "smb347-battery" }; - const struct smb347_charger_platform_data *pdata; - struct power_supply_config mains_usb_cfg = {}, battery_cfg = {}; + struct power_supply_config mains_usb_cfg = {}; struct device *dev = &client->dev; struct smb347_charger *smb; int ret; - pdata = dev->platform_data; - if (!pdata) - return -EINVAL; - - if (!pdata->use_mains && !pdata->use_usb) - return -EINVAL; - smb = devm_kzalloc(dev, sizeof(*smb), GFP_KERNEL); if (!smb) return -ENOMEM; - + smb->dev = &client->dev; + smb->id = id->driver_data; i2c_set_clientdata(client, smb); - mutex_init(&smb->lock); - smb->dev = &client->dev; - smb->pdata = pdata; + smb347_dt_parse_dev_info(smb); + if (!smb->use_mains && !smb->use_usb) + return -EINVAL; smb->regmap = devm_regmap_init_i2c(client, &smb347_regmap); if (IS_ERR(smb->regmap)) return PTR_ERR(smb->regmap); - ret = smb347_hw_init(smb); - if (ret < 0) - return ret; - - mains_usb_cfg.supplied_to = battery; - mains_usb_cfg.num_supplicants = ARRAY_SIZE(battery); mains_usb_cfg.drv_data = smb; - if (smb->pdata->use_mains) { - smb->mains = power_supply_register(dev, &smb347_mains_desc, - &mains_usb_cfg); + mains_usb_cfg.of_node = dev->of_node; + if (smb->use_mains) { + smb->mains = devm_power_supply_register(dev, &smb347_mains_desc, + &mains_usb_cfg); if (IS_ERR(smb->mains)) return PTR_ERR(smb->mains); } - if (smb->pdata->use_usb) { - smb->usb = power_supply_register(dev, &smb347_usb_desc, - &mains_usb_cfg); - if (IS_ERR(smb->usb)) { - if (smb->pdata->use_mains) - power_supply_unregister(smb->mains); + if (smb->use_usb) { + smb->usb = devm_power_supply_register(dev, &smb347_usb_desc, + &mains_usb_cfg); + if (IS_ERR(smb->usb)) return PTR_ERR(smb->usb); - } } - battery_cfg.drv_data = smb; - smb->battery = power_supply_register(dev, &smb347_battery_desc, - &battery_cfg); - if (IS_ERR(smb->battery)) { - if (smb->pdata->use_usb) - power_supply_unregister(smb->usb); - if (smb->pdata->use_mains) - power_supply_unregister(smb->mains); - return PTR_ERR(smb->battery); - } + ret = smb347_get_battery_info(smb); + if (ret) + return ret; + + ret = smb347_hw_init(smb); + if (ret < 0) + return ret; /* * Interrupt pin is optional. If it is connected, we setup the * interrupt support here. */ - if (pdata->irq_gpio >= 0) { + if (client->irq) { ret = smb347_irq_init(smb, client); if (ret < 0) { dev_warn(dev, "failed to initialize IRQ: %d\n", ret); @@ -1299,29 +1357,31 @@ static int smb347_remove(struct i2c_client *client) { struct smb347_charger *smb = i2c_get_clientdata(client); - if (client->irq) { + if (client->irq) smb347_irq_disable(smb); - free_irq(client->irq, smb); - gpio_free(smb->pdata->irq_gpio); - } - - power_supply_unregister(smb->battery); - if (smb->pdata->use_usb) - power_supply_unregister(smb->usb); - if (smb->pdata->use_mains) - power_supply_unregister(smb->mains); return 0; } static const struct i2c_device_id smb347_id[] = { - { "smb347", 0 }, - { } + { "smb345", SMB345 }, + { "smb347", SMB347 }, + { "smb358", SMB358 }, + { }, }; MODULE_DEVICE_TABLE(i2c, smb347_id); +static const struct of_device_id smb3xx_of_match[] = { + { .compatible = "summit,smb345" }, + { .compatible = "summit,smb347" }, + { .compatible = "summit,smb358" }, + { }, +}; +MODULE_DEVICE_TABLE(of, smb3xx_of_match); + static struct i2c_driver smb347_driver = { .driver = { .name = "smb347", + .of_match_table = smb3xx_of_match, }, .probe = smb347_probe, .remove = smb347_remove, diff --git a/drivers/power/supply/test_power.c b/drivers/power/supply/test_power.c index 04acd76bbaa1..5f510ddc946d 100644 --- a/drivers/power/supply/test_power.c +++ b/drivers/power/supply/test_power.c @@ -352,8 +352,8 @@ static int param_set_ac_online(const char *key, const struct kernel_param *kp) static int param_get_ac_online(char *buffer, const struct kernel_param *kp) { - strcpy(buffer, map_get_key(map_ac_online, ac_online, "unknown")); - return strlen(buffer); + return sprintf(buffer, "%s\n", + map_get_key(map_ac_online, ac_online, "unknown")); } static int param_set_usb_online(const char *key, const struct kernel_param *kp) @@ -365,8 +365,8 @@ static int param_set_usb_online(const char *key, const struct kernel_param *kp) static int param_get_usb_online(char *buffer, const struct kernel_param *kp) { - strcpy(buffer, map_get_key(map_ac_online, usb_online, "unknown")); - return strlen(buffer); + return sprintf(buffer, "%s\n", + map_get_key(map_ac_online, usb_online, "unknown")); } static int param_set_battery_status(const char *key, @@ -379,8 +379,8 @@ static int param_set_battery_status(const char *key, static int param_get_battery_status(char *buffer, const struct kernel_param *kp) { - strcpy(buffer, map_get_key(map_status, battery_status, "unknown")); - return strlen(buffer); + return sprintf(buffer, "%s\n", + map_get_key(map_ac_online, battery_status, "unknown")); } static int param_set_battery_health(const char *key, @@ -393,8 +393,8 @@ static int param_set_battery_health(const char *key, static int param_get_battery_health(char *buffer, const struct kernel_param *kp) { - strcpy(buffer, map_get_key(map_health, battery_health, "unknown")); - return strlen(buffer); + return sprintf(buffer, "%s\n", + map_get_key(map_ac_online, battery_health, "unknown")); } static int param_set_battery_present(const char *key, @@ -408,8 +408,8 @@ static int param_set_battery_present(const char *key, static int param_get_battery_present(char *buffer, const struct kernel_param *kp) { - strcpy(buffer, map_get_key(map_present, battery_present, "unknown")); - return strlen(buffer); + return sprintf(buffer, "%s\n", + map_get_key(map_ac_online, battery_present, "unknown")); } static int param_set_battery_technology(const char *key, @@ -424,9 +424,9 @@ static int param_set_battery_technology(const char *key, static int param_get_battery_technology(char *buffer, const struct kernel_param *kp) { - strcpy(buffer, - map_get_key(map_technology, battery_technology, "unknown")); - return strlen(buffer); + return sprintf(buffer, "%s\n", + map_get_key(map_ac_online, battery_technology, + "unknown")); } static int param_set_battery_capacity(const char *key, diff --git a/drivers/power/supply/ucs1002_power.c b/drivers/power/supply/ucs1002_power.c index cdb9a23d825f..ef673ec3db56 100644 --- a/drivers/power/supply/ucs1002_power.c +++ b/drivers/power/supply/ucs1002_power.c @@ -38,6 +38,7 @@ /* Interrupt Status */ #define UCS1002_REG_INTERRUPT_STATUS 0x10 +# define F_ERR BIT(7) # define F_DISCHARGE_ERR BIT(6) # define F_RESET BIT(5) # define F_MIN_KEEP_OUT BIT(4) @@ -103,6 +104,9 @@ struct ucs1002_info { struct regulator_dev *rdev; bool present; bool output_disable; + struct delayed_work health_poll; + int health; + }; static enum power_supply_property ucs1002_props[] = { @@ -362,32 +366,6 @@ static int ucs1002_get_usb_type(struct ucs1002_info *info, return 0; } -static int ucs1002_get_health(struct ucs1002_info *info, - union power_supply_propval *val) -{ - unsigned int reg; - int ret, health; - - ret = regmap_read(info->regmap, UCS1002_REG_INTERRUPT_STATUS, ®); - if (ret) - return ret; - - if (reg & F_TSD) - health = POWER_SUPPLY_HEALTH_OVERHEAT; - else if (reg & (F_OVER_VOLT | F_BACK_VOLT)) - health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; - else if (reg & F_OVER_ILIM) - health = POWER_SUPPLY_HEALTH_OVERCURRENT; - else if (reg & (F_DISCHARGE_ERR | F_MIN_KEEP_OUT)) - health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; - else - health = POWER_SUPPLY_HEALTH_GOOD; - - val->intval = health; - - return 0; -} - static int ucs1002_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) @@ -406,7 +384,7 @@ static int ucs1002_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_USB_TYPE: return ucs1002_get_usb_type(info, val); case POWER_SUPPLY_PROP_HEALTH: - return ucs1002_get_health(info, val); + return val->intval = info->health; case POWER_SUPPLY_PROP_PRESENT: val->intval = info->present; return 0; @@ -458,6 +436,38 @@ static const struct power_supply_desc ucs1002_charger_desc = { .num_properties = ARRAY_SIZE(ucs1002_props), }; +static void ucs1002_health_poll(struct work_struct *work) +{ + struct ucs1002_info *info = container_of(work, struct ucs1002_info, + health_poll.work); + int ret; + u32 reg; + + ret = regmap_read(info->regmap, UCS1002_REG_INTERRUPT_STATUS, ®); + if (ret) + return; + + /* bad health and no status change, just schedule us again in a while */ + if ((reg & F_ERR) && info->health != POWER_SUPPLY_HEALTH_GOOD) { + schedule_delayed_work(&info->health_poll, + msecs_to_jiffies(2000)); + return; + } + + if (reg & F_TSD) + info->health = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (reg & (F_OVER_VOLT | F_BACK_VOLT)) + info->health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + else if (reg & F_OVER_ILIM) + info->health = POWER_SUPPLY_HEALTH_OVERCURRENT; + else if (reg & (F_DISCHARGE_ERR | F_MIN_KEEP_OUT)) + info->health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + else + info->health = POWER_SUPPLY_HEALTH_GOOD; + + sysfs_notify(&info->charger->dev.kobj, NULL, "health"); +} + static irqreturn_t ucs1002_charger_irq(int irq, void *data) { int ret, regval; @@ -484,7 +494,7 @@ static irqreturn_t ucs1002_alert_irq(int irq, void *data) { struct ucs1002_info *info = data; - power_supply_changed(info->charger); + mod_delayed_work(system_wq, &info->health_poll, 0); return IRQ_HANDLED; } @@ -632,6 +642,9 @@ static int ucs1002_probe(struct i2c_client *client, return ret; } + info->health = POWER_SUPPLY_HEALTH_GOOD; + INIT_DELAYED_WORK(&info->health_poll, ucs1002_health_poll); + if (irq_a_det > 0) { ret = devm_request_threaded_irq(dev, irq_a_det, NULL, ucs1002_charger_irq, @@ -645,10 +658,8 @@ static int ucs1002_probe(struct i2c_client *client, } if (irq_alert > 0) { - ret = devm_request_threaded_irq(dev, irq_alert, NULL, - ucs1002_alert_irq, - IRQF_ONESHOT, - "ucs1002-alert", info); + ret = devm_request_irq(dev, irq_alert, ucs1002_alert_irq, + 0,"ucs1002-alert", info); if (ret) { dev_err(dev, "Failed to request ALERT threaded irq: %d\n", ret); |