xref: /OK3568_Linux_fs/kernel/drivers/media/cec/usb/rainshadow/rainshadow-cec.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-or-later
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun  * RainShadow Tech HDMI CEC driver
4*4882a593Smuzhiyun  *
5*4882a593Smuzhiyun  * Copyright 2016 Hans Verkuil <hverkuil@xs4all.nl
6*4882a593Smuzhiyun  */
7*4882a593Smuzhiyun 
8*4882a593Smuzhiyun /*
9*4882a593Smuzhiyun  * Notes:
10*4882a593Smuzhiyun  *
11*4882a593Smuzhiyun  * The higher level protocols are currently disabled. This can be added
12*4882a593Smuzhiyun  * later, similar to how this is done for the Pulse Eight CEC driver.
13*4882a593Smuzhiyun  *
14*4882a593Smuzhiyun  * Documentation of the protocol is available here:
15*4882a593Smuzhiyun  *
16*4882a593Smuzhiyun  * http://rainshadowtech.com/doc/HDMICECtoUSBandRS232v2.0.pdf
17*4882a593Smuzhiyun  */
18*4882a593Smuzhiyun 
19*4882a593Smuzhiyun #include <linux/completion.h>
20*4882a593Smuzhiyun #include <linux/ctype.h>
21*4882a593Smuzhiyun #include <linux/delay.h>
22*4882a593Smuzhiyun #include <linux/init.h>
23*4882a593Smuzhiyun #include <linux/interrupt.h>
24*4882a593Smuzhiyun #include <linux/kernel.h>
25*4882a593Smuzhiyun #include <linux/module.h>
26*4882a593Smuzhiyun #include <linux/serio.h>
27*4882a593Smuzhiyun #include <linux/slab.h>
28*4882a593Smuzhiyun #include <linux/spinlock.h>
29*4882a593Smuzhiyun #include <linux/time.h>
30*4882a593Smuzhiyun #include <linux/workqueue.h>
31*4882a593Smuzhiyun 
32*4882a593Smuzhiyun #include <media/cec.h>
33*4882a593Smuzhiyun 
34*4882a593Smuzhiyun MODULE_AUTHOR("Hans Verkuil <hverkuil@xs4all.nl>");
35*4882a593Smuzhiyun MODULE_DESCRIPTION("RainShadow Tech HDMI CEC driver");
36*4882a593Smuzhiyun MODULE_LICENSE("GPL");
37*4882a593Smuzhiyun 
38*4882a593Smuzhiyun #define DATA_SIZE 256
39*4882a593Smuzhiyun 
40*4882a593Smuzhiyun struct rain {
41*4882a593Smuzhiyun 	struct device *dev;
42*4882a593Smuzhiyun 	struct serio *serio;
43*4882a593Smuzhiyun 	struct cec_adapter *adap;
44*4882a593Smuzhiyun 	struct completion cmd_done;
45*4882a593Smuzhiyun 	struct work_struct work;
46*4882a593Smuzhiyun 
47*4882a593Smuzhiyun 	/* Low-level ringbuffer, collecting incoming characters */
48*4882a593Smuzhiyun 	char buf[DATA_SIZE];
49*4882a593Smuzhiyun 	unsigned int buf_rd_idx;
50*4882a593Smuzhiyun 	unsigned int buf_wr_idx;
51*4882a593Smuzhiyun 	unsigned int buf_len;
52*4882a593Smuzhiyun 	spinlock_t buf_lock;
53*4882a593Smuzhiyun 
54*4882a593Smuzhiyun 	/* command buffer */
55*4882a593Smuzhiyun 	char cmd[DATA_SIZE];
56*4882a593Smuzhiyun 	unsigned int cmd_idx;
57*4882a593Smuzhiyun 	bool cmd_started;
58*4882a593Smuzhiyun 
59*4882a593Smuzhiyun 	/* reply to a command, only used to store the firmware version */
60*4882a593Smuzhiyun 	char cmd_reply[DATA_SIZE];
61*4882a593Smuzhiyun 
62*4882a593Smuzhiyun 	struct mutex write_lock;
63*4882a593Smuzhiyun };
64*4882a593Smuzhiyun 
rain_process_msg(struct rain * rain)65*4882a593Smuzhiyun static void rain_process_msg(struct rain *rain)
66*4882a593Smuzhiyun {
67*4882a593Smuzhiyun 	struct cec_msg msg = {};
68*4882a593Smuzhiyun 	const char *cmd = rain->cmd + 3;
69*4882a593Smuzhiyun 	int stat = -1;
70*4882a593Smuzhiyun 
71*4882a593Smuzhiyun 	for (; *cmd; cmd++) {
72*4882a593Smuzhiyun 		if (!isxdigit(*cmd))
73*4882a593Smuzhiyun 			continue;
74*4882a593Smuzhiyun 		if (isxdigit(cmd[0]) && isxdigit(cmd[1])) {
75*4882a593Smuzhiyun 			if (msg.len == CEC_MAX_MSG_SIZE)
76*4882a593Smuzhiyun 				break;
77*4882a593Smuzhiyun 			if (hex2bin(msg.msg + msg.len, cmd, 1))
78*4882a593Smuzhiyun 				continue;
79*4882a593Smuzhiyun 			msg.len++;
80*4882a593Smuzhiyun 			cmd++;
81*4882a593Smuzhiyun 			continue;
82*4882a593Smuzhiyun 		}
83*4882a593Smuzhiyun 		if (!cmd[1])
84*4882a593Smuzhiyun 			stat = hex_to_bin(cmd[0]);
85*4882a593Smuzhiyun 		break;
86*4882a593Smuzhiyun 	}
87*4882a593Smuzhiyun 
88*4882a593Smuzhiyun 	if (rain->cmd[0] == 'R') {
89*4882a593Smuzhiyun 		if (stat == 1 || stat == 2)
90*4882a593Smuzhiyun 			cec_received_msg(rain->adap, &msg);
91*4882a593Smuzhiyun 		return;
92*4882a593Smuzhiyun 	}
93*4882a593Smuzhiyun 
94*4882a593Smuzhiyun 	switch (stat) {
95*4882a593Smuzhiyun 	case 1:
96*4882a593Smuzhiyun 		cec_transmit_attempt_done(rain->adap, CEC_TX_STATUS_OK);
97*4882a593Smuzhiyun 		break;
98*4882a593Smuzhiyun 	case 2:
99*4882a593Smuzhiyun 		cec_transmit_attempt_done(rain->adap, CEC_TX_STATUS_NACK);
100*4882a593Smuzhiyun 		break;
101*4882a593Smuzhiyun 	default:
102*4882a593Smuzhiyun 		cec_transmit_attempt_done(rain->adap, CEC_TX_STATUS_LOW_DRIVE);
103*4882a593Smuzhiyun 		break;
104*4882a593Smuzhiyun 	}
105*4882a593Smuzhiyun }
106*4882a593Smuzhiyun 
rain_irq_work_handler(struct work_struct * work)107*4882a593Smuzhiyun static void rain_irq_work_handler(struct work_struct *work)
108*4882a593Smuzhiyun {
109*4882a593Smuzhiyun 	struct rain *rain =
110*4882a593Smuzhiyun 		container_of(work, struct rain, work);
111*4882a593Smuzhiyun 
112*4882a593Smuzhiyun 	while (true) {
113*4882a593Smuzhiyun 		unsigned long flags;
114*4882a593Smuzhiyun 		char data;
115*4882a593Smuzhiyun 
116*4882a593Smuzhiyun 		spin_lock_irqsave(&rain->buf_lock, flags);
117*4882a593Smuzhiyun 		if (!rain->buf_len) {
118*4882a593Smuzhiyun 			spin_unlock_irqrestore(&rain->buf_lock, flags);
119*4882a593Smuzhiyun 			break;
120*4882a593Smuzhiyun 		}
121*4882a593Smuzhiyun 
122*4882a593Smuzhiyun 		data = rain->buf[rain->buf_rd_idx];
123*4882a593Smuzhiyun 		rain->buf_len--;
124*4882a593Smuzhiyun 		rain->buf_rd_idx = (rain->buf_rd_idx + 1) & 0xff;
125*4882a593Smuzhiyun 
126*4882a593Smuzhiyun 		spin_unlock_irqrestore(&rain->buf_lock, flags);
127*4882a593Smuzhiyun 
128*4882a593Smuzhiyun 		if (!rain->cmd_started && data != '?')
129*4882a593Smuzhiyun 			continue;
130*4882a593Smuzhiyun 
131*4882a593Smuzhiyun 		switch (data) {
132*4882a593Smuzhiyun 		case '\r':
133*4882a593Smuzhiyun 			rain->cmd[rain->cmd_idx] = '\0';
134*4882a593Smuzhiyun 			dev_dbg(rain->dev, "received: %s\n", rain->cmd);
135*4882a593Smuzhiyun 			if (!memcmp(rain->cmd, "REC", 3) ||
136*4882a593Smuzhiyun 			    !memcmp(rain->cmd, "STA", 3)) {
137*4882a593Smuzhiyun 				rain_process_msg(rain);
138*4882a593Smuzhiyun 			} else {
139*4882a593Smuzhiyun 				strscpy(rain->cmd_reply, rain->cmd,
140*4882a593Smuzhiyun 					sizeof(rain->cmd_reply));
141*4882a593Smuzhiyun 				complete(&rain->cmd_done);
142*4882a593Smuzhiyun 			}
143*4882a593Smuzhiyun 			rain->cmd_idx = 0;
144*4882a593Smuzhiyun 			rain->cmd_started = false;
145*4882a593Smuzhiyun 			break;
146*4882a593Smuzhiyun 
147*4882a593Smuzhiyun 		case '\n':
148*4882a593Smuzhiyun 			rain->cmd_idx = 0;
149*4882a593Smuzhiyun 			rain->cmd_started = false;
150*4882a593Smuzhiyun 			break;
151*4882a593Smuzhiyun 
152*4882a593Smuzhiyun 		case '?':
153*4882a593Smuzhiyun 			rain->cmd_idx = 0;
154*4882a593Smuzhiyun 			rain->cmd_started = true;
155*4882a593Smuzhiyun 			break;
156*4882a593Smuzhiyun 
157*4882a593Smuzhiyun 		default:
158*4882a593Smuzhiyun 			if (rain->cmd_idx >= DATA_SIZE - 1) {
159*4882a593Smuzhiyun 				dev_dbg(rain->dev,
160*4882a593Smuzhiyun 					"throwing away %d bytes of garbage\n", rain->cmd_idx);
161*4882a593Smuzhiyun 				rain->cmd_idx = 0;
162*4882a593Smuzhiyun 			}
163*4882a593Smuzhiyun 			rain->cmd[rain->cmd_idx++] = data;
164*4882a593Smuzhiyun 			break;
165*4882a593Smuzhiyun 		}
166*4882a593Smuzhiyun 	}
167*4882a593Smuzhiyun }
168*4882a593Smuzhiyun 
rain_interrupt(struct serio * serio,unsigned char data,unsigned int flags)169*4882a593Smuzhiyun static irqreturn_t rain_interrupt(struct serio *serio, unsigned char data,
170*4882a593Smuzhiyun 				    unsigned int flags)
171*4882a593Smuzhiyun {
172*4882a593Smuzhiyun 	struct rain *rain = serio_get_drvdata(serio);
173*4882a593Smuzhiyun 
174*4882a593Smuzhiyun 	if (rain->buf_len == DATA_SIZE) {
175*4882a593Smuzhiyun 		dev_warn_once(rain->dev, "buffer overflow\n");
176*4882a593Smuzhiyun 		return IRQ_HANDLED;
177*4882a593Smuzhiyun 	}
178*4882a593Smuzhiyun 	spin_lock(&rain->buf_lock);
179*4882a593Smuzhiyun 	rain->buf_len++;
180*4882a593Smuzhiyun 	rain->buf[rain->buf_wr_idx] = data;
181*4882a593Smuzhiyun 	rain->buf_wr_idx = (rain->buf_wr_idx + 1) & 0xff;
182*4882a593Smuzhiyun 	spin_unlock(&rain->buf_lock);
183*4882a593Smuzhiyun 	schedule_work(&rain->work);
184*4882a593Smuzhiyun 	return IRQ_HANDLED;
185*4882a593Smuzhiyun }
186*4882a593Smuzhiyun 
rain_disconnect(struct serio * serio)187*4882a593Smuzhiyun static void rain_disconnect(struct serio *serio)
188*4882a593Smuzhiyun {
189*4882a593Smuzhiyun 	struct rain *rain = serio_get_drvdata(serio);
190*4882a593Smuzhiyun 
191*4882a593Smuzhiyun 	cancel_work_sync(&rain->work);
192*4882a593Smuzhiyun 	cec_unregister_adapter(rain->adap);
193*4882a593Smuzhiyun 	dev_info(&serio->dev, "disconnected\n");
194*4882a593Smuzhiyun 	serio_close(serio);
195*4882a593Smuzhiyun 	serio_set_drvdata(serio, NULL);
196*4882a593Smuzhiyun 	kfree(rain);
197*4882a593Smuzhiyun }
198*4882a593Smuzhiyun 
rain_send(struct rain * rain,const char * command)199*4882a593Smuzhiyun static int rain_send(struct rain *rain, const char *command)
200*4882a593Smuzhiyun {
201*4882a593Smuzhiyun 	int err = serio_write(rain->serio, '!');
202*4882a593Smuzhiyun 
203*4882a593Smuzhiyun 	dev_dbg(rain->dev, "send: %s\n", command);
204*4882a593Smuzhiyun 	while (!err && *command)
205*4882a593Smuzhiyun 		err = serio_write(rain->serio, *command++);
206*4882a593Smuzhiyun 	if (!err)
207*4882a593Smuzhiyun 		err = serio_write(rain->serio, '~');
208*4882a593Smuzhiyun 
209*4882a593Smuzhiyun 	return err;
210*4882a593Smuzhiyun }
211*4882a593Smuzhiyun 
rain_send_and_wait(struct rain * rain,const char * cmd,const char * reply)212*4882a593Smuzhiyun static int rain_send_and_wait(struct rain *rain,
213*4882a593Smuzhiyun 			      const char *cmd, const char *reply)
214*4882a593Smuzhiyun {
215*4882a593Smuzhiyun 	int err;
216*4882a593Smuzhiyun 
217*4882a593Smuzhiyun 	init_completion(&rain->cmd_done);
218*4882a593Smuzhiyun 
219*4882a593Smuzhiyun 	mutex_lock(&rain->write_lock);
220*4882a593Smuzhiyun 	err = rain_send(rain, cmd);
221*4882a593Smuzhiyun 	if (err)
222*4882a593Smuzhiyun 		goto err;
223*4882a593Smuzhiyun 
224*4882a593Smuzhiyun 	if (!wait_for_completion_timeout(&rain->cmd_done, HZ)) {
225*4882a593Smuzhiyun 		err = -ETIMEDOUT;
226*4882a593Smuzhiyun 		goto err;
227*4882a593Smuzhiyun 	}
228*4882a593Smuzhiyun 	if (reply && strncmp(rain->cmd_reply, reply, strlen(reply))) {
229*4882a593Smuzhiyun 		dev_dbg(rain->dev,
230*4882a593Smuzhiyun 			 "transmit of '%s': received '%s' instead of '%s'\n",
231*4882a593Smuzhiyun 			 cmd, rain->cmd_reply, reply);
232*4882a593Smuzhiyun 		err = -EIO;
233*4882a593Smuzhiyun 	}
234*4882a593Smuzhiyun err:
235*4882a593Smuzhiyun 	mutex_unlock(&rain->write_lock);
236*4882a593Smuzhiyun 	return err;
237*4882a593Smuzhiyun }
238*4882a593Smuzhiyun 
rain_setup(struct rain * rain,struct serio * serio,struct cec_log_addrs * log_addrs,u16 * pa)239*4882a593Smuzhiyun static int rain_setup(struct rain *rain, struct serio *serio,
240*4882a593Smuzhiyun 			struct cec_log_addrs *log_addrs, u16 *pa)
241*4882a593Smuzhiyun {
242*4882a593Smuzhiyun 	int err;
243*4882a593Smuzhiyun 
244*4882a593Smuzhiyun 	err = rain_send_and_wait(rain, "R", "REV");
245*4882a593Smuzhiyun 	if (err)
246*4882a593Smuzhiyun 		return err;
247*4882a593Smuzhiyun 	dev_info(rain->dev, "Firmware version %s\n", rain->cmd_reply + 4);
248*4882a593Smuzhiyun 
249*4882a593Smuzhiyun 	err = rain_send_and_wait(rain, "Q 1", "QTY");
250*4882a593Smuzhiyun 	if (err)
251*4882a593Smuzhiyun 		return err;
252*4882a593Smuzhiyun 	err = rain_send_and_wait(rain, "c0000", "CFG");
253*4882a593Smuzhiyun 	if (err)
254*4882a593Smuzhiyun 		return err;
255*4882a593Smuzhiyun 	return rain_send_and_wait(rain, "A F 0000", "ADR");
256*4882a593Smuzhiyun }
257*4882a593Smuzhiyun 
rain_cec_adap_enable(struct cec_adapter * adap,bool enable)258*4882a593Smuzhiyun static int rain_cec_adap_enable(struct cec_adapter *adap, bool enable)
259*4882a593Smuzhiyun {
260*4882a593Smuzhiyun 	return 0;
261*4882a593Smuzhiyun }
262*4882a593Smuzhiyun 
rain_cec_adap_log_addr(struct cec_adapter * adap,u8 log_addr)263*4882a593Smuzhiyun static int rain_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr)
264*4882a593Smuzhiyun {
265*4882a593Smuzhiyun 	struct rain *rain = cec_get_drvdata(adap);
266*4882a593Smuzhiyun 	u8 cmd[16];
267*4882a593Smuzhiyun 
268*4882a593Smuzhiyun 	if (log_addr == CEC_LOG_ADDR_INVALID)
269*4882a593Smuzhiyun 		log_addr = CEC_LOG_ADDR_UNREGISTERED;
270*4882a593Smuzhiyun 	snprintf(cmd, sizeof(cmd), "A %x", log_addr);
271*4882a593Smuzhiyun 	return rain_send_and_wait(rain, cmd, "ADR");
272*4882a593Smuzhiyun }
273*4882a593Smuzhiyun 
rain_cec_adap_transmit(struct cec_adapter * adap,u8 attempts,u32 signal_free_time,struct cec_msg * msg)274*4882a593Smuzhiyun static int rain_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
275*4882a593Smuzhiyun 				    u32 signal_free_time, struct cec_msg *msg)
276*4882a593Smuzhiyun {
277*4882a593Smuzhiyun 	struct rain *rain = cec_get_drvdata(adap);
278*4882a593Smuzhiyun 	char cmd[2 * CEC_MAX_MSG_SIZE + 16];
279*4882a593Smuzhiyun 	unsigned int i;
280*4882a593Smuzhiyun 	int err;
281*4882a593Smuzhiyun 
282*4882a593Smuzhiyun 	if (msg->len == 1) {
283*4882a593Smuzhiyun 		snprintf(cmd, sizeof(cmd), "x%x", cec_msg_destination(msg));
284*4882a593Smuzhiyun 	} else {
285*4882a593Smuzhiyun 		char hex[3];
286*4882a593Smuzhiyun 
287*4882a593Smuzhiyun 		snprintf(cmd, sizeof(cmd), "x%x %02x ",
288*4882a593Smuzhiyun 			 cec_msg_destination(msg), msg->msg[1]);
289*4882a593Smuzhiyun 		for (i = 2; i < msg->len; i++) {
290*4882a593Smuzhiyun 			snprintf(hex, sizeof(hex), "%02x", msg->msg[i]);
291*4882a593Smuzhiyun 			strlcat(cmd, hex, sizeof(cmd));
292*4882a593Smuzhiyun 		}
293*4882a593Smuzhiyun 	}
294*4882a593Smuzhiyun 	mutex_lock(&rain->write_lock);
295*4882a593Smuzhiyun 	err = rain_send(rain, cmd);
296*4882a593Smuzhiyun 	mutex_unlock(&rain->write_lock);
297*4882a593Smuzhiyun 	return err;
298*4882a593Smuzhiyun }
299*4882a593Smuzhiyun 
300*4882a593Smuzhiyun static const struct cec_adap_ops rain_cec_adap_ops = {
301*4882a593Smuzhiyun 	.adap_enable = rain_cec_adap_enable,
302*4882a593Smuzhiyun 	.adap_log_addr = rain_cec_adap_log_addr,
303*4882a593Smuzhiyun 	.adap_transmit = rain_cec_adap_transmit,
304*4882a593Smuzhiyun };
305*4882a593Smuzhiyun 
rain_connect(struct serio * serio,struct serio_driver * drv)306*4882a593Smuzhiyun static int rain_connect(struct serio *serio, struct serio_driver *drv)
307*4882a593Smuzhiyun {
308*4882a593Smuzhiyun 	u32 caps = CEC_CAP_DEFAULTS | CEC_CAP_PHYS_ADDR | CEC_CAP_MONITOR_ALL;
309*4882a593Smuzhiyun 	struct rain *rain;
310*4882a593Smuzhiyun 	int err = -ENOMEM;
311*4882a593Smuzhiyun 	struct cec_log_addrs log_addrs = {};
312*4882a593Smuzhiyun 	u16 pa = CEC_PHYS_ADDR_INVALID;
313*4882a593Smuzhiyun 
314*4882a593Smuzhiyun 	rain = kzalloc(sizeof(*rain), GFP_KERNEL);
315*4882a593Smuzhiyun 
316*4882a593Smuzhiyun 	if (!rain)
317*4882a593Smuzhiyun 		return -ENOMEM;
318*4882a593Smuzhiyun 
319*4882a593Smuzhiyun 	rain->serio = serio;
320*4882a593Smuzhiyun 	rain->adap = cec_allocate_adapter(&rain_cec_adap_ops, rain,
321*4882a593Smuzhiyun 					  dev_name(&serio->dev), caps, 1);
322*4882a593Smuzhiyun 	err = PTR_ERR_OR_ZERO(rain->adap);
323*4882a593Smuzhiyun 	if (err < 0)
324*4882a593Smuzhiyun 		goto free_device;
325*4882a593Smuzhiyun 
326*4882a593Smuzhiyun 	rain->dev = &serio->dev;
327*4882a593Smuzhiyun 	serio_set_drvdata(serio, rain);
328*4882a593Smuzhiyun 	INIT_WORK(&rain->work, rain_irq_work_handler);
329*4882a593Smuzhiyun 	mutex_init(&rain->write_lock);
330*4882a593Smuzhiyun 	spin_lock_init(&rain->buf_lock);
331*4882a593Smuzhiyun 
332*4882a593Smuzhiyun 	err = serio_open(serio, drv);
333*4882a593Smuzhiyun 	if (err)
334*4882a593Smuzhiyun 		goto delete_adap;
335*4882a593Smuzhiyun 
336*4882a593Smuzhiyun 	err = rain_setup(rain, serio, &log_addrs, &pa);
337*4882a593Smuzhiyun 	if (err)
338*4882a593Smuzhiyun 		goto close_serio;
339*4882a593Smuzhiyun 
340*4882a593Smuzhiyun 	err = cec_register_adapter(rain->adap, &serio->dev);
341*4882a593Smuzhiyun 	if (err < 0)
342*4882a593Smuzhiyun 		goto close_serio;
343*4882a593Smuzhiyun 
344*4882a593Smuzhiyun 	rain->dev = &rain->adap->devnode.dev;
345*4882a593Smuzhiyun 	return 0;
346*4882a593Smuzhiyun 
347*4882a593Smuzhiyun close_serio:
348*4882a593Smuzhiyun 	serio_close(serio);
349*4882a593Smuzhiyun delete_adap:
350*4882a593Smuzhiyun 	cec_delete_adapter(rain->adap);
351*4882a593Smuzhiyun 	serio_set_drvdata(serio, NULL);
352*4882a593Smuzhiyun free_device:
353*4882a593Smuzhiyun 	kfree(rain);
354*4882a593Smuzhiyun 	return err;
355*4882a593Smuzhiyun }
356*4882a593Smuzhiyun 
357*4882a593Smuzhiyun static const struct serio_device_id rain_serio_ids[] = {
358*4882a593Smuzhiyun 	{
359*4882a593Smuzhiyun 		.type	= SERIO_RS232,
360*4882a593Smuzhiyun 		.proto	= SERIO_RAINSHADOW_CEC,
361*4882a593Smuzhiyun 		.id	= SERIO_ANY,
362*4882a593Smuzhiyun 		.extra	= SERIO_ANY,
363*4882a593Smuzhiyun 	},
364*4882a593Smuzhiyun 	{ 0 }
365*4882a593Smuzhiyun };
366*4882a593Smuzhiyun 
367*4882a593Smuzhiyun MODULE_DEVICE_TABLE(serio, rain_serio_ids);
368*4882a593Smuzhiyun 
369*4882a593Smuzhiyun static struct serio_driver rain_drv = {
370*4882a593Smuzhiyun 	.driver		= {
371*4882a593Smuzhiyun 		.name	= "rainshadow-cec",
372*4882a593Smuzhiyun 	},
373*4882a593Smuzhiyun 	.description	= "RainShadow Tech HDMI CEC driver",
374*4882a593Smuzhiyun 	.id_table	= rain_serio_ids,
375*4882a593Smuzhiyun 	.interrupt	= rain_interrupt,
376*4882a593Smuzhiyun 	.connect	= rain_connect,
377*4882a593Smuzhiyun 	.disconnect	= rain_disconnect,
378*4882a593Smuzhiyun };
379*4882a593Smuzhiyun 
380*4882a593Smuzhiyun module_serio_driver(rain_drv);
381