summaryrefslogtreecommitdiff
path: root/drivers/leds/leds-qpnp-flash-v2.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/leds/leds-qpnp-flash-v2.c')
-rw-r--r--drivers/leds/leds-qpnp-flash-v2.c404
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;
}