OSDN Git Service

leds: qpnp-wled: add stepper algorithm using brightness map
authorSubbaraman Narayanamurthy <subbaram@codeaurora.org>
Tue, 12 Sep 2017 03:01:27 +0000 (20:01 -0700)
committerSubbaraman Narayanamurthy <subbaram@codeaurora.org>
Wed, 20 Dec 2017 23:06:21 +0000 (15:06 -0800)
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 <subbaram@codeaurora.org>
Documentation/devicetree/bindings/leds/leds-qpnp-wled.txt
drivers/leds/leds-qpnp-wled.c

index a37e437..c7268ef 100644 (file)
@@ -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.
index 3c8019f..461693e 100644 (file)
@@ -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;