1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Driver for the Chrontel CH7322 CEC Controller |
4 | * |
5 | * Copyright 2020 Google LLC. |
6 | */ |
7 | |
8 | /* |
9 | * Notes |
10 | * |
11 | * - This device powers on in Auto Mode which has limited functionality. This |
12 | * driver disables Auto Mode when it attaches. |
13 | * |
14 | */ |
15 | |
16 | #include <linux/cec.h> |
17 | #include <linux/dmi.h> |
18 | #include <linux/i2c.h> |
19 | #include <linux/interrupt.h> |
20 | #include <linux/module.h> |
21 | #include <linux/mutex.h> |
22 | #include <linux/pci.h> |
23 | #include <linux/regmap.h> |
24 | #include <media/cec.h> |
25 | #include <media/cec-notifier.h> |
26 | |
27 | #define CH7322_WRITE 0x00 |
28 | #define CH7322_WRITE_MSENT 0x80 |
29 | #define CH7322_WRITE_BOK 0x40 |
30 | #define CH7322_WRITE_NMASK 0x0f |
31 | |
32 | /* Write buffer is 0x01-0x10 */ |
33 | #define CH7322_WRBUF 0x01 |
34 | #define CH7322_WRBUF_LEN 0x10 |
35 | |
36 | #define CH7322_READ 0x40 |
37 | #define CH7322_READ_NRDT 0x80 |
38 | #define CH7322_READ_MSENT 0x20 |
39 | #define CH7322_READ_NMASK 0x0f |
40 | |
41 | /* Read buffer is 0x41-0x50 */ |
42 | #define CH7322_RDBUF 0x41 |
43 | #define CH7322_RDBUF_LEN 0x10 |
44 | |
45 | #define CH7322_MODE 0x11 |
46 | #define CH7322_MODE_AUTO 0x78 |
47 | #define CH7322_MODE_SW 0xb5 |
48 | |
49 | #define CH7322_RESET 0x12 |
50 | #define CH7322_RESET_RST 0x00 |
51 | |
52 | #define CH7322_POWER 0x13 |
53 | #define CH7322_POWER_FPD 0x04 |
54 | |
55 | #define CH7322_CFG0 0x17 |
56 | #define CH7322_CFG0_EOBEN 0x40 |
57 | #define CH7322_CFG0_PEOB 0x20 |
58 | #define CH7322_CFG0_CLRSPP 0x10 |
59 | #define CH7322_CFG0_FLOW 0x08 |
60 | |
61 | #define CH7322_CFG1 0x1a |
62 | #define CH7322_CFG1_STDBYO 0x04 |
63 | #define CH7322_CFG1_HPBP 0x02 |
64 | #define CH7322_CFG1_PIO 0x01 |
65 | |
66 | #define CH7322_INTCTL 0x1b |
67 | #define CH7322_INTCTL_INTPB 0x80 |
68 | #define CH7322_INTCTL_STDBY 0x40 |
69 | #define CH7322_INTCTL_HPDFALL 0x20 |
70 | #define CH7322_INTCTL_HPDRISE 0x10 |
71 | #define CH7322_INTCTL_RXMSG 0x08 |
72 | #define CH7322_INTCTL_TXMSG 0x04 |
73 | #define CH7322_INTCTL_NEWPHA 0x02 |
74 | #define CH7322_INTCTL_ERROR 0x01 |
75 | |
76 | #define CH7322_DVCLKFNH 0x1d |
77 | #define CH7322_DVCLKFNL 0x1e |
78 | |
79 | #define CH7322_CTL 0x31 |
80 | #define CH7322_CTL_FSTDBY 0x80 |
81 | #define CH7322_CTL_PLSEN 0x40 |
82 | #define CH7322_CTL_PLSPB 0x20 |
83 | #define CH7322_CTL_SPADL 0x10 |
84 | #define CH7322_CTL_HINIT 0x08 |
85 | #define CH7322_CTL_WPHYA 0x04 |
86 | #define CH7322_CTL_H1T 0x02 |
87 | #define CH7322_CTL_S1T 0x01 |
88 | |
89 | #define CH7322_PAWH 0x32 |
90 | #define CH7322_PAWL 0x33 |
91 | |
92 | #define CH7322_ADDLW 0x34 |
93 | #define CH7322_ADDLW_MASK 0xf0 |
94 | |
95 | #define CH7322_ADDLR 0x3d |
96 | #define CH7322_ADDLR_HPD 0x80 |
97 | #define CH7322_ADDLR_MASK 0x0f |
98 | |
99 | #define CH7322_INTDATA 0x3e |
100 | #define CH7322_INTDATA_MODE 0x80 |
101 | #define CH7322_INTDATA_STDBY 0x40 |
102 | #define CH7322_INTDATA_HPDFALL 0x20 |
103 | #define CH7322_INTDATA_HPDRISE 0x10 |
104 | #define CH7322_INTDATA_RXMSG 0x08 |
105 | #define CH7322_INTDATA_TXMSG 0x04 |
106 | #define CH7322_INTDATA_NEWPHA 0x02 |
107 | #define CH7322_INTDATA_ERROR 0x01 |
108 | |
109 | #define CH7322_EVENT 0x3f |
110 | #define CH7322_EVENT_TXERR 0x80 |
111 | #define CH7322_EVENT_HRST 0x40 |
112 | #define CH7322_EVENT_HFST 0x20 |
113 | #define CH7322_EVENT_PHACHG 0x10 |
114 | #define CH7322_EVENT_ACTST 0x08 |
115 | #define CH7322_EVENT_PHARDY 0x04 |
116 | #define CH7322_EVENT_BSOK 0x02 |
117 | #define CH7322_EVENT_ERRADCF 0x01 |
118 | |
119 | #define CH7322_DID 0x51 |
120 | #define CH7322_DID_CH7322 0x5b |
121 | #define CH7322_DID_CH7323 0x5f |
122 | |
123 | #define CH7322_REVISIONID 0x52 |
124 | |
125 | #define CH7322_PARH 0x53 |
126 | #define CH7322_PARL 0x54 |
127 | |
128 | #define CH7322_IOCFG2 0x75 |
129 | #define CH7322_IOCFG_CIO 0x80 |
130 | #define CH7322_IOCFG_IOCFGMASK 0x78 |
131 | #define CH7322_IOCFG_AUDIO 0x04 |
132 | #define CH7322_IOCFG_SPAMST 0x02 |
133 | #define CH7322_IOCFG_SPAMSP 0x01 |
134 | |
135 | #define CH7322_CTL3 0x7b |
136 | #define CH7322_CTL3_SWENA 0x80 |
137 | #define CH7322_CTL3_FC_INIT 0x40 |
138 | #define CH7322_CTL3_SML_FL 0x20 |
139 | #define CH7322_CTL3_SM_RDST 0x10 |
140 | #define CH7322_CTL3_SPP_CIAH 0x08 |
141 | #define CH7322_CTL3_SPP_CIAL 0x04 |
142 | #define CH7322_CTL3_SPP_ACTH 0x02 |
143 | #define CH7322_CTL3_SPP_ACTL 0x01 |
144 | |
145 | /* BOK status means NACK */ |
146 | #define CH7322_TX_FLAG_NACK BIT(0) |
147 | /* Device will retry automatically */ |
148 | #define CH7322_TX_FLAG_RETRY BIT(1) |
149 | |
150 | struct ch7322 { |
151 | struct i2c_client *i2c; |
152 | struct regmap *regmap; |
153 | struct cec_adapter *cec; |
154 | struct mutex mutex; /* device access mutex */ |
155 | u8 tx_flags; |
156 | }; |
157 | |
158 | static const struct regmap_config ch7322_regmap = { |
159 | .reg_bits = 8, |
160 | .val_bits = 8, |
161 | .max_register = 0x7f, |
162 | .disable_locking = true, |
163 | }; |
164 | |
165 | static int ch7322_send_message(struct ch7322 *ch7322, const struct cec_msg *msg) |
166 | { |
167 | unsigned int val; |
168 | unsigned int len = msg->len; |
169 | int ret; |
170 | int i; |
171 | |
172 | WARN_ON(!mutex_is_locked(&ch7322->mutex)); |
173 | |
174 | if (len > CH7322_WRBUF_LEN || len < 1) |
175 | return -EINVAL; |
176 | |
177 | ret = regmap_read(map: ch7322->regmap, CH7322_WRITE, val: &val); |
178 | if (ret) |
179 | return ret; |
180 | |
181 | /* Buffer not ready */ |
182 | if (!(val & CH7322_WRITE_MSENT)) |
183 | return -EBUSY; |
184 | |
185 | if (cec_msg_opcode(msg) == -1 && |
186 | cec_msg_initiator(msg) == cec_msg_destination(msg)) { |
187 | ch7322->tx_flags = CH7322_TX_FLAG_NACK | CH7322_TX_FLAG_RETRY; |
188 | } else if (cec_msg_is_broadcast(msg)) { |
189 | ch7322->tx_flags = CH7322_TX_FLAG_NACK; |
190 | } else { |
191 | ch7322->tx_flags = CH7322_TX_FLAG_RETRY; |
192 | } |
193 | |
194 | ret = regmap_write(map: ch7322->regmap, CH7322_WRITE, val: len - 1); |
195 | if (ret) |
196 | return ret; |
197 | |
198 | for (i = 0; i < len; i++) { |
199 | ret = regmap_write(map: ch7322->regmap, |
200 | CH7322_WRBUF + i, val: msg->msg[i]); |
201 | if (ret) |
202 | return ret; |
203 | } |
204 | |
205 | return 0; |
206 | } |
207 | |
208 | static int ch7322_receive_message(struct ch7322 *ch7322, struct cec_msg *msg) |
209 | { |
210 | unsigned int val; |
211 | int ret = 0; |
212 | int i; |
213 | |
214 | WARN_ON(!mutex_is_locked(&ch7322->mutex)); |
215 | |
216 | ret = regmap_read(map: ch7322->regmap, CH7322_READ, val: &val); |
217 | if (ret) |
218 | return ret; |
219 | |
220 | /* Message not ready */ |
221 | if (!(val & CH7322_READ_NRDT)) |
222 | return -EIO; |
223 | |
224 | msg->len = (val & CH7322_READ_NMASK) + 1; |
225 | |
226 | /* Read entire RDBUF to clear state */ |
227 | for (i = 0; i < CH7322_RDBUF_LEN; i++) { |
228 | ret = regmap_read(map: ch7322->regmap, CH7322_RDBUF + i, val: &val); |
229 | if (ret) |
230 | return ret; |
231 | msg->msg[i] = (u8)val; |
232 | } |
233 | |
234 | return 0; |
235 | } |
236 | |
237 | static void ch7322_tx_done(struct ch7322 *ch7322) |
238 | { |
239 | int ret; |
240 | unsigned int val; |
241 | u8 status, flags; |
242 | |
243 | mutex_lock(&ch7322->mutex); |
244 | ret = regmap_read(map: ch7322->regmap, CH7322_WRITE, val: &val); |
245 | flags = ch7322->tx_flags; |
246 | mutex_unlock(lock: &ch7322->mutex); |
247 | |
248 | /* |
249 | * The device returns a one-bit OK status which usually means ACK but |
250 | * actually means NACK when sending a logical address query or a |
251 | * broadcast. |
252 | */ |
253 | if (ret) |
254 | status = CEC_TX_STATUS_ERROR; |
255 | else if ((val & CH7322_WRITE_BOK) && (flags & CH7322_TX_FLAG_NACK)) |
256 | status = CEC_TX_STATUS_NACK; |
257 | else if (val & CH7322_WRITE_BOK) |
258 | status = CEC_TX_STATUS_OK; |
259 | else if (flags & CH7322_TX_FLAG_NACK) |
260 | status = CEC_TX_STATUS_OK; |
261 | else |
262 | status = CEC_TX_STATUS_NACK; |
263 | |
264 | if (status == CEC_TX_STATUS_NACK && (flags & CH7322_TX_FLAG_RETRY)) |
265 | status |= CEC_TX_STATUS_MAX_RETRIES; |
266 | |
267 | cec_transmit_attempt_done(adap: ch7322->cec, status); |
268 | } |
269 | |
270 | static void ch7322_rx_done(struct ch7322 *ch7322) |
271 | { |
272 | struct cec_msg msg; |
273 | int ret; |
274 | |
275 | mutex_lock(&ch7322->mutex); |
276 | ret = ch7322_receive_message(ch7322, msg: &msg); |
277 | mutex_unlock(lock: &ch7322->mutex); |
278 | |
279 | if (ret) |
280 | dev_err(&ch7322->i2c->dev, "cec receive error: %d\n" , ret); |
281 | else |
282 | cec_received_msg(adap: ch7322->cec, msg: &msg); |
283 | } |
284 | |
285 | /* |
286 | * This device can either monitor the DDC lines to obtain the physical address |
287 | * or it can allow the host to program it. This driver lets the device obtain |
288 | * it. |
289 | */ |
290 | static void ch7322_phys_addr(struct ch7322 *ch7322) |
291 | { |
292 | unsigned int pah, pal; |
293 | int ret = 0; |
294 | |
295 | mutex_lock(&ch7322->mutex); |
296 | ret |= regmap_read(map: ch7322->regmap, CH7322_PARH, val: &pah); |
297 | ret |= regmap_read(map: ch7322->regmap, CH7322_PARL, val: &pal); |
298 | mutex_unlock(lock: &ch7322->mutex); |
299 | |
300 | if (ret) |
301 | dev_err(&ch7322->i2c->dev, "phys addr error\n" ); |
302 | else |
303 | cec_s_phys_addr(adap: ch7322->cec, phys_addr: pal | (pah << 8), block: false); |
304 | } |
305 | |
306 | static irqreturn_t ch7322_irq(int irq, void *dev) |
307 | { |
308 | struct ch7322 *ch7322 = dev; |
309 | unsigned int data = 0; |
310 | |
311 | mutex_lock(&ch7322->mutex); |
312 | regmap_read(map: ch7322->regmap, CH7322_INTDATA, val: &data); |
313 | regmap_write(map: ch7322->regmap, CH7322_INTDATA, val: data); |
314 | mutex_unlock(lock: &ch7322->mutex); |
315 | |
316 | if (data & CH7322_INTDATA_HPDFALL) |
317 | cec_phys_addr_invalidate(adap: ch7322->cec); |
318 | |
319 | if (data & CH7322_INTDATA_TXMSG) |
320 | ch7322_tx_done(ch7322); |
321 | |
322 | if (data & CH7322_INTDATA_RXMSG) |
323 | ch7322_rx_done(ch7322); |
324 | |
325 | if (data & CH7322_INTDATA_NEWPHA) |
326 | ch7322_phys_addr(ch7322); |
327 | |
328 | if (data & CH7322_INTDATA_ERROR) |
329 | dev_dbg(&ch7322->i2c->dev, "unknown error\n" ); |
330 | |
331 | return IRQ_HANDLED; |
332 | } |
333 | |
334 | /* This device is always enabled */ |
335 | static int ch7322_cec_adap_enable(struct cec_adapter *adap, bool enable) |
336 | { |
337 | return 0; |
338 | } |
339 | |
340 | static int ch7322_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr) |
341 | { |
342 | struct ch7322 *ch7322 = cec_get_drvdata(adap); |
343 | int ret; |
344 | |
345 | mutex_lock(&ch7322->mutex); |
346 | ret = regmap_update_bits(map: ch7322->regmap, CH7322_ADDLW, |
347 | CH7322_ADDLW_MASK, val: log_addr << 4); |
348 | mutex_unlock(lock: &ch7322->mutex); |
349 | |
350 | return ret; |
351 | } |
352 | |
353 | static int ch7322_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, |
354 | u32 signal_free_time, struct cec_msg *msg) |
355 | { |
356 | struct ch7322 *ch7322 = cec_get_drvdata(adap); |
357 | int ret; |
358 | |
359 | mutex_lock(&ch7322->mutex); |
360 | ret = ch7322_send_message(ch7322, msg); |
361 | mutex_unlock(lock: &ch7322->mutex); |
362 | |
363 | return ret; |
364 | } |
365 | |
366 | static const struct cec_adap_ops ch7322_cec_adap_ops = { |
367 | .adap_enable = ch7322_cec_adap_enable, |
368 | .adap_log_addr = ch7322_cec_adap_log_addr, |
369 | .adap_transmit = ch7322_cec_adap_transmit, |
370 | }; |
371 | |
372 | #if IS_ENABLED(CONFIG_PCI) && IS_ENABLED(CONFIG_DMI) |
373 | |
374 | struct ch7322_conn_match { |
375 | const char *dev_name; |
376 | const char *pci_name; |
377 | const char *port_name; |
378 | }; |
379 | |
380 | static struct ch7322_conn_match google_endeavour[] = { |
381 | { "i2c-PRP0001:00" , "0000:00:02.0" , "Port B" }, |
382 | { "i2c-PRP0001:01" , "0000:00:02.0" , "Port C" }, |
383 | { }, |
384 | }; |
385 | |
386 | static const struct dmi_system_id ch7322_dmi_table[] = { |
387 | { |
388 | .matches = { |
389 | DMI_MATCH(DMI_BOARD_VENDOR, "Google" ), |
390 | DMI_MATCH(DMI_BOARD_NAME, "Endeavour" ), |
391 | }, |
392 | .driver_data = google_endeavour, |
393 | }, |
394 | { }, |
395 | }; |
396 | |
397 | /* Make a best-effort attempt to locate a matching HDMI port */ |
398 | static int ch7322_get_port(struct i2c_client *client, |
399 | struct device **dev, |
400 | const char **port) |
401 | { |
402 | const struct dmi_system_id *system; |
403 | const struct ch7322_conn_match *conn; |
404 | |
405 | *dev = NULL; |
406 | *port = NULL; |
407 | |
408 | system = dmi_first_match(list: ch7322_dmi_table); |
409 | if (!system) |
410 | return 0; |
411 | |
412 | for (conn = system->driver_data; conn->dev_name; conn++) { |
413 | if (!strcmp(dev_name(dev: &client->dev), conn->dev_name)) { |
414 | struct device *d; |
415 | |
416 | d = bus_find_device_by_name(bus: &pci_bus_type, NULL, |
417 | name: conn->pci_name); |
418 | if (!d) |
419 | return -EPROBE_DEFER; |
420 | |
421 | put_device(dev: d); |
422 | |
423 | *dev = d; |
424 | *port = conn->port_name; |
425 | |
426 | return 0; |
427 | } |
428 | } |
429 | |
430 | return 0; |
431 | } |
432 | |
433 | #else |
434 | |
435 | static int ch7322_get_port(struct i2c_client *client, |
436 | struct device **dev, |
437 | const char **port) |
438 | { |
439 | *dev = NULL; |
440 | *port = NULL; |
441 | |
442 | return 0; |
443 | } |
444 | |
445 | #endif |
446 | |
447 | static int ch7322_probe(struct i2c_client *client) |
448 | { |
449 | struct device *hdmi_dev; |
450 | const char *port_name; |
451 | struct ch7322 *ch7322; |
452 | struct cec_notifier *notifier = NULL; |
453 | u32 caps = CEC_CAP_DEFAULTS; |
454 | int ret; |
455 | unsigned int val; |
456 | |
457 | ret = ch7322_get_port(client, dev: &hdmi_dev, port: &port_name); |
458 | if (ret) |
459 | return ret; |
460 | |
461 | if (hdmi_dev) |
462 | caps |= CEC_CAP_CONNECTOR_INFO; |
463 | |
464 | ch7322 = devm_kzalloc(dev: &client->dev, size: sizeof(*ch7322), GFP_KERNEL); |
465 | if (!ch7322) |
466 | return -ENOMEM; |
467 | |
468 | ch7322->regmap = devm_regmap_init_i2c(client, &ch7322_regmap); |
469 | if (IS_ERR(ptr: ch7322->regmap)) |
470 | return PTR_ERR(ptr: ch7322->regmap); |
471 | |
472 | ret = regmap_read(map: ch7322->regmap, CH7322_DID, val: &val); |
473 | if (ret) |
474 | return ret; |
475 | |
476 | if (val != CH7322_DID_CH7322) |
477 | return -EOPNOTSUPP; |
478 | |
479 | mutex_init(&ch7322->mutex); |
480 | ch7322->i2c = client; |
481 | ch7322->tx_flags = 0; |
482 | |
483 | i2c_set_clientdata(client, data: ch7322); |
484 | |
485 | /* Disable auto mode */ |
486 | ret = regmap_write(map: ch7322->regmap, CH7322_MODE, CH7322_MODE_SW); |
487 | if (ret) |
488 | goto err_mutex; |
489 | |
490 | /* Enable logical address register */ |
491 | ret = regmap_update_bits(map: ch7322->regmap, CH7322_CTL, |
492 | CH7322_CTL_SPADL, CH7322_CTL_SPADL); |
493 | if (ret) |
494 | goto err_mutex; |
495 | |
496 | ch7322->cec = cec_allocate_adapter(ops: &ch7322_cec_adap_ops, priv: ch7322, |
497 | name: dev_name(dev: &client->dev), |
498 | caps, available_las: 1); |
499 | |
500 | if (IS_ERR(ptr: ch7322->cec)) { |
501 | ret = PTR_ERR(ptr: ch7322->cec); |
502 | goto err_mutex; |
503 | } |
504 | |
505 | ch7322->cec->adap_controls_phys_addr = true; |
506 | |
507 | if (hdmi_dev) { |
508 | notifier = cec_notifier_cec_adap_register(hdmi_dev, |
509 | port_name, |
510 | adap: ch7322->cec); |
511 | if (!notifier) { |
512 | ret = -ENOMEM; |
513 | goto err_cec; |
514 | } |
515 | } |
516 | |
517 | /* Configure, mask, and clear interrupt */ |
518 | ret = regmap_write(map: ch7322->regmap, CH7322_CFG1, val: 0); |
519 | if (ret) |
520 | goto err_notifier; |
521 | ret = regmap_write(map: ch7322->regmap, CH7322_INTCTL, CH7322_INTCTL_INTPB); |
522 | if (ret) |
523 | goto err_notifier; |
524 | ret = regmap_write(map: ch7322->regmap, CH7322_INTDATA, val: 0xff); |
525 | if (ret) |
526 | goto err_notifier; |
527 | |
528 | /* If HPD is up read physical address */ |
529 | ret = regmap_read(map: ch7322->regmap, CH7322_ADDLR, val: &val); |
530 | if (ret) |
531 | goto err_notifier; |
532 | if (val & CH7322_ADDLR_HPD) |
533 | ch7322_phys_addr(ch7322); |
534 | |
535 | ret = devm_request_threaded_irq(dev: &client->dev, irq: client->irq, NULL, |
536 | thread_fn: ch7322_irq, |
537 | IRQF_ONESHOT | IRQF_TRIGGER_RISING, |
538 | devname: client->name, dev_id: ch7322); |
539 | if (ret) |
540 | goto err_notifier; |
541 | |
542 | /* Unmask interrupt */ |
543 | mutex_lock(&ch7322->mutex); |
544 | ret = regmap_write(map: ch7322->regmap, CH7322_INTCTL, val: 0xff); |
545 | mutex_unlock(lock: &ch7322->mutex); |
546 | |
547 | if (ret) |
548 | goto err_notifier; |
549 | |
550 | ret = cec_register_adapter(adap: ch7322->cec, parent: &client->dev); |
551 | if (ret) |
552 | goto err_notifier; |
553 | |
554 | dev_info(&client->dev, "device registered\n" ); |
555 | |
556 | return 0; |
557 | |
558 | err_notifier: |
559 | if (notifier) |
560 | cec_notifier_cec_adap_unregister(n: notifier, adap: ch7322->cec); |
561 | err_cec: |
562 | cec_delete_adapter(adap: ch7322->cec); |
563 | err_mutex: |
564 | mutex_destroy(lock: &ch7322->mutex); |
565 | return ret; |
566 | } |
567 | |
568 | static void ch7322_remove(struct i2c_client *client) |
569 | { |
570 | struct ch7322 *ch7322 = i2c_get_clientdata(client); |
571 | |
572 | /* Mask interrupt */ |
573 | mutex_lock(&ch7322->mutex); |
574 | regmap_write(map: ch7322->regmap, CH7322_INTCTL, CH7322_INTCTL_INTPB); |
575 | mutex_unlock(lock: &ch7322->mutex); |
576 | |
577 | cec_unregister_adapter(adap: ch7322->cec); |
578 | mutex_destroy(lock: &ch7322->mutex); |
579 | |
580 | dev_info(&client->dev, "device unregistered\n" ); |
581 | } |
582 | |
583 | static const struct of_device_id ch7322_of_match[] = { |
584 | { .compatible = "chrontel,ch7322" , }, |
585 | {}, |
586 | }; |
587 | MODULE_DEVICE_TABLE(of, ch7322_of_match); |
588 | |
589 | static struct i2c_driver ch7322_i2c_driver = { |
590 | .driver = { |
591 | .name = "ch7322" , |
592 | .of_match_table = ch7322_of_match, |
593 | }, |
594 | .probe = ch7322_probe, |
595 | .remove = ch7322_remove, |
596 | }; |
597 | |
598 | module_i2c_driver(ch7322_i2c_driver); |
599 | |
600 | MODULE_DESCRIPTION("Chrontel CH7322 CEC Controller Driver" ); |
601 | MODULE_AUTHOR("Jeff Chase <jnchase@google.com>" ); |
602 | MODULE_LICENSE("GPL" ); |
603 | |