1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Driver for STMicroelectronics Multi-Function eXpander (STMFX) core |
4 | * |
5 | * Copyright (C) 2019 STMicroelectronics |
6 | * Author(s): Amelie Delaunay <amelie.delaunay@st.com>. |
7 | */ |
8 | #include <linux/bitfield.h> |
9 | #include <linux/i2c.h> |
10 | #include <linux/interrupt.h> |
11 | #include <linux/irq.h> |
12 | #include <linux/mfd/core.h> |
13 | #include <linux/mfd/stmfx.h> |
14 | #include <linux/module.h> |
15 | #include <linux/regulator/consumer.h> |
16 | |
17 | static bool stmfx_reg_volatile(struct device *dev, unsigned int reg) |
18 | { |
19 | switch (reg) { |
20 | case STMFX_REG_SYS_CTRL: |
21 | case STMFX_REG_IRQ_SRC_EN: |
22 | case STMFX_REG_IRQ_PENDING: |
23 | case STMFX_REG_IRQ_GPI_PENDING1: |
24 | case STMFX_REG_IRQ_GPI_PENDING2: |
25 | case STMFX_REG_IRQ_GPI_PENDING3: |
26 | case STMFX_REG_GPIO_STATE1: |
27 | case STMFX_REG_GPIO_STATE2: |
28 | case STMFX_REG_GPIO_STATE3: |
29 | case STMFX_REG_IRQ_GPI_SRC1: |
30 | case STMFX_REG_IRQ_GPI_SRC2: |
31 | case STMFX_REG_IRQ_GPI_SRC3: |
32 | case STMFX_REG_GPO_SET1: |
33 | case STMFX_REG_GPO_SET2: |
34 | case STMFX_REG_GPO_SET3: |
35 | case STMFX_REG_GPO_CLR1: |
36 | case STMFX_REG_GPO_CLR2: |
37 | case STMFX_REG_GPO_CLR3: |
38 | return true; |
39 | default: |
40 | return false; |
41 | } |
42 | } |
43 | |
44 | static bool stmfx_reg_writeable(struct device *dev, unsigned int reg) |
45 | { |
46 | return (reg >= STMFX_REG_SYS_CTRL); |
47 | } |
48 | |
49 | static const struct regmap_config stmfx_regmap_config = { |
50 | .reg_bits = 8, |
51 | .reg_stride = 1, |
52 | .val_bits = 8, |
53 | .max_register = STMFX_REG_MAX, |
54 | .volatile_reg = stmfx_reg_volatile, |
55 | .writeable_reg = stmfx_reg_writeable, |
56 | .cache_type = REGCACHE_MAPLE, |
57 | }; |
58 | |
59 | static const struct resource stmfx_pinctrl_resources[] = { |
60 | DEFINE_RES_IRQ(STMFX_REG_IRQ_SRC_EN_GPIO), |
61 | }; |
62 | |
63 | static const struct resource stmfx_idd_resources[] = { |
64 | DEFINE_RES_IRQ(STMFX_REG_IRQ_SRC_EN_IDD), |
65 | DEFINE_RES_IRQ(STMFX_REG_IRQ_SRC_EN_ERROR), |
66 | }; |
67 | |
68 | static const struct resource stmfx_ts_resources[] = { |
69 | DEFINE_RES_IRQ(STMFX_REG_IRQ_SRC_EN_TS_DET), |
70 | DEFINE_RES_IRQ(STMFX_REG_IRQ_SRC_EN_TS_NE), |
71 | DEFINE_RES_IRQ(STMFX_REG_IRQ_SRC_EN_TS_TH), |
72 | DEFINE_RES_IRQ(STMFX_REG_IRQ_SRC_EN_TS_FULL), |
73 | DEFINE_RES_IRQ(STMFX_REG_IRQ_SRC_EN_TS_OVF), |
74 | }; |
75 | |
76 | static struct mfd_cell stmfx_cells[] = { |
77 | { |
78 | .of_compatible = "st,stmfx-0300-pinctrl" , |
79 | .name = "stmfx-pinctrl" , |
80 | .resources = stmfx_pinctrl_resources, |
81 | .num_resources = ARRAY_SIZE(stmfx_pinctrl_resources), |
82 | }, |
83 | { |
84 | .of_compatible = "st,stmfx-0300-idd" , |
85 | .name = "stmfx-idd" , |
86 | .resources = stmfx_idd_resources, |
87 | .num_resources = ARRAY_SIZE(stmfx_idd_resources), |
88 | }, |
89 | { |
90 | .of_compatible = "st,stmfx-0300-ts" , |
91 | .name = "stmfx-ts" , |
92 | .resources = stmfx_ts_resources, |
93 | .num_resources = ARRAY_SIZE(stmfx_ts_resources), |
94 | }, |
95 | }; |
96 | |
97 | static u8 stmfx_func_to_mask(u32 func) |
98 | { |
99 | u8 mask = 0; |
100 | |
101 | if (func & STMFX_FUNC_GPIO) |
102 | mask |= STMFX_REG_SYS_CTRL_GPIO_EN; |
103 | |
104 | if ((func & STMFX_FUNC_ALTGPIO_LOW) || (func & STMFX_FUNC_ALTGPIO_HIGH)) |
105 | mask |= STMFX_REG_SYS_CTRL_ALTGPIO_EN; |
106 | |
107 | if (func & STMFX_FUNC_TS) |
108 | mask |= STMFX_REG_SYS_CTRL_TS_EN; |
109 | |
110 | if (func & STMFX_FUNC_IDD) |
111 | mask |= STMFX_REG_SYS_CTRL_IDD_EN; |
112 | |
113 | return mask; |
114 | } |
115 | |
116 | int stmfx_function_enable(struct stmfx *stmfx, u32 func) |
117 | { |
118 | u32 sys_ctrl; |
119 | u8 mask; |
120 | int ret; |
121 | |
122 | ret = regmap_read(map: stmfx->map, STMFX_REG_SYS_CTRL, val: &sys_ctrl); |
123 | if (ret) |
124 | return ret; |
125 | |
126 | /* |
127 | * IDD and TS have priority in STMFX FW, so if IDD and TS are enabled, |
128 | * ALTGPIO function is disabled by STMFX FW. If IDD or TS is enabled, |
129 | * the number of aGPIO available decreases. To avoid GPIO management |
130 | * disturbance, abort IDD or TS function enable in this case. |
131 | */ |
132 | if (((func & STMFX_FUNC_IDD) || (func & STMFX_FUNC_TS)) && |
133 | (sys_ctrl & STMFX_REG_SYS_CTRL_ALTGPIO_EN)) { |
134 | dev_err(stmfx->dev, "ALTGPIO function already enabled\n" ); |
135 | return -EBUSY; |
136 | } |
137 | |
138 | /* If TS is enabled, aGPIO[3:0] cannot be used */ |
139 | if ((func & STMFX_FUNC_ALTGPIO_LOW) && |
140 | (sys_ctrl & STMFX_REG_SYS_CTRL_TS_EN)) { |
141 | dev_err(stmfx->dev, "TS in use, aGPIO[3:0] unavailable\n" ); |
142 | return -EBUSY; |
143 | } |
144 | |
145 | /* If IDD is enabled, aGPIO[7:4] cannot be used */ |
146 | if ((func & STMFX_FUNC_ALTGPIO_HIGH) && |
147 | (sys_ctrl & STMFX_REG_SYS_CTRL_IDD_EN)) { |
148 | dev_err(stmfx->dev, "IDD in use, aGPIO[7:4] unavailable\n" ); |
149 | return -EBUSY; |
150 | } |
151 | |
152 | mask = stmfx_func_to_mask(func); |
153 | |
154 | return regmap_update_bits(map: stmfx->map, STMFX_REG_SYS_CTRL, mask, val: mask); |
155 | } |
156 | EXPORT_SYMBOL_GPL(stmfx_function_enable); |
157 | |
158 | int stmfx_function_disable(struct stmfx *stmfx, u32 func) |
159 | { |
160 | u8 mask = stmfx_func_to_mask(func); |
161 | |
162 | return regmap_update_bits(map: stmfx->map, STMFX_REG_SYS_CTRL, mask, val: 0); |
163 | } |
164 | EXPORT_SYMBOL_GPL(stmfx_function_disable); |
165 | |
166 | static void stmfx_irq_bus_lock(struct irq_data *data) |
167 | { |
168 | struct stmfx *stmfx = irq_data_get_irq_chip_data(d: data); |
169 | |
170 | mutex_lock(&stmfx->lock); |
171 | } |
172 | |
173 | static void stmfx_irq_bus_sync_unlock(struct irq_data *data) |
174 | { |
175 | struct stmfx *stmfx = irq_data_get_irq_chip_data(d: data); |
176 | |
177 | regmap_write(map: stmfx->map, STMFX_REG_IRQ_SRC_EN, val: stmfx->irq_src); |
178 | |
179 | mutex_unlock(lock: &stmfx->lock); |
180 | } |
181 | |
182 | static void stmfx_irq_mask(struct irq_data *data) |
183 | { |
184 | struct stmfx *stmfx = irq_data_get_irq_chip_data(d: data); |
185 | |
186 | stmfx->irq_src &= ~BIT(data->hwirq % 8); |
187 | } |
188 | |
189 | static void stmfx_irq_unmask(struct irq_data *data) |
190 | { |
191 | struct stmfx *stmfx = irq_data_get_irq_chip_data(d: data); |
192 | |
193 | stmfx->irq_src |= BIT(data->hwirq % 8); |
194 | } |
195 | |
196 | static struct irq_chip stmfx_irq_chip = { |
197 | .name = "stmfx-core" , |
198 | .irq_bus_lock = stmfx_irq_bus_lock, |
199 | .irq_bus_sync_unlock = stmfx_irq_bus_sync_unlock, |
200 | .irq_mask = stmfx_irq_mask, |
201 | .irq_unmask = stmfx_irq_unmask, |
202 | }; |
203 | |
204 | static irqreturn_t stmfx_irq_handler(int irq, void *data) |
205 | { |
206 | struct stmfx *stmfx = data; |
207 | unsigned long bits; |
208 | u32 pending, ack; |
209 | int n, ret; |
210 | |
211 | ret = regmap_read(map: stmfx->map, STMFX_REG_IRQ_PENDING, val: &pending); |
212 | if (ret) |
213 | return IRQ_NONE; |
214 | |
215 | /* |
216 | * There is no ACK for GPIO, MFX_REG_IRQ_PENDING_GPIO is a logical OR |
217 | * of MFX_REG_IRQ_GPI _PENDING1/_PENDING2/_PENDING3 |
218 | */ |
219 | ack = pending & ~BIT(STMFX_REG_IRQ_SRC_EN_GPIO); |
220 | if (ack) { |
221 | ret = regmap_write(map: stmfx->map, STMFX_REG_IRQ_ACK, val: ack); |
222 | if (ret) |
223 | return IRQ_NONE; |
224 | } |
225 | |
226 | bits = pending; |
227 | for_each_set_bit(n, &bits, STMFX_REG_IRQ_SRC_MAX) |
228 | handle_nested_irq(irq: irq_find_mapping(domain: stmfx->irq_domain, hwirq: n)); |
229 | |
230 | return IRQ_HANDLED; |
231 | } |
232 | |
233 | static int stmfx_irq_map(struct irq_domain *d, unsigned int virq, |
234 | irq_hw_number_t hwirq) |
235 | { |
236 | irq_set_chip_data(irq: virq, data: d->host_data); |
237 | irq_set_chip_and_handler(irq: virq, chip: &stmfx_irq_chip, handle: handle_simple_irq); |
238 | irq_set_nested_thread(irq: virq, nest: 1); |
239 | irq_set_noprobe(irq: virq); |
240 | |
241 | return 0; |
242 | } |
243 | |
244 | static void stmfx_irq_unmap(struct irq_domain *d, unsigned int virq) |
245 | { |
246 | irq_set_chip_and_handler(irq: virq, NULL, NULL); |
247 | irq_set_chip_data(irq: virq, NULL); |
248 | } |
249 | |
250 | static const struct irq_domain_ops stmfx_irq_ops = { |
251 | .map = stmfx_irq_map, |
252 | .unmap = stmfx_irq_unmap, |
253 | }; |
254 | |
255 | static void stmfx_irq_exit(struct i2c_client *client) |
256 | { |
257 | struct stmfx *stmfx = i2c_get_clientdata(client); |
258 | int hwirq; |
259 | |
260 | for (hwirq = 0; hwirq < STMFX_REG_IRQ_SRC_MAX; hwirq++) |
261 | irq_dispose_mapping(virq: irq_find_mapping(domain: stmfx->irq_domain, hwirq)); |
262 | |
263 | irq_domain_remove(host: stmfx->irq_domain); |
264 | } |
265 | |
266 | static int stmfx_irq_init(struct i2c_client *client) |
267 | { |
268 | struct stmfx *stmfx = i2c_get_clientdata(client); |
269 | u32 irqoutpin = 0, irqtrigger; |
270 | int ret; |
271 | |
272 | stmfx->irq_domain = irq_domain_add_simple(of_node: stmfx->dev->of_node, |
273 | size: STMFX_REG_IRQ_SRC_MAX, first_irq: 0, |
274 | ops: &stmfx_irq_ops, host_data: stmfx); |
275 | if (!stmfx->irq_domain) { |
276 | dev_err(stmfx->dev, "Failed to create IRQ domain\n" ); |
277 | return -EINVAL; |
278 | } |
279 | |
280 | if (!of_property_read_bool(np: stmfx->dev->of_node, propname: "drive-open-drain" )) |
281 | irqoutpin |= STMFX_REG_IRQ_OUT_PIN_TYPE; |
282 | |
283 | irqtrigger = irq_get_trigger_type(irq: client->irq); |
284 | if ((irqtrigger & IRQ_TYPE_EDGE_RISING) || |
285 | (irqtrigger & IRQ_TYPE_LEVEL_HIGH)) |
286 | irqoutpin |= STMFX_REG_IRQ_OUT_PIN_POL; |
287 | |
288 | ret = regmap_write(map: stmfx->map, STMFX_REG_IRQ_OUT_PIN, val: irqoutpin); |
289 | if (ret) |
290 | goto irq_exit; |
291 | |
292 | ret = devm_request_threaded_irq(dev: stmfx->dev, irq: client->irq, |
293 | NULL, thread_fn: stmfx_irq_handler, |
294 | irqflags: irqtrigger | IRQF_ONESHOT, |
295 | devname: "stmfx" , dev_id: stmfx); |
296 | if (ret) |
297 | goto irq_exit; |
298 | |
299 | stmfx->irq = client->irq; |
300 | |
301 | return 0; |
302 | |
303 | irq_exit: |
304 | stmfx_irq_exit(client); |
305 | |
306 | return ret; |
307 | } |
308 | |
309 | static int stmfx_chip_reset(struct stmfx *stmfx) |
310 | { |
311 | int ret; |
312 | |
313 | ret = regmap_write(map: stmfx->map, STMFX_REG_SYS_CTRL, |
314 | STMFX_REG_SYS_CTRL_SWRST); |
315 | if (ret) |
316 | return ret; |
317 | |
318 | msleep(STMFX_BOOT_TIME_MS); |
319 | |
320 | return ret; |
321 | } |
322 | |
323 | static int stmfx_chip_init(struct i2c_client *client) |
324 | { |
325 | struct stmfx *stmfx = i2c_get_clientdata(client); |
326 | u32 id; |
327 | u8 version[2]; |
328 | int ret; |
329 | |
330 | stmfx->vdd = devm_regulator_get_optional(dev: &client->dev, id: "vdd" ); |
331 | ret = PTR_ERR_OR_ZERO(ptr: stmfx->vdd); |
332 | if (ret) { |
333 | stmfx->vdd = NULL; |
334 | if (ret != -ENODEV) |
335 | return dev_err_probe(dev: &client->dev, err: ret, fmt: "Failed to get VDD regulator\n" ); |
336 | } |
337 | |
338 | if (stmfx->vdd) { |
339 | ret = regulator_enable(regulator: stmfx->vdd); |
340 | if (ret) { |
341 | dev_err(&client->dev, "VDD enable failed: %d\n" , ret); |
342 | return ret; |
343 | } |
344 | } |
345 | |
346 | ret = regmap_read(map: stmfx->map, STMFX_REG_CHIP_ID, val: &id); |
347 | if (ret) { |
348 | dev_err(&client->dev, "Error reading chip ID: %d\n" , ret); |
349 | goto err; |
350 | } |
351 | |
352 | /* |
353 | * Check that ID is the complement of the I2C address: |
354 | * STMFX I2C address follows the 7-bit format (MSB), that's why |
355 | * client->addr is shifted. |
356 | * |
357 | * STMFX_I2C_ADDR| STMFX | Linux |
358 | * input pin | I2C device address | I2C device address |
359 | *--------------------------------------------------------- |
360 | * 0 | b: 1000 010x h:0x84 | 0x42 |
361 | * 1 | b: 1000 011x h:0x86 | 0x43 |
362 | */ |
363 | if (FIELD_GET(STMFX_REG_CHIP_ID_MASK, ~id) != (client->addr << 1)) { |
364 | dev_err(&client->dev, "Unknown chip ID: %#x\n" , id); |
365 | ret = -EINVAL; |
366 | goto err; |
367 | } |
368 | |
369 | ret = regmap_bulk_read(map: stmfx->map, STMFX_REG_FW_VERSION_MSB, |
370 | val: version, ARRAY_SIZE(version)); |
371 | if (ret) { |
372 | dev_err(&client->dev, "Error reading FW version: %d\n" , ret); |
373 | goto err; |
374 | } |
375 | |
376 | dev_info(&client->dev, "STMFX id: %#x, fw version: %x.%02x\n" , |
377 | id, version[0], version[1]); |
378 | |
379 | ret = stmfx_chip_reset(stmfx); |
380 | if (ret) { |
381 | dev_err(&client->dev, "Failed to reset chip: %d\n" , ret); |
382 | goto err; |
383 | } |
384 | |
385 | return 0; |
386 | |
387 | err: |
388 | if (stmfx->vdd) |
389 | regulator_disable(regulator: stmfx->vdd); |
390 | |
391 | return ret; |
392 | } |
393 | |
394 | static void stmfx_chip_exit(struct i2c_client *client) |
395 | { |
396 | struct stmfx *stmfx = i2c_get_clientdata(client); |
397 | |
398 | regmap_write(map: stmfx->map, STMFX_REG_IRQ_SRC_EN, val: 0); |
399 | regmap_write(map: stmfx->map, STMFX_REG_SYS_CTRL, val: 0); |
400 | |
401 | if (stmfx->vdd) { |
402 | int ret; |
403 | |
404 | ret = regulator_disable(regulator: stmfx->vdd); |
405 | if (ret) |
406 | dev_err(&client->dev, |
407 | "Failed to disable vdd regulator: %pe\n" , |
408 | ERR_PTR(ret)); |
409 | } |
410 | } |
411 | |
412 | static int stmfx_probe(struct i2c_client *client) |
413 | { |
414 | struct device *dev = &client->dev; |
415 | struct stmfx *stmfx; |
416 | int ret; |
417 | |
418 | stmfx = devm_kzalloc(dev, size: sizeof(*stmfx), GFP_KERNEL); |
419 | if (!stmfx) |
420 | return -ENOMEM; |
421 | |
422 | i2c_set_clientdata(client, data: stmfx); |
423 | |
424 | stmfx->dev = dev; |
425 | |
426 | stmfx->map = devm_regmap_init_i2c(client, &stmfx_regmap_config); |
427 | if (IS_ERR(ptr: stmfx->map)) { |
428 | ret = PTR_ERR(ptr: stmfx->map); |
429 | dev_err(dev, "Failed to allocate register map: %d\n" , ret); |
430 | return ret; |
431 | } |
432 | |
433 | mutex_init(&stmfx->lock); |
434 | |
435 | ret = stmfx_chip_init(client); |
436 | if (ret) { |
437 | if (ret == -ETIMEDOUT) |
438 | return -EPROBE_DEFER; |
439 | return ret; |
440 | } |
441 | |
442 | if (client->irq < 0) { |
443 | dev_err(dev, "Failed to get IRQ: %d\n" , client->irq); |
444 | ret = client->irq; |
445 | goto err_chip_exit; |
446 | } |
447 | |
448 | ret = stmfx_irq_init(client); |
449 | if (ret) |
450 | goto err_chip_exit; |
451 | |
452 | ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, |
453 | cells: stmfx_cells, ARRAY_SIZE(stmfx_cells), NULL, |
454 | irq_base: 0, irq_domain: stmfx->irq_domain); |
455 | if (ret) |
456 | goto err_irq_exit; |
457 | |
458 | return 0; |
459 | |
460 | err_irq_exit: |
461 | stmfx_irq_exit(client); |
462 | err_chip_exit: |
463 | stmfx_chip_exit(client); |
464 | |
465 | return ret; |
466 | } |
467 | |
468 | static void stmfx_remove(struct i2c_client *client) |
469 | { |
470 | stmfx_irq_exit(client); |
471 | |
472 | stmfx_chip_exit(client); |
473 | } |
474 | |
475 | static int stmfx_suspend(struct device *dev) |
476 | { |
477 | struct stmfx *stmfx = dev_get_drvdata(dev); |
478 | int ret; |
479 | |
480 | ret = regmap_raw_read(map: stmfx->map, STMFX_REG_SYS_CTRL, |
481 | val: &stmfx->bkp_sysctrl, val_len: sizeof(stmfx->bkp_sysctrl)); |
482 | if (ret) |
483 | return ret; |
484 | |
485 | ret = regmap_raw_read(map: stmfx->map, STMFX_REG_IRQ_OUT_PIN, |
486 | val: &stmfx->bkp_irqoutpin, |
487 | val_len: sizeof(stmfx->bkp_irqoutpin)); |
488 | if (ret) |
489 | return ret; |
490 | |
491 | disable_irq(irq: stmfx->irq); |
492 | |
493 | if (stmfx->vdd) |
494 | return regulator_disable(regulator: stmfx->vdd); |
495 | |
496 | return 0; |
497 | } |
498 | |
499 | static int stmfx_resume(struct device *dev) |
500 | { |
501 | struct stmfx *stmfx = dev_get_drvdata(dev); |
502 | int ret; |
503 | |
504 | if (stmfx->vdd) { |
505 | ret = regulator_enable(regulator: stmfx->vdd); |
506 | if (ret) { |
507 | dev_err(stmfx->dev, |
508 | "VDD enable failed: %d\n" , ret); |
509 | return ret; |
510 | } |
511 | } |
512 | |
513 | /* Reset STMFX - supply has been stopped during suspend */ |
514 | ret = stmfx_chip_reset(stmfx); |
515 | if (ret) { |
516 | dev_err(stmfx->dev, "Failed to reset chip: %d\n" , ret); |
517 | return ret; |
518 | } |
519 | |
520 | ret = regmap_raw_write(map: stmfx->map, STMFX_REG_SYS_CTRL, |
521 | val: &stmfx->bkp_sysctrl, val_len: sizeof(stmfx->bkp_sysctrl)); |
522 | if (ret) |
523 | return ret; |
524 | |
525 | ret = regmap_raw_write(map: stmfx->map, STMFX_REG_IRQ_OUT_PIN, |
526 | val: &stmfx->bkp_irqoutpin, |
527 | val_len: sizeof(stmfx->bkp_irqoutpin)); |
528 | if (ret) |
529 | return ret; |
530 | |
531 | ret = regmap_raw_write(map: stmfx->map, STMFX_REG_IRQ_SRC_EN, |
532 | val: &stmfx->irq_src, val_len: sizeof(stmfx->irq_src)); |
533 | if (ret) |
534 | return ret; |
535 | |
536 | enable_irq(irq: stmfx->irq); |
537 | |
538 | return 0; |
539 | } |
540 | |
541 | static DEFINE_SIMPLE_DEV_PM_OPS(stmfx_dev_pm_ops, stmfx_suspend, stmfx_resume); |
542 | |
543 | static const struct of_device_id stmfx_of_match[] = { |
544 | { .compatible = "st,stmfx-0300" , }, |
545 | {}, |
546 | }; |
547 | MODULE_DEVICE_TABLE(of, stmfx_of_match); |
548 | |
549 | static struct i2c_driver stmfx_driver = { |
550 | .driver = { |
551 | .name = "stmfx-core" , |
552 | .of_match_table = stmfx_of_match, |
553 | .pm = pm_sleep_ptr(&stmfx_dev_pm_ops), |
554 | }, |
555 | .probe = stmfx_probe, |
556 | .remove = stmfx_remove, |
557 | }; |
558 | module_i2c_driver(stmfx_driver); |
559 | |
560 | MODULE_DESCRIPTION("STMFX core driver" ); |
561 | MODULE_AUTHOR("Amelie Delaunay <amelie.delaunay@st.com>" ); |
562 | MODULE_LICENSE("GPL v2" ); |
563 | |