Commit 934cd3f9 authored by Riku Voipio's avatar Riku Voipio Committed by Richard Purdie

leds: leds-pcs9532 - Move i2c work to a workqueque

Apparently these might be called under atomic context,
and i2c operations may sleep. BUG found by
Ross Burton <ross@burtonini.com>
Signed-off-by: default avatarRiku Voipio <riku.voipio@iki.fi>
Signed-off-by: default avatarRichard Purdie <rpurdie@linux.intel.com>
parent f785d022
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#include <linux/leds.h> #include <linux/leds.h>
#include <linux/input.h> #include <linux/input.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/workqueue.h>
#include <linux/leds-pca9532.h> #include <linux/leds-pca9532.h>
static const unsigned short normal_i2c[] = { /*0x60,*/ I2C_CLIENT_END}; static const unsigned short normal_i2c[] = { /*0x60,*/ I2C_CLIENT_END};
...@@ -34,6 +35,7 @@ struct pca9532_data { ...@@ -34,6 +35,7 @@ struct pca9532_data {
struct pca9532_led leds[16]; struct pca9532_led leds[16];
struct mutex update_lock; struct mutex update_lock;
struct input_dev *idev; struct input_dev *idev;
struct work_struct work;
u8 pwm[2]; u8 pwm[2];
u8 psc[2]; u8 psc[2];
}; };
...@@ -63,7 +65,7 @@ static struct i2c_driver pca9532_driver = { ...@@ -63,7 +65,7 @@ static struct i2c_driver pca9532_driver = {
* as a compromise we average one pwm to the values requested by all * as a compromise we average one pwm to the values requested by all
* leds that are not ON/OFF. * leds that are not ON/OFF.
* */ * */
static int pca9532_setpwm(struct i2c_client *client, int pwm, int blink, static int pca9532_calcpwm(struct i2c_client *client, int pwm, int blink,
enum led_brightness value) enum led_brightness value)
{ {
int a = 0, b = 0, i = 0; int a = 0, b = 0, i = 0;
...@@ -84,11 +86,17 @@ static int pca9532_setpwm(struct i2c_client *client, int pwm, int blink, ...@@ -84,11 +86,17 @@ static int pca9532_setpwm(struct i2c_client *client, int pwm, int blink,
b = b/a; b = b/a;
if (b > 0xFF) if (b > 0xFF)
return -EINVAL; return -EINVAL;
mutex_lock(&data->update_lock);
data->pwm[pwm] = b; data->pwm[pwm] = b;
data->psc[pwm] = blink;
return 0;
}
static int pca9532_setpwm(struct i2c_client *client, int pwm)
{
struct pca9532_data *data = i2c_get_clientdata(client);
mutex_lock(&data->update_lock);
i2c_smbus_write_byte_data(client, PCA9532_REG_PWM(pwm), i2c_smbus_write_byte_data(client, PCA9532_REG_PWM(pwm),
data->pwm[pwm]); data->pwm[pwm]);
data->psc[pwm] = blink;
i2c_smbus_write_byte_data(client, PCA9532_REG_PSC(pwm), i2c_smbus_write_byte_data(client, PCA9532_REG_PSC(pwm),
data->psc[pwm]); data->psc[pwm]);
mutex_unlock(&data->update_lock); mutex_unlock(&data->update_lock);
...@@ -124,11 +132,11 @@ static void pca9532_set_brightness(struct led_classdev *led_cdev, ...@@ -124,11 +132,11 @@ static void pca9532_set_brightness(struct led_classdev *led_cdev,
led->state = PCA9532_ON; led->state = PCA9532_ON;
else { else {
led->state = PCA9532_PWM0; /* Thecus: hardcode one pwm */ led->state = PCA9532_PWM0; /* Thecus: hardcode one pwm */
err = pca9532_setpwm(led->client, 0, 0, value); err = pca9532_calcpwm(led->client, 0, 0, value);
if (err) if (err)
return; /* XXX: led api doesn't allow error code? */ return; /* XXX: led api doesn't allow error code? */
} }
pca9532_setled(led); schedule_work(&led->work);
} }
static int pca9532_set_blink(struct led_classdev *led_cdev, static int pca9532_set_blink(struct led_classdev *led_cdev,
...@@ -137,6 +145,7 @@ static int pca9532_set_blink(struct led_classdev *led_cdev, ...@@ -137,6 +145,7 @@ static int pca9532_set_blink(struct led_classdev *led_cdev,
struct pca9532_led *led = ldev_to_led(led_cdev); struct pca9532_led *led = ldev_to_led(led_cdev);
struct i2c_client *client = led->client; struct i2c_client *client = led->client;
int psc; int psc;
int err = 0;
if (*delay_on == 0 && *delay_off == 0) { if (*delay_on == 0 && *delay_off == 0) {
/* led subsystem ask us for a blink rate */ /* led subsystem ask us for a blink rate */
...@@ -148,7 +157,11 @@ static int pca9532_set_blink(struct led_classdev *led_cdev, ...@@ -148,7 +157,11 @@ static int pca9532_set_blink(struct led_classdev *led_cdev,
/* Thecus specific: only use PSC/PWM 0 */ /* Thecus specific: only use PSC/PWM 0 */
psc = (*delay_on * 152-1)/1000; psc = (*delay_on * 152-1)/1000;
return pca9532_setpwm(client, 0, psc, led_cdev->brightness); err = pca9532_calcpwm(client, 0, psc, led_cdev->brightness);
if (err)
return err;
schedule_work(&led->work);
return 0;
} }
static int pca9532_event(struct input_dev *dev, unsigned int type, static int pca9532_event(struct input_dev *dev, unsigned int type,
...@@ -165,13 +178,28 @@ static int pca9532_event(struct input_dev *dev, unsigned int type, ...@@ -165,13 +178,28 @@ static int pca9532_event(struct input_dev *dev, unsigned int type,
else else
data->pwm[1] = 0; data->pwm[1] = 0;
dev_info(&dev->dev, "setting beep to %d \n", data->pwm[1]); schedule_work(&data->work);
return 0;
}
static void pca9532_input_work(struct work_struct *work)
{
struct pca9532_data *data;
data = container_of(work, struct pca9532_data, work);
mutex_lock(&data->update_lock); mutex_lock(&data->update_lock);
i2c_smbus_write_byte_data(data->client, PCA9532_REG_PWM(1), i2c_smbus_write_byte_data(data->client, PCA9532_REG_PWM(1),
data->pwm[1]); data->pwm[1]);
mutex_unlock(&data->update_lock); mutex_unlock(&data->update_lock);
}
return 0; static void pca9532_led_work(struct work_struct *work)
{
struct pca9532_led *led;
led = container_of(work, struct pca9532_led, work);
if (led->state == PCA9532_PWM0)
pca9532_setpwm(led->client, 0);
pca9532_setled(led);
} }
static int pca9532_configure(struct i2c_client *client, static int pca9532_configure(struct i2c_client *client,
...@@ -204,6 +232,7 @@ static int pca9532_configure(struct i2c_client *client, ...@@ -204,6 +232,7 @@ static int pca9532_configure(struct i2c_client *client,
led->ldev.brightness = LED_OFF; led->ldev.brightness = LED_OFF;
led->ldev.brightness_set = pca9532_set_brightness; led->ldev.brightness_set = pca9532_set_brightness;
led->ldev.blink_set = pca9532_set_blink; led->ldev.blink_set = pca9532_set_blink;
INIT_WORK(&led->work, pca9532_led_work);
err = led_classdev_register(&client->dev, &led->ldev); err = led_classdev_register(&client->dev, &led->ldev);
if (err < 0) { if (err < 0) {
dev_err(&client->dev, dev_err(&client->dev,
...@@ -233,9 +262,11 @@ static int pca9532_configure(struct i2c_client *client, ...@@ -233,9 +262,11 @@ static int pca9532_configure(struct i2c_client *client,
BIT_MASK(SND_TONE); BIT_MASK(SND_TONE);
data->idev->event = pca9532_event; data->idev->event = pca9532_event;
input_set_drvdata(data->idev, data); input_set_drvdata(data->idev, data);
INIT_WORK(&data->work, pca9532_input_work);
err = input_register_device(data->idev); err = input_register_device(data->idev);
if (err) { if (err) {
input_free_device(data->idev); input_free_device(data->idev);
cancel_work_sync(&data->work);
data->idev = NULL; data->idev = NULL;
goto exit; goto exit;
} }
...@@ -252,11 +283,13 @@ exit: ...@@ -252,11 +283,13 @@ exit:
break; break;
case PCA9532_TYPE_LED: case PCA9532_TYPE_LED:
led_classdev_unregister(&data->leds[i].ldev); led_classdev_unregister(&data->leds[i].ldev);
cancel_work_sync(&data->leds[i].work);
break; break;
case PCA9532_TYPE_N2100_BEEP: case PCA9532_TYPE_N2100_BEEP:
if (data->idev != NULL) { if (data->idev != NULL) {
input_unregister_device(data->idev); input_unregister_device(data->idev);
input_free_device(data->idev); input_free_device(data->idev);
cancel_work_sync(&data->work);
data->idev = NULL; data->idev = NULL;
} }
break; break;
...@@ -307,11 +340,13 @@ static int pca9532_remove(struct i2c_client *client) ...@@ -307,11 +340,13 @@ static int pca9532_remove(struct i2c_client *client)
break; break;
case PCA9532_TYPE_LED: case PCA9532_TYPE_LED:
led_classdev_unregister(&data->leds[i].ldev); led_classdev_unregister(&data->leds[i].ldev);
cancel_work_sync(&data->leds[i].work);
break; break;
case PCA9532_TYPE_N2100_BEEP: case PCA9532_TYPE_N2100_BEEP:
if (data->idev != NULL) { if (data->idev != NULL) {
input_unregister_device(data->idev); input_unregister_device(data->idev);
input_free_device(data->idev); input_free_device(data->idev);
cancel_work_sync(&data->work);
data->idev = NULL; data->idev = NULL;
} }
break; break;
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#define __LINUX_PCA9532_H #define __LINUX_PCA9532_H
#include <linux/leds.h> #include <linux/leds.h>
#include <linux/workqueue.h>
enum pca9532_state { enum pca9532_state {
PCA9532_OFF = 0x0, PCA9532_OFF = 0x0,
...@@ -31,6 +32,7 @@ struct pca9532_led { ...@@ -31,6 +32,7 @@ struct pca9532_led {
struct i2c_client *client; struct i2c_client *client;
char *name; char *name;
struct led_classdev ldev; struct led_classdev ldev;
struct work_struct work;
enum pca9532_type type; enum pca9532_type type;
enum pca9532_state state; enum pca9532_state state;
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment