xref: /linux/drivers/media/cec/usb/pulse8/pulse8-cec.c (revision a81068181aad78a5f6980408a4b6115d22bb8aed)
1*a8106818SMauro Carvalho Chehab // SPDX-License-Identifier: GPL-2.0-or-later
2*a8106818SMauro Carvalho Chehab /*
3*a8106818SMauro Carvalho Chehab  * Pulse Eight HDMI CEC driver
4*a8106818SMauro Carvalho Chehab  *
5*a8106818SMauro Carvalho Chehab  * Copyright 2016 Hans Verkuil <hverkuil@xs4all.nl
6*a8106818SMauro Carvalho Chehab  */
7*a8106818SMauro Carvalho Chehab 
8*a8106818SMauro Carvalho Chehab /*
9*a8106818SMauro Carvalho Chehab  * Notes:
10*a8106818SMauro Carvalho Chehab  *
11*a8106818SMauro Carvalho Chehab  * - Devices with firmware version < 2 do not store their configuration in
12*a8106818SMauro Carvalho Chehab  *   EEPROM.
13*a8106818SMauro Carvalho Chehab  *
14*a8106818SMauro Carvalho Chehab  * - In autonomous mode, only messages from a TV will be acknowledged, even
15*a8106818SMauro Carvalho Chehab  *   polling messages. Upon receiving a message from a TV, the dongle will
16*a8106818SMauro Carvalho Chehab  *   respond to messages from any logical address.
17*a8106818SMauro Carvalho Chehab  *
18*a8106818SMauro Carvalho Chehab  * - In autonomous mode, the dongle will by default reply Feature Abort
19*a8106818SMauro Carvalho Chehab  *   [Unrecognized Opcode] when it receives Give Device Vendor ID. It will
20*a8106818SMauro Carvalho Chehab  *   however observe vendor ID's reported by other devices and possibly
21*a8106818SMauro Carvalho Chehab  *   alter this behavior. When TV's (and TV's only) report that their vendor ID
22*a8106818SMauro Carvalho Chehab  *   is LG (0x00e091), the dongle will itself reply that it has the same vendor
23*a8106818SMauro Carvalho Chehab  *   ID, and it will respond to at least one vendor specific command.
24*a8106818SMauro Carvalho Chehab  *
25*a8106818SMauro Carvalho Chehab  * - In autonomous mode, the dongle is known to attempt wakeup if it receives
26*a8106818SMauro Carvalho Chehab  *   <User Control Pressed> ["Power On"], ["Power] or ["Power Toggle"], or if it
27*a8106818SMauro Carvalho Chehab  *   receives <Set Stream Path> with its own physical address. It also does this
28*a8106818SMauro Carvalho Chehab  *   if it receives <Vendor Specific Command> [0x03 0x00] from an LG TV.
29*a8106818SMauro Carvalho Chehab  */
30*a8106818SMauro Carvalho Chehab 
31*a8106818SMauro Carvalho Chehab #include <linux/completion.h>
32*a8106818SMauro Carvalho Chehab #include <linux/init.h>
33*a8106818SMauro Carvalho Chehab #include <linux/interrupt.h>
34*a8106818SMauro Carvalho Chehab #include <linux/kernel.h>
35*a8106818SMauro Carvalho Chehab #include <linux/module.h>
36*a8106818SMauro Carvalho Chehab #include <linux/workqueue.h>
37*a8106818SMauro Carvalho Chehab #include <linux/serio.h>
38*a8106818SMauro Carvalho Chehab #include <linux/slab.h>
39*a8106818SMauro Carvalho Chehab #include <linux/time.h>
40*a8106818SMauro Carvalho Chehab #include <linux/delay.h>
41*a8106818SMauro Carvalho Chehab 
42*a8106818SMauro Carvalho Chehab #include <media/cec.h>
43*a8106818SMauro Carvalho Chehab 
44*a8106818SMauro Carvalho Chehab MODULE_AUTHOR("Hans Verkuil <hverkuil@xs4all.nl>");
45*a8106818SMauro Carvalho Chehab MODULE_DESCRIPTION("Pulse Eight HDMI CEC driver");
46*a8106818SMauro Carvalho Chehab MODULE_LICENSE("GPL");
47*a8106818SMauro Carvalho Chehab 
48*a8106818SMauro Carvalho Chehab static int debug;
49*a8106818SMauro Carvalho Chehab static int persistent_config;
50*a8106818SMauro Carvalho Chehab module_param(debug, int, 0644);
51*a8106818SMauro Carvalho Chehab module_param(persistent_config, int, 0644);
52*a8106818SMauro Carvalho Chehab MODULE_PARM_DESC(debug, "debug level (0-2)");
53*a8106818SMauro Carvalho Chehab MODULE_PARM_DESC(persistent_config, "read config from persistent memory (0-1)");
54*a8106818SMauro Carvalho Chehab 
55*a8106818SMauro Carvalho Chehab enum pulse8_msgcodes {
56*a8106818SMauro Carvalho Chehab 	MSGCODE_NOTHING = 0,
57*a8106818SMauro Carvalho Chehab 	MSGCODE_PING,
58*a8106818SMauro Carvalho Chehab 	MSGCODE_TIMEOUT_ERROR,
59*a8106818SMauro Carvalho Chehab 	MSGCODE_HIGH_ERROR,
60*a8106818SMauro Carvalho Chehab 	MSGCODE_LOW_ERROR,
61*a8106818SMauro Carvalho Chehab 	MSGCODE_FRAME_START,
62*a8106818SMauro Carvalho Chehab 	MSGCODE_FRAME_DATA,
63*a8106818SMauro Carvalho Chehab 	MSGCODE_RECEIVE_FAILED,
64*a8106818SMauro Carvalho Chehab 	MSGCODE_COMMAND_ACCEPTED,	/* 0x08 */
65*a8106818SMauro Carvalho Chehab 	MSGCODE_COMMAND_REJECTED,
66*a8106818SMauro Carvalho Chehab 	MSGCODE_SET_ACK_MASK,
67*a8106818SMauro Carvalho Chehab 	MSGCODE_TRANSMIT,
68*a8106818SMauro Carvalho Chehab 	MSGCODE_TRANSMIT_EOM,
69*a8106818SMauro Carvalho Chehab 	MSGCODE_TRANSMIT_IDLETIME,
70*a8106818SMauro Carvalho Chehab 	MSGCODE_TRANSMIT_ACK_POLARITY,
71*a8106818SMauro Carvalho Chehab 	MSGCODE_TRANSMIT_LINE_TIMEOUT,
72*a8106818SMauro Carvalho Chehab 	MSGCODE_TRANSMIT_SUCCEEDED,	/* 0x10 */
73*a8106818SMauro Carvalho Chehab 	MSGCODE_TRANSMIT_FAILED_LINE,
74*a8106818SMauro Carvalho Chehab 	MSGCODE_TRANSMIT_FAILED_ACK,
75*a8106818SMauro Carvalho Chehab 	MSGCODE_TRANSMIT_FAILED_TIMEOUT_DATA,
76*a8106818SMauro Carvalho Chehab 	MSGCODE_TRANSMIT_FAILED_TIMEOUT_LINE,
77*a8106818SMauro Carvalho Chehab 	MSGCODE_FIRMWARE_VERSION,
78*a8106818SMauro Carvalho Chehab 	MSGCODE_START_BOOTLOADER,
79*a8106818SMauro Carvalho Chehab 	MSGCODE_GET_BUILDDATE,
80*a8106818SMauro Carvalho Chehab 	MSGCODE_SET_CONTROLLED,		/* 0x18 */
81*a8106818SMauro Carvalho Chehab 	MSGCODE_GET_AUTO_ENABLED,
82*a8106818SMauro Carvalho Chehab 	MSGCODE_SET_AUTO_ENABLED,
83*a8106818SMauro Carvalho Chehab 	MSGCODE_GET_DEFAULT_LOGICAL_ADDRESS,
84*a8106818SMauro Carvalho Chehab 	MSGCODE_SET_DEFAULT_LOGICAL_ADDRESS,
85*a8106818SMauro Carvalho Chehab 	MSGCODE_GET_LOGICAL_ADDRESS_MASK,
86*a8106818SMauro Carvalho Chehab 	MSGCODE_SET_LOGICAL_ADDRESS_MASK,
87*a8106818SMauro Carvalho Chehab 	MSGCODE_GET_PHYSICAL_ADDRESS,
88*a8106818SMauro Carvalho Chehab 	MSGCODE_SET_PHYSICAL_ADDRESS,	/* 0x20 */
89*a8106818SMauro Carvalho Chehab 	MSGCODE_GET_DEVICE_TYPE,
90*a8106818SMauro Carvalho Chehab 	MSGCODE_SET_DEVICE_TYPE,
91*a8106818SMauro Carvalho Chehab 	MSGCODE_GET_HDMI_VERSION,
92*a8106818SMauro Carvalho Chehab 	MSGCODE_SET_HDMI_VERSION,
93*a8106818SMauro Carvalho Chehab 	MSGCODE_GET_OSD_NAME,
94*a8106818SMauro Carvalho Chehab 	MSGCODE_SET_OSD_NAME,
95*a8106818SMauro Carvalho Chehab 	MSGCODE_WRITE_EEPROM,
96*a8106818SMauro Carvalho Chehab 	MSGCODE_GET_ADAPTER_TYPE,	/* 0x28 */
97*a8106818SMauro Carvalho Chehab 	MSGCODE_SET_ACTIVE_SOURCE,
98*a8106818SMauro Carvalho Chehab 
99*a8106818SMauro Carvalho Chehab 	MSGCODE_FRAME_EOM = 0x80,
100*a8106818SMauro Carvalho Chehab 	MSGCODE_FRAME_ACK = 0x40,
101*a8106818SMauro Carvalho Chehab };
102*a8106818SMauro Carvalho Chehab 
103*a8106818SMauro Carvalho Chehab static const char * const pulse8_msgnames[] = {
104*a8106818SMauro Carvalho Chehab 	"NOTHING",
105*a8106818SMauro Carvalho Chehab 	"PING",
106*a8106818SMauro Carvalho Chehab 	"TIMEOUT_ERROR",
107*a8106818SMauro Carvalho Chehab 	"HIGH_ERROR",
108*a8106818SMauro Carvalho Chehab 	"LOW_ERROR",
109*a8106818SMauro Carvalho Chehab 	"FRAME_START",
110*a8106818SMauro Carvalho Chehab 	"FRAME_DATA",
111*a8106818SMauro Carvalho Chehab 	"RECEIVE_FAILED",
112*a8106818SMauro Carvalho Chehab 	"COMMAND_ACCEPTED",
113*a8106818SMauro Carvalho Chehab 	"COMMAND_REJECTED",
114*a8106818SMauro Carvalho Chehab 	"SET_ACK_MASK",
115*a8106818SMauro Carvalho Chehab 	"TRANSMIT",
116*a8106818SMauro Carvalho Chehab 	"TRANSMIT_EOM",
117*a8106818SMauro Carvalho Chehab 	"TRANSMIT_IDLETIME",
118*a8106818SMauro Carvalho Chehab 	"TRANSMIT_ACK_POLARITY",
119*a8106818SMauro Carvalho Chehab 	"TRANSMIT_LINE_TIMEOUT",
120*a8106818SMauro Carvalho Chehab 	"TRANSMIT_SUCCEEDED",
121*a8106818SMauro Carvalho Chehab 	"TRANSMIT_FAILED_LINE",
122*a8106818SMauro Carvalho Chehab 	"TRANSMIT_FAILED_ACK",
123*a8106818SMauro Carvalho Chehab 	"TRANSMIT_FAILED_TIMEOUT_DATA",
124*a8106818SMauro Carvalho Chehab 	"TRANSMIT_FAILED_TIMEOUT_LINE",
125*a8106818SMauro Carvalho Chehab 	"FIRMWARE_VERSION",
126*a8106818SMauro Carvalho Chehab 	"START_BOOTLOADER",
127*a8106818SMauro Carvalho Chehab 	"GET_BUILDDATE",
128*a8106818SMauro Carvalho Chehab 	"SET_CONTROLLED",
129*a8106818SMauro Carvalho Chehab 	"GET_AUTO_ENABLED",
130*a8106818SMauro Carvalho Chehab 	"SET_AUTO_ENABLED",
131*a8106818SMauro Carvalho Chehab 	"GET_DEFAULT_LOGICAL_ADDRESS",
132*a8106818SMauro Carvalho Chehab 	"SET_DEFAULT_LOGICAL_ADDRESS",
133*a8106818SMauro Carvalho Chehab 	"GET_LOGICAL_ADDRESS_MASK",
134*a8106818SMauro Carvalho Chehab 	"SET_LOGICAL_ADDRESS_MASK",
135*a8106818SMauro Carvalho Chehab 	"GET_PHYSICAL_ADDRESS",
136*a8106818SMauro Carvalho Chehab 	"SET_PHYSICAL_ADDRESS",
137*a8106818SMauro Carvalho Chehab 	"GET_DEVICE_TYPE",
138*a8106818SMauro Carvalho Chehab 	"SET_DEVICE_TYPE",
139*a8106818SMauro Carvalho Chehab 	"GET_HDMI_VERSION",
140*a8106818SMauro Carvalho Chehab 	"SET_HDMI_VERSION",
141*a8106818SMauro Carvalho Chehab 	"GET_OSD_NAME",
142*a8106818SMauro Carvalho Chehab 	"SET_OSD_NAME",
143*a8106818SMauro Carvalho Chehab 	"WRITE_EEPROM",
144*a8106818SMauro Carvalho Chehab 	"GET_ADAPTER_TYPE",
145*a8106818SMauro Carvalho Chehab 	"SET_ACTIVE_SOURCE",
146*a8106818SMauro Carvalho Chehab };
147*a8106818SMauro Carvalho Chehab 
148*a8106818SMauro Carvalho Chehab static const char *pulse8_msgname(u8 cmd)
149*a8106818SMauro Carvalho Chehab {
150*a8106818SMauro Carvalho Chehab 	static char unknown_msg[5];
151*a8106818SMauro Carvalho Chehab 
152*a8106818SMauro Carvalho Chehab 	if ((cmd & 0x3f) < ARRAY_SIZE(pulse8_msgnames))
153*a8106818SMauro Carvalho Chehab 		return pulse8_msgnames[cmd & 0x3f];
154*a8106818SMauro Carvalho Chehab 	snprintf(unknown_msg, sizeof(unknown_msg), "0x%02x", cmd);
155*a8106818SMauro Carvalho Chehab 	return unknown_msg;
156*a8106818SMauro Carvalho Chehab }
157*a8106818SMauro Carvalho Chehab 
158*a8106818SMauro Carvalho Chehab #define MSGSTART	0xff
159*a8106818SMauro Carvalho Chehab #define MSGEND		0xfe
160*a8106818SMauro Carvalho Chehab #define MSGESC		0xfd
161*a8106818SMauro Carvalho Chehab #define MSGOFFSET	3
162*a8106818SMauro Carvalho Chehab 
163*a8106818SMauro Carvalho Chehab #define DATA_SIZE 256
164*a8106818SMauro Carvalho Chehab 
165*a8106818SMauro Carvalho Chehab #define PING_PERIOD	(15 * HZ)
166*a8106818SMauro Carvalho Chehab 
167*a8106818SMauro Carvalho Chehab #define NUM_MSGS 8
168*a8106818SMauro Carvalho Chehab 
169*a8106818SMauro Carvalho Chehab struct pulse8 {
170*a8106818SMauro Carvalho Chehab 	struct device *dev;
171*a8106818SMauro Carvalho Chehab 	struct serio *serio;
172*a8106818SMauro Carvalho Chehab 	struct cec_adapter *adap;
173*a8106818SMauro Carvalho Chehab 	unsigned int vers;
174*a8106818SMauro Carvalho Chehab 
175*a8106818SMauro Carvalho Chehab 	struct delayed_work ping_eeprom_work;
176*a8106818SMauro Carvalho Chehab 
177*a8106818SMauro Carvalho Chehab 	struct work_struct irq_work;
178*a8106818SMauro Carvalho Chehab 	struct cec_msg rx_msg[NUM_MSGS];
179*a8106818SMauro Carvalho Chehab 	unsigned int rx_msg_cur_idx, rx_msg_num;
180*a8106818SMauro Carvalho Chehab 	/* protect rx_msg_cur_idx and rx_msg_num */
181*a8106818SMauro Carvalho Chehab 	spinlock_t msg_lock;
182*a8106818SMauro Carvalho Chehab 	u8 new_rx_msg[CEC_MAX_MSG_SIZE];
183*a8106818SMauro Carvalho Chehab 	u8 new_rx_msg_len;
184*a8106818SMauro Carvalho Chehab 
185*a8106818SMauro Carvalho Chehab 	struct work_struct tx_work;
186*a8106818SMauro Carvalho Chehab 	u32 tx_done_status;
187*a8106818SMauro Carvalho Chehab 	u32 tx_signal_free_time;
188*a8106818SMauro Carvalho Chehab 	struct cec_msg tx_msg;
189*a8106818SMauro Carvalho Chehab 	bool tx_msg_is_bcast;
190*a8106818SMauro Carvalho Chehab 
191*a8106818SMauro Carvalho Chehab 	struct completion cmd_done;
192*a8106818SMauro Carvalho Chehab 	u8 data[DATA_SIZE];
193*a8106818SMauro Carvalho Chehab 	unsigned int len;
194*a8106818SMauro Carvalho Chehab 	u8 buf[DATA_SIZE];
195*a8106818SMauro Carvalho Chehab 	unsigned int idx;
196*a8106818SMauro Carvalho Chehab 	bool escape;
197*a8106818SMauro Carvalho Chehab 	bool started;
198*a8106818SMauro Carvalho Chehab 
199*a8106818SMauro Carvalho Chehab 	/* locks access to the adapter */
200*a8106818SMauro Carvalho Chehab 	struct mutex lock;
201*a8106818SMauro Carvalho Chehab 	bool config_pending;
202*a8106818SMauro Carvalho Chehab 	bool restoring_config;
203*a8106818SMauro Carvalho Chehab 	bool autonomous;
204*a8106818SMauro Carvalho Chehab };
205*a8106818SMauro Carvalho Chehab 
206*a8106818SMauro Carvalho Chehab static int pulse8_send(struct serio *serio, const u8 *command, u8 cmd_len)
207*a8106818SMauro Carvalho Chehab {
208*a8106818SMauro Carvalho Chehab 	int err = 0;
209*a8106818SMauro Carvalho Chehab 
210*a8106818SMauro Carvalho Chehab 	err = serio_write(serio, MSGSTART);
211*a8106818SMauro Carvalho Chehab 	if (err)
212*a8106818SMauro Carvalho Chehab 		return err;
213*a8106818SMauro Carvalho Chehab 	for (; !err && cmd_len; command++, cmd_len--) {
214*a8106818SMauro Carvalho Chehab 		if (*command >= MSGESC) {
215*a8106818SMauro Carvalho Chehab 			err = serio_write(serio, MSGESC);
216*a8106818SMauro Carvalho Chehab 			if (!err)
217*a8106818SMauro Carvalho Chehab 				err = serio_write(serio, *command - MSGOFFSET);
218*a8106818SMauro Carvalho Chehab 		} else {
219*a8106818SMauro Carvalho Chehab 			err = serio_write(serio, *command);
220*a8106818SMauro Carvalho Chehab 		}
221*a8106818SMauro Carvalho Chehab 	}
222*a8106818SMauro Carvalho Chehab 	if (!err)
223*a8106818SMauro Carvalho Chehab 		err = serio_write(serio, MSGEND);
224*a8106818SMauro Carvalho Chehab 
225*a8106818SMauro Carvalho Chehab 	return err;
226*a8106818SMauro Carvalho Chehab }
227*a8106818SMauro Carvalho Chehab 
228*a8106818SMauro Carvalho Chehab static int pulse8_send_and_wait_once(struct pulse8 *pulse8,
229*a8106818SMauro Carvalho Chehab 				     const u8 *cmd, u8 cmd_len,
230*a8106818SMauro Carvalho Chehab 				     u8 response, u8 size)
231*a8106818SMauro Carvalho Chehab {
232*a8106818SMauro Carvalho Chehab 	int err;
233*a8106818SMauro Carvalho Chehab 
234*a8106818SMauro Carvalho Chehab 	if (debug > 1)
235*a8106818SMauro Carvalho Chehab 		dev_info(pulse8->dev, "transmit %s: %*ph\n",
236*a8106818SMauro Carvalho Chehab 			 pulse8_msgname(cmd[0]), cmd_len, cmd);
237*a8106818SMauro Carvalho Chehab 	init_completion(&pulse8->cmd_done);
238*a8106818SMauro Carvalho Chehab 
239*a8106818SMauro Carvalho Chehab 	err = pulse8_send(pulse8->serio, cmd, cmd_len);
240*a8106818SMauro Carvalho Chehab 	if (err)
241*a8106818SMauro Carvalho Chehab 		return err;
242*a8106818SMauro Carvalho Chehab 
243*a8106818SMauro Carvalho Chehab 	if (!wait_for_completion_timeout(&pulse8->cmd_done, HZ))
244*a8106818SMauro Carvalho Chehab 		return -ETIMEDOUT;
245*a8106818SMauro Carvalho Chehab 	if ((pulse8->data[0] & 0x3f) == MSGCODE_COMMAND_REJECTED &&
246*a8106818SMauro Carvalho Chehab 	    cmd[0] != MSGCODE_SET_CONTROLLED &&
247*a8106818SMauro Carvalho Chehab 	    cmd[0] != MSGCODE_SET_AUTO_ENABLED &&
248*a8106818SMauro Carvalho Chehab 	    cmd[0] != MSGCODE_GET_BUILDDATE)
249*a8106818SMauro Carvalho Chehab 		return -ENOTTY;
250*a8106818SMauro Carvalho Chehab 	if (response &&
251*a8106818SMauro Carvalho Chehab 	    ((pulse8->data[0] & 0x3f) != response || pulse8->len < size + 1)) {
252*a8106818SMauro Carvalho Chehab 		dev_info(pulse8->dev, "transmit %s failed with %s\n",
253*a8106818SMauro Carvalho Chehab 			 pulse8_msgname(cmd[0]),
254*a8106818SMauro Carvalho Chehab 			 pulse8_msgname(pulse8->data[0]));
255*a8106818SMauro Carvalho Chehab 		return -EIO;
256*a8106818SMauro Carvalho Chehab 	}
257*a8106818SMauro Carvalho Chehab 	return 0;
258*a8106818SMauro Carvalho Chehab }
259*a8106818SMauro Carvalho Chehab 
260*a8106818SMauro Carvalho Chehab static int pulse8_send_and_wait(struct pulse8 *pulse8,
261*a8106818SMauro Carvalho Chehab 				const u8 *cmd, u8 cmd_len, u8 response, u8 size)
262*a8106818SMauro Carvalho Chehab {
263*a8106818SMauro Carvalho Chehab 	u8 cmd_sc[2];
264*a8106818SMauro Carvalho Chehab 	int err;
265*a8106818SMauro Carvalho Chehab 
266*a8106818SMauro Carvalho Chehab 	err = pulse8_send_and_wait_once(pulse8, cmd, cmd_len, response, size);
267*a8106818SMauro Carvalho Chehab 	if (err != -ENOTTY)
268*a8106818SMauro Carvalho Chehab 		return err;
269*a8106818SMauro Carvalho Chehab 
270*a8106818SMauro Carvalho Chehab 	cmd_sc[0] = MSGCODE_SET_CONTROLLED;
271*a8106818SMauro Carvalho Chehab 	cmd_sc[1] = 1;
272*a8106818SMauro Carvalho Chehab 	err = pulse8_send_and_wait_once(pulse8, cmd_sc, 2,
273*a8106818SMauro Carvalho Chehab 					MSGCODE_COMMAND_ACCEPTED, 1);
274*a8106818SMauro Carvalho Chehab 	if (!err)
275*a8106818SMauro Carvalho Chehab 		err = pulse8_send_and_wait_once(pulse8, cmd, cmd_len,
276*a8106818SMauro Carvalho Chehab 						response, size);
277*a8106818SMauro Carvalho Chehab 	return err == -ENOTTY ? -EIO : err;
278*a8106818SMauro Carvalho Chehab }
279*a8106818SMauro Carvalho Chehab 
280*a8106818SMauro Carvalho Chehab static void pulse8_tx_work_handler(struct work_struct *work)
281*a8106818SMauro Carvalho Chehab {
282*a8106818SMauro Carvalho Chehab 	struct pulse8 *pulse8 = container_of(work, struct pulse8, tx_work);
283*a8106818SMauro Carvalho Chehab 	struct cec_msg *msg = &pulse8->tx_msg;
284*a8106818SMauro Carvalho Chehab 	unsigned int i;
285*a8106818SMauro Carvalho Chehab 	u8 cmd[2];
286*a8106818SMauro Carvalho Chehab 	int err;
287*a8106818SMauro Carvalho Chehab 
288*a8106818SMauro Carvalho Chehab 	if (msg->len == 0)
289*a8106818SMauro Carvalho Chehab 		return;
290*a8106818SMauro Carvalho Chehab 
291*a8106818SMauro Carvalho Chehab 	mutex_lock(&pulse8->lock);
292*a8106818SMauro Carvalho Chehab 	cmd[0] = MSGCODE_TRANSMIT_IDLETIME;
293*a8106818SMauro Carvalho Chehab 	cmd[1] = pulse8->tx_signal_free_time;
294*a8106818SMauro Carvalho Chehab 	err = pulse8_send_and_wait(pulse8, cmd, 2,
295*a8106818SMauro Carvalho Chehab 				   MSGCODE_COMMAND_ACCEPTED, 1);
296*a8106818SMauro Carvalho Chehab 	cmd[0] = MSGCODE_TRANSMIT_ACK_POLARITY;
297*a8106818SMauro Carvalho Chehab 	cmd[1] = cec_msg_is_broadcast(msg);
298*a8106818SMauro Carvalho Chehab 	pulse8->tx_msg_is_bcast = cec_msg_is_broadcast(msg);
299*a8106818SMauro Carvalho Chehab 	if (!err)
300*a8106818SMauro Carvalho Chehab 		err = pulse8_send_and_wait(pulse8, cmd, 2,
301*a8106818SMauro Carvalho Chehab 					   MSGCODE_COMMAND_ACCEPTED, 1);
302*a8106818SMauro Carvalho Chehab 	cmd[0] = msg->len == 1 ? MSGCODE_TRANSMIT_EOM : MSGCODE_TRANSMIT;
303*a8106818SMauro Carvalho Chehab 	cmd[1] = msg->msg[0];
304*a8106818SMauro Carvalho Chehab 	if (!err)
305*a8106818SMauro Carvalho Chehab 		err = pulse8_send_and_wait(pulse8, cmd, 2,
306*a8106818SMauro Carvalho Chehab 					   MSGCODE_COMMAND_ACCEPTED, 1);
307*a8106818SMauro Carvalho Chehab 	if (!err && msg->len > 1) {
308*a8106818SMauro Carvalho Chehab 		for (i = 1; !err && i < msg->len; i++) {
309*a8106818SMauro Carvalho Chehab 			cmd[0] = ((i == msg->len - 1)) ?
310*a8106818SMauro Carvalho Chehab 				MSGCODE_TRANSMIT_EOM : MSGCODE_TRANSMIT;
311*a8106818SMauro Carvalho Chehab 			cmd[1] = msg->msg[i];
312*a8106818SMauro Carvalho Chehab 			err = pulse8_send_and_wait(pulse8, cmd, 2,
313*a8106818SMauro Carvalho Chehab 						   MSGCODE_COMMAND_ACCEPTED, 1);
314*a8106818SMauro Carvalho Chehab 		}
315*a8106818SMauro Carvalho Chehab 	}
316*a8106818SMauro Carvalho Chehab 	if (err && debug)
317*a8106818SMauro Carvalho Chehab 		dev_info(pulse8->dev, "%s(0x%02x) failed with error %d for msg %*ph\n",
318*a8106818SMauro Carvalho Chehab 			 pulse8_msgname(cmd[0]), cmd[1],
319*a8106818SMauro Carvalho Chehab 			 err, msg->len, msg->msg);
320*a8106818SMauro Carvalho Chehab 	msg->len = 0;
321*a8106818SMauro Carvalho Chehab 	mutex_unlock(&pulse8->lock);
322*a8106818SMauro Carvalho Chehab 	if (err)
323*a8106818SMauro Carvalho Chehab 		cec_transmit_attempt_done(pulse8->adap, CEC_TX_STATUS_ERROR);
324*a8106818SMauro Carvalho Chehab }
325*a8106818SMauro Carvalho Chehab 
326*a8106818SMauro Carvalho Chehab static void pulse8_irq_work_handler(struct work_struct *work)
327*a8106818SMauro Carvalho Chehab {
328*a8106818SMauro Carvalho Chehab 	struct pulse8 *pulse8 =
329*a8106818SMauro Carvalho Chehab 		container_of(work, struct pulse8, irq_work);
330*a8106818SMauro Carvalho Chehab 	unsigned long flags;
331*a8106818SMauro Carvalho Chehab 	u32 status;
332*a8106818SMauro Carvalho Chehab 
333*a8106818SMauro Carvalho Chehab 	spin_lock_irqsave(&pulse8->msg_lock, flags);
334*a8106818SMauro Carvalho Chehab 	while (pulse8->rx_msg_num) {
335*a8106818SMauro Carvalho Chehab 		spin_unlock_irqrestore(&pulse8->msg_lock, flags);
336*a8106818SMauro Carvalho Chehab 		if (debug)
337*a8106818SMauro Carvalho Chehab 			dev_info(pulse8->dev, "adap received %*ph\n",
338*a8106818SMauro Carvalho Chehab 				 pulse8->rx_msg[pulse8->rx_msg_cur_idx].len,
339*a8106818SMauro Carvalho Chehab 				 pulse8->rx_msg[pulse8->rx_msg_cur_idx].msg);
340*a8106818SMauro Carvalho Chehab 		cec_received_msg(pulse8->adap,
341*a8106818SMauro Carvalho Chehab 				 &pulse8->rx_msg[pulse8->rx_msg_cur_idx]);
342*a8106818SMauro Carvalho Chehab 		spin_lock_irqsave(&pulse8->msg_lock, flags);
343*a8106818SMauro Carvalho Chehab 		if (pulse8->rx_msg_num)
344*a8106818SMauro Carvalho Chehab 			pulse8->rx_msg_num--;
345*a8106818SMauro Carvalho Chehab 		pulse8->rx_msg_cur_idx =
346*a8106818SMauro Carvalho Chehab 			(pulse8->rx_msg_cur_idx + 1) % NUM_MSGS;
347*a8106818SMauro Carvalho Chehab 	}
348*a8106818SMauro Carvalho Chehab 	spin_unlock_irqrestore(&pulse8->msg_lock, flags);
349*a8106818SMauro Carvalho Chehab 
350*a8106818SMauro Carvalho Chehab 	mutex_lock(&pulse8->lock);
351*a8106818SMauro Carvalho Chehab 	status = pulse8->tx_done_status;
352*a8106818SMauro Carvalho Chehab 	pulse8->tx_done_status = 0;
353*a8106818SMauro Carvalho Chehab 	mutex_unlock(&pulse8->lock);
354*a8106818SMauro Carvalho Chehab 	if (status)
355*a8106818SMauro Carvalho Chehab 		cec_transmit_attempt_done(pulse8->adap, status);
356*a8106818SMauro Carvalho Chehab }
357*a8106818SMauro Carvalho Chehab 
358*a8106818SMauro Carvalho Chehab static irqreturn_t pulse8_interrupt(struct serio *serio, unsigned char data,
359*a8106818SMauro Carvalho Chehab 				    unsigned int flags)
360*a8106818SMauro Carvalho Chehab {
361*a8106818SMauro Carvalho Chehab 	struct pulse8 *pulse8 = serio_get_drvdata(serio);
362*a8106818SMauro Carvalho Chehab 	unsigned long irq_flags;
363*a8106818SMauro Carvalho Chehab 	unsigned int idx;
364*a8106818SMauro Carvalho Chehab 
365*a8106818SMauro Carvalho Chehab 	if (!pulse8->started && data != MSGSTART)
366*a8106818SMauro Carvalho Chehab 		return IRQ_HANDLED;
367*a8106818SMauro Carvalho Chehab 	if (data == MSGESC) {
368*a8106818SMauro Carvalho Chehab 		pulse8->escape = true;
369*a8106818SMauro Carvalho Chehab 		return IRQ_HANDLED;
370*a8106818SMauro Carvalho Chehab 	}
371*a8106818SMauro Carvalho Chehab 	if (pulse8->escape) {
372*a8106818SMauro Carvalho Chehab 		data += MSGOFFSET;
373*a8106818SMauro Carvalho Chehab 		pulse8->escape = false;
374*a8106818SMauro Carvalho Chehab 	} else if (data == MSGEND) {
375*a8106818SMauro Carvalho Chehab 		u8 msgcode = pulse8->buf[0];
376*a8106818SMauro Carvalho Chehab 
377*a8106818SMauro Carvalho Chehab 		if (debug > 1)
378*a8106818SMauro Carvalho Chehab 			dev_info(pulse8->dev, "received %s: %*ph\n",
379*a8106818SMauro Carvalho Chehab 				 pulse8_msgname(msgcode),
380*a8106818SMauro Carvalho Chehab 				 pulse8->idx, pulse8->buf);
381*a8106818SMauro Carvalho Chehab 		switch (msgcode & 0x3f) {
382*a8106818SMauro Carvalho Chehab 		case MSGCODE_FRAME_START:
383*a8106818SMauro Carvalho Chehab 			/*
384*a8106818SMauro Carvalho Chehab 			 * Test if we are receiving a new msg when a previous
385*a8106818SMauro Carvalho Chehab 			 * message is still pending.
386*a8106818SMauro Carvalho Chehab 			 */
387*a8106818SMauro Carvalho Chehab 			if (!(msgcode & MSGCODE_FRAME_EOM)) {
388*a8106818SMauro Carvalho Chehab 				pulse8->new_rx_msg_len = 1;
389*a8106818SMauro Carvalho Chehab 				pulse8->new_rx_msg[0] = pulse8->buf[1];
390*a8106818SMauro Carvalho Chehab 				break;
391*a8106818SMauro Carvalho Chehab 			}
392*a8106818SMauro Carvalho Chehab 			/* fall through */
393*a8106818SMauro Carvalho Chehab 		case MSGCODE_FRAME_DATA:
394*a8106818SMauro Carvalho Chehab 			if (pulse8->new_rx_msg_len < CEC_MAX_MSG_SIZE)
395*a8106818SMauro Carvalho Chehab 				pulse8->new_rx_msg[pulse8->new_rx_msg_len++] =
396*a8106818SMauro Carvalho Chehab 					pulse8->buf[1];
397*a8106818SMauro Carvalho Chehab 			if (!(msgcode & MSGCODE_FRAME_EOM))
398*a8106818SMauro Carvalho Chehab 				break;
399*a8106818SMauro Carvalho Chehab 
400*a8106818SMauro Carvalho Chehab 			spin_lock_irqsave(&pulse8->msg_lock, irq_flags);
401*a8106818SMauro Carvalho Chehab 			idx = (pulse8->rx_msg_cur_idx + pulse8->rx_msg_num) %
402*a8106818SMauro Carvalho Chehab 				NUM_MSGS;
403*a8106818SMauro Carvalho Chehab 			if (pulse8->rx_msg_num == NUM_MSGS) {
404*a8106818SMauro Carvalho Chehab 				dev_warn(pulse8->dev,
405*a8106818SMauro Carvalho Chehab 					 "message queue is full, dropping %*ph\n",
406*a8106818SMauro Carvalho Chehab 					 pulse8->new_rx_msg_len,
407*a8106818SMauro Carvalho Chehab 					 pulse8->new_rx_msg);
408*a8106818SMauro Carvalho Chehab 				spin_unlock_irqrestore(&pulse8->msg_lock,
409*a8106818SMauro Carvalho Chehab 						       irq_flags);
410*a8106818SMauro Carvalho Chehab 				pulse8->new_rx_msg_len = 0;
411*a8106818SMauro Carvalho Chehab 				break;
412*a8106818SMauro Carvalho Chehab 			}
413*a8106818SMauro Carvalho Chehab 			pulse8->rx_msg_num++;
414*a8106818SMauro Carvalho Chehab 			memcpy(pulse8->rx_msg[idx].msg, pulse8->new_rx_msg,
415*a8106818SMauro Carvalho Chehab 			       pulse8->new_rx_msg_len);
416*a8106818SMauro Carvalho Chehab 			pulse8->rx_msg[idx].len = pulse8->new_rx_msg_len;
417*a8106818SMauro Carvalho Chehab 			spin_unlock_irqrestore(&pulse8->msg_lock, irq_flags);
418*a8106818SMauro Carvalho Chehab 			schedule_work(&pulse8->irq_work);
419*a8106818SMauro Carvalho Chehab 			pulse8->new_rx_msg_len = 0;
420*a8106818SMauro Carvalho Chehab 			break;
421*a8106818SMauro Carvalho Chehab 		case MSGCODE_TRANSMIT_SUCCEEDED:
422*a8106818SMauro Carvalho Chehab 			WARN_ON(pulse8->tx_done_status);
423*a8106818SMauro Carvalho Chehab 			pulse8->tx_done_status = CEC_TX_STATUS_OK;
424*a8106818SMauro Carvalho Chehab 			schedule_work(&pulse8->irq_work);
425*a8106818SMauro Carvalho Chehab 			break;
426*a8106818SMauro Carvalho Chehab 		case MSGCODE_TRANSMIT_FAILED_ACK:
427*a8106818SMauro Carvalho Chehab 			/*
428*a8106818SMauro Carvalho Chehab 			 * A NACK for a broadcast message makes no sense, these
429*a8106818SMauro Carvalho Chehab 			 * seem to be spurious messages and are skipped.
430*a8106818SMauro Carvalho Chehab 			 */
431*a8106818SMauro Carvalho Chehab 			if (pulse8->tx_msg_is_bcast)
432*a8106818SMauro Carvalho Chehab 				break;
433*a8106818SMauro Carvalho Chehab 			WARN_ON(pulse8->tx_done_status);
434*a8106818SMauro Carvalho Chehab 			pulse8->tx_done_status = CEC_TX_STATUS_NACK;
435*a8106818SMauro Carvalho Chehab 			schedule_work(&pulse8->irq_work);
436*a8106818SMauro Carvalho Chehab 			break;
437*a8106818SMauro Carvalho Chehab 		case MSGCODE_TRANSMIT_FAILED_LINE:
438*a8106818SMauro Carvalho Chehab 		case MSGCODE_TRANSMIT_FAILED_TIMEOUT_DATA:
439*a8106818SMauro Carvalho Chehab 		case MSGCODE_TRANSMIT_FAILED_TIMEOUT_LINE:
440*a8106818SMauro Carvalho Chehab 			WARN_ON(pulse8->tx_done_status);
441*a8106818SMauro Carvalho Chehab 			pulse8->tx_done_status = CEC_TX_STATUS_ERROR;
442*a8106818SMauro Carvalho Chehab 			schedule_work(&pulse8->irq_work);
443*a8106818SMauro Carvalho Chehab 			break;
444*a8106818SMauro Carvalho Chehab 		case MSGCODE_HIGH_ERROR:
445*a8106818SMauro Carvalho Chehab 		case MSGCODE_LOW_ERROR:
446*a8106818SMauro Carvalho Chehab 		case MSGCODE_RECEIVE_FAILED:
447*a8106818SMauro Carvalho Chehab 		case MSGCODE_TIMEOUT_ERROR:
448*a8106818SMauro Carvalho Chehab 			pulse8->new_rx_msg_len = 0;
449*a8106818SMauro Carvalho Chehab 			break;
450*a8106818SMauro Carvalho Chehab 		case MSGCODE_COMMAND_ACCEPTED:
451*a8106818SMauro Carvalho Chehab 		case MSGCODE_COMMAND_REJECTED:
452*a8106818SMauro Carvalho Chehab 		default:
453*a8106818SMauro Carvalho Chehab 			if (pulse8->idx == 0)
454*a8106818SMauro Carvalho Chehab 				break;
455*a8106818SMauro Carvalho Chehab 			memcpy(pulse8->data, pulse8->buf, pulse8->idx);
456*a8106818SMauro Carvalho Chehab 			pulse8->len = pulse8->idx;
457*a8106818SMauro Carvalho Chehab 			complete(&pulse8->cmd_done);
458*a8106818SMauro Carvalho Chehab 			break;
459*a8106818SMauro Carvalho Chehab 		}
460*a8106818SMauro Carvalho Chehab 		pulse8->idx = 0;
461*a8106818SMauro Carvalho Chehab 		pulse8->started = false;
462*a8106818SMauro Carvalho Chehab 		return IRQ_HANDLED;
463*a8106818SMauro Carvalho Chehab 	} else if (data == MSGSTART) {
464*a8106818SMauro Carvalho Chehab 		pulse8->idx = 0;
465*a8106818SMauro Carvalho Chehab 		pulse8->started = true;
466*a8106818SMauro Carvalho Chehab 		return IRQ_HANDLED;
467*a8106818SMauro Carvalho Chehab 	}
468*a8106818SMauro Carvalho Chehab 
469*a8106818SMauro Carvalho Chehab 	if (pulse8->idx >= DATA_SIZE) {
470*a8106818SMauro Carvalho Chehab 		dev_dbg(pulse8->dev,
471*a8106818SMauro Carvalho Chehab 			"throwing away %d bytes of garbage\n", pulse8->idx);
472*a8106818SMauro Carvalho Chehab 		pulse8->idx = 0;
473*a8106818SMauro Carvalho Chehab 	}
474*a8106818SMauro Carvalho Chehab 	pulse8->buf[pulse8->idx++] = data;
475*a8106818SMauro Carvalho Chehab 	return IRQ_HANDLED;
476*a8106818SMauro Carvalho Chehab }
477*a8106818SMauro Carvalho Chehab 
478*a8106818SMauro Carvalho Chehab static int pulse8_cec_adap_enable(struct cec_adapter *adap, bool enable)
479*a8106818SMauro Carvalho Chehab {
480*a8106818SMauro Carvalho Chehab 	struct pulse8 *pulse8 = cec_get_drvdata(adap);
481*a8106818SMauro Carvalho Chehab 	u8 cmd[16];
482*a8106818SMauro Carvalho Chehab 	int err;
483*a8106818SMauro Carvalho Chehab 
484*a8106818SMauro Carvalho Chehab 	mutex_lock(&pulse8->lock);
485*a8106818SMauro Carvalho Chehab 	cmd[0] = MSGCODE_SET_CONTROLLED;
486*a8106818SMauro Carvalho Chehab 	cmd[1] = enable;
487*a8106818SMauro Carvalho Chehab 	err = pulse8_send_and_wait(pulse8, cmd, 2,
488*a8106818SMauro Carvalho Chehab 				   MSGCODE_COMMAND_ACCEPTED, 1);
489*a8106818SMauro Carvalho Chehab 	if (!enable) {
490*a8106818SMauro Carvalho Chehab 		pulse8->rx_msg_num = 0;
491*a8106818SMauro Carvalho Chehab 		pulse8->tx_done_status = 0;
492*a8106818SMauro Carvalho Chehab 	}
493*a8106818SMauro Carvalho Chehab 	mutex_unlock(&pulse8->lock);
494*a8106818SMauro Carvalho Chehab 	return enable ? err : 0;
495*a8106818SMauro Carvalho Chehab }
496*a8106818SMauro Carvalho Chehab 
497*a8106818SMauro Carvalho Chehab static int pulse8_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr)
498*a8106818SMauro Carvalho Chehab {
499*a8106818SMauro Carvalho Chehab 	struct pulse8 *pulse8 = cec_get_drvdata(adap);
500*a8106818SMauro Carvalho Chehab 	u16 mask = 0;
501*a8106818SMauro Carvalho Chehab 	u16 pa = adap->phys_addr;
502*a8106818SMauro Carvalho Chehab 	u8 cmd[16];
503*a8106818SMauro Carvalho Chehab 	int err = 0;
504*a8106818SMauro Carvalho Chehab 
505*a8106818SMauro Carvalho Chehab 	mutex_lock(&pulse8->lock);
506*a8106818SMauro Carvalho Chehab 	if (log_addr != CEC_LOG_ADDR_INVALID)
507*a8106818SMauro Carvalho Chehab 		mask = 1 << log_addr;
508*a8106818SMauro Carvalho Chehab 	cmd[0] = MSGCODE_SET_ACK_MASK;
509*a8106818SMauro Carvalho Chehab 	cmd[1] = mask >> 8;
510*a8106818SMauro Carvalho Chehab 	cmd[2] = mask & 0xff;
511*a8106818SMauro Carvalho Chehab 	err = pulse8_send_and_wait(pulse8, cmd, 3,
512*a8106818SMauro Carvalho Chehab 				   MSGCODE_COMMAND_ACCEPTED, 0);
513*a8106818SMauro Carvalho Chehab 	if ((err && mask != 0) || pulse8->restoring_config)
514*a8106818SMauro Carvalho Chehab 		goto unlock;
515*a8106818SMauro Carvalho Chehab 
516*a8106818SMauro Carvalho Chehab 	cmd[0] = MSGCODE_SET_AUTO_ENABLED;
517*a8106818SMauro Carvalho Chehab 	cmd[1] = log_addr == CEC_LOG_ADDR_INVALID ? 0 : 1;
518*a8106818SMauro Carvalho Chehab 	err = pulse8_send_and_wait(pulse8, cmd, 2,
519*a8106818SMauro Carvalho Chehab 				   MSGCODE_COMMAND_ACCEPTED, 0);
520*a8106818SMauro Carvalho Chehab 	if (err)
521*a8106818SMauro Carvalho Chehab 		goto unlock;
522*a8106818SMauro Carvalho Chehab 	pulse8->autonomous = cmd[1];
523*a8106818SMauro Carvalho Chehab 	if (log_addr == CEC_LOG_ADDR_INVALID)
524*a8106818SMauro Carvalho Chehab 		goto unlock;
525*a8106818SMauro Carvalho Chehab 
526*a8106818SMauro Carvalho Chehab 	cmd[0] = MSGCODE_SET_DEVICE_TYPE;
527*a8106818SMauro Carvalho Chehab 	cmd[1] = adap->log_addrs.primary_device_type[0];
528*a8106818SMauro Carvalho Chehab 	err = pulse8_send_and_wait(pulse8, cmd, 2,
529*a8106818SMauro Carvalho Chehab 				   MSGCODE_COMMAND_ACCEPTED, 0);
530*a8106818SMauro Carvalho Chehab 	if (err)
531*a8106818SMauro Carvalho Chehab 		goto unlock;
532*a8106818SMauro Carvalho Chehab 
533*a8106818SMauro Carvalho Chehab 	switch (adap->log_addrs.primary_device_type[0]) {
534*a8106818SMauro Carvalho Chehab 	case CEC_OP_PRIM_DEVTYPE_TV:
535*a8106818SMauro Carvalho Chehab 		mask = CEC_LOG_ADDR_MASK_TV;
536*a8106818SMauro Carvalho Chehab 		break;
537*a8106818SMauro Carvalho Chehab 	case CEC_OP_PRIM_DEVTYPE_RECORD:
538*a8106818SMauro Carvalho Chehab 		mask = CEC_LOG_ADDR_MASK_RECORD;
539*a8106818SMauro Carvalho Chehab 		break;
540*a8106818SMauro Carvalho Chehab 	case CEC_OP_PRIM_DEVTYPE_TUNER:
541*a8106818SMauro Carvalho Chehab 		mask = CEC_LOG_ADDR_MASK_TUNER;
542*a8106818SMauro Carvalho Chehab 		break;
543*a8106818SMauro Carvalho Chehab 	case CEC_OP_PRIM_DEVTYPE_PLAYBACK:
544*a8106818SMauro Carvalho Chehab 		mask = CEC_LOG_ADDR_MASK_PLAYBACK;
545*a8106818SMauro Carvalho Chehab 		break;
546*a8106818SMauro Carvalho Chehab 	case CEC_OP_PRIM_DEVTYPE_AUDIOSYSTEM:
547*a8106818SMauro Carvalho Chehab 		mask = CEC_LOG_ADDR_MASK_AUDIOSYSTEM;
548*a8106818SMauro Carvalho Chehab 		break;
549*a8106818SMauro Carvalho Chehab 	case CEC_OP_PRIM_DEVTYPE_SWITCH:
550*a8106818SMauro Carvalho Chehab 		mask = CEC_LOG_ADDR_MASK_UNREGISTERED;
551*a8106818SMauro Carvalho Chehab 		break;
552*a8106818SMauro Carvalho Chehab 	case CEC_OP_PRIM_DEVTYPE_PROCESSOR:
553*a8106818SMauro Carvalho Chehab 		mask = CEC_LOG_ADDR_MASK_SPECIFIC;
554*a8106818SMauro Carvalho Chehab 		break;
555*a8106818SMauro Carvalho Chehab 	default:
556*a8106818SMauro Carvalho Chehab 		mask = 0;
557*a8106818SMauro Carvalho Chehab 		break;
558*a8106818SMauro Carvalho Chehab 	}
559*a8106818SMauro Carvalho Chehab 	cmd[0] = MSGCODE_SET_LOGICAL_ADDRESS_MASK;
560*a8106818SMauro Carvalho Chehab 	cmd[1] = mask >> 8;
561*a8106818SMauro Carvalho Chehab 	cmd[2] = mask & 0xff;
562*a8106818SMauro Carvalho Chehab 	err = pulse8_send_and_wait(pulse8, cmd, 3,
563*a8106818SMauro Carvalho Chehab 				   MSGCODE_COMMAND_ACCEPTED, 0);
564*a8106818SMauro Carvalho Chehab 	if (err)
565*a8106818SMauro Carvalho Chehab 		goto unlock;
566*a8106818SMauro Carvalho Chehab 
567*a8106818SMauro Carvalho Chehab 	cmd[0] = MSGCODE_SET_DEFAULT_LOGICAL_ADDRESS;
568*a8106818SMauro Carvalho Chehab 	cmd[1] = log_addr;
569*a8106818SMauro Carvalho Chehab 	err = pulse8_send_and_wait(pulse8, cmd, 2,
570*a8106818SMauro Carvalho Chehab 				   MSGCODE_COMMAND_ACCEPTED, 0);
571*a8106818SMauro Carvalho Chehab 	if (err)
572*a8106818SMauro Carvalho Chehab 		goto unlock;
573*a8106818SMauro Carvalho Chehab 
574*a8106818SMauro Carvalho Chehab 	cmd[0] = MSGCODE_SET_PHYSICAL_ADDRESS;
575*a8106818SMauro Carvalho Chehab 	cmd[1] = pa >> 8;
576*a8106818SMauro Carvalho Chehab 	cmd[2] = pa & 0xff;
577*a8106818SMauro Carvalho Chehab 	err = pulse8_send_and_wait(pulse8, cmd, 3,
578*a8106818SMauro Carvalho Chehab 				   MSGCODE_COMMAND_ACCEPTED, 0);
579*a8106818SMauro Carvalho Chehab 	if (err)
580*a8106818SMauro Carvalho Chehab 		goto unlock;
581*a8106818SMauro Carvalho Chehab 
582*a8106818SMauro Carvalho Chehab 	cmd[0] = MSGCODE_SET_HDMI_VERSION;
583*a8106818SMauro Carvalho Chehab 	cmd[1] = adap->log_addrs.cec_version;
584*a8106818SMauro Carvalho Chehab 	err = pulse8_send_and_wait(pulse8, cmd, 2,
585*a8106818SMauro Carvalho Chehab 				   MSGCODE_COMMAND_ACCEPTED, 0);
586*a8106818SMauro Carvalho Chehab 	if (err)
587*a8106818SMauro Carvalho Chehab 		goto unlock;
588*a8106818SMauro Carvalho Chehab 
589*a8106818SMauro Carvalho Chehab 	if (adap->log_addrs.osd_name[0]) {
590*a8106818SMauro Carvalho Chehab 		size_t osd_len = strlen(adap->log_addrs.osd_name);
591*a8106818SMauro Carvalho Chehab 		char *osd_str = cmd + 1;
592*a8106818SMauro Carvalho Chehab 
593*a8106818SMauro Carvalho Chehab 		cmd[0] = MSGCODE_SET_OSD_NAME;
594*a8106818SMauro Carvalho Chehab 		strscpy(cmd + 1, adap->log_addrs.osd_name, sizeof(cmd) - 1);
595*a8106818SMauro Carvalho Chehab 		if (osd_len < 4) {
596*a8106818SMauro Carvalho Chehab 			memset(osd_str + osd_len, ' ', 4 - osd_len);
597*a8106818SMauro Carvalho Chehab 			osd_len = 4;
598*a8106818SMauro Carvalho Chehab 			osd_str[osd_len] = '\0';
599*a8106818SMauro Carvalho Chehab 			strscpy(adap->log_addrs.osd_name, osd_str,
600*a8106818SMauro Carvalho Chehab 				sizeof(adap->log_addrs.osd_name));
601*a8106818SMauro Carvalho Chehab 		}
602*a8106818SMauro Carvalho Chehab 		err = pulse8_send_and_wait(pulse8, cmd, 1 + osd_len,
603*a8106818SMauro Carvalho Chehab 					   MSGCODE_COMMAND_ACCEPTED, 0);
604*a8106818SMauro Carvalho Chehab 		if (err)
605*a8106818SMauro Carvalho Chehab 			goto unlock;
606*a8106818SMauro Carvalho Chehab 	}
607*a8106818SMauro Carvalho Chehab 
608*a8106818SMauro Carvalho Chehab unlock:
609*a8106818SMauro Carvalho Chehab 	if (pulse8->restoring_config)
610*a8106818SMauro Carvalho Chehab 		pulse8->restoring_config = false;
611*a8106818SMauro Carvalho Chehab 	else
612*a8106818SMauro Carvalho Chehab 		pulse8->config_pending = true;
613*a8106818SMauro Carvalho Chehab 	mutex_unlock(&pulse8->lock);
614*a8106818SMauro Carvalho Chehab 	return log_addr == CEC_LOG_ADDR_INVALID ? 0 : err;
615*a8106818SMauro Carvalho Chehab }
616*a8106818SMauro Carvalho Chehab 
617*a8106818SMauro Carvalho Chehab static int pulse8_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
618*a8106818SMauro Carvalho Chehab 				    u32 signal_free_time, struct cec_msg *msg)
619*a8106818SMauro Carvalho Chehab {
620*a8106818SMauro Carvalho Chehab 	struct pulse8 *pulse8 = cec_get_drvdata(adap);
621*a8106818SMauro Carvalho Chehab 
622*a8106818SMauro Carvalho Chehab 	pulse8->tx_msg = *msg;
623*a8106818SMauro Carvalho Chehab 	if (debug)
624*a8106818SMauro Carvalho Chehab 		dev_info(pulse8->dev, "adap transmit %*ph\n",
625*a8106818SMauro Carvalho Chehab 			 msg->len, msg->msg);
626*a8106818SMauro Carvalho Chehab 	pulse8->tx_signal_free_time = signal_free_time;
627*a8106818SMauro Carvalho Chehab 	schedule_work(&pulse8->tx_work);
628*a8106818SMauro Carvalho Chehab 	return 0;
629*a8106818SMauro Carvalho Chehab }
630*a8106818SMauro Carvalho Chehab 
631*a8106818SMauro Carvalho Chehab static void pulse8_cec_adap_free(struct cec_adapter *adap)
632*a8106818SMauro Carvalho Chehab {
633*a8106818SMauro Carvalho Chehab 	struct pulse8 *pulse8 = cec_get_drvdata(adap);
634*a8106818SMauro Carvalho Chehab 
635*a8106818SMauro Carvalho Chehab 	cancel_delayed_work_sync(&pulse8->ping_eeprom_work);
636*a8106818SMauro Carvalho Chehab 	cancel_work_sync(&pulse8->irq_work);
637*a8106818SMauro Carvalho Chehab 	cancel_work_sync(&pulse8->tx_work);
638*a8106818SMauro Carvalho Chehab 	kfree(pulse8);
639*a8106818SMauro Carvalho Chehab }
640*a8106818SMauro Carvalho Chehab 
641*a8106818SMauro Carvalho Chehab static const struct cec_adap_ops pulse8_cec_adap_ops = {
642*a8106818SMauro Carvalho Chehab 	.adap_enable = pulse8_cec_adap_enable,
643*a8106818SMauro Carvalho Chehab 	.adap_log_addr = pulse8_cec_adap_log_addr,
644*a8106818SMauro Carvalho Chehab 	.adap_transmit = pulse8_cec_adap_transmit,
645*a8106818SMauro Carvalho Chehab 	.adap_free = pulse8_cec_adap_free,
646*a8106818SMauro Carvalho Chehab };
647*a8106818SMauro Carvalho Chehab 
648*a8106818SMauro Carvalho Chehab static void pulse8_disconnect(struct serio *serio)
649*a8106818SMauro Carvalho Chehab {
650*a8106818SMauro Carvalho Chehab 	struct pulse8 *pulse8 = serio_get_drvdata(serio);
651*a8106818SMauro Carvalho Chehab 
652*a8106818SMauro Carvalho Chehab 	cec_unregister_adapter(pulse8->adap);
653*a8106818SMauro Carvalho Chehab 	pulse8->serio = NULL;
654*a8106818SMauro Carvalho Chehab 	serio_set_drvdata(serio, NULL);
655*a8106818SMauro Carvalho Chehab 	serio_close(serio);
656*a8106818SMauro Carvalho Chehab }
657*a8106818SMauro Carvalho Chehab 
658*a8106818SMauro Carvalho Chehab static int pulse8_setup(struct pulse8 *pulse8, struct serio *serio,
659*a8106818SMauro Carvalho Chehab 			struct cec_log_addrs *log_addrs, u16 *pa)
660*a8106818SMauro Carvalho Chehab {
661*a8106818SMauro Carvalho Chehab 	u8 *data = pulse8->data + 1;
662*a8106818SMauro Carvalho Chehab 	u8 cmd[2];
663*a8106818SMauro Carvalho Chehab 	int err;
664*a8106818SMauro Carvalho Chehab 	struct tm tm;
665*a8106818SMauro Carvalho Chehab 	time64_t date;
666*a8106818SMauro Carvalho Chehab 
667*a8106818SMauro Carvalho Chehab 	pulse8->vers = 0;
668*a8106818SMauro Carvalho Chehab 
669*a8106818SMauro Carvalho Chehab 	cmd[0] = MSGCODE_FIRMWARE_VERSION;
670*a8106818SMauro Carvalho Chehab 	err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 2);
671*a8106818SMauro Carvalho Chehab 	if (err)
672*a8106818SMauro Carvalho Chehab 		return err;
673*a8106818SMauro Carvalho Chehab 	pulse8->vers = (data[0] << 8) | data[1];
674*a8106818SMauro Carvalho Chehab 	dev_info(pulse8->dev, "Firmware version %04x\n", pulse8->vers);
675*a8106818SMauro Carvalho Chehab 	if (pulse8->vers < 2) {
676*a8106818SMauro Carvalho Chehab 		*pa = CEC_PHYS_ADDR_INVALID;
677*a8106818SMauro Carvalho Chehab 		return 0;
678*a8106818SMauro Carvalho Chehab 	}
679*a8106818SMauro Carvalho Chehab 
680*a8106818SMauro Carvalho Chehab 	cmd[0] = MSGCODE_GET_BUILDDATE;
681*a8106818SMauro Carvalho Chehab 	err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 4);
682*a8106818SMauro Carvalho Chehab 	if (err)
683*a8106818SMauro Carvalho Chehab 		return err;
684*a8106818SMauro Carvalho Chehab 	date = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
685*a8106818SMauro Carvalho Chehab 	time64_to_tm(date, 0, &tm);
686*a8106818SMauro Carvalho Chehab 	dev_info(pulse8->dev, "Firmware build date %04ld.%02d.%02d %02d:%02d:%02d\n",
687*a8106818SMauro Carvalho Chehab 		 tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
688*a8106818SMauro Carvalho Chehab 		 tm.tm_hour, tm.tm_min, tm.tm_sec);
689*a8106818SMauro Carvalho Chehab 
690*a8106818SMauro Carvalho Chehab 	dev_dbg(pulse8->dev, "Persistent config:\n");
691*a8106818SMauro Carvalho Chehab 	cmd[0] = MSGCODE_GET_AUTO_ENABLED;
692*a8106818SMauro Carvalho Chehab 	err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 1);
693*a8106818SMauro Carvalho Chehab 	if (err)
694*a8106818SMauro Carvalho Chehab 		return err;
695*a8106818SMauro Carvalho Chehab 	pulse8->autonomous = data[0];
696*a8106818SMauro Carvalho Chehab 	dev_dbg(pulse8->dev, "Autonomous mode: %s",
697*a8106818SMauro Carvalho Chehab 		data[0] ? "on" : "off");
698*a8106818SMauro Carvalho Chehab 
699*a8106818SMauro Carvalho Chehab 	cmd[0] = MSGCODE_GET_DEVICE_TYPE;
700*a8106818SMauro Carvalho Chehab 	err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 1);
701*a8106818SMauro Carvalho Chehab 	if (err)
702*a8106818SMauro Carvalho Chehab 		return err;
703*a8106818SMauro Carvalho Chehab 	log_addrs->primary_device_type[0] = data[0];
704*a8106818SMauro Carvalho Chehab 	dev_dbg(pulse8->dev, "Primary device type: %d\n", data[0]);
705*a8106818SMauro Carvalho Chehab 	switch (log_addrs->primary_device_type[0]) {
706*a8106818SMauro Carvalho Chehab 	case CEC_OP_PRIM_DEVTYPE_TV:
707*a8106818SMauro Carvalho Chehab 		log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_TV;
708*a8106818SMauro Carvalho Chehab 		log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_TV;
709*a8106818SMauro Carvalho Chehab 		break;
710*a8106818SMauro Carvalho Chehab 	case CEC_OP_PRIM_DEVTYPE_RECORD:
711*a8106818SMauro Carvalho Chehab 		log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_RECORD;
712*a8106818SMauro Carvalho Chehab 		log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_RECORD;
713*a8106818SMauro Carvalho Chehab 		break;
714*a8106818SMauro Carvalho Chehab 	case CEC_OP_PRIM_DEVTYPE_TUNER:
715*a8106818SMauro Carvalho Chehab 		log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_TUNER;
716*a8106818SMauro Carvalho Chehab 		log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_TUNER;
717*a8106818SMauro Carvalho Chehab 		break;
718*a8106818SMauro Carvalho Chehab 	case CEC_OP_PRIM_DEVTYPE_PLAYBACK:
719*a8106818SMauro Carvalho Chehab 		log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_PLAYBACK;
720*a8106818SMauro Carvalho Chehab 		log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_PLAYBACK;
721*a8106818SMauro Carvalho Chehab 		break;
722*a8106818SMauro Carvalho Chehab 	case CEC_OP_PRIM_DEVTYPE_AUDIOSYSTEM:
723*a8106818SMauro Carvalho Chehab 		log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_PLAYBACK;
724*a8106818SMauro Carvalho Chehab 		log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_AUDIOSYSTEM;
725*a8106818SMauro Carvalho Chehab 		break;
726*a8106818SMauro Carvalho Chehab 	case CEC_OP_PRIM_DEVTYPE_SWITCH:
727*a8106818SMauro Carvalho Chehab 		log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_UNREGISTERED;
728*a8106818SMauro Carvalho Chehab 		log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_SWITCH;
729*a8106818SMauro Carvalho Chehab 		break;
730*a8106818SMauro Carvalho Chehab 	case CEC_OP_PRIM_DEVTYPE_PROCESSOR:
731*a8106818SMauro Carvalho Chehab 		log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_SPECIFIC;
732*a8106818SMauro Carvalho Chehab 		log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_SWITCH;
733*a8106818SMauro Carvalho Chehab 		break;
734*a8106818SMauro Carvalho Chehab 	default:
735*a8106818SMauro Carvalho Chehab 		log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_UNREGISTERED;
736*a8106818SMauro Carvalho Chehab 		log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_SWITCH;
737*a8106818SMauro Carvalho Chehab 		dev_info(pulse8->dev, "Unknown Primary Device Type: %d\n",
738*a8106818SMauro Carvalho Chehab 			 log_addrs->primary_device_type[0]);
739*a8106818SMauro Carvalho Chehab 		break;
740*a8106818SMauro Carvalho Chehab 	}
741*a8106818SMauro Carvalho Chehab 
742*a8106818SMauro Carvalho Chehab 	cmd[0] = MSGCODE_GET_LOGICAL_ADDRESS_MASK;
743*a8106818SMauro Carvalho Chehab 	err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 2);
744*a8106818SMauro Carvalho Chehab 	if (err)
745*a8106818SMauro Carvalho Chehab 		return err;
746*a8106818SMauro Carvalho Chehab 	log_addrs->log_addr_mask = (data[0] << 8) | data[1];
747*a8106818SMauro Carvalho Chehab 	dev_dbg(pulse8->dev, "Logical address ACK mask: %x\n",
748*a8106818SMauro Carvalho Chehab 		log_addrs->log_addr_mask);
749*a8106818SMauro Carvalho Chehab 	if (log_addrs->log_addr_mask)
750*a8106818SMauro Carvalho Chehab 		log_addrs->num_log_addrs = 1;
751*a8106818SMauro Carvalho Chehab 
752*a8106818SMauro Carvalho Chehab 	cmd[0] = MSGCODE_GET_PHYSICAL_ADDRESS;
753*a8106818SMauro Carvalho Chehab 	err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 1);
754*a8106818SMauro Carvalho Chehab 	if (err)
755*a8106818SMauro Carvalho Chehab 		return err;
756*a8106818SMauro Carvalho Chehab 	*pa = (data[0] << 8) | data[1];
757*a8106818SMauro Carvalho Chehab 	dev_dbg(pulse8->dev, "Physical address: %x.%x.%x.%x\n",
758*a8106818SMauro Carvalho Chehab 		cec_phys_addr_exp(*pa));
759*a8106818SMauro Carvalho Chehab 
760*a8106818SMauro Carvalho Chehab 	cmd[0] = MSGCODE_GET_HDMI_VERSION;
761*a8106818SMauro Carvalho Chehab 	err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 1);
762*a8106818SMauro Carvalho Chehab 	if (err)
763*a8106818SMauro Carvalho Chehab 		return err;
764*a8106818SMauro Carvalho Chehab 	log_addrs->cec_version = data[0];
765*a8106818SMauro Carvalho Chehab 	dev_dbg(pulse8->dev, "CEC version: %d\n", log_addrs->cec_version);
766*a8106818SMauro Carvalho Chehab 
767*a8106818SMauro Carvalho Chehab 	cmd[0] = MSGCODE_GET_OSD_NAME;
768*a8106818SMauro Carvalho Chehab 	err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 0);
769*a8106818SMauro Carvalho Chehab 	if (err)
770*a8106818SMauro Carvalho Chehab 		return err;
771*a8106818SMauro Carvalho Chehab 	strscpy(log_addrs->osd_name, data, sizeof(log_addrs->osd_name));
772*a8106818SMauro Carvalho Chehab 	dev_dbg(pulse8->dev, "OSD name: %s\n", log_addrs->osd_name);
773*a8106818SMauro Carvalho Chehab 
774*a8106818SMauro Carvalho Chehab 	return 0;
775*a8106818SMauro Carvalho Chehab }
776*a8106818SMauro Carvalho Chehab 
777*a8106818SMauro Carvalho Chehab static int pulse8_apply_persistent_config(struct pulse8 *pulse8,
778*a8106818SMauro Carvalho Chehab 					  struct cec_log_addrs *log_addrs,
779*a8106818SMauro Carvalho Chehab 					  u16 pa)
780*a8106818SMauro Carvalho Chehab {
781*a8106818SMauro Carvalho Chehab 	int err;
782*a8106818SMauro Carvalho Chehab 
783*a8106818SMauro Carvalho Chehab 	err = cec_s_log_addrs(pulse8->adap, log_addrs, false);
784*a8106818SMauro Carvalho Chehab 	if (err)
785*a8106818SMauro Carvalho Chehab 		return err;
786*a8106818SMauro Carvalho Chehab 
787*a8106818SMauro Carvalho Chehab 	cec_s_phys_addr(pulse8->adap, pa, false);
788*a8106818SMauro Carvalho Chehab 
789*a8106818SMauro Carvalho Chehab 	return 0;
790*a8106818SMauro Carvalho Chehab }
791*a8106818SMauro Carvalho Chehab 
792*a8106818SMauro Carvalho Chehab static void pulse8_ping_eeprom_work_handler(struct work_struct *work)
793*a8106818SMauro Carvalho Chehab {
794*a8106818SMauro Carvalho Chehab 	struct pulse8 *pulse8 =
795*a8106818SMauro Carvalho Chehab 		container_of(work, struct pulse8, ping_eeprom_work.work);
796*a8106818SMauro Carvalho Chehab 	u8 cmd;
797*a8106818SMauro Carvalho Chehab 
798*a8106818SMauro Carvalho Chehab 	mutex_lock(&pulse8->lock);
799*a8106818SMauro Carvalho Chehab 	cmd = MSGCODE_PING;
800*a8106818SMauro Carvalho Chehab 	pulse8_send_and_wait(pulse8, &cmd, 1,
801*a8106818SMauro Carvalho Chehab 			     MSGCODE_COMMAND_ACCEPTED, 0);
802*a8106818SMauro Carvalho Chehab 
803*a8106818SMauro Carvalho Chehab 	if (pulse8->vers < 2)
804*a8106818SMauro Carvalho Chehab 		goto unlock;
805*a8106818SMauro Carvalho Chehab 
806*a8106818SMauro Carvalho Chehab 	if (pulse8->config_pending && persistent_config) {
807*a8106818SMauro Carvalho Chehab 		dev_dbg(pulse8->dev, "writing pending config to EEPROM\n");
808*a8106818SMauro Carvalho Chehab 		cmd = MSGCODE_WRITE_EEPROM;
809*a8106818SMauro Carvalho Chehab 		if (pulse8_send_and_wait(pulse8, &cmd, 1,
810*a8106818SMauro Carvalho Chehab 					 MSGCODE_COMMAND_ACCEPTED, 0))
811*a8106818SMauro Carvalho Chehab 			dev_info(pulse8->dev, "failed to write pending config to EEPROM\n");
812*a8106818SMauro Carvalho Chehab 		else
813*a8106818SMauro Carvalho Chehab 			pulse8->config_pending = false;
814*a8106818SMauro Carvalho Chehab 	}
815*a8106818SMauro Carvalho Chehab unlock:
816*a8106818SMauro Carvalho Chehab 	schedule_delayed_work(&pulse8->ping_eeprom_work, PING_PERIOD);
817*a8106818SMauro Carvalho Chehab 	mutex_unlock(&pulse8->lock);
818*a8106818SMauro Carvalho Chehab }
819*a8106818SMauro Carvalho Chehab 
820*a8106818SMauro Carvalho Chehab static int pulse8_connect(struct serio *serio, struct serio_driver *drv)
821*a8106818SMauro Carvalho Chehab {
822*a8106818SMauro Carvalho Chehab 	u32 caps = CEC_CAP_DEFAULTS | CEC_CAP_PHYS_ADDR | CEC_CAP_MONITOR_ALL;
823*a8106818SMauro Carvalho Chehab 	struct pulse8 *pulse8;
824*a8106818SMauro Carvalho Chehab 	int err = -ENOMEM;
825*a8106818SMauro Carvalho Chehab 	struct cec_log_addrs log_addrs = {};
826*a8106818SMauro Carvalho Chehab 	u16 pa = CEC_PHYS_ADDR_INVALID;
827*a8106818SMauro Carvalho Chehab 
828*a8106818SMauro Carvalho Chehab 	pulse8 = kzalloc(sizeof(*pulse8), GFP_KERNEL);
829*a8106818SMauro Carvalho Chehab 
830*a8106818SMauro Carvalho Chehab 	if (!pulse8)
831*a8106818SMauro Carvalho Chehab 		return -ENOMEM;
832*a8106818SMauro Carvalho Chehab 
833*a8106818SMauro Carvalho Chehab 	pulse8->serio = serio;
834*a8106818SMauro Carvalho Chehab 	pulse8->adap = cec_allocate_adapter(&pulse8_cec_adap_ops, pulse8,
835*a8106818SMauro Carvalho Chehab 					    dev_name(&serio->dev), caps, 1);
836*a8106818SMauro Carvalho Chehab 	err = PTR_ERR_OR_ZERO(pulse8->adap);
837*a8106818SMauro Carvalho Chehab 	if (err < 0)
838*a8106818SMauro Carvalho Chehab 		goto free_device;
839*a8106818SMauro Carvalho Chehab 
840*a8106818SMauro Carvalho Chehab 	pulse8->dev = &serio->dev;
841*a8106818SMauro Carvalho Chehab 	serio_set_drvdata(serio, pulse8);
842*a8106818SMauro Carvalho Chehab 	INIT_WORK(&pulse8->irq_work, pulse8_irq_work_handler);
843*a8106818SMauro Carvalho Chehab 	INIT_WORK(&pulse8->tx_work, pulse8_tx_work_handler);
844*a8106818SMauro Carvalho Chehab 	INIT_DELAYED_WORK(&pulse8->ping_eeprom_work,
845*a8106818SMauro Carvalho Chehab 			  pulse8_ping_eeprom_work_handler);
846*a8106818SMauro Carvalho Chehab 	mutex_init(&pulse8->lock);
847*a8106818SMauro Carvalho Chehab 	spin_lock_init(&pulse8->msg_lock);
848*a8106818SMauro Carvalho Chehab 	pulse8->config_pending = false;
849*a8106818SMauro Carvalho Chehab 
850*a8106818SMauro Carvalho Chehab 	err = serio_open(serio, drv);
851*a8106818SMauro Carvalho Chehab 	if (err)
852*a8106818SMauro Carvalho Chehab 		goto delete_adap;
853*a8106818SMauro Carvalho Chehab 
854*a8106818SMauro Carvalho Chehab 	err = pulse8_setup(pulse8, serio, &log_addrs, &pa);
855*a8106818SMauro Carvalho Chehab 	if (err)
856*a8106818SMauro Carvalho Chehab 		goto close_serio;
857*a8106818SMauro Carvalho Chehab 
858*a8106818SMauro Carvalho Chehab 	err = cec_register_adapter(pulse8->adap, &serio->dev);
859*a8106818SMauro Carvalho Chehab 	if (err < 0)
860*a8106818SMauro Carvalho Chehab 		goto close_serio;
861*a8106818SMauro Carvalho Chehab 
862*a8106818SMauro Carvalho Chehab 	pulse8->dev = &pulse8->adap->devnode.dev;
863*a8106818SMauro Carvalho Chehab 
864*a8106818SMauro Carvalho Chehab 	if (persistent_config && pulse8->autonomous) {
865*a8106818SMauro Carvalho Chehab 		err = pulse8_apply_persistent_config(pulse8, &log_addrs, pa);
866*a8106818SMauro Carvalho Chehab 		if (err)
867*a8106818SMauro Carvalho Chehab 			goto close_serio;
868*a8106818SMauro Carvalho Chehab 		pulse8->restoring_config = true;
869*a8106818SMauro Carvalho Chehab 	}
870*a8106818SMauro Carvalho Chehab 
871*a8106818SMauro Carvalho Chehab 	schedule_delayed_work(&pulse8->ping_eeprom_work, PING_PERIOD);
872*a8106818SMauro Carvalho Chehab 
873*a8106818SMauro Carvalho Chehab 	return 0;
874*a8106818SMauro Carvalho Chehab 
875*a8106818SMauro Carvalho Chehab close_serio:
876*a8106818SMauro Carvalho Chehab 	pulse8->serio = NULL;
877*a8106818SMauro Carvalho Chehab 	serio_set_drvdata(serio, NULL);
878*a8106818SMauro Carvalho Chehab 	serio_close(serio);
879*a8106818SMauro Carvalho Chehab delete_adap:
880*a8106818SMauro Carvalho Chehab 	cec_delete_adapter(pulse8->adap);
881*a8106818SMauro Carvalho Chehab free_device:
882*a8106818SMauro Carvalho Chehab 	kfree(pulse8);
883*a8106818SMauro Carvalho Chehab 	return err;
884*a8106818SMauro Carvalho Chehab }
885*a8106818SMauro Carvalho Chehab 
886*a8106818SMauro Carvalho Chehab static const struct serio_device_id pulse8_serio_ids[] = {
887*a8106818SMauro Carvalho Chehab 	{
888*a8106818SMauro Carvalho Chehab 		.type	= SERIO_RS232,
889*a8106818SMauro Carvalho Chehab 		.proto	= SERIO_PULSE8_CEC,
890*a8106818SMauro Carvalho Chehab 		.id	= SERIO_ANY,
891*a8106818SMauro Carvalho Chehab 		.extra	= SERIO_ANY,
892*a8106818SMauro Carvalho Chehab 	},
893*a8106818SMauro Carvalho Chehab 	{ 0 }
894*a8106818SMauro Carvalho Chehab };
895*a8106818SMauro Carvalho Chehab 
896*a8106818SMauro Carvalho Chehab MODULE_DEVICE_TABLE(serio, pulse8_serio_ids);
897*a8106818SMauro Carvalho Chehab 
898*a8106818SMauro Carvalho Chehab static struct serio_driver pulse8_drv = {
899*a8106818SMauro Carvalho Chehab 	.driver		= {
900*a8106818SMauro Carvalho Chehab 		.name	= "pulse8-cec",
901*a8106818SMauro Carvalho Chehab 	},
902*a8106818SMauro Carvalho Chehab 	.description	= "Pulse Eight HDMI CEC driver",
903*a8106818SMauro Carvalho Chehab 	.id_table	= pulse8_serio_ids,
904*a8106818SMauro Carvalho Chehab 	.interrupt	= pulse8_interrupt,
905*a8106818SMauro Carvalho Chehab 	.connect	= pulse8_connect,
906*a8106818SMauro Carvalho Chehab 	.disconnect	= pulse8_disconnect,
907*a8106818SMauro Carvalho Chehab };
908*a8106818SMauro Carvalho Chehab 
909*a8106818SMauro Carvalho Chehab module_serio_driver(pulse8_drv);
910