leds/mc13892: Use workqueue for setting LED brightness
From: Jeremy Kerr <jeremy.kerr@canonical.com>
BugLink: http://bugs.launchpad.net/bugs/531696
Setting the LED brightness on mc13892 boards uses spi_sync, which may
sleep. The LED class infrastructure requires that the brightness_set
operation does not sleep, as it may be called from atomic contexts.
This results in a 'scheduling while atomic BUG' when doing the
following:
echo mmc0 > /sys/class/leds/xxx/trigger
This change modifies the driver to use a workqueue to do the SPI
operation, so that we can sleep. The led_classdev->brightness_set
callback just updates the brightness value and schedules work, making it
suitable to call with irqs disabled.
Because we've split the set operation into two parts, we need to define
some context (struct mc13892_led) to pass between the led classdev code
and the work function.
/* set current with medium value, in case current is too large */
- mc13892_bklit_set_current(led_ch, LIT_CURR_12);
+ mc13892_bklit_set_current(led->channel, LIT_CURR_12);
/* max duty cycle is 63, brightness needs to be divided by 4 */
- mc13892_bklit_set_dutycycle(led_ch, value / 4);
+ mc13892_bklit_set_dutycycle(led->channel, led->brightness / 4);
+}
+
- led_cdev = kzalloc(sizeof(struct led_classdev), GFP_KERNEL);
- if (led_cdev == NULL) {
- dev_err(&dev->dev, "No memory for device
");
- return -ENOMEM;
+ /* ensure we have space for the channel name and a NUL */
+ if (strlen(dev->name) > LED_NAME_LEN - 2) {
+ dev_err(&dev->dev, "led name is too long
");
+ return -EINVAL;
}
- name = kzalloc(LED_NAME_LEN, GFP_KERNEL);
- if (name == NULL) {
- dev_err(&dev->dev, "No memory for device
");
- ret = -ENOMEM;
- goto exit_err;
+
+ chan = mc13892_led_channel(dev->id);
+ if (chan == -1) {
+ dev_err(&dev->dev, "invalid LED id '%d'
", dev->id);
+ return -EINVAL;
}
- strcpy(name, dev->name);
- ret = strlen(dev->name);
- if (ret > LED_NAME_LEN - 2) {
- dev_err(&dev->dev, "led name is too long
");
- goto exit_err1;
+ led = kzalloc(sizeof(*led), GFP_KERNEL);
+ if (!led) {
+ dev_err(&dev->dev, "No memory for device
");
+ return -ENOMEM;
}
- name[ret] = dev->id;
- name[ret + 1] = ' ';
- led_cdev->name = name;
- led_cdev->brightness_set = mc13892_led_set;