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