Blame view

drivers/leds/leds-88pm860x.c 5.67 KB
0a2f915b4   Haojian Zhuang   led: Enable led i...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  /*
   * LED driver for Marvell 88PM860x
   *
   * Copyright (C) 2009 Marvell International Ltd.
   *	Haojian Zhuang <haojian.zhuang@marvell.com>
   *
   * This program is free software; you can redistribute it and/or modify
   * it under the terms of the GNU General Public License version 2 as
   * published by the Free Software Foundation.
   *
   */
  
  #include <linux/kernel.h>
  #include <linux/init.h>
  #include <linux/platform_device.h>
  #include <linux/i2c.h>
  #include <linux/leds.h>
5a0e3ad6a   Tejun Heo   include cleanup: ...
18
  #include <linux/slab.h>
0a2f915b4   Haojian Zhuang   led: Enable led i...
19
20
  #include <linux/workqueue.h>
  #include <linux/mfd/88pm860x.h>
54f4dedb5   Paul Gortmaker   drivers/leds: Add...
21
  #include <linux/module.h>
0a2f915b4   Haojian Zhuang   led: Enable led i...
22
23
24
25
26
27
  
  #define LED_PWM_SHIFT		(3)
  #define LED_PWM_MASK		(0x1F)
  #define LED_CURRENT_MASK	(0x07 << 5)
  
  #define LED_BLINK_ON_MASK	(0x07)
0a2f915b4   Haojian Zhuang   led: Enable led i...
28
29
30
  #define LED_BLINK_MASK		(0x7F)
  
  #define LED_BLINK_ON(x)		((x & 0x7) * 66 + 66)
0a2f915b4   Haojian Zhuang   led: Enable led i...
31
32
  #define LED_BLINK_ON_MIN	LED_BLINK_ON(0)
  #define LED_BLINK_ON_MAX	LED_BLINK_ON(0x7)
f5d59fc57   Haojian Zhuang   leds: Remove auto...
33
  #define LED_ON_CONTINUOUS	(0x0F << 3)
0a2f915b4   Haojian Zhuang   led: Enable led i...
34
  #define LED_TO_ON(x)		((x - 66) / 66)
0a2f915b4   Haojian Zhuang   led: Enable led i...
35
36
37
  
  #define LED1_BLINK_EN		(1 << 1)
  #define LED2_BLINK_EN		(1 << 2)
0a2f915b4   Haojian Zhuang   led: Enable led i...
38
39
40
41
42
43
44
45
46
47
  struct pm860x_led {
  	struct led_classdev cdev;
  	struct i2c_client *i2c;
  	struct work_struct work;
  	struct pm860x_chip *chip;
  	struct mutex lock;
  	char name[MFD_NAME_SIZE];
  
  	int port;
  	int iset;
0a2f915b4   Haojian Zhuang   led: Enable led i...
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
  	unsigned char brightness;
  	unsigned char current_brightness;
  
  	int blink_data;
  	int blink_time;
  	int blink_on;
  	int blink_off;
  };
  
  /* return offset of color register */
  static inline int __led_off(int port)
  {
  	int ret = -EINVAL;
  
  	switch (port) {
  	case PM8606_LED1_RED:
  	case PM8606_LED1_GREEN:
  	case PM8606_LED1_BLUE:
  		ret = port - PM8606_LED1_RED + PM8606_RGB1B;
  		break;
  	case PM8606_LED2_RED:
  	case PM8606_LED2_GREEN:
  	case PM8606_LED2_BLUE:
  		ret = port - PM8606_LED2_RED + PM8606_RGB2B;
  		break;
  	}
  	return ret;
  }
  
  /* return offset of blink register */
  static inline int __blink_off(int port)
  {
  	int ret = -EINVAL;
  
  	switch (port) {
  	case PM8606_LED1_RED:
  	case PM8606_LED1_GREEN:
  	case PM8606_LED1_BLUE:
  		ret = PM8606_RGB1A;
f5d59fc57   Haojian Zhuang   leds: Remove auto...
87
  		break;
0a2f915b4   Haojian Zhuang   led: Enable led i...
88
89
90
91
  	case PM8606_LED2_RED:
  	case PM8606_LED2_GREEN:
  	case PM8606_LED2_BLUE:
  		ret = PM8606_RGB2A;
f5d59fc57   Haojian Zhuang   leds: Remove auto...
92
  		break;
0a2f915b4   Haojian Zhuang   led: Enable led i...
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
  	}
  	return ret;
  }
  
  static inline int __blink_ctl_mask(int port)
  {
  	int ret = -EINVAL;
  
  	switch (port) {
  	case PM8606_LED1_RED:
  	case PM8606_LED1_GREEN:
  	case PM8606_LED1_BLUE:
  		ret = LED1_BLINK_EN;
  		break;
  	case PM8606_LED2_RED:
  	case PM8606_LED2_GREEN:
  	case PM8606_LED2_BLUE:
  		ret = LED2_BLINK_EN;
  		break;
  	}
  	return ret;
  }
