Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

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

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
Loading
Loading
Loading
Loading
+43 −8
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
#include <linux/leds.h>
#include <linux/input.h>
#include <linux/mutex.h>
#include <linux/workqueue.h>
#include <linux/leds-pca9532.h>

static const unsigned short normal_i2c[] = { /*0x60,*/ I2C_CLIENT_END};
@@ -34,6 +35,7 @@ struct pca9532_data {
	struct pca9532_led leds[16];
	struct mutex update_lock;
	struct input_dev    *idev;
       struct work_struct work;
	u8 pwm[2];
	u8 psc[2];
};
@@ -63,7 +65,7 @@ static struct i2c_driver pca9532_driver = {
 * as a compromise we average one pwm to the values requested by all
 * 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)
{
	int a = 0, b = 0, i = 0;
@@ -84,11 +86,17 @@ static int pca9532_setpwm(struct i2c_client *client, int pwm, int blink,
	b = b/a;
	if (b > 0xFF)
		return -EINVAL;
	mutex_lock(&data->update_lock);
	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),
		data->pwm[pwm]);
	data->psc[pwm] = blink;
	i2c_smbus_write_byte_data(client, PCA9532_REG_PSC(pwm),
		data->psc[pwm]);
	mutex_unlock(&data->update_lock);
@@ -124,11 +132,11 @@ static void pca9532_set_brightness(struct led_classdev *led_cdev,
		led->state = PCA9532_ON;
	else {
		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)
			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,
@@ -137,6 +145,7 @@ static int pca9532_set_blink(struct led_classdev *led_cdev,
	struct pca9532_led *led = ldev_to_led(led_cdev);
	struct i2c_client *client = led->client;
	int psc;
       int err = 0;

	if (*delay_on == 0 && *delay_off == 0) {
	/* led subsystem ask us for a blink rate */
@@ -148,7 +157,11 @@ static int pca9532_set_blink(struct led_classdev *led_cdev,

	/* Thecus specific: only use PSC/PWM 0 */
	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,
@@ -165,13 +178,28 @@ static int pca9532_event(struct input_dev *dev, unsigned int type,
	else
		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);
	i2c_smbus_write_byte_data(data->client, PCA9532_REG_PWM(1),
		data->pwm[1]);
	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,
@@ -204,6 +232,7 @@ static int pca9532_configure(struct i2c_client *client,
			led->ldev.brightness = LED_OFF;
			led->ldev.brightness_set = pca9532_set_brightness;
			led->ldev.blink_set = pca9532_set_blink;
                       INIT_WORK(&led->work, pca9532_led_work);
			err = led_classdev_register(&client->dev, &led->ldev);
			if (err < 0) {
				dev_err(&client->dev,
@@ -233,9 +262,11 @@ static int pca9532_configure(struct i2c_client *client,
						BIT_MASK(SND_TONE);
			data->idev->event = pca9532_event;
			input_set_drvdata(data->idev, data);
                       INIT_WORK(&data->work, pca9532_input_work);
			err = input_register_device(data->idev);
			if (err) {
				input_free_device(data->idev);
                               cancel_work_sync(&data->work);
				data->idev = NULL;
				goto exit;
			}
@@ -252,11 +283,13 @@ exit:
				break;
			case PCA9532_TYPE_LED:
				led_classdev_unregister(&data->leds[i].ldev);
                               cancel_work_sync(&data->leds[i].work);
				break;
			case PCA9532_TYPE_N2100_BEEP:
				if (data->idev != NULL) {
					input_unregister_device(data->idev);
					input_free_device(data->idev);
                                       cancel_work_sync(&data->work);
					data->idev = NULL;
				}
				break;
@@ -307,11 +340,13 @@ static int pca9532_remove(struct i2c_client *client)
			break;
		case PCA9532_TYPE_LED:
			led_classdev_unregister(&data->leds[i].ldev);
                       cancel_work_sync(&data->leds[i].work);
			break;
		case PCA9532_TYPE_N2100_BEEP:
			if (data->idev != NULL) {
				input_unregister_device(data->idev);
				input_free_device(data->idev);
                               cancel_work_sync(&data->work);
				data->idev = NULL;
			}
			break;
+2 −0
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
#define __LINUX_PCA9532_H

#include <linux/leds.h>
#include <linux/workqueue.h>

enum pca9532_state {
	PCA9532_OFF  = 0x0,
@@ -31,6 +32,7 @@ struct pca9532_led {
	struct i2c_client *client;
	char *name;
	struct led_classdev ldev;
       struct work_struct work;
	enum pca9532_type type;
	enum pca9532_state state;
};