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