diff options
Diffstat (limited to 'drivers/leds/leds-qpnp-flash-v2.c')
| -rw-r--r-- | drivers/leds/leds-qpnp-flash-v2.c | 404 |
1 files changed, 378 insertions, 26 deletions
diff --git a/drivers/leds/leds-qpnp-flash-v2.c b/drivers/leds/leds-qpnp-flash-v2.c index 786ffa822851..291720db72ce 100644 --- a/drivers/leds/leds-qpnp-flash-v2.c +++ b/drivers/leds/leds-qpnp-flash-v2.c @@ -16,13 +16,21 @@ #include <linux/errno.h> #include <linux/slab.h> #include <linux/of.h> +#include <linux/of_irq.h> #include <linux/of_gpio.h> #include <linux/gpio.h> #include <linux/regmap.h> +#include <linux/power_supply.h> #include <linux/platform_device.h> +#include <linux/interrupt.h> #include <linux/regulator/consumer.h> +#include <linux/leds-qpnp-flash.h> #include <linux/leds-qpnp-flash-v2.h> +#include "leds.h" +#define FLASH_LED_REG_LED_STATUS1(base) (base + 0x08) +#define FLASH_LED_REG_LED_STATUS2(base) (base + 0x09) +#define FLASH_LED_REG_INT_RT_STS(base) (base + 0x10) #define FLASH_LED_REG_SAFETY_TMR(base) (base + 0x40) #define FLASH_LED_REG_TGR_CURRENT(base) (base + 0x43) #define FLASH_LED_REG_MOD_CTRL(base) (base + 0x46) @@ -32,20 +40,40 @@ #define FLASH_LED_EN_LED_CTRL(base) (base + 0x4C) #define FLASH_LED_REG_HDRM_PRGM(base) (base + 0x4D) #define FLASH_LED_REG_HDRM_AUTO_MODE_CTRL(base) (base + 0x50) +#define FLASH_LED_REG_WARMUP_DELAY(base) (base + 0x51) #define FLASH_LED_REG_ISC_DELAY(base) (base + 0x52) +#define FLASH_LED_REG_VPH_DROOP_THRESHOLD(base) (base + 0x61) +#define FLASH_LED_REG_VPH_DROOP_DEBOUNCE(base) (base + 0x62) +#define FLASH_LED_REG_CURRENT_DERATE_EN(base) (base + 0x76) #define FLASH_LED_HDRM_MODE_PRGM_MASK GENMASK(7, 0) #define FLASH_LED_HDRM_VOL_MASK GENMASK(7, 4) #define FLASH_LED_CURRENT_MASK GENMASK(6, 0) #define FLASH_LED_ENABLE_MASK GENMASK(2, 0) #define FLASH_LED_SAFETY_TMR_MASK GENMASK(7, 0) -#define FLASH_LED_ISC_DELAY_MASK GENMASK(1, 0) +#define FLASH_LED_INT_RT_STS_MASK GENMASK(7, 0) +#define FLASH_LED_ISC_WARMUP_DELAY_MASK GENMASK(1, 0) +#define FLASH_LED_CURRENT_DERATE_EN_MASK GENMASK(2, 0) +#define FLASH_LED_VPH_DROOP_DEBOUNCE_MASK GENMASK(1, 0) +#define FLASH_LED_VPH_DROOP_HYSTERESIS_MASK GENMASK(5, 4) +#define FLASH_LED_VPH_DROOP_THRESHOLD_MASK GENMASK(2, 0) #define FLASH_LED_MOD_CTRL_MASK BIT(7) #define FLASH_LED_HW_SW_STROBE_SEL_MASK BIT(2) - -#define FLASH_LED_HEADROOM_AUTO_MODE_ENABLED true -#define FLASH_LED_ISC_DELAY_SHIFT 6 -#define FLASH_LED_ISC_DELAY_DEFAULT_US 3 +#define FLASH_LED_VPH_DROOP_FAULT_MASK BIT(4) + +#define VPH_DROOP_DEBOUNCE_US_TO_VAL(val_us) (val_us / 8) +#define VPH_DROOP_HYST_MV_TO_VAL(val_mv) (val_mv / 25) +#define VPH_DROOP_THRESH_MV_TO_VAL(val_mv) ((val_mv / 100) - 25) + +#define FLASH_LED_ISC_WARMUP_DELAY_SHIFT 6 +#define FLASH_LED_WARMUP_DELAY_DEFAULT 2 +#define FLASH_LED_ISC_DELAY_DEFAULT 3 +#define FLASH_LED_VPH_DROOP_DEBOUNCE_DEFAULT 2 +#define FLASH_LED_VPH_DROOP_HYST_DEFAULT 2 +#define FLASH_LED_VPH_DROOP_THRESH_DEFAULT 5 +#define FLASH_LED_VPH_DROOP_DEBOUNCE_MAX 3 +#define FLASH_LED_VPH_DROOP_HYST_MAX 3 +#define FLASH_LED_VPH_DROOP_THRESH_MAX 7 #define FLASH_LED_SAFETY_TMR_VAL_OFFSET 1 #define FLASH_LED_SAFETY_TMR_VAL_DIVISOR 10 #define FLASH_LED_SAFETY_TMR_ENABLE BIT(7) @@ -69,6 +97,9 @@ #define FLASH_LED_SAFETY_TMR_DISABLED 0x13 #define FLASH_LED_MIN_CURRENT_MA 25 +/* notifier call chain for flash-led irqs */ +static ATOMIC_NOTIFIER_HEAD(irq_notifier_list); + enum flash_led_type { FLASH_LED_TYPE_FLASH, FLASH_LED_TYPE_TORCH, @@ -125,9 +156,17 @@ struct flash_switch_data { * Flash LED configuration read from device tree */ struct flash_led_platform_data { - u8 isc_delay_us; - u8 hw_strobe_option; - bool hdrm_auto_mode_en; + int all_ramp_up_done_irq; + int all_ramp_down_done_irq; + int led_fault_irq; + u8 isc_delay; + u8 warmup_delay; + u8 current_derate_en_cfg; + u8 vph_droop_threshold; + u8 vph_droop_hysteresis; + u8 vph_droop_debounce; + u8 hw_strobe_option; + bool hdrm_auto_mode_en; }; /* @@ -139,6 +178,8 @@ struct qpnp_flash_led { struct regmap *regmap; struct flash_node_data *fnode; struct flash_switch_data *snode; + struct power_supply *bms_psy; + struct notifier_block nb; spinlock_t lock; int num_fnodes; int num_snodes; @@ -147,19 +188,36 @@ struct qpnp_flash_led { }; static int +qpnp_flash_led_read(struct qpnp_flash_led *led, u16 addr, u8 *data) +{ + int rc; + uint val; + + rc = regmap_read(led->regmap, addr, &val); + if (rc < 0) + dev_err(&led->pdev->dev, "Unable to read from 0x%04X rc = %d\n", + addr, rc); + else + dev_dbg(&led->pdev->dev, "Read 0x%02X from addr 0x%04X\n", + val, addr); + + *data = (u8)val; + return rc; +} + +static int qpnp_flash_led_masked_write(struct qpnp_flash_led *led, u16 addr, u8 mask, - u8 val) + u8 val) { int rc; rc = regmap_update_bits(led->regmap, addr, mask, val); if (rc < 0) - dev_err(&led->pdev->dev, - "Unable to update bits from 0x%02X, rc = %d\n", - addr, rc); + dev_err(&led->pdev->dev, "Unable to update bits from 0x%04X, rc = %d\n", + addr, rc); else - dev_dbg(&led->pdev->dev, "Wrote 0x%02X to addr 0x%02X\n", - val, addr); + dev_dbg(&led->pdev->dev, "Wrote 0x%02X to addr 0x%04X\n", + val, addr); return rc; } @@ -195,7 +253,43 @@ static int qpnp_flash_led_init_settings(struct qpnp_flash_led *led) rc = qpnp_flash_led_masked_write(led, FLASH_LED_REG_ISC_DELAY(led->base), - FLASH_LED_ISC_DELAY_MASK, led->pdata->isc_delay_us); + FLASH_LED_ISC_WARMUP_DELAY_MASK, + led->pdata->isc_delay); + if (rc < 0) + return rc; + + rc = qpnp_flash_led_masked_write(led, + FLASH_LED_REG_WARMUP_DELAY(led->base), + FLASH_LED_ISC_WARMUP_DELAY_MASK, + led->pdata->warmup_delay); + if (rc < 0) + return rc; + + rc = qpnp_flash_led_masked_write(led, + FLASH_LED_REG_CURRENT_DERATE_EN(led->base), + FLASH_LED_CURRENT_DERATE_EN_MASK, + led->pdata->current_derate_en_cfg); + if (rc < 0) + return rc; + + rc = qpnp_flash_led_masked_write(led, + FLASH_LED_REG_VPH_DROOP_DEBOUNCE(led->base), + FLASH_LED_VPH_DROOP_DEBOUNCE_MASK, + led->pdata->vph_droop_debounce); + if (rc < 0) + return rc; + + rc = qpnp_flash_led_masked_write(led, + FLASH_LED_REG_VPH_DROOP_THRESHOLD(led->base), + FLASH_LED_VPH_DROOP_THRESHOLD_MASK, + led->pdata->vph_droop_threshold); + if (rc < 0) + return rc; + + rc = qpnp_flash_led_masked_write(led, + FLASH_LED_REG_VPH_DROOP_THRESHOLD(led->base), + FLASH_LED_VPH_DROOP_HYSTERESIS_MASK, + led->pdata->vph_droop_hysteresis); if (rc < 0) return rc; @@ -459,13 +553,21 @@ static int qpnp_flash_led_switch_set(struct flash_switch_data *snode, bool on) return 0; } -int qpnp_flash_led_prepare(struct led_classdev *led_cdev, int options) +int qpnp_flash_led_prepare(struct led_trigger *trig, int options) { - struct flash_switch_data *snode = - container_of(led_cdev, struct flash_switch_data, cdev); - struct qpnp_flash_led *led = dev_get_drvdata(&snode->pdev->dev); + struct led_classdev *led_cdev = trigger_to_lcdev(trig); + struct flash_switch_data *snode; + struct qpnp_flash_led *led; int rc, val = 0; + if (!led_cdev) { + pr_err("Invalid led_trigger provided\n"); + return -EINVAL; + } + + snode = container_of(led_cdev, struct flash_switch_data, cdev); + led = dev_get_drvdata(&snode->pdev->dev); + if (!(options & (ENABLE_REGULATOR | QUERY_MAX_CURRENT))) { dev_err(&led->pdev->dev, "Invalid options %d\n", options); return -EINVAL; @@ -521,6 +623,112 @@ static void qpnp_flash_led_brightness_set(struct led_classdev *led_cdev, spin_unlock(&led->lock); } +static int flash_led_psy_notifier_call(struct notifier_block *nb, + unsigned long ev, void *v) +{ + struct power_supply *psy = v; + struct qpnp_flash_led *led = + container_of(nb, struct qpnp_flash_led, nb); + + if (ev != PSY_EVENT_PROP_CHANGED) + return NOTIFY_OK; + + if (!strcmp(psy->desc->name, "bms")) { + led->bms_psy = power_supply_get_by_name("bms"); + if (!led->bms_psy) + dev_err(&led->pdev->dev, "Failed to get bms power_supply\n"); + else + power_supply_unreg_notifier(&led->nb); + } + + return NOTIFY_OK; +} + +static int flash_led_psy_register_notifier(struct qpnp_flash_led *led) +{ + int rc; + + led->nb.notifier_call = flash_led_psy_notifier_call; + rc = power_supply_reg_notifier(&led->nb); + if (rc < 0) { + pr_err("Couldn't register psy notifier, rc = %d\n", rc); + return rc; + } + + return 0; +} + +/* irq handler */ +static irqreturn_t qpnp_flash_led_irq_handler(int irq, void *_led) +{ + struct qpnp_flash_led *led = _led; + enum flash_led_irq_type irq_type = INVALID_IRQ; + int rc; + u8 irq_status, led_status1, led_status2; + + dev_dbg(&led->pdev->dev, "irq received, irq=%d\n", irq); + + rc = qpnp_flash_led_read(led, + FLASH_LED_REG_INT_RT_STS(led->base), &irq_status); + if (rc < 0) { + dev_err(&led->pdev->dev, "Failed to read interrupt status reg, rc=%d\n", + rc); + goto exit; + } + + if (irq == led->pdata->all_ramp_up_done_irq) + irq_type = ALL_RAMP_UP_DONE_IRQ; + else if (irq == led->pdata->all_ramp_down_done_irq) + irq_type = ALL_RAMP_DOWN_DONE_IRQ; + else if (irq == led->pdata->led_fault_irq) + irq_type = LED_FAULT_IRQ; + + if (irq_type == ALL_RAMP_UP_DONE_IRQ) + atomic_notifier_call_chain(&irq_notifier_list, + irq_type, NULL); + + if (irq_type == LED_FAULT_IRQ) { + rc = qpnp_flash_led_read(led, + FLASH_LED_REG_LED_STATUS1(led->base), &led_status1); + if (rc < 0) { + dev_err(&led->pdev->dev, "Failed to read led_status1 reg, rc=%d\n", + rc); + goto exit; + } + + rc = qpnp_flash_led_read(led, + FLASH_LED_REG_LED_STATUS2(led->base), &led_status2); + if (rc < 0) { + dev_err(&led->pdev->dev, "Failed to read led_status2 reg, rc=%d\n", + rc); + goto exit; + } + + if (led_status1) + dev_emerg(&led->pdev->dev, "led short/open fault detected! led_status1=%x\n", + led_status1); + + if (led_status2 & FLASH_LED_VPH_DROOP_FAULT_MASK) + dev_emerg(&led->pdev->dev, "led vph_droop fault detected!\n"); + } + + dev_dbg(&led->pdev->dev, "irq handled, irq_type=%x, irq_status=%x\n", + irq_type, irq_status); + +exit: + return IRQ_HANDLED; +} + +int qpnp_flash_led_register_irq_notifier(struct notifier_block *nb) +{ + return atomic_notifier_chain_register(&irq_notifier_list, nb); +} + +int qpnp_flash_led_unregister_irq_notifier(struct notifier_block *nb) +{ + return atomic_notifier_chain_unregister(&irq_notifier_list, nb); +} + static int qpnp_flash_led_regulator_setup(struct qpnp_flash_led *led, struct flash_switch_data *snode, bool on) { @@ -901,28 +1109,116 @@ static int qpnp_flash_led_parse_common_dt(struct qpnp_flash_led *led, { int rc; u32 val; + bool short_circuit_det, open_circuit_det, vph_droop_det; - led->pdata->hdrm_auto_mode_en = FLASH_LED_HEADROOM_AUTO_MODE_ENABLED; led->pdata->hdrm_auto_mode_en = of_property_read_bool(node, "qcom,hdrm-auto-mode"); - led->pdata->isc_delay_us = FLASH_LED_ISC_DELAY_DEFAULT_US; - rc = of_property_read_u32(node, "qcom,isc-delay", &val); + led->pdata->isc_delay = FLASH_LED_ISC_DELAY_DEFAULT; + rc = of_property_read_u32(node, "qcom,isc-delay-us", &val); + if (!rc) { + led->pdata->isc_delay = + val >> FLASH_LED_ISC_WARMUP_DELAY_SHIFT; + } else if (rc != -EINVAL) { + dev_err(&led->pdev->dev, + "Unable to read ISC delay, rc=%d\n", rc); + return rc; + } + + led->pdata->warmup_delay = FLASH_LED_WARMUP_DELAY_DEFAULT; + rc = of_property_read_u32(node, "qcom,warmup-delay-us", &val); + if (!rc) { + led->pdata->warmup_delay = + val >> FLASH_LED_ISC_WARMUP_DELAY_SHIFT; + } else if (rc != -EINVAL) { + dev_err(&led->pdev->dev, + "Unable to read WARMUP delay, rc=%d\n", rc); + return rc; + } + + short_circuit_det = + of_property_read_bool(node, "qcom,short-circuit-det"); + open_circuit_det = of_property_read_bool(node, "qcom,open-circuit-det"); + vph_droop_det = of_property_read_bool(node, "qcom,vph-droop-det"); + led->pdata->current_derate_en_cfg = (vph_droop_det << 2) | + (open_circuit_det << 1) | short_circuit_det; + + led->pdata->vph_droop_debounce = FLASH_LED_VPH_DROOP_DEBOUNCE_DEFAULT; + rc = of_property_read_u32(node, "qcom,vph-droop-debounce-us", &val); if (!rc) { - led->pdata->isc_delay_us = val >> FLASH_LED_ISC_DELAY_SHIFT; + led->pdata->vph_droop_debounce = + VPH_DROOP_DEBOUNCE_US_TO_VAL(val); } else if (rc != -EINVAL) { - dev_err(&led->pdev->dev, "Unable to read ISC delay\n"); + dev_err(&led->pdev->dev, + "Unable to read VPH droop debounce, rc=%d\n", rc); return rc; } + if (led->pdata->vph_droop_debounce > FLASH_LED_VPH_DROOP_DEBOUNCE_MAX) { + dev_err(&led->pdev->dev, + "Invalid VPH droop debounce specified"); + return -EINVAL; + } + + led->pdata->vph_droop_threshold = FLASH_LED_VPH_DROOP_THRESH_DEFAULT; + rc = of_property_read_u32(node, "qcom,vph-droop-threshold-mv", &val); + if (!rc) { + led->pdata->vph_droop_threshold = + VPH_DROOP_THRESH_MV_TO_VAL(val); + } else if (rc != -EINVAL) { + dev_err(&led->pdev->dev, + "Unable to read VPH droop threshold, rc=%d\n", rc); + return rc; + } + + if (led->pdata->vph_droop_threshold > FLASH_LED_VPH_DROOP_THRESH_MAX) { + dev_err(&led->pdev->dev, + "Invalid VPH droop threshold specified"); + return -EINVAL; + } + + led->pdata->vph_droop_hysteresis = + FLASH_LED_VPH_DROOP_HYST_DEFAULT; + rc = of_property_read_u32(node, "qcom,vph-droop-hysteresis-mv", &val); + if (!rc) { + led->pdata->vph_droop_hysteresis = + VPH_DROOP_HYST_MV_TO_VAL(val); + } else if (rc != -EINVAL) { + dev_err(&led->pdev->dev, + "Unable to read VPH droop hysteresis, rc=%d\n", rc); + return rc; + } + + if (led->pdata->vph_droop_hysteresis > FLASH_LED_VPH_DROOP_HYST_MAX) { + dev_err(&led->pdev->dev, + "Invalid VPH droop hysteresis specified"); + return -EINVAL; + } + rc = of_property_read_u32(node, "qcom,hw-strobe-option", &val); if (!rc) { led->pdata->hw_strobe_option = (u8)val; } else if (rc != -EINVAL) { - dev_err(&led->pdev->dev, "Unable to parse hw strobe option\n"); + dev_err(&led->pdev->dev, + "Unable to parse hw strobe option, rc=%d\n", rc); return rc; } + led->pdata->all_ramp_up_done_irq = + of_irq_get_byname(node, "all-ramp-up-done-irq"); + if (led->pdata->all_ramp_up_done_irq < 0) + dev_dbg(&led->pdev->dev, "all-ramp-up-done-irq not used\n"); + + led->pdata->all_ramp_down_done_irq = + of_irq_get_byname(node, "all-ramp-down-done-irq"); + if (led->pdata->all_ramp_down_done_irq < 0) + dev_dbg(&led->pdev->dev, "all-ramp-down-done-irq not used\n"); + + led->pdata->led_fault_irq = + of_irq_get_byname(node, "led-fault-irq"); + if (led->pdata->led_fault_irq < 0) + dev_dbg(&led->pdev->dev, "led-fault-irq not used\n"); + return 0; } @@ -1033,11 +1329,64 @@ static int qpnp_flash_led_probe(struct platform_device *pdev) } } + /* setup irqs */ + if (led->pdata->all_ramp_up_done_irq >= 0) { + rc = devm_request_threaded_irq(&led->pdev->dev, + led->pdata->all_ramp_up_done_irq, + NULL, qpnp_flash_led_irq_handler, + IRQF_ONESHOT, + "qpnp_flash_led_all_ramp_up_done_irq", led); + if (rc < 0) { + dev_err(&pdev->dev, + "Unable to request all_ramp_up_done(%d) IRQ(err:%d)\n", + led->pdata->all_ramp_up_done_irq, rc); + goto error_switch_register; + } + } + + if (led->pdata->all_ramp_down_done_irq >= 0) { + rc = devm_request_threaded_irq(&led->pdev->dev, + led->pdata->all_ramp_down_done_irq, + NULL, qpnp_flash_led_irq_handler, + IRQF_ONESHOT, + "qpnp_flash_led_all_ramp_down_done_irq", led); + if (rc < 0) { + dev_err(&pdev->dev, + "Unable to request all_ramp_down_done(%d) IRQ(err:%d)\n", + led->pdata->all_ramp_down_done_irq, rc); + goto error_switch_register; + } + } + + if (led->pdata->led_fault_irq >= 0) { + rc = devm_request_threaded_irq(&led->pdev->dev, + led->pdata->led_fault_irq, + NULL, qpnp_flash_led_irq_handler, + IRQF_ONESHOT, + "qpnp_flash_led_fault_irq", led); + if (rc < 0) { + dev_err(&pdev->dev, + "Unable to request led_fault(%d) IRQ(err:%d)\n", + led->pdata->led_fault_irq, rc); + goto error_switch_register; + } + } + + led->bms_psy = power_supply_get_by_name("bms"); + if (!led->bms_psy) { + rc = flash_led_psy_register_notifier(led); + if (rc < 0) { + dev_err(&pdev->dev, "Couldn't register psy notifier, rc = %d\n", + rc); + goto error_switch_register; + } + } + rc = qpnp_flash_led_init_settings(led); if (rc < 0) { dev_err(&pdev->dev, "Failed to initialize flash LED, rc=%d\n", rc); - goto error_switch_register; + goto unreg_notifier; } spin_lock_init(&led->lock); @@ -1046,6 +1395,8 @@ static int qpnp_flash_led_probe(struct platform_device *pdev) return 0; +unreg_notifier: + power_supply_unreg_notifier(&led->nb); error_switch_register: while (i > 0) led_classdev_unregister(&led->snode[--i].cdev); @@ -1078,6 +1429,7 @@ static int qpnp_flash_led_remove(struct platform_device *pdev) while (i > 0) led_classdev_unregister(&led->fnode[--i].cdev); + power_supply_unreg_notifier(&led->nb); return 0; } |
