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 } 392*1771e9fbSGustavo A. R. Silva fallthrough; 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]; 684a98f670eSLinus 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