f5d59fc57   Haojian Zhuang   leds: Remove auto...
115
  static void pm860x_led_work(struct work_struct *work)
0a2f915b4   Haojian Zhuang   led: Enable led i...
116
  {
0a2f915b4   Haojian Zhuang   led: Enable led i...
117

f5d59fc57   Haojian Zhuang   leds: Remove auto...
118
119
  	struct pm860x_led *led;
  	struct pm860x_chip *chip;
3154c3446   Haojian Zhuang   mfd: Adopt mfd_da...
120
121
  	unsigned char buf[3];
  	int mask, ret;
f5d59fc57   Haojian Zhuang   leds: Remove auto...
122
123
124
  
  	led = container_of(work, struct pm860x_led, work);
  	chip = led->chip;
0a2f915b4   Haojian Zhuang   led: Enable led i...
125
  	mutex_lock(&led->lock);
f5d59fc57   Haojian Zhuang   leds: Remove auto...
126
127
128
129
  	if ((led->current_brightness == 0) && led->brightness) {
  		if (led->iset) {
  			pm860x_set_bits(led->i2c, __led_off(led->port),
  					LED_CURRENT_MASK, led->iset);
0a2f915b4   Haojian Zhuang   led: Enable led i...
130
  		}
3154c3446   Haojian Zhuang   mfd: Adopt mfd_da...
131
132
  		pm860x_set_bits(led->i2c, __blink_off(led->port),
  				LED_BLINK_MASK, LED_ON_CONTINUOUS);
0a2f915b4   Haojian Zhuang   led: Enable led i...
133
  		mask = __blink_ctl_mask(led->port);
f5d59fc57   Haojian Zhuang   leds: Remove auto...
134
  		pm860x_set_bits(led->i2c, PM8606_WLED3B, mask, mask);
0a2f915b4   Haojian Zhuang   led: Enable led i...
135
  	}
f5d59fc57   Haojian Zhuang   leds: Remove auto...
136
137
  	pm860x_set_bits(led->i2c, __led_off(led->port), LED_PWM_MASK,
  			led->brightness);
3154c3446   Haojian Zhuang   mfd: Adopt mfd_da...
138
139
140
141
142
143
144
145
146
147
148
149
150
151
  
  	if (led->brightness == 0) {
  		pm860x_bulk_read(led->i2c, __led_off(led->port), 3, buf);
  		ret = buf[0] & LED_PWM_MASK;
  		ret |= buf[1] & LED_PWM_MASK;
  		ret |= buf[2] & LED_PWM_MASK;
  		if (ret == 0) {
  			/* unset current since no led is lighting */
  			pm860x_set_bits(led->i2c, __led_off(led->port),
  					LED_CURRENT_MASK, 0);
  			mask = __blink_ctl_mask(led->port);
  			pm860x_set_bits(led->i2c, PM8606_WLED3B, mask, 0);
  		}
  	}
f5d59fc57   Haojian Zhuang   leds: Remove auto...
152
153
154
155
  	led->current_brightness = led->brightness;
  	dev_dbg(chip->dev, "Update LED. (reg:%d, brightness:%d)
  ",
  		__led_off(led->port), led->brightness);
0a2f915b4   Haojian Zhuang   led: Enable led i...
156
  	mutex_unlock(&led->lock);
0a2f915b4   Haojian Zhuang   led: Enable led i...
157
158
159
160
161
162
  }
  
  static void pm860x_led_set(struct led_classdev *cdev,
  			   enum led_brightness value)
  {
  	struct pm860x_led *data = container_of(cdev, struct pm860x_led, cdev);
0a2f915b4   Haojian Zhuang   led: Enable led i...
163
  	data->brightness = value >> 3;
0a2f915b4   Haojian Zhuang   led: Enable led i...
164
  	schedule_work(&data->work);
0a2f915b4   Haojian Zhuang   led: Enable led i...
165
  }
