From 3615693f7459ce25a5d4744a1c6ee60415e0e8ca Mon Sep 17 00:00:00 2001 From: Subbaraman Narayanamurthy Date: Mon, 11 Sep 2017 20:01:27 -0700 Subject: [PATCH] leds: qpnp-wled: add stepper algorithm using brightness map Add stepper algorithm support with dynamically calculated step size or delay based on the brightness level change using the brightness map table. To help with running the algorithm efficiently, use a separate workqueue with high priority to process the brightness levels. Change-Id: Iea2a8da73b6bee3eaa7b28a12fd82c2a1507db99 Signed-off-by: Subbaraman Narayanamurthy --- .../bindings/leds/leds-qpnp-wled.txt | 3 + drivers/leds/leds-qpnp-wled.c | 97 ++++++++++++++++++- 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/leds/leds-qpnp-wled.txt b/Documentation/devicetree/bindings/leds/leds-qpnp-wled.txt index a37e437d0208..c7268ef07f59 100644 --- a/Documentation/devicetree/bindings/leds/leds-qpnp-wled.txt +++ b/Documentation/devicetree/bindings/leds/leds-qpnp-wled.txt @@ -84,6 +84,9 @@ Optional properties for WLED: These codes will be mapped to the brightness level requested in the scale of 0-4095. Code entry is of 16 bit size. +- qcom,wled-stepper-en : A boolean property to specify if stepper algorithm + needs to be enabled. This needs the brightness map + table to be specified. Optional properties if 'qcom,disp-type-amoled' is mentioned in DT: - qcom,loop-comp-res-kohm : control to select the compensation resistor in kohm. default is 320. diff --git a/drivers/leds/leds-qpnp-wled.c b/drivers/leds/leds-qpnp-wled.c index 3c8019fbf904..461693ef9d27 100644 --- a/drivers/leds/leds-qpnp-wled.c +++ b/drivers/leds/leds-qpnp-wled.c @@ -316,6 +316,7 @@ static struct wled_vref_setting vref_setting_pmi8998 = { * @ cdev - led class device * @ pdev - platform device * @ work - worker for led operation + * @ wq - workqueue for setting brightness level * @ lock - mutex lock for exclusive access * @ fdbk_op - output feedback mode * @ dim_mode - dimming mode @@ -359,6 +360,7 @@ static struct wled_vref_setting vref_setting_pmi8998 = { * @ disp_type_amoled - type of display: LCD/AMOLED * @ en_ext_pfet_sc_pro - enable sc protection on external pfet * @ prev_state - previous state of WLED + * @ stepper_en - Flag to enable stepper algorithm * @ ovp_irq_disabled - OVP interrupt disable status * @ auto_calib_enabled - Flag to enable auto calibration feature * @ auto_calib_done - Flag to indicate auto calibration is done @@ -371,6 +373,7 @@ struct qpnp_wled { struct regmap *regmap; struct pmic_revid_data *pmic_rev_id; struct work_struct work; + struct workqueue_struct *wq; struct mutex lock; struct mutex bus_lock; enum qpnp_wled_fdbk_op fdbk_op; @@ -415,6 +418,7 @@ struct qpnp_wled { bool disp_type_amoled; bool en_ext_pfet_sc_pro; bool prev_state; + bool stepper_en; bool ovp_irq_disabled; bool auto_calib_enabled; bool auto_calib_done; @@ -422,6 +426,21 @@ struct qpnp_wled { ktime_t start_ovp_fault_time; }; +static int qpnp_wled_step_delay_us = 52000; +module_param_named( + total_step_delay_us, qpnp_wled_step_delay_us, int, 0600 +); + +static int qpnp_wled_step_size_threshold = 3; +module_param_named( + step_size_threshold, qpnp_wled_step_size_threshold, int, 0600 +); + +static int qpnp_wled_step_delay_gain = 2; +module_param_named( + step_delay_gain, qpnp_wled_step_delay_gain, int, 0600 +); + /* helper to read a pmic register */ static int qpnp_wled_read_reg(struct qpnp_wled *wled, u16 addr, u8 *data) { @@ -583,6 +602,7 @@ static int qpnp_wled_set_level(struct qpnp_wled *wled, int level) return rc; } + pr_debug("level:%d\n", level); return 0; } @@ -613,6 +633,65 @@ static int qpnp_wled_set_map_level(struct qpnp_wled *wled, int level) return 0; } +static int qpnp_wled_set_step_level(struct qpnp_wled *wled, int new_level) +{ + int rc, i, num_steps, delay_us; + u16 level, start_level, end_level, step_size; + bool level_inc = false; + + level = wled->prev_level; + start_level = wled->brt_map_table[level]; + end_level = wled->brt_map_table[new_level]; + level_inc = (new_level > level); + + num_steps = abs(start_level - end_level); + if (!num_steps) + return 0; + + delay_us = qpnp_wled_step_delay_us / num_steps; + pr_debug("level goes from [%d %d] num_steps: %d, delay: %d\n", + start_level, end_level, num_steps, delay_us); + + if (delay_us < 500) { + step_size = 1000 / delay_us; + num_steps = num_steps / step_size; + delay_us = 1000; + } else { + if (num_steps < qpnp_wled_step_size_threshold) + delay_us *= qpnp_wled_step_delay_gain; + + step_size = 1; + } + + i = start_level; + while (num_steps--) { + if (level_inc) + i += step_size; + else + i -= step_size; + + rc = qpnp_wled_set_level(wled, i); + if (rc < 0) + return rc; + + if (delay_us > 0) { + if (delay_us < 20000) + usleep_range(delay_us, delay_us + 1); + else + msleep(delay_us / USEC_PER_MSEC); + } + } + + if (i != end_level) { + i = end_level; + rc = qpnp_wled_set_level(wled, i); + if (rc < 0) + return rc; + } + + return 0; +} + static int qpnp_wled_psm_config(struct qpnp_wled *wled, bool enable) { int rc; @@ -999,7 +1078,10 @@ static void qpnp_wled_work(struct work_struct *work) level_255 = 255; pr_debug("level: %d level_255: %d\n", level, level_255); - rc = qpnp_wled_set_map_level(wled, level_255); + if (wled->stepper_en) + rc = qpnp_wled_set_step_level(wled, level_255); + else + rc = qpnp_wled_set_map_level(wled, level_255); if (rc) { dev_err(&wled->pdev->dev, "wled set level failed\n"); goto unlock_mutex; @@ -1064,7 +1146,7 @@ static void qpnp_wled_set(struct led_classdev *led_cdev, level = wled->cdev.max_brightness; wled->cdev.brightness = level; - schedule_work(&wled->work); + queue_work(wled->wq, &wled->work); } static int qpnp_wled_set_disp(struct qpnp_wled *wled, u16 base_addr) @@ -2226,6 +2308,8 @@ static int qpnp_wled_parse_dt(struct qpnp_wled *wled) } } + wled->stepper_en = of_property_read_bool(pdev->dev.of_node, + "qcom,wled-stepper-en"); wled->disp_type_amoled = of_property_read_bool(pdev->dev.of_node, "qcom,disp-type-amoled"); if (wled->disp_type_amoled) { @@ -2561,6 +2645,7 @@ static int qpnp_wled_probe(struct platform_device *pdev) } wled->pmic_rev_id = get_revid_data(revid_node); + of_node_put(revid_node); if (IS_ERR_OR_NULL(wled->pmic_rev_id)) { pr_err("Unable to get pmic_revid rc=%ld\n", PTR_ERR(wled->pmic_rev_id)); @@ -2575,6 +2660,12 @@ static int qpnp_wled_probe(struct platform_device *pdev) pr_debug("PMIC subtype %d Digital major %d\n", wled->pmic_rev_id->pmic_subtype, wled->pmic_rev_id->rev4); + wled->wq = alloc_ordered_workqueue("qpnp_wled_wq", WQ_HIGHPRI); + if (!wled->wq) { + pr_err("Unable to alloc workqueue for WLED\n"); + return -ENOMEM; + } + prop = of_get_address_by_name(pdev->dev.of_node, QPNP_WLED_SINK_BASE, NULL, NULL); if (!prop) { @@ -2640,6 +2731,7 @@ sysfs_fail: led_classdev_unregister(&wled->cdev); wled_register_fail: cancel_work_sync(&wled->work); + destroy_workqueue(wled->wq); mutex_destroy(&wled->lock); return rc; } @@ -2655,6 +2747,7 @@ static int qpnp_wled_remove(struct platform_device *pdev) led_classdev_unregister(&wled->cdev); cancel_work_sync(&wled->work); + destroy_workqueue(wled->wq); mutex_destroy(&wled->lock); return 0;