diff options
author | Robin van der Gracht <robin@protonic.nl> | 2016-11-07 04:56:35 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2016-11-10 10:53:19 -0500 |
commit | 8992da44c6805d53b920fe538992eae4afd6f22e (patch) | |
tree | 47f607d1a9f5694690e63e4847df5bd4ea632851 | |
parent | 7279b238badec09efd0545293e64c21feee97f73 (diff) |
auxdisplay: ht16k33: Driver for LED controller
Added a driver for the Holtek HT16K33 LED controller with keyscan.
Signed-off-by: Robin van der Gracht <robin@protonic.nl>
CC: Miguel Ojeda Sandonis <miguel.ojeda.sandonis@gmail.com>
Acked-by: Rob Herring <robh@kernel.org>
Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r-- | Documentation/devicetree/bindings/display/ht16k33.txt | 42 | ||||
-rw-r--r-- | drivers/auxdisplay/Kconfig | 9 | ||||
-rw-r--r-- | drivers/auxdisplay/Makefile | 1 | ||||
-rw-r--r-- | drivers/auxdisplay/ht16k33.c | 563 |
4 files changed, 615 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/display/ht16k33.txt b/Documentation/devicetree/bindings/display/ht16k33.txt new file mode 100644 index 000000000000..8e5b30b87754 --- /dev/null +++ b/Documentation/devicetree/bindings/display/ht16k33.txt | |||
@@ -0,0 +1,42 @@ | |||
1 | Holtek ht16k33 RAM mapping 16*8 LED controller driver with keyscan | ||
2 | ------------------------------------------------------------------------------- | ||
3 | |||
4 | Required properties: | ||
5 | - compatible: "holtek,ht16k33" | ||
6 | - reg: I2C slave address of the chip. | ||
7 | - interrupt-parent: A phandle pointing to the interrupt controller | ||
8 | serving the interrupt for this chip. | ||
9 | - interrupts: Interrupt specification for the key pressed interrupt. | ||
10 | - refresh-rate-hz: Display update interval in HZ. | ||
11 | - debounce-delay-ms: Debouncing interval time in milliseconds. | ||
12 | - linux,keymap: The keymap for keys as described in the binding | ||
13 | document (devicetree/bindings/input/matrix-keymap.txt). | ||
14 | |||
15 | Optional properties: | ||
16 | - linux,no-autorepeat: Disable keyrepeat. | ||
17 | - default-brightness-level: Initial brightness level [0-15] (default: 15). | ||
18 | |||
19 | Example: | ||
20 | |||
21 | &i2c1 { | ||
22 | ht16k33: ht16k33@70 { | ||
23 | compatible = "holtek,ht16k33"; | ||
24 | reg = <0x70>; | ||
25 | refresh-rate-hz = <20>; | ||
26 | debounce-delay-ms = <50>; | ||
27 | interrupt-parent = <&gpio4>; | ||
28 | interrupts = <5 (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_EDGE_RISING)>; | ||
29 | linux,keymap = < | ||
30 | MATRIX_KEY(2, 0, KEY_F6) | ||
31 | MATRIX_KEY(3, 0, KEY_F8) | ||
32 | MATRIX_KEY(4, 0, KEY_F10) | ||
33 | MATRIX_KEY(5, 0, KEY_F4) | ||
34 | MATRIX_KEY(6, 0, KEY_F2) | ||
35 | MATRIX_KEY(2, 1, KEY_F5) | ||
36 | MATRIX_KEY(3, 1, KEY_F7) | ||
37 | MATRIX_KEY(4, 1, KEY_F9) | ||
38 | MATRIX_KEY(5, 1, KEY_F3) | ||
39 | MATRIX_KEY(6, 1, KEY_F1) | ||
40 | >; | ||
41 | }; | ||
42 | }; | ||
diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig index 10e1b9eee10e..a230ea797b92 100644 --- a/drivers/auxdisplay/Kconfig +++ b/drivers/auxdisplay/Kconfig | |||
@@ -128,4 +128,13 @@ config IMG_ASCII_LCD | |||
128 | development boards such as the MIPS Boston, MIPS Malta & MIPS SEAD3 | 128 | development boards such as the MIPS Boston, MIPS Malta & MIPS SEAD3 |
129 | from Imagination Technologies. | 129 | from Imagination Technologies. |
130 | 130 | ||
131 | config HT16K33 | ||
132 | tristate "Holtek Ht16K33 LED controller with keyscan" | ||
133 | depends on FB && OF && I2C && INPUT | ||
134 | select INPUT_MATRIXKMAP | ||
135 | select FB_BACKLIGHT | ||
136 | help | ||
137 | Say yes here to add support for Holtek HT16K33, RAM mapping 16*8 | ||
138 | LED controller driver with keyscan. | ||
139 | |||
131 | endif # AUXDISPLAY | 140 | endif # AUXDISPLAY |
diff --git a/drivers/auxdisplay/Makefile b/drivers/auxdisplay/Makefile index 3127175c89df..cb3dd847713b 100644 --- a/drivers/auxdisplay/Makefile +++ b/drivers/auxdisplay/Makefile | |||
@@ -5,3 +5,4 @@ | |||
5 | obj-$(CONFIG_KS0108) += ks0108.o | 5 | obj-$(CONFIG_KS0108) += ks0108.o |
6 | obj-$(CONFIG_CFAG12864B) += cfag12864b.o cfag12864bfb.o | 6 | obj-$(CONFIG_CFAG12864B) += cfag12864b.o cfag12864bfb.o |
7 | obj-$(CONFIG_IMG_ASCII_LCD) += img-ascii-lcd.o | 7 | obj-$(CONFIG_IMG_ASCII_LCD) += img-ascii-lcd.o |
8 | obj-$(CONFIG_HT16K33) += ht16k33.o | ||
diff --git a/drivers/auxdisplay/ht16k33.c b/drivers/auxdisplay/ht16k33.c new file mode 100644 index 000000000000..eeb323f56c07 --- /dev/null +++ b/drivers/auxdisplay/ht16k33.c | |||
@@ -0,0 +1,563 @@ | |||
1 | /* | ||
2 | * HT16K33 driver | ||
3 | * | ||
4 | * Author: Robin van der Gracht <robin@protonic.nl> | ||
5 | * | ||
6 | * Copyright: (C) 2016 Protonic Holland. | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License version 2 as | ||
10 | * published by the Free Software Foundation. | ||
11 | * | ||
12 | * This program is distributed in the hope that it will be useful, but | ||
13 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
15 | * General Public License for more details. | ||
16 | */ | ||
17 | |||
18 | #include <linux/kernel.h> | ||
19 | #include <linux/module.h> | ||
20 | #include <linux/interrupt.h> | ||
21 | #include <linux/i2c.h> | ||
22 | #include <linux/of.h> | ||
23 | #include <linux/fb.h> | ||
24 | #include <linux/slab.h> | ||
25 | #include <linux/backlight.h> | ||
26 | #include <linux/input.h> | ||
27 | #include <linux/input/matrix_keypad.h> | ||
28 | #include <linux/workqueue.h> | ||
29 | #include <linux/mm.h> | ||
30 | |||
31 | /* Registers */ | ||
32 | #define REG_SYSTEM_SETUP 0x20 | ||
33 | #define REG_SYSTEM_SETUP_OSC_ON BIT(0) | ||
34 | |||
35 | #define REG_DISPLAY_SETUP 0x80 | ||
36 | #define REG_DISPLAY_SETUP_ON BIT(0) | ||
37 | |||
38 | #define REG_ROWINT_SET 0xA0 | ||
39 | #define REG_ROWINT_SET_INT_EN BIT(0) | ||
40 | #define REG_ROWINT_SET_INT_ACT_HIGH BIT(1) | ||
41 | |||
42 | #define REG_BRIGHTNESS 0xE0 | ||
43 | |||
44 | /* Defines */ | ||
45 | #define DRIVER_NAME "ht16k33" | ||
46 | |||
47 | #define MIN_BRIGHTNESS 0x1 | ||
48 | #define MAX_BRIGHTNESS 0x10 | ||
49 | |||
50 | #define HT16K33_MATRIX_LED_MAX_COLS 8 | ||
51 | #define HT16K33_MATRIX_LED_MAX_ROWS 16 | ||
52 | #define HT16K33_MATRIX_KEYPAD_MAX_COLS 3 | ||
53 | #define HT16K33_MATRIX_KEYPAD_MAX_ROWS 12 | ||
54 | |||
55 | #define BYTES_PER_ROW (HT16K33_MATRIX_LED_MAX_ROWS / 8) | ||
56 | #define HT16K33_FB_SIZE (HT16K33_MATRIX_LED_MAX_COLS * BYTES_PER_ROW) | ||
57 | |||
58 | struct ht16k33_keypad { | ||
59 | struct input_dev *dev; | ||
60 | spinlock_t lock; | ||
61 | struct delayed_work work; | ||
62 | uint32_t cols; | ||
63 | uint32_t rows; | ||
64 | uint32_t row_shift; | ||
65 | uint32_t debounce_ms; | ||
66 | uint16_t last_key_state[HT16K33_MATRIX_KEYPAD_MAX_COLS]; | ||
67 | }; | ||
68 | |||
69 | struct ht16k33_fbdev { | ||
70 | struct fb_info *info; | ||
71 | uint32_t refresh_rate; | ||
72 | uint8_t *buffer; | ||
73 | uint8_t *cache; | ||
74 | struct delayed_work work; | ||
75 | }; | ||
76 | |||
77 | struct ht16k33_priv { | ||
78 | struct i2c_client *client; | ||
79 | struct ht16k33_keypad keypad; | ||
80 | struct ht16k33_fbdev fbdev; | ||
81 | struct workqueue_struct *workqueue; | ||
82 | }; | ||
83 | |||
84 | static struct fb_fix_screeninfo ht16k33_fb_fix = { | ||
85 | .id = DRIVER_NAME, | ||
86 | .type = FB_TYPE_PACKED_PIXELS, | ||
87 | .visual = FB_VISUAL_MONO10, | ||
88 | .xpanstep = 0, | ||
89 | .ypanstep = 0, | ||
90 | .ywrapstep = 0, | ||
91 | .line_length = HT16K33_MATRIX_LED_MAX_ROWS, | ||
92 | .accel = FB_ACCEL_NONE, | ||
93 | }; | ||
94 | |||
95 | static struct fb_var_screeninfo ht16k33_fb_var = { | ||
96 | .xres = HT16K33_MATRIX_LED_MAX_ROWS, | ||
97 | .yres = HT16K33_MATRIX_LED_MAX_COLS, | ||
98 | .xres_virtual = HT16K33_MATRIX_LED_MAX_ROWS, | ||
99 | .yres_virtual = HT16K33_MATRIX_LED_MAX_COLS, | ||
100 | .bits_per_pixel = 1, | ||
101 | .red = { 0, 1, 0 }, | ||
102 | .green = { 0, 1, 0 }, | ||
103 | .blue = { 0, 1, 0 }, | ||
104 | .left_margin = 0, | ||
105 | .right_margin = 0, | ||
106 | .upper_margin = 0, | ||
107 | .lower_margin = 0, | ||
108 | .vmode = FB_VMODE_NONINTERLACED, | ||
109 | }; | ||
110 | |||
111 | static int ht16k33_display_on(struct ht16k33_priv *priv) | ||
112 | { | ||
113 | uint8_t data = REG_DISPLAY_SETUP | REG_DISPLAY_SETUP_ON; | ||
114 | |||
115 | return i2c_smbus_write_byte(priv->client, data); | ||
116 | } | ||
117 | |||
118 | static int ht16k33_display_off(struct ht16k33_priv *priv) | ||
119 | { | ||
120 | return i2c_smbus_write_byte(priv->client, REG_DISPLAY_SETUP); | ||
121 | } | ||
122 | |||
123 | static void ht16k33_fb_queue(struct ht16k33_priv *priv) | ||
124 | { | ||
125 | struct ht16k33_fbdev *fbdev = &priv->fbdev; | ||
126 | |||
127 | queue_delayed_work(priv->workqueue, &fbdev->work, | ||
128 | msecs_to_jiffies(HZ / fbdev->refresh_rate)); | ||
129 | } | ||
130 | |||
131 | static void ht16k33_keypad_queue(struct ht16k33_priv *priv) | ||
132 | { | ||
133 | struct ht16k33_keypad *keypad = &priv->keypad; | ||
134 | |||
135 | queue_delayed_work(priv->workqueue, &keypad->work, | ||
136 | msecs_to_jiffies(keypad->debounce_ms)); | ||
137 | } | ||
138 | |||
139 | /* | ||
140 | * This gets the fb data from cache and copies it to ht16k33 display RAM | ||
141 | */ | ||
142 | static void ht16k33_fb_update(struct work_struct *work) | ||
143 | { | ||
144 | struct ht16k33_fbdev *fbdev = | ||
145 | container_of(work, struct ht16k33_fbdev, work.work); | ||
146 | struct ht16k33_priv *priv = | ||
147 | container_of(fbdev, struct ht16k33_priv, fbdev); | ||
148 | |||
149 | uint8_t *p1, *p2; | ||
150 | int len, pos = 0, first = -1; | ||
151 | |||
152 | p1 = fbdev->cache; | ||
153 | p2 = fbdev->buffer; | ||
154 | |||
155 | /* Search for the first byte with changes */ | ||
156 | while (pos < HT16K33_FB_SIZE && first < 0) { | ||
157 | if (*(p1++) - *(p2++)) | ||
158 | first = pos; | ||
159 | pos++; | ||
160 | } | ||
161 | |||
162 | /* No changes found */ | ||
163 | if (first < 0) | ||
164 | goto requeue; | ||
165 | |||
166 | len = HT16K33_FB_SIZE - first; | ||
167 | p1 = fbdev->cache + HT16K33_FB_SIZE - 1; | ||
168 | p2 = fbdev->buffer + HT16K33_FB_SIZE - 1; | ||
169 | |||
170 | /* Determine i2c transfer length */ | ||
171 | while (len > 1) { | ||
172 | if (*(p1--) - *(p2--)) | ||
173 | break; | ||
174 | len--; | ||
175 | } | ||
176 | |||
177 | p1 = fbdev->cache + first; | ||
178 | p2 = fbdev->buffer + first; | ||
179 | if (!i2c_smbus_write_i2c_block_data(priv->client, first, len, p2)) | ||
180 | memcpy(p1, p2, len); | ||
181 | requeue: | ||
182 | ht16k33_fb_queue(priv); | ||
183 | } | ||
184 | |||
185 | static int ht16k33_keypad_start(struct input_dev *dev) | ||
186 | { | ||
187 | struct ht16k33_priv *priv = input_get_drvdata(dev); | ||
188 | struct ht16k33_keypad *keypad = &priv->keypad; | ||
189 | |||
190 | /* | ||
191 | * Schedule an immediate key scan to capture current key state; | ||
192 | * columns will be activated and IRQs be enabled after the scan. | ||
193 | */ | ||
194 | queue_delayed_work(priv->workqueue, &keypad->work, 0); | ||
195 | return 0; | ||
196 | } | ||
197 | |||
198 | static void ht16k33_keypad_stop(struct input_dev *dev) | ||
199 | { | ||
200 | struct ht16k33_priv *priv = input_get_drvdata(dev); | ||
201 | struct ht16k33_keypad *keypad = &priv->keypad; | ||
202 | |||
203 | cancel_delayed_work(&keypad->work); | ||
204 | /* | ||
205 | * ht16k33_keypad_scan() will leave IRQs enabled; | ||
206 | * we should disable them now. | ||
207 | */ | ||
208 | disable_irq_nosync(priv->client->irq); | ||
209 | } | ||
210 | |||
211 | static int ht16k33_initialize(struct ht16k33_priv *priv) | ||
212 | { | ||
213 | uint8_t byte; | ||
214 | int err; | ||
215 | uint8_t data[HT16K33_MATRIX_LED_MAX_COLS * 2]; | ||
216 | |||
217 | /* Clear RAM (8 * 16 bits) */ | ||
218 | memset(data, 0, sizeof(data)); | ||
219 | err = i2c_smbus_write_block_data(priv->client, 0, sizeof(data), data); | ||
220 | if (err) | ||
221 | return err; | ||
222 | |||
223 | /* Turn on internal oscillator */ | ||
224 | byte = REG_SYSTEM_SETUP_OSC_ON | REG_SYSTEM_SETUP; | ||
225 | err = i2c_smbus_write_byte(priv->client, byte); | ||
226 | if (err) | ||
227 | return err; | ||
228 | |||
229 | /* Configure INT pin */ | ||
230 | byte = REG_ROWINT_SET | REG_ROWINT_SET_INT_ACT_HIGH; | ||
231 | if (priv->client->irq > 0) | ||
232 | byte |= REG_ROWINT_SET_INT_EN; | ||
233 | return i2c_smbus_write_byte(priv->client, byte); | ||
234 | } | ||
235 | |||
236 | /* | ||
237 | * This gets the keys from keypad and reports it to input subsystem | ||
238 | */ | ||
239 | static void ht16k33_keypad_scan(struct work_struct *work) | ||
240 | { | ||
241 | struct ht16k33_keypad *keypad = | ||
242 | container_of(work, struct ht16k33_keypad, work.work); | ||
243 | struct ht16k33_priv *priv = | ||
244 | container_of(keypad, struct ht16k33_priv, keypad); | ||
245 | const unsigned short *keycodes = keypad->dev->keycode; | ||
246 | uint16_t bits_changed, new_state[HT16K33_MATRIX_KEYPAD_MAX_COLS]; | ||
247 | uint8_t data[HT16K33_MATRIX_KEYPAD_MAX_COLS * 2]; | ||
248 | int row, col, code; | ||
249 | bool reschedule = false; | ||
250 | |||
251 | if (i2c_smbus_read_i2c_block_data(priv->client, 0x40, 6, data) != 6) { | ||
252 | dev_err(&priv->client->dev, "Failed to read key data\n"); | ||
253 | goto end; | ||
254 | } | ||
255 | |||
256 | for (col = 0; col < keypad->cols; col++) { | ||
257 | new_state[col] = (data[col * 2 + 1] << 8) | data[col * 2]; | ||
258 | if (new_state[col]) | ||
259 | reschedule = true; | ||
260 | bits_changed = keypad->last_key_state[col] ^ new_state[col]; | ||
261 | |||
262 | while (bits_changed) { | ||
263 | row = ffs(bits_changed) - 1; | ||
264 | code = MATRIX_SCAN_CODE(row, col, keypad->row_shift); | ||
265 | input_event(keypad->dev, EV_MSC, MSC_SCAN, code); | ||
266 | input_report_key(keypad->dev, keycodes[code], | ||
267 | new_state[col] & BIT(row)); | ||
268 | bits_changed &= ~BIT(row); | ||
269 | } | ||
270 | } | ||
271 | input_sync(keypad->dev); | ||
272 | memcpy(keypad->last_key_state, new_state, sizeof(new_state)); | ||
273 | |||
274 | end: | ||
275 | if (reschedule) | ||
276 | ht16k33_keypad_queue(priv); | ||
277 | else | ||
278 | enable_irq(priv->client->irq); | ||
279 | } | ||
280 | |||
281 | static irqreturn_t ht16k33_irq_thread(int irq, void *dev) | ||
282 | { | ||
283 | struct ht16k33_priv *priv = dev; | ||
284 | |||
285 | disable_irq_nosync(priv->client->irq); | ||
286 | ht16k33_keypad_queue(priv); | ||
287 | |||
288 | return IRQ_HANDLED; | ||
289 | } | ||
290 | |||
291 | static int ht16k33_bl_update_status(struct backlight_device *bl) | ||
292 | { | ||
293 | int brightness = bl->props.brightness; | ||
294 | struct ht16k33_priv *priv = bl_get_data(bl); | ||
295 | |||
296 | if (bl->props.power != FB_BLANK_UNBLANK || | ||
297 | bl->props.fb_blank != FB_BLANK_UNBLANK || | ||
298 | bl->props.state & BL_CORE_FBBLANK || brightness == 0) { | ||
299 | return ht16k33_display_off(priv); | ||
300 | } | ||
301 | |||
302 | ht16k33_display_on(priv); | ||
303 | return i2c_smbus_write_byte(priv->client, | ||
304 | REG_BRIGHTNESS | (brightness - 1)); | ||
305 | } | ||
306 | |||
307 | static int ht16k33_bl_check_fb(struct backlight_device *bl, struct fb_info *fi) | ||
308 | { | ||
309 | struct ht16k33_priv *priv = bl_get_data(bl); | ||
310 | |||
311 | return (fi == NULL) || (fi->par == priv); | ||
312 | } | ||
313 | |||
314 | static const struct backlight_ops ht16k33_bl_ops = { | ||
315 | .update_status = ht16k33_bl_update_status, | ||
316 | .check_fb = ht16k33_bl_check_fb, | ||
317 | }; | ||
318 | |||
319 | static int ht16k33_mmap(struct fb_info *info, struct vm_area_struct *vma) | ||
320 | { | ||
321 | struct ht16k33_priv *priv = info->par; | ||
322 | |||
323 | return vm_insert_page(vma, vma->vm_start, | ||
324 | virt_to_page(priv->fbdev.buffer)); | ||
325 | } | ||
326 | |||
327 | static struct fb_ops ht16k33_fb_ops = { | ||
328 | .owner = THIS_MODULE, | ||
329 | .fb_read = fb_sys_read, | ||
330 | .fb_write = fb_sys_write, | ||
331 | .fb_fillrect = sys_fillrect, | ||
332 | .fb_copyarea = sys_copyarea, | ||
333 | .fb_imageblit = sys_imageblit, | ||
334 | .fb_mmap = ht16k33_mmap, | ||
335 | }; | ||
336 | |||
337 | static int ht16k33_probe(struct i2c_client *client, | ||
338 | const struct i2c_device_id *id) | ||
339 | { | ||
340 | int err; | ||
341 | uint32_t rows, cols, dft_brightness; | ||
342 | struct backlight_device *bl; | ||
343 | struct backlight_properties bl_props; | ||
344 | struct ht16k33_priv *priv; | ||
345 | struct ht16k33_keypad *keypad; | ||
346 | struct ht16k33_fbdev *fbdev; | ||
347 | struct device_node *node = client->dev.of_node; | ||
348 | |||
349 | if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { | ||
350 | dev_err(&client->dev, "i2c_check_functionality error\n"); | ||
351 | return -EIO; | ||
352 | } | ||
353 | |||
354 | if (client->irq <= 0) { | ||
355 | dev_err(&client->dev, "No IRQ specified\n"); | ||
356 | return -EINVAL; | ||
357 | } | ||
358 | |||
359 | priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); | ||
360 | if (!priv) | ||
361 | return -ENOMEM; | ||
362 | |||
363 | priv->client = client; | ||
364 | i2c_set_clientdata(client, priv); | ||
365 | fbdev = &priv->fbdev; | ||
366 | keypad = &priv->keypad; | ||
367 | |||
368 | priv->workqueue = create_singlethread_workqueue(DRIVER_NAME "-wq"); | ||
369 | if (priv->workqueue == NULL) | ||
370 | return -ENOMEM; | ||
371 | |||
372 | err = ht16k33_initialize(priv); | ||
373 | if (err) | ||
374 | goto err_destroy_wq; | ||
375 | |||
376 | /* Framebuffer (2 bytes per column) */ | ||
377 | BUILD_BUG_ON(PAGE_SIZE < HT16K33_FB_SIZE); | ||
378 | fbdev->buffer = (unsigned char *) get_zeroed_page(GFP_KERNEL); | ||
379 | if (!fbdev->buffer) { | ||
380 | err = -ENOMEM; | ||
381 | goto err_free_fbdev; | ||
382 | } | ||
383 | |||
384 | fbdev->cache = devm_kmalloc(&client->dev, HT16K33_FB_SIZE, GFP_KERNEL); | ||
385 | if (!fbdev->cache) { | ||
386 | err = -ENOMEM; | ||
387 | goto err_fbdev_buffer; | ||
388 | } | ||
389 | |||
390 | fbdev->info = framebuffer_alloc(0, &client->dev); | ||
391 | if (!fbdev->info) { | ||
392 | err = -ENOMEM; | ||
393 | goto err_fbdev_buffer; | ||
394 | } | ||
395 | |||
396 | err = of_property_read_u32(node, "refresh-rate-hz", | ||
397 | &fbdev->refresh_rate); | ||
398 | if (err) { | ||
399 | dev_err(&client->dev, "refresh rate not specified\n"); | ||
400 | goto err_fbdev_info; | ||
401 | } | ||
402 | fb_bl_default_curve(fbdev->info, 0, MIN_BRIGHTNESS, MAX_BRIGHTNESS); | ||
403 | |||
404 | INIT_DELAYED_WORK(&fbdev->work, ht16k33_fb_update); | ||
405 | fbdev->info->fbops = &ht16k33_fb_ops; | ||
406 | fbdev->info->screen_base = (char __iomem *) fbdev->buffer; | ||
407 | fbdev->info->screen_size = HT16K33_FB_SIZE; | ||
408 | fbdev->info->fix = ht16k33_fb_fix; | ||
409 | fbdev->info->var = ht16k33_fb_var; | ||
410 | fbdev->info->pseudo_palette = NULL; | ||
411 | fbdev->info->flags = FBINFO_FLAG_DEFAULT; | ||
412 | fbdev->info->par = priv; | ||
413 | |||
414 | err = register_framebuffer(fbdev->info); | ||
415 | if (err) | ||
416 | goto err_fbdev_info; | ||
417 | |||
418 | /* Keypad */ | ||
419 | keypad->dev = devm_input_allocate_device(&client->dev); | ||
420 | if (!keypad->dev) { | ||
421 | err = -ENOMEM; | ||
422 | goto err_fbdev_unregister; | ||
423 | } | ||
424 | |||
425 | keypad->dev->name = DRIVER_NAME"-keypad"; | ||
426 | keypad->dev->id.bustype = BUS_I2C; | ||
427 | keypad->dev->open = ht16k33_keypad_start; | ||
428 | keypad->dev->close = ht16k33_keypad_stop; | ||
429 | |||
430 | if (!of_get_property(node, "linux,no-autorepeat", NULL)) | ||
431 | __set_bit(EV_REP, keypad->dev->evbit); | ||
432 | |||
433 | err = of_property_read_u32(node, "debounce-delay-ms", | ||
434 | &keypad->debounce_ms); | ||
435 | if (err) { | ||
436 | dev_err(&client->dev, "key debounce delay not specified\n"); | ||
437 | goto err_fbdev_unregister; | ||
438 | } | ||
439 | |||
440 | err = devm_request_threaded_irq(&client->dev, client->irq, NULL, | ||
441 | ht16k33_irq_thread, | ||
442 | IRQF_TRIGGER_RISING | IRQF_ONESHOT, | ||
443 | DRIVER_NAME, priv); | ||
444 | if (err) { | ||
445 | dev_err(&client->dev, "irq request failed %d, error %d\n", | ||
446 | client->irq, err); | ||
447 | goto err_fbdev_unregister; | ||
448 | } | ||
449 | |||
450 | disable_irq_nosync(client->irq); | ||
451 | rows = HT16K33_MATRIX_KEYPAD_MAX_ROWS; | ||
452 | cols = HT16K33_MATRIX_KEYPAD_MAX_COLS; | ||
453 | err = matrix_keypad_parse_of_params(&client->dev, &rows, &cols); | ||
454 | if (err) | ||
455 | goto err_fbdev_unregister; | ||
456 | |||
457 | err = matrix_keypad_build_keymap(NULL, NULL, rows, cols, NULL, | ||
458 | keypad->dev); | ||
459 | if (err) { | ||
460 | dev_err(&client->dev, "failed to build keymap\n"); | ||
461 | goto err_fbdev_unregister; | ||
462 | } | ||
463 | |||
464 | input_set_drvdata(keypad->dev, priv); | ||
465 | keypad->rows = rows; | ||
466 | keypad->cols = cols; | ||
467 | keypad->row_shift = get_count_order(cols); | ||
468 | INIT_DELAYED_WORK(&keypad->work, ht16k33_keypad_scan); | ||
469 | |||
470 | err = input_register_device(keypad->dev); | ||
471 | if (err) | ||
472 | goto err_fbdev_unregister; | ||
473 | |||
474 | /* Backlight */ | ||
475 | memset(&bl_props, 0, sizeof(struct backlight_properties)); | ||
476 | bl_props.type = BACKLIGHT_RAW; | ||
477 | bl_props.max_brightness = MAX_BRIGHTNESS; | ||
478 | |||
479 | bl = devm_backlight_device_register(&client->dev, DRIVER_NAME"-bl", | ||
480 | &client->dev, priv, | ||
481 | &ht16k33_bl_ops, &bl_props); | ||
482 | if (IS_ERR(bl)) { | ||
483 | dev_err(&client->dev, "failed to register backlight\n"); | ||
484 | err = PTR_ERR(bl); | ||
485 | goto err_keypad_unregister; | ||
486 | } | ||
487 | |||
488 | err = of_property_read_u32(node, "default-brightness-level", | ||
489 | &dft_brightness); | ||
490 | if (err) { | ||
491 | dft_brightness = MAX_BRIGHTNESS; | ||
492 | } else if (dft_brightness > MAX_BRIGHTNESS) { | ||
493 | dev_warn(&client->dev, | ||
494 | "invalid default brightness level: %u, using %u\n", | ||
495 | dft_brightness, MAX_BRIGHTNESS); | ||
496 | dft_brightness = MAX_BRIGHTNESS; | ||
497 | } | ||
498 | |||
499 | bl->props.brightness = dft_brightness; | ||
500 | ht16k33_bl_update_status(bl); | ||
501 | |||
502 | ht16k33_fb_queue(priv); | ||
503 | return 0; | ||
504 | |||
505 | err_keypad_unregister: | ||
506 | input_unregister_device(keypad->dev); | ||
507 | err_fbdev_unregister: | ||
508 | unregister_framebuffer(fbdev->info); | ||
509 | err_fbdev_info: | ||
510 | framebuffer_release(fbdev->info); | ||
511 | err_fbdev_buffer: | ||
512 | free_page((unsigned long) fbdev->buffer); | ||
513 | err_free_fbdev: | ||
514 | kfree(fbdev); | ||
515 | err_destroy_wq: | ||
516 | destroy_workqueue(priv->workqueue); | ||
517 | |||
518 | return err; | ||
519 | } | ||
520 | |||
521 | static int ht16k33_remove(struct i2c_client *client) | ||
522 | { | ||
523 | struct ht16k33_priv *priv = i2c_get_clientdata(client); | ||
524 | struct ht16k33_keypad *keypad = &priv->keypad; | ||
525 | struct ht16k33_fbdev *fbdev = &priv->fbdev; | ||
526 | |||
527 | ht16k33_keypad_stop(keypad->dev); | ||
528 | |||
529 | cancel_delayed_work(&fbdev->work); | ||
530 | unregister_framebuffer(fbdev->info); | ||
531 | framebuffer_release(fbdev->info); | ||
532 | free_page((unsigned long) fbdev->buffer); | ||
533 | |||
534 | destroy_workqueue(priv->workqueue); | ||
535 | return 0; | ||
536 | } | ||
537 | |||
538 | static const struct i2c_device_id ht16k33_i2c_match[] = { | ||
539 | { "ht16k33", 0 }, | ||
540 | { } | ||
541 | }; | ||
542 | MODULE_DEVICE_TABLE(i2c, ht16k33_i2c_match); | ||
543 | |||
544 | static const struct of_device_id ht16k33_of_match[] = { | ||
545 | { .compatible = "holtek,ht16k33", }, | ||
546 | { } | ||
547 | }; | ||
548 | MODULE_DEVICE_TABLE(of, ht16k33_of_match); | ||
549 | |||
550 | static struct i2c_driver ht16k33_driver = { | ||
551 | .probe = ht16k33_probe, | ||
552 | .remove = ht16k33_remove, | ||
553 | .driver = { | ||
554 | .name = DRIVER_NAME, | ||
555 | .of_match_table = of_match_ptr(ht16k33_of_match), | ||
556 | }, | ||
557 | .id_table = ht16k33_i2c_match, | ||
558 | }; | ||
559 | module_i2c_driver(ht16k33_driver); | ||
560 | |||
561 | MODULE_DESCRIPTION("Holtek HT16K33 driver"); | ||
562 | MODULE_LICENSE("GPL"); | ||
563 | MODULE_AUTHOR("Robin van der Gracht <robin@protonic.nl>"); | ||