0a2f915b4   Haojian Zhuang   led: Enable led i...
166
167
168
  static int pm860x_led_probe(struct platform_device *pdev)
  {
  	struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent);
0a2f915b4   Haojian Zhuang   led: Enable led i...
169
170
171
172
173
174
175
176
177
178
179
  	struct pm860x_led_pdata *pdata;
  	struct pm860x_led *data;
  	struct resource *res;
  	int ret;
  
  	res = platform_get_resource(pdev, IORESOURCE_IO, 0);
  	if (res == NULL) {
  		dev_err(&pdev->dev, "No I/O resource!
  ");
  		return -EINVAL;
  	}
07259a709   Samuel Ortiz   mfd: Use mfd cell...
180
  	pdata = pdev->dev.platform_data;
3154c3446   Haojian Zhuang   mfd: Adopt mfd_da...
181
  	if (pdata == NULL) {
f5d59fc57   Haojian Zhuang   leds: Remove auto...
182
183
  		dev_err(&pdev->dev, "No platform data!
  ");
98652efce   Christoph Fritz   leds: 88pm860x - ...
184
185
  		return -EINVAL;
  	}
0a2f915b4   Haojian Zhuang   led: Enable led i...
186
187
188
189
  
  	data = kzalloc(sizeof(struct pm860x_led), GFP_KERNEL);
  	if (data == NULL)
  		return -ENOMEM;
3154c3446   Haojian Zhuang   mfd: Adopt mfd_da...
190
  	strncpy(data->name, res->name, MFD_NAME_SIZE - 1);
0a2f915b4   Haojian Zhuang   led: Enable led i...
191
192
193
194
  	dev_set_drvdata(&pdev->dev, data);
  	data->chip = chip;
  	data->i2c = (chip->id == CHIP_PM8606) ? chip->client : chip->companion;
  	data->iset = pdata->iset;
3154c3446   Haojian Zhuang   mfd: Adopt mfd_da...
195
  	data->port = pdata->flags;
98652efce   Christoph Fritz   leds: 88pm860x - ...
196
197
198
199
  	if (data->port < 0) {
  		dev_err(&pdev->dev, "check device failed
  ");
  		kfree(data);
0a2f915b4   Haojian Zhuang   led: Enable led i...
200
  		return -EINVAL;
98652efce   Christoph Fritz   leds: 88pm860x - ...
201
  	}
0a2f915b4   Haojian Zhuang   led: Enable led i...
202
203
204
205
  
  	data->current_brightness = 0;
  	data->cdev.name = data->name;
  	data->cdev.brightness_set = pm860x_led_set;
0a2f915b4   Haojian Zhuang   led: Enable led i...
206
207
208
209
210
211
212
213
214
  	mutex_init(&data->lock);
  	INIT_WORK(&data->work, pm860x_led_work);
  
  	ret = led_classdev_register(chip->dev, &data->cdev);
  	if (ret < 0) {
  		dev_err(&pdev->dev, "Failed to register LED: %d
  ", ret);
  		goto out;
  	}
3154c3446   Haojian Zhuang   mfd: Adopt mfd_da...
215
  	pm860x_led_set(&data->cdev, 0);
0a2f915b4   Haojian Zhuang   led: Enable led i...
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
  	return 0;
  out:
  	kfree(data);
  	return ret;
  }
  
  static int pm860x_led_remove(struct platform_device *pdev)
  {
  	struct pm860x_led *data = platform_get_drvdata(pdev);
  
  	led_classdev_unregister(&data->cdev);
  	kfree(data);
  
  	return 0;
  }
  
  static struct platform_driver pm860x_led_driver = {
  	.driver	= {
  		.name	= "88pm860x-led",
  		.owner	= THIS_MODULE,
  	},
  	.probe	= pm860x_led_probe,
  	.remove	= pm860x_led_remove,
  };
892a8843f   Axel Lin   leds: convert led...
240
  module_platform_driver(pm860x_led_driver);
0a2f915b4   Haojian Zhuang   led: Enable led i...
241
242
243
244
245
  
  MODULE_DESCRIPTION("LED driver for Marvell PM860x");
  MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
  MODULE_LICENSE("GPL");
  MODULE_ALIAS("platform:88pm860x-led");