Re: [PATCH v3 2/3] leds: Add driver for Qualcomm LPG
From: Bjorn Andersson <hidden>
Date: 2017-11-20 21:10:40
Also in:
linux-arm-msm, linux-leds, lkml
On Sun 19 Nov 13:36 PST 2017, Jacek Anaszewski wrote:
Hi Bjorn, Thanks for the patch. Please refer to my comments in the code. On 11/15/2017 08:13 AM, Bjorn Andersson wrote:
[..]
quoted
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 52ea34e337cd..ccc3aa4b2474 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig@@ -651,6 +651,13 @@ config LEDS_POWERNV To compile this driver as a module, choose 'm' here: the module will be called leds-powernv. +config LEDS_QCOM_LPG + tristate "LED support for Qualcomm LPG" + depends on LEDS_CLASSYou were mentioning that this driver is for a MFD child block, so we should probably depend on the parent MFD driver?
There's no build time dependency between the two, so it's not strictly necessary. Adding a dependency on MFD_SPMI_PMIC would indirectly add a dependency on ARCH_QCOM, which limit build testing and stop some static code checkers to check the driver. So, unless you strongly object I would prefer not to mention the MFD.
quoted
+ help + This option enables support for the Light Pulse Generator found in a + wide variety of Qualcomm PMICs. +
[..]
quoted
diff --git a/drivers/leds/leds-qcom-lpg.c b/drivers/leds/leds-qcom-lpg.c
[..]
quoted
+#define LPG_PATTERN_CONFIG_REG 0x40 +#define LPG_SIZE_CLK_REG 0x41 +#define LPG_PREDIV_CLK_REG 0x42 +#define PWM_TYPE_CONFIG_REG 0x43 +#define PWM_VALUE_REG 0x44 +#define PWM_ENABLE_CONTROL_REG 0x46 +#define PWM_SYNC_REG 0x47 +#define LPG_RAMP_DURATION_REG 0x50 +#define LPG_HI_PAUSE_REG 0x52 +#define LPG_LO_PAUSE_REG 0x54 +#define LPG_HI_IDX_REG 0x56 +#define LPG_LO_IDX_REG 0x57 +#define PWM_SEC_ACCESS_REG 0xd0 +#define PWM_DTEST_REG(x) (0xe2 + (x) - 1) + +#define TRI_LED_SRC_SEL 0x45 +#define TRI_LED_EN_CTL 0x46 +#define TRI_LED_ATC_CTL 0x47 + +#define LPG_LUT_REG(x) (0x40 + (x) * 2) +#define RAMP_CONTROL_REG 0xc8Please add QCOM_ namespacing prefix to the macros. At least PWM prefix is reserved for pwm subsystem.
Will fix. [..]
quoted
+static void lpg_calc_freq(struct lpg_channel *chan, unsigned int period_us) +{ + int n, m, clk, div; + int best_m, best_div, best_clk; + unsigned int last_err, cur_err, min_err; + unsigned int tmp_p, period_n; + + if (period_us == chan->period_us) + return; + + /* PWM Period / N */ + if (period_us < ((unsigned int)(-1) / NSEC_PER_USEC)) { + period_n = (period_us * NSEC_PER_USEC) >> 6; + n = 6; + } else { + period_n = (period_us >> 9) * NSEC_PER_USEC; + n = 9; + }Please provide macros for 6 and 9 magic numbers.
They really aren't magic numbers, they represent the number of bits of resolution, referred to as "size" in the rest of the driver. I'll replace "n" with "pwm_size" to clarify this. Ok? [..]
quoted
+static void lpg_apply_freq(struct lpg_channel *chan) +{ + unsigned long val; + struct lpg *lpg = chan->lpg; + + if (!chan->enabled) + return; + + /* Clock register values are off-by-one from lpg_clk_table */ + val = chan->clk + 1; + + if (chan->pwm_size == 9) + val |= lpg->data->pwm_9bit_mask; + + regmap_write(lpg->map, chan->base + LPG_SIZE_CLK_REG, val); + + val = chan->pre_div << 5 | chan->pre_div_exp; + regmap_write(lpg->map, chan->base + LPG_PREDIV_CLK_REG, val);Please provide macros for 5 and 9.
5 definitely deserves a macro. 9 is, as above, the number of bits of resolution (or "size of the pwm"). Is there a name different than "pwm_size" that would make this more obvious? [..]
quoted
+static void lpg_brightness_set(struct led_classdev *cdev, + enum led_brightness value) +{
[..]
quoted
+ /* Trigger start of ramp generator(s) */ + if (lut_mask) + lpg_lut_sync(lpg, lut_mask);We need some synchronization while changing device state in few steps, to prevent troubles when we are preempted by other process in the middle. spin_lock() in this case since it seems that we are not going to sleep while accessing device registers.
You're right, we need to protect the TRILED during the read-modify-write of the enable bits and we also need a lock around the allocation of bits in the LUT block. Will fix this. As far as I can see the framework is expected to protect me from concurrent accesses on the same LED though. [..]
quoted
+static int lpg_add_led(struct lpg *lpg, struct device_node *np) +{ + struct lpg_led *led; + const char *state; + int sources; + int size; + u32 chan; + int ret; + int i; + + sources = of_property_count_u32_elems(np, "led-sources"); + if (sources <= 0) { + dev_err(lpg->dev, "invalid led-sources of %s\n", + np->name); + return -EINVAL; + } + + size = sizeof(*led) + sources * sizeof(struct lpg_channel*);To fix checkpatch.pl complaint: s/lpg_channel*/lpg_channel */
Sorry, will fix.
quoted
+ led = devm_kzalloc(lpg->dev, size, GFP_KERNEL); + if (!led) + return -ENOMEM; + + led->lpg = lpg; + led->num_channels = sources; + + for (i = 0; i < sources; i++) { + ret = of_property_read_u32_index(np, "led-sources", + i, &chan); + if (ret || !chan || chan > lpg->num_channels) { + dev_err(lpg->dev, + "invalid led-sources of %s\n", + np->name); + return -EINVAL; + } + + led->channels[i] = &lpg->channels[chan - 1]; + + led->channels[i]->in_use = true; + } + + /* Use label else node name */ + led->cdev.name = of_get_property(np, "label", NULL) ? : np->name;Documentation/leds/leds-class.txt states that LED class device name pattern is devicename:colour:function. This is not explicitly stated in the common LED DT bindings, but label should be prepended with devicename by the driver.
I was under the impression that "devicename" referred to the board name, but I presume then that this refer to the name of the "LED hardware"?
Not all LED class drivers adhere to this rule and we have some mess in this area currently, but we will fix it soon I hope.
I presume the default name should be built on the form of dev_name(dev)::of_node->name then? Unfortunately I can't find a single driver that does this, so please let me know the format you would like and I'll update the driver with this.
quoted
+ led->cdev.default_trigger = of_get_property(np, "linux,default-trigger", NULL); + led->cdev.brightness_set = lpg_brightness_set; + led->cdev.brightness_get = lpg_brightness_get; + led->cdev.blink_set = lpg_blink_set; + led->cdev.max_brightness = 255;You can skip this line, since it will be set to LED_FULL in case passed 0 to led_classdev_init().
Convenient. Thank you for the review! Regards, Bjorn