162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Pulse Eight HDMI CEC driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright 2016 Hans Verkuil <hverkuil@xs4all.nl 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci/* 962306a36Sopenharmony_ci * Notes: 1062306a36Sopenharmony_ci * 1162306a36Sopenharmony_ci * - Devices with firmware version < 2 do not store their configuration in 1262306a36Sopenharmony_ci * EEPROM. 1362306a36Sopenharmony_ci * 1462306a36Sopenharmony_ci * - In autonomous mode, only messages from a TV will be acknowledged, even 1562306a36Sopenharmony_ci * polling messages. Upon receiving a message from a TV, the dongle will 1662306a36Sopenharmony_ci * respond to messages from any logical address. 1762306a36Sopenharmony_ci * 1862306a36Sopenharmony_ci * - In autonomous mode, the dongle will by default reply Feature Abort 1962306a36Sopenharmony_ci * [Unrecognized Opcode] when it receives Give Device Vendor ID. It will 2062306a36Sopenharmony_ci * however observe vendor ID's reported by other devices and possibly 2162306a36Sopenharmony_ci * alter this behavior. When TV's (and TV's only) report that their vendor ID 2262306a36Sopenharmony_ci * is LG (0x00e091), the dongle will itself reply that it has the same vendor 2362306a36Sopenharmony_ci * ID, and it will respond to at least one vendor specific command. 2462306a36Sopenharmony_ci * 2562306a36Sopenharmony_ci * - In autonomous mode, the dongle is known to attempt wakeup if it receives 2662306a36Sopenharmony_ci * <User Control Pressed> ["Power On"], ["Power] or ["Power Toggle"], or if it 2762306a36Sopenharmony_ci * receives <Set Stream Path> with its own physical address. It also does this 2862306a36Sopenharmony_ci * if it receives <Vendor Specific Command> [0x03 0x00] from an LG TV. 2962306a36Sopenharmony_ci */ 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci#include <linux/completion.h> 3262306a36Sopenharmony_ci#include <linux/init.h> 3362306a36Sopenharmony_ci#include <linux/interrupt.h> 3462306a36Sopenharmony_ci#include <linux/kernel.h> 3562306a36Sopenharmony_ci#include <linux/module.h> 3662306a36Sopenharmony_ci#include <linux/workqueue.h> 3762306a36Sopenharmony_ci#include <linux/serio.h> 3862306a36Sopenharmony_ci#include <linux/slab.h> 3962306a36Sopenharmony_ci#include <linux/time.h> 4062306a36Sopenharmony_ci#include <linux/delay.h> 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci#include <media/cec.h> 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ciMODULE_AUTHOR("Hans Verkuil <hverkuil@xs4all.nl>"); 4562306a36Sopenharmony_ciMODULE_DESCRIPTION("Pulse Eight HDMI CEC driver"); 4662306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistatic int debug; 4962306a36Sopenharmony_cistatic int persistent_config; 5062306a36Sopenharmony_cimodule_param(debug, int, 0644); 5162306a36Sopenharmony_cimodule_param(persistent_config, int, 0644); 5262306a36Sopenharmony_ciMODULE_PARM_DESC(debug, "debug level (0-2)"); 5362306a36Sopenharmony_ciMODULE_PARM_DESC(persistent_config, "read config from persistent memory (0-1)"); 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_cienum pulse8_msgcodes { 5662306a36Sopenharmony_ci MSGCODE_NOTHING = 0, 5762306a36Sopenharmony_ci MSGCODE_PING, 5862306a36Sopenharmony_ci MSGCODE_TIMEOUT_ERROR, 5962306a36Sopenharmony_ci MSGCODE_HIGH_ERROR, 6062306a36Sopenharmony_ci MSGCODE_LOW_ERROR, 6162306a36Sopenharmony_ci MSGCODE_FRAME_START, 6262306a36Sopenharmony_ci MSGCODE_FRAME_DATA, 6362306a36Sopenharmony_ci MSGCODE_RECEIVE_FAILED, 6462306a36Sopenharmony_ci MSGCODE_COMMAND_ACCEPTED, /* 0x08 */ 6562306a36Sopenharmony_ci MSGCODE_COMMAND_REJECTED, 6662306a36Sopenharmony_ci MSGCODE_SET_ACK_MASK, 6762306a36Sopenharmony_ci MSGCODE_TRANSMIT, 6862306a36Sopenharmony_ci MSGCODE_TRANSMIT_EOM, 6962306a36Sopenharmony_ci MSGCODE_TRANSMIT_IDLETIME, 7062306a36Sopenharmony_ci MSGCODE_TRANSMIT_ACK_POLARITY, 7162306a36Sopenharmony_ci MSGCODE_TRANSMIT_LINE_TIMEOUT, 7262306a36Sopenharmony_ci MSGCODE_TRANSMIT_SUCCEEDED, /* 0x10 */ 7362306a36Sopenharmony_ci MSGCODE_TRANSMIT_FAILED_LINE, 7462306a36Sopenharmony_ci MSGCODE_TRANSMIT_FAILED_ACK, 7562306a36Sopenharmony_ci MSGCODE_TRANSMIT_FAILED_TIMEOUT_DATA, 7662306a36Sopenharmony_ci MSGCODE_TRANSMIT_FAILED_TIMEOUT_LINE, 7762306a36Sopenharmony_ci MSGCODE_FIRMWARE_VERSION, 7862306a36Sopenharmony_ci MSGCODE_START_BOOTLOADER, 7962306a36Sopenharmony_ci MSGCODE_GET_BUILDDATE, 8062306a36Sopenharmony_ci MSGCODE_SET_CONTROLLED, /* 0x18 */ 8162306a36Sopenharmony_ci MSGCODE_GET_AUTO_ENABLED, 8262306a36Sopenharmony_ci MSGCODE_SET_AUTO_ENABLED, 8362306a36Sopenharmony_ci MSGCODE_GET_DEFAULT_LOGICAL_ADDRESS, 8462306a36Sopenharmony_ci MSGCODE_SET_DEFAULT_LOGICAL_ADDRESS, 8562306a36Sopenharmony_ci MSGCODE_GET_LOGICAL_ADDRESS_MASK, 8662306a36Sopenharmony_ci MSGCODE_SET_LOGICAL_ADDRESS_MASK, 8762306a36Sopenharmony_ci MSGCODE_GET_PHYSICAL_ADDRESS, 8862306a36Sopenharmony_ci MSGCODE_SET_PHYSICAL_ADDRESS, /* 0x20 */ 8962306a36Sopenharmony_ci MSGCODE_GET_DEVICE_TYPE, 9062306a36Sopenharmony_ci MSGCODE_SET_DEVICE_TYPE, 9162306a36Sopenharmony_ci MSGCODE_GET_HDMI_VERSION, /* Removed in FW >= 10 */ 9262306a36Sopenharmony_ci MSGCODE_SET_HDMI_VERSION, 9362306a36Sopenharmony_ci MSGCODE_GET_OSD_NAME, 9462306a36Sopenharmony_ci MSGCODE_SET_OSD_NAME, 9562306a36Sopenharmony_ci MSGCODE_WRITE_EEPROM, 9662306a36Sopenharmony_ci MSGCODE_GET_ADAPTER_TYPE, /* 0x28 */ 9762306a36Sopenharmony_ci MSGCODE_SET_ACTIVE_SOURCE, 9862306a36Sopenharmony_ci MSGCODE_GET_AUTO_POWER_ON, /* New for FW >= 10 */ 9962306a36Sopenharmony_ci MSGCODE_SET_AUTO_POWER_ON, 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci MSGCODE_FRAME_EOM = 0x80, 10262306a36Sopenharmony_ci MSGCODE_FRAME_ACK = 0x40, 10362306a36Sopenharmony_ci}; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_cistatic const char * const pulse8_msgnames[] = { 10662306a36Sopenharmony_ci "NOTHING", 10762306a36Sopenharmony_ci "PING", 10862306a36Sopenharmony_ci "TIMEOUT_ERROR", 10962306a36Sopenharmony_ci "HIGH_ERROR", 11062306a36Sopenharmony_ci "LOW_ERROR", 11162306a36Sopenharmony_ci "FRAME_START", 11262306a36Sopenharmony_ci "FRAME_DATA", 11362306a36Sopenharmony_ci "RECEIVE_FAILED", 11462306a36Sopenharmony_ci "COMMAND_ACCEPTED", 11562306a36Sopenharmony_ci "COMMAND_REJECTED", 11662306a36Sopenharmony_ci "SET_ACK_MASK", 11762306a36Sopenharmony_ci "TRANSMIT", 11862306a36Sopenharmony_ci "TRANSMIT_EOM", 11962306a36Sopenharmony_ci "TRANSMIT_IDLETIME", 12062306a36Sopenharmony_ci "TRANSMIT_ACK_POLARITY", 12162306a36Sopenharmony_ci "TRANSMIT_LINE_TIMEOUT", 12262306a36Sopenharmony_ci "TRANSMIT_SUCCEEDED", 12362306a36Sopenharmony_ci "TRANSMIT_FAILED_LINE", 12462306a36Sopenharmony_ci "TRANSMIT_FAILED_ACK", 12562306a36Sopenharmony_ci "TRANSMIT_FAILED_TIMEOUT_DATA", 12662306a36Sopenharmony_ci "TRANSMIT_FAILED_TIMEOUT_LINE", 12762306a36Sopenharmony_ci "FIRMWARE_VERSION", 12862306a36Sopenharmony_ci "START_BOOTLOADER", 12962306a36Sopenharmony_ci "GET_BUILDDATE", 13062306a36Sopenharmony_ci "SET_CONTROLLED", 13162306a36Sopenharmony_ci "GET_AUTO_ENABLED", 13262306a36Sopenharmony_ci "SET_AUTO_ENABLED", 13362306a36Sopenharmony_ci "GET_DEFAULT_LOGICAL_ADDRESS", 13462306a36Sopenharmony_ci "SET_DEFAULT_LOGICAL_ADDRESS", 13562306a36Sopenharmony_ci "GET_LOGICAL_ADDRESS_MASK", 13662306a36Sopenharmony_ci "SET_LOGICAL_ADDRESS_MASK", 13762306a36Sopenharmony_ci "GET_PHYSICAL_ADDRESS", 13862306a36Sopenharmony_ci "SET_PHYSICAL_ADDRESS", 13962306a36Sopenharmony_ci "GET_DEVICE_TYPE", 14062306a36Sopenharmony_ci "SET_DEVICE_TYPE", 14162306a36Sopenharmony_ci "GET_HDMI_VERSION", 14262306a36Sopenharmony_ci "SET_HDMI_VERSION", 14362306a36Sopenharmony_ci "GET_OSD_NAME", 14462306a36Sopenharmony_ci "SET_OSD_NAME", 14562306a36Sopenharmony_ci "WRITE_EEPROM", 14662306a36Sopenharmony_ci "GET_ADAPTER_TYPE", 14762306a36Sopenharmony_ci "SET_ACTIVE_SOURCE", 14862306a36Sopenharmony_ci "GET_AUTO_POWER_ON", 14962306a36Sopenharmony_ci "SET_AUTO_POWER_ON", 15062306a36Sopenharmony_ci}; 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_cistatic const char *pulse8_msgname(u8 cmd) 15362306a36Sopenharmony_ci{ 15462306a36Sopenharmony_ci static char unknown_msg[5]; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci if ((cmd & 0x3f) < ARRAY_SIZE(pulse8_msgnames)) 15762306a36Sopenharmony_ci return pulse8_msgnames[cmd & 0x3f]; 15862306a36Sopenharmony_ci snprintf(unknown_msg, sizeof(unknown_msg), "0x%02x", cmd); 15962306a36Sopenharmony_ci return unknown_msg; 16062306a36Sopenharmony_ci} 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci#define MSGSTART 0xff 16362306a36Sopenharmony_ci#define MSGEND 0xfe 16462306a36Sopenharmony_ci#define MSGESC 0xfd 16562306a36Sopenharmony_ci#define MSGOFFSET 3 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci#define DATA_SIZE 256 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci#define PING_PERIOD (15 * HZ) 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci#define NUM_MSGS 8 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_cistruct pulse8 { 17462306a36Sopenharmony_ci struct device *dev; 17562306a36Sopenharmony_ci struct serio *serio; 17662306a36Sopenharmony_ci struct cec_adapter *adap; 17762306a36Sopenharmony_ci unsigned int vers; 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci struct delayed_work ping_eeprom_work; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci struct work_struct irq_work; 18262306a36Sopenharmony_ci struct cec_msg rx_msg[NUM_MSGS]; 18362306a36Sopenharmony_ci unsigned int rx_msg_cur_idx, rx_msg_num; 18462306a36Sopenharmony_ci /* protect rx_msg_cur_idx and rx_msg_num */ 18562306a36Sopenharmony_ci spinlock_t msg_lock; 18662306a36Sopenharmony_ci u8 new_rx_msg[CEC_MAX_MSG_SIZE]; 18762306a36Sopenharmony_ci u8 new_rx_msg_len; 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci struct work_struct tx_work; 19062306a36Sopenharmony_ci u32 tx_done_status; 19162306a36Sopenharmony_ci u32 tx_signal_free_time; 19262306a36Sopenharmony_ci struct cec_msg tx_msg; 19362306a36Sopenharmony_ci bool tx_msg_is_bcast; 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci struct completion cmd_done; 19662306a36Sopenharmony_ci u8 data[DATA_SIZE]; 19762306a36Sopenharmony_ci unsigned int len; 19862306a36Sopenharmony_ci u8 buf[DATA_SIZE]; 19962306a36Sopenharmony_ci unsigned int idx; 20062306a36Sopenharmony_ci bool escape; 20162306a36Sopenharmony_ci bool started; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci /* locks access to the adapter */ 20462306a36Sopenharmony_ci struct mutex lock; 20562306a36Sopenharmony_ci bool config_pending; 20662306a36Sopenharmony_ci bool restoring_config; 20762306a36Sopenharmony_ci bool autonomous; 20862306a36Sopenharmony_ci}; 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_cistatic int pulse8_send(struct serio *serio, const u8 *command, u8 cmd_len) 21162306a36Sopenharmony_ci{ 21262306a36Sopenharmony_ci int err = 0; 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci err = serio_write(serio, MSGSTART); 21562306a36Sopenharmony_ci if (err) 21662306a36Sopenharmony_ci return err; 21762306a36Sopenharmony_ci for (; !err && cmd_len; command++, cmd_len--) { 21862306a36Sopenharmony_ci if (*command >= MSGESC) { 21962306a36Sopenharmony_ci err = serio_write(serio, MSGESC); 22062306a36Sopenharmony_ci if (!err) 22162306a36Sopenharmony_ci err = serio_write(serio, *command - MSGOFFSET); 22262306a36Sopenharmony_ci } else { 22362306a36Sopenharmony_ci err = serio_write(serio, *command); 22462306a36Sopenharmony_ci } 22562306a36Sopenharmony_ci } 22662306a36Sopenharmony_ci if (!err) 22762306a36Sopenharmony_ci err = serio_write(serio, MSGEND); 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci return err; 23062306a36Sopenharmony_ci} 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_cistatic int pulse8_send_and_wait_once(struct pulse8 *pulse8, 23362306a36Sopenharmony_ci const u8 *cmd, u8 cmd_len, 23462306a36Sopenharmony_ci u8 response, u8 size) 23562306a36Sopenharmony_ci{ 23662306a36Sopenharmony_ci int err; 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci if (debug > 1) 23962306a36Sopenharmony_ci dev_info(pulse8->dev, "transmit %s: %*ph\n", 24062306a36Sopenharmony_ci pulse8_msgname(cmd[0]), cmd_len, cmd); 24162306a36Sopenharmony_ci init_completion(&pulse8->cmd_done); 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci err = pulse8_send(pulse8->serio, cmd, cmd_len); 24462306a36Sopenharmony_ci if (err) 24562306a36Sopenharmony_ci return err; 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci if (!wait_for_completion_timeout(&pulse8->cmd_done, HZ)) 24862306a36Sopenharmony_ci return -ETIMEDOUT; 24962306a36Sopenharmony_ci if ((pulse8->data[0] & 0x3f) == MSGCODE_COMMAND_REJECTED && 25062306a36Sopenharmony_ci cmd[0] != MSGCODE_SET_CONTROLLED && 25162306a36Sopenharmony_ci cmd[0] != MSGCODE_SET_AUTO_ENABLED && 25262306a36Sopenharmony_ci cmd[0] != MSGCODE_GET_BUILDDATE) 25362306a36Sopenharmony_ci return -ENOTTY; 25462306a36Sopenharmony_ci if (response && 25562306a36Sopenharmony_ci ((pulse8->data[0] & 0x3f) != response || pulse8->len < size + 1)) { 25662306a36Sopenharmony_ci dev_info(pulse8->dev, "transmit %s failed with %s\n", 25762306a36Sopenharmony_ci pulse8_msgname(cmd[0]), 25862306a36Sopenharmony_ci pulse8_msgname(pulse8->data[0])); 25962306a36Sopenharmony_ci return -EIO; 26062306a36Sopenharmony_ci } 26162306a36Sopenharmony_ci return 0; 26262306a36Sopenharmony_ci} 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_cistatic int pulse8_send_and_wait(struct pulse8 *pulse8, 26562306a36Sopenharmony_ci const u8 *cmd, u8 cmd_len, u8 response, u8 size) 26662306a36Sopenharmony_ci{ 26762306a36Sopenharmony_ci u8 cmd_sc[2]; 26862306a36Sopenharmony_ci int err; 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci err = pulse8_send_and_wait_once(pulse8, cmd, cmd_len, response, size); 27162306a36Sopenharmony_ci if (err != -ENOTTY) 27262306a36Sopenharmony_ci return err; 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci cmd_sc[0] = MSGCODE_SET_CONTROLLED; 27562306a36Sopenharmony_ci cmd_sc[1] = 1; 27662306a36Sopenharmony_ci err = pulse8_send_and_wait_once(pulse8, cmd_sc, 2, 27762306a36Sopenharmony_ci MSGCODE_COMMAND_ACCEPTED, 1); 27862306a36Sopenharmony_ci if (!err) 27962306a36Sopenharmony_ci err = pulse8_send_and_wait_once(pulse8, cmd, cmd_len, 28062306a36Sopenharmony_ci response, size); 28162306a36Sopenharmony_ci return err == -ENOTTY ? -EIO : err; 28262306a36Sopenharmony_ci} 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_cistatic void pulse8_tx_work_handler(struct work_struct *work) 28562306a36Sopenharmony_ci{ 28662306a36Sopenharmony_ci struct pulse8 *pulse8 = container_of(work, struct pulse8, tx_work); 28762306a36Sopenharmony_ci struct cec_msg *msg = &pulse8->tx_msg; 28862306a36Sopenharmony_ci unsigned int i; 28962306a36Sopenharmony_ci u8 cmd[2]; 29062306a36Sopenharmony_ci int err; 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci if (msg->len == 0) 29362306a36Sopenharmony_ci return; 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ci mutex_lock(&pulse8->lock); 29662306a36Sopenharmony_ci cmd[0] = MSGCODE_TRANSMIT_IDLETIME; 29762306a36Sopenharmony_ci cmd[1] = pulse8->tx_signal_free_time; 29862306a36Sopenharmony_ci err = pulse8_send_and_wait(pulse8, cmd, 2, 29962306a36Sopenharmony_ci MSGCODE_COMMAND_ACCEPTED, 1); 30062306a36Sopenharmony_ci cmd[0] = MSGCODE_TRANSMIT_ACK_POLARITY; 30162306a36Sopenharmony_ci cmd[1] = cec_msg_is_broadcast(msg); 30262306a36Sopenharmony_ci pulse8->tx_msg_is_bcast = cec_msg_is_broadcast(msg); 30362306a36Sopenharmony_ci if (!err) 30462306a36Sopenharmony_ci err = pulse8_send_and_wait(pulse8, cmd, 2, 30562306a36Sopenharmony_ci MSGCODE_COMMAND_ACCEPTED, 1); 30662306a36Sopenharmony_ci cmd[0] = msg->len == 1 ? MSGCODE_TRANSMIT_EOM : MSGCODE_TRANSMIT; 30762306a36Sopenharmony_ci cmd[1] = msg->msg[0]; 30862306a36Sopenharmony_ci if (!err) 30962306a36Sopenharmony_ci err = pulse8_send_and_wait(pulse8, cmd, 2, 31062306a36Sopenharmony_ci MSGCODE_COMMAND_ACCEPTED, 1); 31162306a36Sopenharmony_ci if (!err && msg->len > 1) { 31262306a36Sopenharmony_ci for (i = 1; !err && i < msg->len; i++) { 31362306a36Sopenharmony_ci cmd[0] = ((i == msg->len - 1)) ? 31462306a36Sopenharmony_ci MSGCODE_TRANSMIT_EOM : MSGCODE_TRANSMIT; 31562306a36Sopenharmony_ci cmd[1] = msg->msg[i]; 31662306a36Sopenharmony_ci err = pulse8_send_and_wait(pulse8, cmd, 2, 31762306a36Sopenharmony_ci MSGCODE_COMMAND_ACCEPTED, 1); 31862306a36Sopenharmony_ci } 31962306a36Sopenharmony_ci } 32062306a36Sopenharmony_ci if (err && debug) 32162306a36Sopenharmony_ci dev_info(pulse8->dev, "%s(0x%02x) failed with error %d for msg %*ph\n", 32262306a36Sopenharmony_ci pulse8_msgname(cmd[0]), cmd[1], 32362306a36Sopenharmony_ci err, msg->len, msg->msg); 32462306a36Sopenharmony_ci msg->len = 0; 32562306a36Sopenharmony_ci mutex_unlock(&pulse8->lock); 32662306a36Sopenharmony_ci if (err) 32762306a36Sopenharmony_ci cec_transmit_attempt_done(pulse8->adap, CEC_TX_STATUS_ERROR); 32862306a36Sopenharmony_ci} 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_cistatic void pulse8_irq_work_handler(struct work_struct *work) 33162306a36Sopenharmony_ci{ 33262306a36Sopenharmony_ci struct pulse8 *pulse8 = 33362306a36Sopenharmony_ci container_of(work, struct pulse8, irq_work); 33462306a36Sopenharmony_ci unsigned long flags; 33562306a36Sopenharmony_ci u32 status; 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_ci spin_lock_irqsave(&pulse8->msg_lock, flags); 33862306a36Sopenharmony_ci while (pulse8->rx_msg_num) { 33962306a36Sopenharmony_ci spin_unlock_irqrestore(&pulse8->msg_lock, flags); 34062306a36Sopenharmony_ci if (debug) 34162306a36Sopenharmony_ci dev_info(pulse8->dev, "adap received %*ph\n", 34262306a36Sopenharmony_ci pulse8->rx_msg[pulse8->rx_msg_cur_idx].len, 34362306a36Sopenharmony_ci pulse8->rx_msg[pulse8->rx_msg_cur_idx].msg); 34462306a36Sopenharmony_ci cec_received_msg(pulse8->adap, 34562306a36Sopenharmony_ci &pulse8->rx_msg[pulse8->rx_msg_cur_idx]); 34662306a36Sopenharmony_ci spin_lock_irqsave(&pulse8->msg_lock, flags); 34762306a36Sopenharmony_ci if (pulse8->rx_msg_num) 34862306a36Sopenharmony_ci pulse8->rx_msg_num--; 34962306a36Sopenharmony_ci pulse8->rx_msg_cur_idx = 35062306a36Sopenharmony_ci (pulse8->rx_msg_cur_idx + 1) % NUM_MSGS; 35162306a36Sopenharmony_ci } 35262306a36Sopenharmony_ci spin_unlock_irqrestore(&pulse8->msg_lock, flags); 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_ci mutex_lock(&pulse8->lock); 35562306a36Sopenharmony_ci status = pulse8->tx_done_status; 35662306a36Sopenharmony_ci pulse8->tx_done_status = 0; 35762306a36Sopenharmony_ci mutex_unlock(&pulse8->lock); 35862306a36Sopenharmony_ci if (status) 35962306a36Sopenharmony_ci cec_transmit_attempt_done(pulse8->adap, status); 36062306a36Sopenharmony_ci} 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_cistatic irqreturn_t pulse8_interrupt(struct serio *serio, unsigned char data, 36362306a36Sopenharmony_ci unsigned int flags) 36462306a36Sopenharmony_ci{ 36562306a36Sopenharmony_ci struct pulse8 *pulse8 = serio_get_drvdata(serio); 36662306a36Sopenharmony_ci unsigned long irq_flags; 36762306a36Sopenharmony_ci unsigned int idx; 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_ci if (!pulse8->started && data != MSGSTART) 37062306a36Sopenharmony_ci return IRQ_HANDLED; 37162306a36Sopenharmony_ci if (data == MSGESC) { 37262306a36Sopenharmony_ci pulse8->escape = true; 37362306a36Sopenharmony_ci return IRQ_HANDLED; 37462306a36Sopenharmony_ci } 37562306a36Sopenharmony_ci if (pulse8->escape) { 37662306a36Sopenharmony_ci data += MSGOFFSET; 37762306a36Sopenharmony_ci pulse8->escape = false; 37862306a36Sopenharmony_ci } else if (data == MSGEND) { 37962306a36Sopenharmony_ci u8 msgcode = pulse8->buf[0]; 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ci if (debug > 1) 38262306a36Sopenharmony_ci dev_info(pulse8->dev, "received %s: %*ph\n", 38362306a36Sopenharmony_ci pulse8_msgname(msgcode), 38462306a36Sopenharmony_ci pulse8->idx, pulse8->buf); 38562306a36Sopenharmony_ci switch (msgcode & 0x3f) { 38662306a36Sopenharmony_ci case MSGCODE_FRAME_START: 38762306a36Sopenharmony_ci /* 38862306a36Sopenharmony_ci * Test if we are receiving a new msg when a previous 38962306a36Sopenharmony_ci * message is still pending. 39062306a36Sopenharmony_ci */ 39162306a36Sopenharmony_ci if (!(msgcode & MSGCODE_FRAME_EOM)) { 39262306a36Sopenharmony_ci pulse8->new_rx_msg_len = 1; 39362306a36Sopenharmony_ci pulse8->new_rx_msg[0] = pulse8->buf[1]; 39462306a36Sopenharmony_ci break; 39562306a36Sopenharmony_ci } 39662306a36Sopenharmony_ci fallthrough; 39762306a36Sopenharmony_ci case MSGCODE_FRAME_DATA: 39862306a36Sopenharmony_ci if (pulse8->new_rx_msg_len < CEC_MAX_MSG_SIZE) 39962306a36Sopenharmony_ci pulse8->new_rx_msg[pulse8->new_rx_msg_len++] = 40062306a36Sopenharmony_ci pulse8->buf[1]; 40162306a36Sopenharmony_ci if (!(msgcode & MSGCODE_FRAME_EOM)) 40262306a36Sopenharmony_ci break; 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_ci spin_lock_irqsave(&pulse8->msg_lock, irq_flags); 40562306a36Sopenharmony_ci idx = (pulse8->rx_msg_cur_idx + pulse8->rx_msg_num) % 40662306a36Sopenharmony_ci NUM_MSGS; 40762306a36Sopenharmony_ci if (pulse8->rx_msg_num == NUM_MSGS) { 40862306a36Sopenharmony_ci dev_warn(pulse8->dev, 40962306a36Sopenharmony_ci "message queue is full, dropping %*ph\n", 41062306a36Sopenharmony_ci pulse8->new_rx_msg_len, 41162306a36Sopenharmony_ci pulse8->new_rx_msg); 41262306a36Sopenharmony_ci spin_unlock_irqrestore(&pulse8->msg_lock, 41362306a36Sopenharmony_ci irq_flags); 41462306a36Sopenharmony_ci pulse8->new_rx_msg_len = 0; 41562306a36Sopenharmony_ci break; 41662306a36Sopenharmony_ci } 41762306a36Sopenharmony_ci pulse8->rx_msg_num++; 41862306a36Sopenharmony_ci memcpy(pulse8->rx_msg[idx].msg, pulse8->new_rx_msg, 41962306a36Sopenharmony_ci pulse8->new_rx_msg_len); 42062306a36Sopenharmony_ci pulse8->rx_msg[idx].len = pulse8->new_rx_msg_len; 42162306a36Sopenharmony_ci spin_unlock_irqrestore(&pulse8->msg_lock, irq_flags); 42262306a36Sopenharmony_ci schedule_work(&pulse8->irq_work); 42362306a36Sopenharmony_ci pulse8->new_rx_msg_len = 0; 42462306a36Sopenharmony_ci break; 42562306a36Sopenharmony_ci case MSGCODE_TRANSMIT_SUCCEEDED: 42662306a36Sopenharmony_ci WARN_ON(pulse8->tx_done_status); 42762306a36Sopenharmony_ci pulse8->tx_done_status = CEC_TX_STATUS_OK; 42862306a36Sopenharmony_ci schedule_work(&pulse8->irq_work); 42962306a36Sopenharmony_ci break; 43062306a36Sopenharmony_ci case MSGCODE_TRANSMIT_FAILED_ACK: 43162306a36Sopenharmony_ci /* 43262306a36Sopenharmony_ci * A NACK for a broadcast message makes no sense, these 43362306a36Sopenharmony_ci * seem to be spurious messages and are skipped. 43462306a36Sopenharmony_ci */ 43562306a36Sopenharmony_ci if (pulse8->tx_msg_is_bcast) 43662306a36Sopenharmony_ci break; 43762306a36Sopenharmony_ci WARN_ON(pulse8->tx_done_status); 43862306a36Sopenharmony_ci pulse8->tx_done_status = CEC_TX_STATUS_NACK; 43962306a36Sopenharmony_ci schedule_work(&pulse8->irq_work); 44062306a36Sopenharmony_ci break; 44162306a36Sopenharmony_ci case MSGCODE_TRANSMIT_FAILED_LINE: 44262306a36Sopenharmony_ci case MSGCODE_TRANSMIT_FAILED_TIMEOUT_DATA: 44362306a36Sopenharmony_ci case MSGCODE_TRANSMIT_FAILED_TIMEOUT_LINE: 44462306a36Sopenharmony_ci WARN_ON(pulse8->tx_done_status); 44562306a36Sopenharmony_ci pulse8->tx_done_status = CEC_TX_STATUS_ERROR; 44662306a36Sopenharmony_ci schedule_work(&pulse8->irq_work); 44762306a36Sopenharmony_ci break; 44862306a36Sopenharmony_ci case MSGCODE_HIGH_ERROR: 44962306a36Sopenharmony_ci case MSGCODE_LOW_ERROR: 45062306a36Sopenharmony_ci case MSGCODE_RECEIVE_FAILED: 45162306a36Sopenharmony_ci case MSGCODE_TIMEOUT_ERROR: 45262306a36Sopenharmony_ci pulse8->new_rx_msg_len = 0; 45362306a36Sopenharmony_ci break; 45462306a36Sopenharmony_ci case MSGCODE_COMMAND_ACCEPTED: 45562306a36Sopenharmony_ci case MSGCODE_COMMAND_REJECTED: 45662306a36Sopenharmony_ci default: 45762306a36Sopenharmony_ci if (pulse8->idx == 0) 45862306a36Sopenharmony_ci break; 45962306a36Sopenharmony_ci memcpy(pulse8->data, pulse8->buf, pulse8->idx); 46062306a36Sopenharmony_ci pulse8->len = pulse8->idx; 46162306a36Sopenharmony_ci complete(&pulse8->cmd_done); 46262306a36Sopenharmony_ci break; 46362306a36Sopenharmony_ci } 46462306a36Sopenharmony_ci pulse8->idx = 0; 46562306a36Sopenharmony_ci pulse8->started = false; 46662306a36Sopenharmony_ci return IRQ_HANDLED; 46762306a36Sopenharmony_ci } else if (data == MSGSTART) { 46862306a36Sopenharmony_ci pulse8->idx = 0; 46962306a36Sopenharmony_ci pulse8->started = true; 47062306a36Sopenharmony_ci return IRQ_HANDLED; 47162306a36Sopenharmony_ci } 47262306a36Sopenharmony_ci 47362306a36Sopenharmony_ci if (pulse8->idx >= DATA_SIZE) { 47462306a36Sopenharmony_ci dev_dbg(pulse8->dev, 47562306a36Sopenharmony_ci "throwing away %d bytes of garbage\n", pulse8->idx); 47662306a36Sopenharmony_ci pulse8->idx = 0; 47762306a36Sopenharmony_ci } 47862306a36Sopenharmony_ci pulse8->buf[pulse8->idx++] = data; 47962306a36Sopenharmony_ci return IRQ_HANDLED; 48062306a36Sopenharmony_ci} 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_cistatic int pulse8_cec_adap_enable(struct cec_adapter *adap, bool enable) 48362306a36Sopenharmony_ci{ 48462306a36Sopenharmony_ci struct pulse8 *pulse8 = cec_get_drvdata(adap); 48562306a36Sopenharmony_ci u8 cmd[16]; 48662306a36Sopenharmony_ci int err; 48762306a36Sopenharmony_ci 48862306a36Sopenharmony_ci mutex_lock(&pulse8->lock); 48962306a36Sopenharmony_ci cmd[0] = MSGCODE_SET_CONTROLLED; 49062306a36Sopenharmony_ci cmd[1] = enable; 49162306a36Sopenharmony_ci err = pulse8_send_and_wait(pulse8, cmd, 2, 49262306a36Sopenharmony_ci MSGCODE_COMMAND_ACCEPTED, 1); 49362306a36Sopenharmony_ci if (!enable) { 49462306a36Sopenharmony_ci pulse8->rx_msg_num = 0; 49562306a36Sopenharmony_ci pulse8->tx_done_status = 0; 49662306a36Sopenharmony_ci } 49762306a36Sopenharmony_ci mutex_unlock(&pulse8->lock); 49862306a36Sopenharmony_ci return enable ? err : 0; 49962306a36Sopenharmony_ci} 50062306a36Sopenharmony_ci 50162306a36Sopenharmony_cistatic int pulse8_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr) 50262306a36Sopenharmony_ci{ 50362306a36Sopenharmony_ci struct pulse8 *pulse8 = cec_get_drvdata(adap); 50462306a36Sopenharmony_ci u16 mask = 0; 50562306a36Sopenharmony_ci u16 pa = adap->phys_addr; 50662306a36Sopenharmony_ci u8 cmd[16]; 50762306a36Sopenharmony_ci int err = 0; 50862306a36Sopenharmony_ci 50962306a36Sopenharmony_ci mutex_lock(&pulse8->lock); 51062306a36Sopenharmony_ci if (log_addr != CEC_LOG_ADDR_INVALID) 51162306a36Sopenharmony_ci mask = 1 << log_addr; 51262306a36Sopenharmony_ci cmd[0] = MSGCODE_SET_ACK_MASK; 51362306a36Sopenharmony_ci cmd[1] = mask >> 8; 51462306a36Sopenharmony_ci cmd[2] = mask & 0xff; 51562306a36Sopenharmony_ci err = pulse8_send_and_wait(pulse8, cmd, 3, 51662306a36Sopenharmony_ci MSGCODE_COMMAND_ACCEPTED, 0); 51762306a36Sopenharmony_ci if ((err && mask != 0) || pulse8->restoring_config) 51862306a36Sopenharmony_ci goto unlock; 51962306a36Sopenharmony_ci 52062306a36Sopenharmony_ci cmd[0] = MSGCODE_SET_AUTO_ENABLED; 52162306a36Sopenharmony_ci cmd[1] = log_addr == CEC_LOG_ADDR_INVALID ? 0 : 1; 52262306a36Sopenharmony_ci err = pulse8_send_and_wait(pulse8, cmd, 2, 52362306a36Sopenharmony_ci MSGCODE_COMMAND_ACCEPTED, 0); 52462306a36Sopenharmony_ci if (err) 52562306a36Sopenharmony_ci goto unlock; 52662306a36Sopenharmony_ci pulse8->autonomous = cmd[1]; 52762306a36Sopenharmony_ci if (log_addr == CEC_LOG_ADDR_INVALID) 52862306a36Sopenharmony_ci goto unlock; 52962306a36Sopenharmony_ci 53062306a36Sopenharmony_ci cmd[0] = MSGCODE_SET_DEVICE_TYPE; 53162306a36Sopenharmony_ci cmd[1] = adap->log_addrs.primary_device_type[0]; 53262306a36Sopenharmony_ci err = pulse8_send_and_wait(pulse8, cmd, 2, 53362306a36Sopenharmony_ci MSGCODE_COMMAND_ACCEPTED, 0); 53462306a36Sopenharmony_ci if (err) 53562306a36Sopenharmony_ci goto unlock; 53662306a36Sopenharmony_ci 53762306a36Sopenharmony_ci switch (adap->log_addrs.primary_device_type[0]) { 53862306a36Sopenharmony_ci case CEC_OP_PRIM_DEVTYPE_TV: 53962306a36Sopenharmony_ci mask = CEC_LOG_ADDR_MASK_TV; 54062306a36Sopenharmony_ci break; 54162306a36Sopenharmony_ci case CEC_OP_PRIM_DEVTYPE_RECORD: 54262306a36Sopenharmony_ci mask = CEC_LOG_ADDR_MASK_RECORD; 54362306a36Sopenharmony_ci break; 54462306a36Sopenharmony_ci case CEC_OP_PRIM_DEVTYPE_TUNER: 54562306a36Sopenharmony_ci mask = CEC_LOG_ADDR_MASK_TUNER; 54662306a36Sopenharmony_ci break; 54762306a36Sopenharmony_ci case CEC_OP_PRIM_DEVTYPE_PLAYBACK: 54862306a36Sopenharmony_ci mask = CEC_LOG_ADDR_MASK_PLAYBACK; 54962306a36Sopenharmony_ci break; 55062306a36Sopenharmony_ci case CEC_OP_PRIM_DEVTYPE_AUDIOSYSTEM: 55162306a36Sopenharmony_ci mask = CEC_LOG_ADDR_MASK_AUDIOSYSTEM; 55262306a36Sopenharmony_ci break; 55362306a36Sopenharmony_ci case CEC_OP_PRIM_DEVTYPE_SWITCH: 55462306a36Sopenharmony_ci mask = CEC_LOG_ADDR_MASK_UNREGISTERED; 55562306a36Sopenharmony_ci break; 55662306a36Sopenharmony_ci case CEC_OP_PRIM_DEVTYPE_PROCESSOR: 55762306a36Sopenharmony_ci mask = CEC_LOG_ADDR_MASK_SPECIFIC; 55862306a36Sopenharmony_ci break; 55962306a36Sopenharmony_ci default: 56062306a36Sopenharmony_ci mask = 0; 56162306a36Sopenharmony_ci break; 56262306a36Sopenharmony_ci } 56362306a36Sopenharmony_ci cmd[0] = MSGCODE_SET_LOGICAL_ADDRESS_MASK; 56462306a36Sopenharmony_ci cmd[1] = mask >> 8; 56562306a36Sopenharmony_ci cmd[2] = mask & 0xff; 56662306a36Sopenharmony_ci err = pulse8_send_and_wait(pulse8, cmd, 3, 56762306a36Sopenharmony_ci MSGCODE_COMMAND_ACCEPTED, 0); 56862306a36Sopenharmony_ci if (err) 56962306a36Sopenharmony_ci goto unlock; 57062306a36Sopenharmony_ci 57162306a36Sopenharmony_ci cmd[0] = MSGCODE_SET_DEFAULT_LOGICAL_ADDRESS; 57262306a36Sopenharmony_ci cmd[1] = log_addr; 57362306a36Sopenharmony_ci err = pulse8_send_and_wait(pulse8, cmd, 2, 57462306a36Sopenharmony_ci MSGCODE_COMMAND_ACCEPTED, 0); 57562306a36Sopenharmony_ci if (err) 57662306a36Sopenharmony_ci goto unlock; 57762306a36Sopenharmony_ci 57862306a36Sopenharmony_ci cmd[0] = MSGCODE_SET_PHYSICAL_ADDRESS; 57962306a36Sopenharmony_ci cmd[1] = pa >> 8; 58062306a36Sopenharmony_ci cmd[2] = pa & 0xff; 58162306a36Sopenharmony_ci err = pulse8_send_and_wait(pulse8, cmd, 3, 58262306a36Sopenharmony_ci MSGCODE_COMMAND_ACCEPTED, 0); 58362306a36Sopenharmony_ci if (err) 58462306a36Sopenharmony_ci goto unlock; 58562306a36Sopenharmony_ci 58662306a36Sopenharmony_ci if (pulse8->vers < 10) { 58762306a36Sopenharmony_ci cmd[0] = MSGCODE_SET_HDMI_VERSION; 58862306a36Sopenharmony_ci cmd[1] = adap->log_addrs.cec_version; 58962306a36Sopenharmony_ci err = pulse8_send_and_wait(pulse8, cmd, 2, 59062306a36Sopenharmony_ci MSGCODE_COMMAND_ACCEPTED, 0); 59162306a36Sopenharmony_ci if (err) 59262306a36Sopenharmony_ci goto unlock; 59362306a36Sopenharmony_ci } 59462306a36Sopenharmony_ci 59562306a36Sopenharmony_ci if (adap->log_addrs.osd_name[0]) { 59662306a36Sopenharmony_ci size_t osd_len = strlen(adap->log_addrs.osd_name); 59762306a36Sopenharmony_ci char *osd_str = cmd + 1; 59862306a36Sopenharmony_ci 59962306a36Sopenharmony_ci cmd[0] = MSGCODE_SET_OSD_NAME; 60062306a36Sopenharmony_ci strscpy(cmd + 1, adap->log_addrs.osd_name, sizeof(cmd) - 1); 60162306a36Sopenharmony_ci if (osd_len < 4) { 60262306a36Sopenharmony_ci memset(osd_str + osd_len, ' ', 4 - osd_len); 60362306a36Sopenharmony_ci osd_len = 4; 60462306a36Sopenharmony_ci osd_str[osd_len] = '\0'; 60562306a36Sopenharmony_ci strscpy(adap->log_addrs.osd_name, osd_str, 60662306a36Sopenharmony_ci sizeof(adap->log_addrs.osd_name)); 60762306a36Sopenharmony_ci } 60862306a36Sopenharmony_ci err = pulse8_send_and_wait(pulse8, cmd, 1 + osd_len, 60962306a36Sopenharmony_ci MSGCODE_COMMAND_ACCEPTED, 0); 61062306a36Sopenharmony_ci if (err) 61162306a36Sopenharmony_ci goto unlock; 61262306a36Sopenharmony_ci } 61362306a36Sopenharmony_ci 61462306a36Sopenharmony_ciunlock: 61562306a36Sopenharmony_ci if (pulse8->restoring_config) 61662306a36Sopenharmony_ci pulse8->restoring_config = false; 61762306a36Sopenharmony_ci else 61862306a36Sopenharmony_ci pulse8->config_pending = true; 61962306a36Sopenharmony_ci mutex_unlock(&pulse8->lock); 62062306a36Sopenharmony_ci return log_addr == CEC_LOG_ADDR_INVALID ? 0 : err; 62162306a36Sopenharmony_ci} 62262306a36Sopenharmony_ci 62362306a36Sopenharmony_cistatic int pulse8_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, 62462306a36Sopenharmony_ci u32 signal_free_time, struct cec_msg *msg) 62562306a36Sopenharmony_ci{ 62662306a36Sopenharmony_ci struct pulse8 *pulse8 = cec_get_drvdata(adap); 62762306a36Sopenharmony_ci 62862306a36Sopenharmony_ci pulse8->tx_msg = *msg; 62962306a36Sopenharmony_ci if (debug) 63062306a36Sopenharmony_ci dev_info(pulse8->dev, "adap transmit %*ph\n", 63162306a36Sopenharmony_ci msg->len, msg->msg); 63262306a36Sopenharmony_ci pulse8->tx_signal_free_time = signal_free_time; 63362306a36Sopenharmony_ci schedule_work(&pulse8->tx_work); 63462306a36Sopenharmony_ci return 0; 63562306a36Sopenharmony_ci} 63662306a36Sopenharmony_ci 63762306a36Sopenharmony_cistatic void pulse8_cec_adap_free(struct cec_adapter *adap) 63862306a36Sopenharmony_ci{ 63962306a36Sopenharmony_ci struct pulse8 *pulse8 = cec_get_drvdata(adap); 64062306a36Sopenharmony_ci 64162306a36Sopenharmony_ci cancel_delayed_work_sync(&pulse8->ping_eeprom_work); 64262306a36Sopenharmony_ci cancel_work_sync(&pulse8->irq_work); 64362306a36Sopenharmony_ci cancel_work_sync(&pulse8->tx_work); 64462306a36Sopenharmony_ci kfree(pulse8); 64562306a36Sopenharmony_ci} 64662306a36Sopenharmony_ci 64762306a36Sopenharmony_cistatic const struct cec_adap_ops pulse8_cec_adap_ops = { 64862306a36Sopenharmony_ci .adap_enable = pulse8_cec_adap_enable, 64962306a36Sopenharmony_ci .adap_log_addr = pulse8_cec_adap_log_addr, 65062306a36Sopenharmony_ci .adap_transmit = pulse8_cec_adap_transmit, 65162306a36Sopenharmony_ci .adap_free = pulse8_cec_adap_free, 65262306a36Sopenharmony_ci}; 65362306a36Sopenharmony_ci 65462306a36Sopenharmony_cistatic void pulse8_disconnect(struct serio *serio) 65562306a36Sopenharmony_ci{ 65662306a36Sopenharmony_ci struct pulse8 *pulse8 = serio_get_drvdata(serio); 65762306a36Sopenharmony_ci 65862306a36Sopenharmony_ci cec_unregister_adapter(pulse8->adap); 65962306a36Sopenharmony_ci serio_set_drvdata(serio, NULL); 66062306a36Sopenharmony_ci serio_close(serio); 66162306a36Sopenharmony_ci} 66262306a36Sopenharmony_ci 66362306a36Sopenharmony_cistatic int pulse8_setup(struct pulse8 *pulse8, struct serio *serio, 66462306a36Sopenharmony_ci struct cec_log_addrs *log_addrs, u16 *pa) 66562306a36Sopenharmony_ci{ 66662306a36Sopenharmony_ci u8 *data = pulse8->data + 1; 66762306a36Sopenharmony_ci u8 cmd[2]; 66862306a36Sopenharmony_ci int err; 66962306a36Sopenharmony_ci time64_t date; 67062306a36Sopenharmony_ci 67162306a36Sopenharmony_ci pulse8->vers = 0; 67262306a36Sopenharmony_ci 67362306a36Sopenharmony_ci cmd[0] = MSGCODE_FIRMWARE_VERSION; 67462306a36Sopenharmony_ci err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 2); 67562306a36Sopenharmony_ci if (err) 67662306a36Sopenharmony_ci return err; 67762306a36Sopenharmony_ci pulse8->vers = (data[0] << 8) | data[1]; 67862306a36Sopenharmony_ci dev_info(pulse8->dev, "Firmware version %04x\n", pulse8->vers); 67962306a36Sopenharmony_ci if (pulse8->vers < 2) { 68062306a36Sopenharmony_ci *pa = CEC_PHYS_ADDR_INVALID; 68162306a36Sopenharmony_ci return 0; 68262306a36Sopenharmony_ci } 68362306a36Sopenharmony_ci 68462306a36Sopenharmony_ci cmd[0] = MSGCODE_GET_BUILDDATE; 68562306a36Sopenharmony_ci err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 4); 68662306a36Sopenharmony_ci if (err) 68762306a36Sopenharmony_ci return err; 68862306a36Sopenharmony_ci date = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]; 68962306a36Sopenharmony_ci dev_info(pulse8->dev, "Firmware build date %ptT\n", &date); 69062306a36Sopenharmony_ci 69162306a36Sopenharmony_ci dev_dbg(pulse8->dev, "Persistent config:\n"); 69262306a36Sopenharmony_ci cmd[0] = MSGCODE_GET_AUTO_ENABLED; 69362306a36Sopenharmony_ci err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 1); 69462306a36Sopenharmony_ci if (err) 69562306a36Sopenharmony_ci return err; 69662306a36Sopenharmony_ci pulse8->autonomous = data[0]; 69762306a36Sopenharmony_ci dev_dbg(pulse8->dev, "Autonomous mode: %s", 69862306a36Sopenharmony_ci data[0] ? "on" : "off"); 69962306a36Sopenharmony_ci 70062306a36Sopenharmony_ci if (pulse8->vers >= 10) { 70162306a36Sopenharmony_ci cmd[0] = MSGCODE_GET_AUTO_POWER_ON; 70262306a36Sopenharmony_ci err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 1); 70362306a36Sopenharmony_ci if (!err) 70462306a36Sopenharmony_ci dev_dbg(pulse8->dev, "Auto Power On: %s", 70562306a36Sopenharmony_ci data[0] ? "on" : "off"); 70662306a36Sopenharmony_ci } 70762306a36Sopenharmony_ci 70862306a36Sopenharmony_ci cmd[0] = MSGCODE_GET_DEVICE_TYPE; 70962306a36Sopenharmony_ci err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 1); 71062306a36Sopenharmony_ci if (err) 71162306a36Sopenharmony_ci return err; 71262306a36Sopenharmony_ci log_addrs->primary_device_type[0] = data[0]; 71362306a36Sopenharmony_ci dev_dbg(pulse8->dev, "Primary device type: %d\n", data[0]); 71462306a36Sopenharmony_ci switch (log_addrs->primary_device_type[0]) { 71562306a36Sopenharmony_ci case CEC_OP_PRIM_DEVTYPE_TV: 71662306a36Sopenharmony_ci log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_TV; 71762306a36Sopenharmony_ci log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_TV; 71862306a36Sopenharmony_ci break; 71962306a36Sopenharmony_ci case CEC_OP_PRIM_DEVTYPE_RECORD: 72062306a36Sopenharmony_ci log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_RECORD; 72162306a36Sopenharmony_ci log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_RECORD; 72262306a36Sopenharmony_ci break; 72362306a36Sopenharmony_ci case CEC_OP_PRIM_DEVTYPE_TUNER: 72462306a36Sopenharmony_ci log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_TUNER; 72562306a36Sopenharmony_ci log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_TUNER; 72662306a36Sopenharmony_ci break; 72762306a36Sopenharmony_ci case CEC_OP_PRIM_DEVTYPE_PLAYBACK: 72862306a36Sopenharmony_ci log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_PLAYBACK; 72962306a36Sopenharmony_ci log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_PLAYBACK; 73062306a36Sopenharmony_ci break; 73162306a36Sopenharmony_ci case CEC_OP_PRIM_DEVTYPE_AUDIOSYSTEM: 73262306a36Sopenharmony_ci log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_PLAYBACK; 73362306a36Sopenharmony_ci log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_AUDIOSYSTEM; 73462306a36Sopenharmony_ci break; 73562306a36Sopenharmony_ci case CEC_OP_PRIM_DEVTYPE_SWITCH: 73662306a36Sopenharmony_ci log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_UNREGISTERED; 73762306a36Sopenharmony_ci log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_SWITCH; 73862306a36Sopenharmony_ci break; 73962306a36Sopenharmony_ci case CEC_OP_PRIM_DEVTYPE_PROCESSOR: 74062306a36Sopenharmony_ci log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_SPECIFIC; 74162306a36Sopenharmony_ci log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_SWITCH; 74262306a36Sopenharmony_ci break; 74362306a36Sopenharmony_ci default: 74462306a36Sopenharmony_ci log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_UNREGISTERED; 74562306a36Sopenharmony_ci log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_SWITCH; 74662306a36Sopenharmony_ci dev_info(pulse8->dev, "Unknown Primary Device Type: %d\n", 74762306a36Sopenharmony_ci log_addrs->primary_device_type[0]); 74862306a36Sopenharmony_ci break; 74962306a36Sopenharmony_ci } 75062306a36Sopenharmony_ci 75162306a36Sopenharmony_ci cmd[0] = MSGCODE_GET_LOGICAL_ADDRESS_MASK; 75262306a36Sopenharmony_ci err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 2); 75362306a36Sopenharmony_ci if (err) 75462306a36Sopenharmony_ci return err; 75562306a36Sopenharmony_ci log_addrs->log_addr_mask = (data[0] << 8) | data[1]; 75662306a36Sopenharmony_ci dev_dbg(pulse8->dev, "Logical address ACK mask: %x\n", 75762306a36Sopenharmony_ci log_addrs->log_addr_mask); 75862306a36Sopenharmony_ci if (log_addrs->log_addr_mask) 75962306a36Sopenharmony_ci log_addrs->num_log_addrs = 1; 76062306a36Sopenharmony_ci 76162306a36Sopenharmony_ci cmd[0] = MSGCODE_GET_PHYSICAL_ADDRESS; 76262306a36Sopenharmony_ci err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 1); 76362306a36Sopenharmony_ci if (err) 76462306a36Sopenharmony_ci return err; 76562306a36Sopenharmony_ci *pa = (data[0] << 8) | data[1]; 76662306a36Sopenharmony_ci dev_dbg(pulse8->dev, "Physical address: %x.%x.%x.%x\n", 76762306a36Sopenharmony_ci cec_phys_addr_exp(*pa)); 76862306a36Sopenharmony_ci 76962306a36Sopenharmony_ci log_addrs->cec_version = CEC_OP_CEC_VERSION_1_4; 77062306a36Sopenharmony_ci if (pulse8->vers < 10) { 77162306a36Sopenharmony_ci cmd[0] = MSGCODE_GET_HDMI_VERSION; 77262306a36Sopenharmony_ci err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 1); 77362306a36Sopenharmony_ci if (err) 77462306a36Sopenharmony_ci return err; 77562306a36Sopenharmony_ci log_addrs->cec_version = data[0]; 77662306a36Sopenharmony_ci dev_dbg(pulse8->dev, "CEC version: %d\n", log_addrs->cec_version); 77762306a36Sopenharmony_ci } 77862306a36Sopenharmony_ci 77962306a36Sopenharmony_ci cmd[0] = MSGCODE_GET_OSD_NAME; 78062306a36Sopenharmony_ci err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 0); 78162306a36Sopenharmony_ci if (err) 78262306a36Sopenharmony_ci return err; 78362306a36Sopenharmony_ci strscpy(log_addrs->osd_name, data, sizeof(log_addrs->osd_name)); 78462306a36Sopenharmony_ci dev_dbg(pulse8->dev, "OSD name: %s\n", log_addrs->osd_name); 78562306a36Sopenharmony_ci 78662306a36Sopenharmony_ci return 0; 78762306a36Sopenharmony_ci} 78862306a36Sopenharmony_ci 78962306a36Sopenharmony_cistatic int pulse8_apply_persistent_config(struct pulse8 *pulse8, 79062306a36Sopenharmony_ci struct cec_log_addrs *log_addrs, 79162306a36Sopenharmony_ci u16 pa) 79262306a36Sopenharmony_ci{ 79362306a36Sopenharmony_ci int err; 79462306a36Sopenharmony_ci 79562306a36Sopenharmony_ci err = cec_s_log_addrs(pulse8->adap, log_addrs, false); 79662306a36Sopenharmony_ci if (err) 79762306a36Sopenharmony_ci return err; 79862306a36Sopenharmony_ci 79962306a36Sopenharmony_ci cec_s_phys_addr(pulse8->adap, pa, false); 80062306a36Sopenharmony_ci 80162306a36Sopenharmony_ci return 0; 80262306a36Sopenharmony_ci} 80362306a36Sopenharmony_ci 80462306a36Sopenharmony_cistatic void pulse8_ping_eeprom_work_handler(struct work_struct *work) 80562306a36Sopenharmony_ci{ 80662306a36Sopenharmony_ci struct pulse8 *pulse8 = 80762306a36Sopenharmony_ci container_of(work, struct pulse8, ping_eeprom_work.work); 80862306a36Sopenharmony_ci u8 cmd; 80962306a36Sopenharmony_ci 81062306a36Sopenharmony_ci mutex_lock(&pulse8->lock); 81162306a36Sopenharmony_ci cmd = MSGCODE_PING; 81262306a36Sopenharmony_ci if (pulse8_send_and_wait(pulse8, &cmd, 1, 81362306a36Sopenharmony_ci MSGCODE_COMMAND_ACCEPTED, 0)) { 81462306a36Sopenharmony_ci dev_warn(pulse8->dev, "failed to ping EEPROM\n"); 81562306a36Sopenharmony_ci goto unlock; 81662306a36Sopenharmony_ci } 81762306a36Sopenharmony_ci 81862306a36Sopenharmony_ci if (pulse8->vers < 2) 81962306a36Sopenharmony_ci goto unlock; 82062306a36Sopenharmony_ci 82162306a36Sopenharmony_ci if (pulse8->config_pending && persistent_config) { 82262306a36Sopenharmony_ci dev_dbg(pulse8->dev, "writing pending config to EEPROM\n"); 82362306a36Sopenharmony_ci cmd = MSGCODE_WRITE_EEPROM; 82462306a36Sopenharmony_ci if (pulse8_send_and_wait(pulse8, &cmd, 1, 82562306a36Sopenharmony_ci MSGCODE_COMMAND_ACCEPTED, 0)) 82662306a36Sopenharmony_ci dev_info(pulse8->dev, "failed to write pending config to EEPROM\n"); 82762306a36Sopenharmony_ci else 82862306a36Sopenharmony_ci pulse8->config_pending = false; 82962306a36Sopenharmony_ci } 83062306a36Sopenharmony_ciunlock: 83162306a36Sopenharmony_ci schedule_delayed_work(&pulse8->ping_eeprom_work, PING_PERIOD); 83262306a36Sopenharmony_ci mutex_unlock(&pulse8->lock); 83362306a36Sopenharmony_ci} 83462306a36Sopenharmony_ci 83562306a36Sopenharmony_cistatic int pulse8_connect(struct serio *serio, struct serio_driver *drv) 83662306a36Sopenharmony_ci{ 83762306a36Sopenharmony_ci u32 caps = CEC_CAP_DEFAULTS | CEC_CAP_PHYS_ADDR | CEC_CAP_MONITOR_ALL; 83862306a36Sopenharmony_ci struct pulse8 *pulse8; 83962306a36Sopenharmony_ci int err = -ENOMEM; 84062306a36Sopenharmony_ci struct cec_log_addrs log_addrs = {}; 84162306a36Sopenharmony_ci u16 pa = CEC_PHYS_ADDR_INVALID; 84262306a36Sopenharmony_ci 84362306a36Sopenharmony_ci pulse8 = kzalloc(sizeof(*pulse8), GFP_KERNEL); 84462306a36Sopenharmony_ci 84562306a36Sopenharmony_ci if (!pulse8) 84662306a36Sopenharmony_ci return -ENOMEM; 84762306a36Sopenharmony_ci 84862306a36Sopenharmony_ci pulse8->serio = serio; 84962306a36Sopenharmony_ci pulse8->adap = cec_allocate_adapter(&pulse8_cec_adap_ops, pulse8, 85062306a36Sopenharmony_ci dev_name(&serio->dev), caps, 1); 85162306a36Sopenharmony_ci err = PTR_ERR_OR_ZERO(pulse8->adap); 85262306a36Sopenharmony_ci if (err < 0) { 85362306a36Sopenharmony_ci kfree(pulse8); 85462306a36Sopenharmony_ci return err; 85562306a36Sopenharmony_ci } 85662306a36Sopenharmony_ci 85762306a36Sopenharmony_ci pulse8->dev = &serio->dev; 85862306a36Sopenharmony_ci serio_set_drvdata(serio, pulse8); 85962306a36Sopenharmony_ci INIT_WORK(&pulse8->irq_work, pulse8_irq_work_handler); 86062306a36Sopenharmony_ci INIT_WORK(&pulse8->tx_work, pulse8_tx_work_handler); 86162306a36Sopenharmony_ci INIT_DELAYED_WORK(&pulse8->ping_eeprom_work, 86262306a36Sopenharmony_ci pulse8_ping_eeprom_work_handler); 86362306a36Sopenharmony_ci mutex_init(&pulse8->lock); 86462306a36Sopenharmony_ci spin_lock_init(&pulse8->msg_lock); 86562306a36Sopenharmony_ci pulse8->config_pending = false; 86662306a36Sopenharmony_ci 86762306a36Sopenharmony_ci err = serio_open(serio, drv); 86862306a36Sopenharmony_ci if (err) 86962306a36Sopenharmony_ci goto delete_adap; 87062306a36Sopenharmony_ci 87162306a36Sopenharmony_ci err = pulse8_setup(pulse8, serio, &log_addrs, &pa); 87262306a36Sopenharmony_ci if (err) 87362306a36Sopenharmony_ci goto close_serio; 87462306a36Sopenharmony_ci 87562306a36Sopenharmony_ci err = cec_register_adapter(pulse8->adap, &serio->dev); 87662306a36Sopenharmony_ci if (err < 0) 87762306a36Sopenharmony_ci goto close_serio; 87862306a36Sopenharmony_ci 87962306a36Sopenharmony_ci pulse8->dev = &pulse8->adap->devnode.dev; 88062306a36Sopenharmony_ci 88162306a36Sopenharmony_ci if (persistent_config && pulse8->autonomous) { 88262306a36Sopenharmony_ci err = pulse8_apply_persistent_config(pulse8, &log_addrs, pa); 88362306a36Sopenharmony_ci if (err) 88462306a36Sopenharmony_ci goto close_serio; 88562306a36Sopenharmony_ci pulse8->restoring_config = true; 88662306a36Sopenharmony_ci } 88762306a36Sopenharmony_ci 88862306a36Sopenharmony_ci schedule_delayed_work(&pulse8->ping_eeprom_work, PING_PERIOD); 88962306a36Sopenharmony_ci 89062306a36Sopenharmony_ci return 0; 89162306a36Sopenharmony_ci 89262306a36Sopenharmony_ciclose_serio: 89362306a36Sopenharmony_ci pulse8->serio = NULL; 89462306a36Sopenharmony_ci serio_set_drvdata(serio, NULL); 89562306a36Sopenharmony_ci serio_close(serio); 89662306a36Sopenharmony_cidelete_adap: 89762306a36Sopenharmony_ci cec_delete_adapter(pulse8->adap); 89862306a36Sopenharmony_ci return err; 89962306a36Sopenharmony_ci} 90062306a36Sopenharmony_ci 90162306a36Sopenharmony_cistatic const struct serio_device_id pulse8_serio_ids[] = { 90262306a36Sopenharmony_ci { 90362306a36Sopenharmony_ci .type = SERIO_RS232, 90462306a36Sopenharmony_ci .proto = SERIO_PULSE8_CEC, 90562306a36Sopenharmony_ci .id = SERIO_ANY, 90662306a36Sopenharmony_ci .extra = SERIO_ANY, 90762306a36Sopenharmony_ci }, 90862306a36Sopenharmony_ci { 0 } 90962306a36Sopenharmony_ci}; 91062306a36Sopenharmony_ci 91162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(serio, pulse8_serio_ids); 91262306a36Sopenharmony_ci 91362306a36Sopenharmony_cistatic struct serio_driver pulse8_drv = { 91462306a36Sopenharmony_ci .driver = { 91562306a36Sopenharmony_ci .name = "pulse8-cec", 91662306a36Sopenharmony_ci }, 91762306a36Sopenharmony_ci .description = "Pulse Eight HDMI CEC driver", 91862306a36Sopenharmony_ci .id_table = pulse8_serio_ids, 91962306a36Sopenharmony_ci .interrupt = pulse8_interrupt, 92062306a36Sopenharmony_ci .connect = pulse8_connect, 92162306a36Sopenharmony_ci .disconnect = pulse8_disconnect, 92262306a36Sopenharmony_ci}; 92362306a36Sopenharmony_ci 92462306a36Sopenharmony_cimodule_serio_driver(pulse8_drv); 925