Commit | Line | Data |
---|---|---|
e70f18e1 AS |
1 | /* |
2 | * Cirrus Logic CLPS711X Keypad driver | |
3 | * | |
4 | * Copyright (C) 2014 Alexander Shiyan <shc_work@mail.ru> | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 2 of the License, or | |
9 | * (at your option) any later version. | |
10 | */ | |
11 | ||
12 | #include <linux/input.h> | |
13 | #include <linux/input-polldev.h> | |
14 | #include <linux/module.h> | |
15 | #include <linux/of_gpio.h> | |
16 | #include <linux/platform_device.h> | |
17 | #include <linux/regmap.h> | |
18 | #include <linux/sched.h> | |
19 | #include <linux/input/matrix_keypad.h> | |
20 | #include <linux/mfd/syscon.h> | |
21 | #include <linux/mfd/syscon/clps711x.h> | |
22 | ||
23 | #define CLPS711X_KEYPAD_COL_COUNT 8 | |
24 | ||
25 | struct clps711x_gpio_data { | |
26 | struct gpio_desc *desc; | |
27 | DECLARE_BITMAP(last_state, CLPS711X_KEYPAD_COL_COUNT); | |
28 | }; | |
29 | ||
30 | struct clps711x_keypad_data { | |
31 | struct regmap *syscon; | |
32 | int row_count; | |
33 | unsigned int row_shift; | |
34 | struct clps711x_gpio_data *gpio_data; | |
35 | }; | |
36 | ||
37 | static void clps711x_keypad_poll(struct input_polled_dev *dev) | |
38 | { | |
39 | const unsigned short *keycodes = dev->input->keycode; | |
40 | struct clps711x_keypad_data *priv = dev->private; | |
41 | bool sync = false; | |
42 | int col, row; | |
43 | ||
44 | for (col = 0; col < CLPS711X_KEYPAD_COL_COUNT; col++) { | |
45 | /* Assert column */ | |
46 | regmap_update_bits(priv->syscon, SYSCON_OFFSET, | |
47 | SYSCON1_KBDSCAN_MASK, | |
48 | SYSCON1_KBDSCAN(8 + col)); | |
49 | ||
50 | /* Scan rows */ | |
51 | for (row = 0; row < priv->row_count; row++) { | |
52 | struct clps711x_gpio_data *data = &priv->gpio_data[row]; | |
53 | bool state, state1; | |
54 | ||
55 | /* Read twice for protection against fluctuations */ | |
56 | do { | |
57 | state = gpiod_get_value_cansleep(data->desc); | |
58 | cond_resched(); | |
59 | state1 = gpiod_get_value_cansleep(data->desc); | |
60 | } while (state != state1); | |
61 | ||
62 | if (test_bit(col, data->last_state) != state) { | |
63 | int code = MATRIX_SCAN_CODE(row, col, | |
64 | priv->row_shift); | |
65 | ||
66 | if (state) { | |
67 | set_bit(col, data->last_state); | |
68 | input_event(dev->input, EV_MSC, | |
69 | MSC_SCAN, code); | |
70 | } else { | |
71 | clear_bit(col, data->last_state); | |
72 | } | |
73 | ||
74 | if (keycodes[code]) | |
75 | input_report_key(dev->input, | |
76 | keycodes[code], state); | |
77 | sync = true; | |
78 | } | |
79 | } | |
80 | ||
81 | /* Set all columns to low */ | |
82 | regmap_update_bits(priv->syscon, SYSCON_OFFSET, | |
83 | SYSCON1_KBDSCAN_MASK, SYSCON1_KBDSCAN(1)); | |
84 | } | |
85 | ||
86 | if (sync) | |
87 | input_sync(dev->input); | |
88 | } | |
89 | ||
90 | static int clps711x_keypad_probe(struct platform_device *pdev) | |
91 | { | |
92 | struct clps711x_keypad_data *priv; | |
93 | struct device *dev = &pdev->dev; | |
94 | struct device_node *np = dev->of_node; | |
95 | struct input_polled_dev *poll_dev; | |
96 | u32 poll_interval; | |
97 | int i, err; | |
98 | ||
99 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | |
100 | if (!priv) | |
101 | return -ENOMEM; | |
102 | ||
103 | priv->syscon = | |
104 | syscon_regmap_lookup_by_compatible("cirrus,clps711x-syscon1"); | |
105 | if (IS_ERR(priv->syscon)) | |
106 | return PTR_ERR(priv->syscon); | |
107 | ||
108 | priv->row_count = of_gpio_named_count(np, "row-gpios"); | |
109 | if (priv->row_count < 1) | |
110 | return -EINVAL; | |
111 | ||
112 | priv->gpio_data = devm_kzalloc(dev, | |
113 | sizeof(*priv->gpio_data) * priv->row_count, | |
114 | GFP_KERNEL); | |
115 | if (!priv->gpio_data) | |
116 | return -ENOMEM; | |
117 | ||
118 | priv->row_shift = get_count_order(CLPS711X_KEYPAD_COL_COUNT); | |
119 | ||
120 | for (i = 0; i < priv->row_count; i++) { | |
121 | struct clps711x_gpio_data *data = &priv->gpio_data[i]; | |
122 | ||
ea0afac4 | 123 | data->desc = devm_gpiod_get_index(dev, "row", i, GPIOD_IN); |
e70f18e1 AS |
124 | if (IS_ERR(data->desc)) |
125 | return PTR_ERR(data->desc); | |
e70f18e1 AS |
126 | } |
127 | ||
128 | err = of_property_read_u32(np, "poll-interval", &poll_interval); | |
129 | if (err) | |
130 | return err; | |
131 | ||
132 | poll_dev = input_allocate_polled_device(); | |
133 | if (!poll_dev) | |
134 | return -ENOMEM; | |
135 | ||
136 | poll_dev->private = priv; | |
137 | poll_dev->poll = clps711x_keypad_poll; | |
138 | poll_dev->poll_interval = poll_interval; | |
139 | poll_dev->input->name = pdev->name; | |
140 | poll_dev->input->dev.parent = dev; | |
141 | poll_dev->input->id.bustype = BUS_HOST; | |
142 | poll_dev->input->id.vendor = 0x0001; | |
143 | poll_dev->input->id.product = 0x0001; | |
144 | poll_dev->input->id.version = 0x0100; | |
145 | ||
146 | err = matrix_keypad_build_keymap(NULL, NULL, priv->row_count, | |
147 | CLPS711X_KEYPAD_COL_COUNT, | |
148 | NULL, poll_dev->input); | |
149 | if (err) | |
150 | goto out_err; | |
151 | ||
152 | input_set_capability(poll_dev->input, EV_MSC, MSC_SCAN); | |
153 | if (of_property_read_bool(np, "autorepeat")) | |
154 | __set_bit(EV_REP, poll_dev->input->evbit); | |
155 | ||
156 | platform_set_drvdata(pdev, poll_dev); | |
157 | ||
158 | /* Set all columns to low */ | |
159 | regmap_update_bits(priv->syscon, SYSCON_OFFSET, SYSCON1_KBDSCAN_MASK, | |
160 | SYSCON1_KBDSCAN(1)); | |
161 | ||
162 | err = input_register_polled_device(poll_dev); | |
163 | if (err) | |
164 | goto out_err; | |
165 | ||
166 | return 0; | |
167 | ||
168 | out_err: | |
169 | input_free_polled_device(poll_dev); | |
170 | return err; | |
171 | } | |
172 | ||
173 | static int clps711x_keypad_remove(struct platform_device *pdev) | |
174 | { | |
175 | struct input_polled_dev *poll_dev = platform_get_drvdata(pdev); | |
176 | ||
177 | input_unregister_polled_device(poll_dev); | |
178 | input_free_polled_device(poll_dev); | |
179 | ||
180 | return 0; | |
181 | } | |
182 | ||
af4cf6d3 | 183 | static const struct of_device_id clps711x_keypad_of_match[] = { |
e70f18e1 AS |
184 | { .compatible = "cirrus,clps711x-keypad", }, |
185 | { } | |
186 | }; | |
187 | MODULE_DEVICE_TABLE(of, clps711x_keypad_of_match); | |
188 | ||
189 | static struct platform_driver clps711x_keypad_driver = { | |
190 | .driver = { | |
191 | .name = "clps711x-keypad", | |
e70f18e1 AS |
192 | .of_match_table = clps711x_keypad_of_match, |
193 | }, | |
194 | .probe = clps711x_keypad_probe, | |
195 | .remove = clps711x_keypad_remove, | |
196 | }; | |
197 | module_platform_driver(clps711x_keypad_driver); | |
198 | ||
199 | MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>"); | |
200 | MODULE_DESCRIPTION("Cirrus Logic CLPS711X Keypad driver"); | |
201 | MODULE_LICENSE("GPL"); |