--- /dev/null
- * Copyright 2016 Russell King <rmk+kernel@arm.linux.org.uk>
+ // SPDX-License-Identifier: GPL-2.0-only
+ /*
+ * cec-notifier.c - notify CEC drivers of physical address changes
+ *
++ * Copyright 2016 Russell King.
+ * Copyright 2016-2017 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
+ */
+
+ #include <linux/export.h>
+ #include <linux/string.h>
+ #include <linux/slab.h>
+ #include <linux/list.h>
+ #include <linux/kref.h>
+ #include <linux/of_platform.h>
+
+ #include <media/cec.h>
+ #include <media/cec-notifier.h>
+ #include <drm/drm_edid.h>
+
+ struct cec_notifier {
+ struct mutex lock;
+ struct list_head head;
+ struct kref kref;
+ struct device *hdmi_dev;
+ struct cec_connector_info conn_info;
+ const char *port_name;
+ struct cec_adapter *cec_adap;
+
+ u16 phys_addr;
+ };
+
+ static LIST_HEAD(cec_notifiers);
+ static DEFINE_MUTEX(cec_notifiers_lock);
+
+ /**
+ * cec_notifier_get_conn - find or create a new cec_notifier for the given
+ * device and connector tuple.
+ * @hdmi_dev: device that sends the events.
+ * @port_name: the connector name from which the event occurs
+ *
+ * If a notifier for device @dev already exists, then increase the refcount
+ * and return that notifier.
+ *
+ * If it doesn't exist, then allocate a new notifier struct and return a
+ * pointer to that new struct.
+ *
+ * Return NULL if the memory could not be allocated.
+ */
+ static struct cec_notifier *
+ cec_notifier_get_conn(struct device *hdmi_dev, const char *port_name)
+ {
+ struct cec_notifier *n;
+
+ mutex_lock(&cec_notifiers_lock);
+ list_for_each_entry(n, &cec_notifiers, head) {
+ if (n->hdmi_dev == hdmi_dev &&
+ (!port_name ||
+ (n->port_name && !strcmp(n->port_name, port_name)))) {
+ kref_get(&n->kref);
+ mutex_unlock(&cec_notifiers_lock);
+ return n;
+ }
+ }
+ n = kzalloc(sizeof(*n), GFP_KERNEL);
+ if (!n)
+ goto unlock;
+ n->hdmi_dev = hdmi_dev;
+ if (port_name) {
+ n->port_name = kstrdup(port_name, GFP_KERNEL);
+ if (!n->port_name) {
+ kfree(n);
+ n = NULL;
+ goto unlock;
+ }
+ }
+ n->phys_addr = CEC_PHYS_ADDR_INVALID;
+
+ mutex_init(&n->lock);
+ kref_init(&n->kref);
+ list_add_tail(&n->head, &cec_notifiers);
+ unlock:
+ mutex_unlock(&cec_notifiers_lock);
+ return n;
+ }
+
+ static void cec_notifier_release(struct kref *kref)
+ {
+ struct cec_notifier *n =
+ container_of(kref, struct cec_notifier, kref);
+
+ list_del(&n->head);
+ kfree(n->port_name);
+ kfree(n);
+ }
+
+ static void cec_notifier_put(struct cec_notifier *n)
+ {
+ mutex_lock(&cec_notifiers_lock);
+ kref_put(&n->kref, cec_notifier_release);
+ mutex_unlock(&cec_notifiers_lock);
+ }
+
+ struct cec_notifier *
+ cec_notifier_conn_register(struct device *hdmi_dev, const char *port_name,
+ const struct cec_connector_info *conn_info)
+ {
+ struct cec_notifier *n = cec_notifier_get_conn(hdmi_dev, port_name);
+
+ if (!n)
+ return n;
+
+ mutex_lock(&n->lock);
+ n->phys_addr = CEC_PHYS_ADDR_INVALID;
+ if (conn_info)
+ n->conn_info = *conn_info;
+ else
+ memset(&n->conn_info, 0, sizeof(n->conn_info));
+ if (n->cec_adap) {
+ cec_phys_addr_invalidate(n->cec_adap);
+ cec_s_conn_info(n->cec_adap, conn_info);
+ }
+ mutex_unlock(&n->lock);
+ return n;
+ }
+ EXPORT_SYMBOL_GPL(cec_notifier_conn_register);
+
+ void cec_notifier_conn_unregister(struct cec_notifier *n)
+ {
+ if (!n)
+ return;
+
+ mutex_lock(&n->lock);
+ memset(&n->conn_info, 0, sizeof(n->conn_info));
+ n->phys_addr = CEC_PHYS_ADDR_INVALID;
+ if (n->cec_adap) {
+ cec_phys_addr_invalidate(n->cec_adap);
+ cec_s_conn_info(n->cec_adap, NULL);
+ }
+ mutex_unlock(&n->lock);
+ cec_notifier_put(n);
+ }
+ EXPORT_SYMBOL_GPL(cec_notifier_conn_unregister);
+
+ struct cec_notifier *
+ cec_notifier_cec_adap_register(struct device *hdmi_dev, const char *port_name,
+ struct cec_adapter *adap)
+ {
+ struct cec_notifier *n;
+
+ if (WARN_ON(!adap))
+ return NULL;
+
+ n = cec_notifier_get_conn(hdmi_dev, port_name);
+ if (!n)
+ return n;
+
+ mutex_lock(&n->lock);
+ n->cec_adap = adap;
+ adap->conn_info = n->conn_info;
+ adap->notifier = n;
+ cec_s_phys_addr(adap, n->phys_addr, false);
+ mutex_unlock(&n->lock);
+ return n;
+ }
+ EXPORT_SYMBOL_GPL(cec_notifier_cec_adap_register);
+
+ void cec_notifier_cec_adap_unregister(struct cec_notifier *n,
+ struct cec_adapter *adap)
+ {
+ if (!n)
+ return;
+
+ mutex_lock(&n->lock);
+ adap->notifier = NULL;
+ n->cec_adap = NULL;
+ mutex_unlock(&n->lock);
+ cec_notifier_put(n);
+ }
+ EXPORT_SYMBOL_GPL(cec_notifier_cec_adap_unregister);
+
+ void cec_notifier_set_phys_addr(struct cec_notifier *n, u16 pa)
+ {
+ if (n == NULL)
+ return;
+
+ mutex_lock(&n->lock);
+ n->phys_addr = pa;
+ if (n->cec_adap)
+ cec_s_phys_addr(n->cec_adap, n->phys_addr, false);
+ mutex_unlock(&n->lock);
+ }
+ EXPORT_SYMBOL_GPL(cec_notifier_set_phys_addr);
+
+ void cec_notifier_set_phys_addr_from_edid(struct cec_notifier *n,
+ const struct edid *edid)
+ {
+ u16 pa = CEC_PHYS_ADDR_INVALID;
+
+ if (n == NULL)
+ return;
+
+ if (edid && edid->extensions)
+ pa = cec_get_edid_phys_addr((const u8 *)edid,
+ EDID_LENGTH * (edid->extensions + 1), NULL);
+ cec_notifier_set_phys_addr(n, pa);
+ }
+ EXPORT_SYMBOL_GPL(cec_notifier_set_phys_addr_from_edid);
+
+ struct device *cec_notifier_parse_hdmi_phandle(struct device *dev)
+ {
+ struct platform_device *hdmi_pdev;
+ struct device *hdmi_dev = NULL;
+ struct device_node *np;
+
+ np = of_parse_phandle(dev->of_node, "hdmi-phandle", 0);
+
+ if (!np) {
+ dev_err(dev, "Failed to find HDMI node in device tree\n");
+ return ERR_PTR(-ENODEV);
+ }
+ hdmi_pdev = of_find_device_by_node(np);
+ of_node_put(np);
+ if (hdmi_pdev) {
+ hdmi_dev = &hdmi_pdev->dev;
+ /*
+ * Note that the device struct is only used as a key into the
+ * cec_notifiers list, it is never actually accessed.
+ * So we decrement the reference here so we don't leak
+ * memory.
+ */
+ put_device(hdmi_dev);
+ return hdmi_dev;
+ }
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+ EXPORT_SYMBOL_GPL(cec_notifier_parse_hdmi_phandle);
--- /dev/null
- struct tm tm;
+ // SPDX-License-Identifier: GPL-2.0-or-later
+ /*
+ * Pulse Eight HDMI CEC driver
+ *
+ * Copyright 2016 Hans Verkuil <hverkuil@xs4all.nl
+ */
+
+ /*
+ * Notes:
+ *
+ * - Devices with firmware version < 2 do not store their configuration in
+ * EEPROM.
+ *
+ * - In autonomous mode, only messages from a TV will be acknowledged, even
+ * polling messages. Upon receiving a message from a TV, the dongle will
+ * respond to messages from any logical address.
+ *
+ * - In autonomous mode, the dongle will by default reply Feature Abort
+ * [Unrecognized Opcode] when it receives Give Device Vendor ID. It will
+ * however observe vendor ID's reported by other devices and possibly
+ * alter this behavior. When TV's (and TV's only) report that their vendor ID
+ * is LG (0x00e091), the dongle will itself reply that it has the same vendor
+ * ID, and it will respond to at least one vendor specific command.
+ *
+ * - In autonomous mode, the dongle is known to attempt wakeup if it receives
+ * <User Control Pressed> ["Power On"], ["Power] or ["Power Toggle"], or if it
+ * receives <Set Stream Path> with its own physical address. It also does this
+ * if it receives <Vendor Specific Command> [0x03 0x00] from an LG TV.
+ */
+
+ #include <linux/completion.h>
+ #include <linux/init.h>
+ #include <linux/interrupt.h>
+ #include <linux/kernel.h>
+ #include <linux/module.h>
+ #include <linux/workqueue.h>
+ #include <linux/serio.h>
+ #include <linux/slab.h>
+ #include <linux/time.h>
+ #include <linux/delay.h>
+
+ #include <media/cec.h>
+
+ MODULE_AUTHOR("Hans Verkuil <hverkuil@xs4all.nl>");
+ MODULE_DESCRIPTION("Pulse Eight HDMI CEC driver");
+ MODULE_LICENSE("GPL");
+
+ static int debug;
+ static int persistent_config;
+ module_param(debug, int, 0644);
+ module_param(persistent_config, int, 0644);
+ MODULE_PARM_DESC(debug, "debug level (0-2)");
+ MODULE_PARM_DESC(persistent_config, "read config from persistent memory (0-1)");
+
+ enum pulse8_msgcodes {
+ MSGCODE_NOTHING = 0,
+ MSGCODE_PING,
+ MSGCODE_TIMEOUT_ERROR,
+ MSGCODE_HIGH_ERROR,
+ MSGCODE_LOW_ERROR,
+ MSGCODE_FRAME_START,
+ MSGCODE_FRAME_DATA,
+ MSGCODE_RECEIVE_FAILED,
+ MSGCODE_COMMAND_ACCEPTED, /* 0x08 */
+ MSGCODE_COMMAND_REJECTED,
+ MSGCODE_SET_ACK_MASK,
+ MSGCODE_TRANSMIT,
+ MSGCODE_TRANSMIT_EOM,
+ MSGCODE_TRANSMIT_IDLETIME,
+ MSGCODE_TRANSMIT_ACK_POLARITY,
+ MSGCODE_TRANSMIT_LINE_TIMEOUT,
+ MSGCODE_TRANSMIT_SUCCEEDED, /* 0x10 */
+ MSGCODE_TRANSMIT_FAILED_LINE,
+ MSGCODE_TRANSMIT_FAILED_ACK,
+ MSGCODE_TRANSMIT_FAILED_TIMEOUT_DATA,
+ MSGCODE_TRANSMIT_FAILED_TIMEOUT_LINE,
+ MSGCODE_FIRMWARE_VERSION,
+ MSGCODE_START_BOOTLOADER,
+ MSGCODE_GET_BUILDDATE,
+ MSGCODE_SET_CONTROLLED, /* 0x18 */
+ MSGCODE_GET_AUTO_ENABLED,
+ MSGCODE_SET_AUTO_ENABLED,
+ MSGCODE_GET_DEFAULT_LOGICAL_ADDRESS,
+ MSGCODE_SET_DEFAULT_LOGICAL_ADDRESS,
+ MSGCODE_GET_LOGICAL_ADDRESS_MASK,
+ MSGCODE_SET_LOGICAL_ADDRESS_MASK,
+ MSGCODE_GET_PHYSICAL_ADDRESS,
+ MSGCODE_SET_PHYSICAL_ADDRESS, /* 0x20 */
+ MSGCODE_GET_DEVICE_TYPE,
+ MSGCODE_SET_DEVICE_TYPE,
+ MSGCODE_GET_HDMI_VERSION,
+ MSGCODE_SET_HDMI_VERSION,
+ MSGCODE_GET_OSD_NAME,
+ MSGCODE_SET_OSD_NAME,
+ MSGCODE_WRITE_EEPROM,
+ MSGCODE_GET_ADAPTER_TYPE, /* 0x28 */
+ MSGCODE_SET_ACTIVE_SOURCE,
+
+ MSGCODE_FRAME_EOM = 0x80,
+ MSGCODE_FRAME_ACK = 0x40,
+ };
+
+ static const char * const pulse8_msgnames[] = {
+ "NOTHING",
+ "PING",
+ "TIMEOUT_ERROR",
+ "HIGH_ERROR",
+ "LOW_ERROR",
+ "FRAME_START",
+ "FRAME_DATA",
+ "RECEIVE_FAILED",
+ "COMMAND_ACCEPTED",
+ "COMMAND_REJECTED",
+ "SET_ACK_MASK",
+ "TRANSMIT",
+ "TRANSMIT_EOM",
+ "TRANSMIT_IDLETIME",
+ "TRANSMIT_ACK_POLARITY",
+ "TRANSMIT_LINE_TIMEOUT",
+ "TRANSMIT_SUCCEEDED",
+ "TRANSMIT_FAILED_LINE",
+ "TRANSMIT_FAILED_ACK",
+ "TRANSMIT_FAILED_TIMEOUT_DATA",
+ "TRANSMIT_FAILED_TIMEOUT_LINE",
+ "FIRMWARE_VERSION",
+ "START_BOOTLOADER",
+ "GET_BUILDDATE",
+ "SET_CONTROLLED",
+ "GET_AUTO_ENABLED",
+ "SET_AUTO_ENABLED",
+ "GET_DEFAULT_LOGICAL_ADDRESS",
+ "SET_DEFAULT_LOGICAL_ADDRESS",
+ "GET_LOGICAL_ADDRESS_MASK",
+ "SET_LOGICAL_ADDRESS_MASK",
+ "GET_PHYSICAL_ADDRESS",
+ "SET_PHYSICAL_ADDRESS",
+ "GET_DEVICE_TYPE",
+ "SET_DEVICE_TYPE",
+ "GET_HDMI_VERSION",
+ "SET_HDMI_VERSION",
+ "GET_OSD_NAME",
+ "SET_OSD_NAME",
+ "WRITE_EEPROM",
+ "GET_ADAPTER_TYPE",
+ "SET_ACTIVE_SOURCE",
+ };
+
+ static const char *pulse8_msgname(u8 cmd)
+ {
+ static char unknown_msg[5];
+
+ if ((cmd & 0x3f) < ARRAY_SIZE(pulse8_msgnames))
+ return pulse8_msgnames[cmd & 0x3f];
+ snprintf(unknown_msg, sizeof(unknown_msg), "0x%02x", cmd);
+ return unknown_msg;
+ }
+
+ #define MSGSTART 0xff
+ #define MSGEND 0xfe
+ #define MSGESC 0xfd
+ #define MSGOFFSET 3
+
+ #define DATA_SIZE 256
+
+ #define PING_PERIOD (15 * HZ)
+
+ #define NUM_MSGS 8
+
+ struct pulse8 {
+ struct device *dev;
+ struct serio *serio;
+ struct cec_adapter *adap;
+ unsigned int vers;
+
+ struct delayed_work ping_eeprom_work;
+
+ struct work_struct irq_work;
+ struct cec_msg rx_msg[NUM_MSGS];
+ unsigned int rx_msg_cur_idx, rx_msg_num;
+ /* protect rx_msg_cur_idx and rx_msg_num */
+ spinlock_t msg_lock;
+ u8 new_rx_msg[CEC_MAX_MSG_SIZE];
+ u8 new_rx_msg_len;
+
+ struct work_struct tx_work;
+ u32 tx_done_status;
+ u32 tx_signal_free_time;
+ struct cec_msg tx_msg;
+ bool tx_msg_is_bcast;
+
+ struct completion cmd_done;
+ u8 data[DATA_SIZE];
+ unsigned int len;
+ u8 buf[DATA_SIZE];
+ unsigned int idx;
+ bool escape;
+ bool started;
+
+ /* locks access to the adapter */
+ struct mutex lock;
+ bool config_pending;
+ bool restoring_config;
+ bool autonomous;
+ };
+
+ static int pulse8_send(struct serio *serio, const u8 *command, u8 cmd_len)
+ {
+ int err = 0;
+
+ err = serio_write(serio, MSGSTART);
+ if (err)
+ return err;
+ for (; !err && cmd_len; command++, cmd_len--) {
+ if (*command >= MSGESC) {
+ err = serio_write(serio, MSGESC);
+ if (!err)
+ err = serio_write(serio, *command - MSGOFFSET);
+ } else {
+ err = serio_write(serio, *command);
+ }
+ }
+ if (!err)
+ err = serio_write(serio, MSGEND);
+
+ return err;
+ }
+
+ static int pulse8_send_and_wait_once(struct pulse8 *pulse8,
+ const u8 *cmd, u8 cmd_len,
+ u8 response, u8 size)
+ {
+ int err;
+
+ if (debug > 1)
+ dev_info(pulse8->dev, "transmit %s: %*ph\n",
+ pulse8_msgname(cmd[0]), cmd_len, cmd);
+ init_completion(&pulse8->cmd_done);
+
+ err = pulse8_send(pulse8->serio, cmd, cmd_len);
+ if (err)
+ return err;
+
+ if (!wait_for_completion_timeout(&pulse8->cmd_done, HZ))
+ return -ETIMEDOUT;
+ if ((pulse8->data[0] & 0x3f) == MSGCODE_COMMAND_REJECTED &&
+ cmd[0] != MSGCODE_SET_CONTROLLED &&
+ cmd[0] != MSGCODE_SET_AUTO_ENABLED &&
+ cmd[0] != MSGCODE_GET_BUILDDATE)
+ return -ENOTTY;
+ if (response &&
+ ((pulse8->data[0] & 0x3f) != response || pulse8->len < size + 1)) {
+ dev_info(pulse8->dev, "transmit %s failed with %s\n",
+ pulse8_msgname(cmd[0]),
+ pulse8_msgname(pulse8->data[0]));
+ return -EIO;
+ }
+ return 0;
+ }
+
+ static int pulse8_send_and_wait(struct pulse8 *pulse8,
+ const u8 *cmd, u8 cmd_len, u8 response, u8 size)
+ {
+ u8 cmd_sc[2];
+ int err;
+
+ err = pulse8_send_and_wait_once(pulse8, cmd, cmd_len, response, size);
+ if (err != -ENOTTY)
+ return err;
+
+ cmd_sc[0] = MSGCODE_SET_CONTROLLED;
+ cmd_sc[1] = 1;
+ err = pulse8_send_and_wait_once(pulse8, cmd_sc, 2,
+ MSGCODE_COMMAND_ACCEPTED, 1);
+ if (!err)
+ err = pulse8_send_and_wait_once(pulse8, cmd, cmd_len,
+ response, size);
+ return err == -ENOTTY ? -EIO : err;
+ }
+
+ static void pulse8_tx_work_handler(struct work_struct *work)
+ {
+ struct pulse8 *pulse8 = container_of(work, struct pulse8, tx_work);
+ struct cec_msg *msg = &pulse8->tx_msg;
+ unsigned int i;
+ u8 cmd[2];
+ int err;
+
+ if (msg->len == 0)
+ return;
+
+ mutex_lock(&pulse8->lock);
+ cmd[0] = MSGCODE_TRANSMIT_IDLETIME;
+ cmd[1] = pulse8->tx_signal_free_time;
+ err = pulse8_send_and_wait(pulse8, cmd, 2,
+ MSGCODE_COMMAND_ACCEPTED, 1);
+ cmd[0] = MSGCODE_TRANSMIT_ACK_POLARITY;
+ cmd[1] = cec_msg_is_broadcast(msg);
+ pulse8->tx_msg_is_bcast = cec_msg_is_broadcast(msg);
+ if (!err)
+ err = pulse8_send_and_wait(pulse8, cmd, 2,
+ MSGCODE_COMMAND_ACCEPTED, 1);
+ cmd[0] = msg->len == 1 ? MSGCODE_TRANSMIT_EOM : MSGCODE_TRANSMIT;
+ cmd[1] = msg->msg[0];
+ if (!err)
+ err = pulse8_send_and_wait(pulse8, cmd, 2,
+ MSGCODE_COMMAND_ACCEPTED, 1);
+ if (!err && msg->len > 1) {
+ for (i = 1; !err && i < msg->len; i++) {
+ cmd[0] = ((i == msg->len - 1)) ?
+ MSGCODE_TRANSMIT_EOM : MSGCODE_TRANSMIT;
+ cmd[1] = msg->msg[i];
+ err = pulse8_send_and_wait(pulse8, cmd, 2,
+ MSGCODE_COMMAND_ACCEPTED, 1);
+ }
+ }
+ if (err && debug)
+ dev_info(pulse8->dev, "%s(0x%02x) failed with error %d for msg %*ph\n",
+ pulse8_msgname(cmd[0]), cmd[1],
+ err, msg->len, msg->msg);
+ msg->len = 0;
+ mutex_unlock(&pulse8->lock);
+ if (err)
+ cec_transmit_attempt_done(pulse8->adap, CEC_TX_STATUS_ERROR);
+ }
+
+ static void pulse8_irq_work_handler(struct work_struct *work)
+ {
+ struct pulse8 *pulse8 =
+ container_of(work, struct pulse8, irq_work);
+ unsigned long flags;
+ u32 status;
+
+ spin_lock_irqsave(&pulse8->msg_lock, flags);
+ while (pulse8->rx_msg_num) {
+ spin_unlock_irqrestore(&pulse8->msg_lock, flags);
+ if (debug)
+ dev_info(pulse8->dev, "adap received %*ph\n",
+ pulse8->rx_msg[pulse8->rx_msg_cur_idx].len,
+ pulse8->rx_msg[pulse8->rx_msg_cur_idx].msg);
+ cec_received_msg(pulse8->adap,
+ &pulse8->rx_msg[pulse8->rx_msg_cur_idx]);
+ spin_lock_irqsave(&pulse8->msg_lock, flags);
+ if (pulse8->rx_msg_num)
+ pulse8->rx_msg_num--;
+ pulse8->rx_msg_cur_idx =
+ (pulse8->rx_msg_cur_idx + 1) % NUM_MSGS;
+ }
+ spin_unlock_irqrestore(&pulse8->msg_lock, flags);
+
+ mutex_lock(&pulse8->lock);
+ status = pulse8->tx_done_status;
+ pulse8->tx_done_status = 0;
+ mutex_unlock(&pulse8->lock);
+ if (status)
+ cec_transmit_attempt_done(pulse8->adap, status);
+ }
+
+ static irqreturn_t pulse8_interrupt(struct serio *serio, unsigned char data,
+ unsigned int flags)
+ {
+ struct pulse8 *pulse8 = serio_get_drvdata(serio);
+ unsigned long irq_flags;
+ unsigned int idx;
+
+ if (!pulse8->started && data != MSGSTART)
+ return IRQ_HANDLED;
+ if (data == MSGESC) {
+ pulse8->escape = true;
+ return IRQ_HANDLED;
+ }
+ if (pulse8->escape) {
+ data += MSGOFFSET;
+ pulse8->escape = false;
+ } else if (data == MSGEND) {
+ u8 msgcode = pulse8->buf[0];
+
+ if (debug > 1)
+ dev_info(pulse8->dev, "received %s: %*ph\n",
+ pulse8_msgname(msgcode),
+ pulse8->idx, pulse8->buf);
+ switch (msgcode & 0x3f) {
+ case MSGCODE_FRAME_START:
+ /*
+ * Test if we are receiving a new msg when a previous
+ * message is still pending.
+ */
+ if (!(msgcode & MSGCODE_FRAME_EOM)) {
+ pulse8->new_rx_msg_len = 1;
+ pulse8->new_rx_msg[0] = pulse8->buf[1];
+ break;
+ }
+ /* fall through */
+ case MSGCODE_FRAME_DATA:
+ if (pulse8->new_rx_msg_len < CEC_MAX_MSG_SIZE)
+ pulse8->new_rx_msg[pulse8->new_rx_msg_len++] =
+ pulse8->buf[1];
+ if (!(msgcode & MSGCODE_FRAME_EOM))
+ break;
+
+ spin_lock_irqsave(&pulse8->msg_lock, irq_flags);
+ idx = (pulse8->rx_msg_cur_idx + pulse8->rx_msg_num) %
+ NUM_MSGS;
+ if (pulse8->rx_msg_num == NUM_MSGS) {
+ dev_warn(pulse8->dev,
+ "message queue is full, dropping %*ph\n",
+ pulse8->new_rx_msg_len,
+ pulse8->new_rx_msg);
+ spin_unlock_irqrestore(&pulse8->msg_lock,
+ irq_flags);
+ pulse8->new_rx_msg_len = 0;
+ break;
+ }
+ pulse8->rx_msg_num++;
+ memcpy(pulse8->rx_msg[idx].msg, pulse8->new_rx_msg,
+ pulse8->new_rx_msg_len);
+ pulse8->rx_msg[idx].len = pulse8->new_rx_msg_len;
+ spin_unlock_irqrestore(&pulse8->msg_lock, irq_flags);
+ schedule_work(&pulse8->irq_work);
+ pulse8->new_rx_msg_len = 0;
+ break;
+ case MSGCODE_TRANSMIT_SUCCEEDED:
+ WARN_ON(pulse8->tx_done_status);
+ pulse8->tx_done_status = CEC_TX_STATUS_OK;
+ schedule_work(&pulse8->irq_work);
+ break;
+ case MSGCODE_TRANSMIT_FAILED_ACK:
+ /*
+ * A NACK for a broadcast message makes no sense, these
+ * seem to be spurious messages and are skipped.
+ */
+ if (pulse8->tx_msg_is_bcast)
+ break;
+ WARN_ON(pulse8->tx_done_status);
+ pulse8->tx_done_status = CEC_TX_STATUS_NACK;
+ schedule_work(&pulse8->irq_work);
+ break;
+ case MSGCODE_TRANSMIT_FAILED_LINE:
+ case MSGCODE_TRANSMIT_FAILED_TIMEOUT_DATA:
+ case MSGCODE_TRANSMIT_FAILED_TIMEOUT_LINE:
+ WARN_ON(pulse8->tx_done_status);
+ pulse8->tx_done_status = CEC_TX_STATUS_ERROR;
+ schedule_work(&pulse8->irq_work);
+ break;
+ case MSGCODE_HIGH_ERROR:
+ case MSGCODE_LOW_ERROR:
+ case MSGCODE_RECEIVE_FAILED:
+ case MSGCODE_TIMEOUT_ERROR:
+ pulse8->new_rx_msg_len = 0;
+ break;
+ case MSGCODE_COMMAND_ACCEPTED:
+ case MSGCODE_COMMAND_REJECTED:
+ default:
+ if (pulse8->idx == 0)
+ break;
+ memcpy(pulse8->data, pulse8->buf, pulse8->idx);
+ pulse8->len = pulse8->idx;
+ complete(&pulse8->cmd_done);
+ break;
+ }
+ pulse8->idx = 0;
+ pulse8->started = false;
+ return IRQ_HANDLED;
+ } else if (data == MSGSTART) {
+ pulse8->idx = 0;
+ pulse8->started = true;
+ return IRQ_HANDLED;
+ }
+
+ if (pulse8->idx >= DATA_SIZE) {
+ dev_dbg(pulse8->dev,
+ "throwing away %d bytes of garbage\n", pulse8->idx);
+ pulse8->idx = 0;
+ }
+ pulse8->buf[pulse8->idx++] = data;
+ return IRQ_HANDLED;
+ }
+
+ static int pulse8_cec_adap_enable(struct cec_adapter *adap, bool enable)
+ {
+ struct pulse8 *pulse8 = cec_get_drvdata(adap);
+ u8 cmd[16];
+ int err;
+
+ mutex_lock(&pulse8->lock);
+ cmd[0] = MSGCODE_SET_CONTROLLED;
+ cmd[1] = enable;
+ err = pulse8_send_and_wait(pulse8, cmd, 2,
+ MSGCODE_COMMAND_ACCEPTED, 1);
+ if (!enable) {
+ pulse8->rx_msg_num = 0;
+ pulse8->tx_done_status = 0;
+ }
+ mutex_unlock(&pulse8->lock);
+ return enable ? err : 0;
+ }
+
+ static int pulse8_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr)
+ {
+ struct pulse8 *pulse8 = cec_get_drvdata(adap);
+ u16 mask = 0;
+ u16 pa = adap->phys_addr;
+ u8 cmd[16];
+ int err = 0;
+
+ mutex_lock(&pulse8->lock);
+ if (log_addr != CEC_LOG_ADDR_INVALID)
+ mask = 1 << log_addr;
+ cmd[0] = MSGCODE_SET_ACK_MASK;
+ cmd[1] = mask >> 8;
+ cmd[2] = mask & 0xff;
+ err = pulse8_send_and_wait(pulse8, cmd, 3,
+ MSGCODE_COMMAND_ACCEPTED, 0);
+ if ((err && mask != 0) || pulse8->restoring_config)
+ goto unlock;
+
+ cmd[0] = MSGCODE_SET_AUTO_ENABLED;
+ cmd[1] = log_addr == CEC_LOG_ADDR_INVALID ? 0 : 1;
+ err = pulse8_send_and_wait(pulse8, cmd, 2,
+ MSGCODE_COMMAND_ACCEPTED, 0);
+ if (err)
+ goto unlock;
+ pulse8->autonomous = cmd[1];
+ if (log_addr == CEC_LOG_ADDR_INVALID)
+ goto unlock;
+
+ cmd[0] = MSGCODE_SET_DEVICE_TYPE;
+ cmd[1] = adap->log_addrs.primary_device_type[0];
+ err = pulse8_send_and_wait(pulse8, cmd, 2,
+ MSGCODE_COMMAND_ACCEPTED, 0);
+ if (err)
+ goto unlock;
+
+ switch (adap->log_addrs.primary_device_type[0]) {
+ case CEC_OP_PRIM_DEVTYPE_TV:
+ mask = CEC_LOG_ADDR_MASK_TV;
+ break;
+ case CEC_OP_PRIM_DEVTYPE_RECORD:
+ mask = CEC_LOG_ADDR_MASK_RECORD;
+ break;
+ case CEC_OP_PRIM_DEVTYPE_TUNER:
+ mask = CEC_LOG_ADDR_MASK_TUNER;
+ break;
+ case CEC_OP_PRIM_DEVTYPE_PLAYBACK:
+ mask = CEC_LOG_ADDR_MASK_PLAYBACK;
+ break;
+ case CEC_OP_PRIM_DEVTYPE_AUDIOSYSTEM:
+ mask = CEC_LOG_ADDR_MASK_AUDIOSYSTEM;
+ break;
+ case CEC_OP_PRIM_DEVTYPE_SWITCH:
+ mask = CEC_LOG_ADDR_MASK_UNREGISTERED;
+ break;
+ case CEC_OP_PRIM_DEVTYPE_PROCESSOR:
+ mask = CEC_LOG_ADDR_MASK_SPECIFIC;
+ break;
+ default:
+ mask = 0;
+ break;
+ }
+ cmd[0] = MSGCODE_SET_LOGICAL_ADDRESS_MASK;
+ cmd[1] = mask >> 8;
+ cmd[2] = mask & 0xff;
+ err = pulse8_send_and_wait(pulse8, cmd, 3,
+ MSGCODE_COMMAND_ACCEPTED, 0);
+ if (err)
+ goto unlock;
+
+ cmd[0] = MSGCODE_SET_DEFAULT_LOGICAL_ADDRESS;
+ cmd[1] = log_addr;
+ err = pulse8_send_and_wait(pulse8, cmd, 2,
+ MSGCODE_COMMAND_ACCEPTED, 0);
+ if (err)
+ goto unlock;
+
+ cmd[0] = MSGCODE_SET_PHYSICAL_ADDRESS;
+ cmd[1] = pa >> 8;
+ cmd[2] = pa & 0xff;
+ err = pulse8_send_and_wait(pulse8, cmd, 3,
+ MSGCODE_COMMAND_ACCEPTED, 0);
+ if (err)
+ goto unlock;
+
+ cmd[0] = MSGCODE_SET_HDMI_VERSION;
+ cmd[1] = adap->log_addrs.cec_version;
+ err = pulse8_send_and_wait(pulse8, cmd, 2,
+ MSGCODE_COMMAND_ACCEPTED, 0);
+ if (err)
+ goto unlock;
+
+ if (adap->log_addrs.osd_name[0]) {
+ size_t osd_len = strlen(adap->log_addrs.osd_name);
+ char *osd_str = cmd + 1;
+
+ cmd[0] = MSGCODE_SET_OSD_NAME;
+ strscpy(cmd + 1, adap->log_addrs.osd_name, sizeof(cmd) - 1);
+ if (osd_len < 4) {
+ memset(osd_str + osd_len, ' ', 4 - osd_len);
+ osd_len = 4;
+ osd_str[osd_len] = '\0';
+ strscpy(adap->log_addrs.osd_name, osd_str,
+ sizeof(adap->log_addrs.osd_name));
+ }
+ err = pulse8_send_and_wait(pulse8, cmd, 1 + osd_len,
+ MSGCODE_COMMAND_ACCEPTED, 0);
+ if (err)
+ goto unlock;
+ }
+
+ unlock:
+ if (pulse8->restoring_config)
+ pulse8->restoring_config = false;
+ else
+ pulse8->config_pending = true;
+ mutex_unlock(&pulse8->lock);
+ return log_addr == CEC_LOG_ADDR_INVALID ? 0 : err;
+ }
+
+ static int pulse8_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
+ u32 signal_free_time, struct cec_msg *msg)
+ {
+ struct pulse8 *pulse8 = cec_get_drvdata(adap);
+
+ pulse8->tx_msg = *msg;
+ if (debug)
+ dev_info(pulse8->dev, "adap transmit %*ph\n",
+ msg->len, msg->msg);
+ pulse8->tx_signal_free_time = signal_free_time;
+ schedule_work(&pulse8->tx_work);
+ return 0;
+ }
+
+ static void pulse8_cec_adap_free(struct cec_adapter *adap)
+ {
+ struct pulse8 *pulse8 = cec_get_drvdata(adap);
+
+ cancel_delayed_work_sync(&pulse8->ping_eeprom_work);
+ cancel_work_sync(&pulse8->irq_work);
+ cancel_work_sync(&pulse8->tx_work);
+ kfree(pulse8);
+ }
+
+ static const struct cec_adap_ops pulse8_cec_adap_ops = {
+ .adap_enable = pulse8_cec_adap_enable,
+ .adap_log_addr = pulse8_cec_adap_log_addr,
+ .adap_transmit = pulse8_cec_adap_transmit,
+ .adap_free = pulse8_cec_adap_free,
+ };
+
+ static void pulse8_disconnect(struct serio *serio)
+ {
+ struct pulse8 *pulse8 = serio_get_drvdata(serio);
+
+ cec_unregister_adapter(pulse8->adap);
+ pulse8->serio = NULL;
+ serio_set_drvdata(serio, NULL);
+ serio_close(serio);
+ }
+
+ static int pulse8_setup(struct pulse8 *pulse8, struct serio *serio,
+ struct cec_log_addrs *log_addrs, u16 *pa)
+ {
+ u8 *data = pulse8->data + 1;
+ u8 cmd[2];
+ int err;
- time64_to_tm(date, 0, &tm);
- dev_info(pulse8->dev, "Firmware build date %04ld.%02d.%02d %02d:%02d:%02d\n",
- tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
- tm.tm_hour, tm.tm_min, tm.tm_sec);
+ time64_t date;
+
+ pulse8->vers = 0;
+
+ cmd[0] = MSGCODE_FIRMWARE_VERSION;
+ err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 2);
+ if (err)
+ return err;
+ pulse8->vers = (data[0] << 8) | data[1];
+ dev_info(pulse8->dev, "Firmware version %04x\n", pulse8->vers);
+ if (pulse8->vers < 2) {
+ *pa = CEC_PHYS_ADDR_INVALID;
+ return 0;
+ }
+
+ cmd[0] = MSGCODE_GET_BUILDDATE;
+ err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 4);
+ if (err)
+ return err;
+ date = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
++ dev_info(pulse8->dev, "Firmware build date %ptT\n", &date);
+
+ dev_dbg(pulse8->dev, "Persistent config:\n");
+ cmd[0] = MSGCODE_GET_AUTO_ENABLED;
+ err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 1);
+ if (err)
+ return err;
+ pulse8->autonomous = data[0];
+ dev_dbg(pulse8->dev, "Autonomous mode: %s",
+ data[0] ? "on" : "off");
+
+ cmd[0] = MSGCODE_GET_DEVICE_TYPE;
+ err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 1);
+ if (err)
+ return err;
+ log_addrs->primary_device_type[0] = data[0];
+ dev_dbg(pulse8->dev, "Primary device type: %d\n", data[0]);
+ switch (log_addrs->primary_device_type[0]) {
+ case CEC_OP_PRIM_DEVTYPE_TV:
+ log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_TV;
+ log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_TV;
+ break;
+ case CEC_OP_PRIM_DEVTYPE_RECORD:
+ log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_RECORD;
+ log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_RECORD;
+ break;
+ case CEC_OP_PRIM_DEVTYPE_TUNER:
+ log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_TUNER;
+ log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_TUNER;
+ break;
+ case CEC_OP_PRIM_DEVTYPE_PLAYBACK:
+ log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_PLAYBACK;
+ log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_PLAYBACK;
+ break;
+ case CEC_OP_PRIM_DEVTYPE_AUDIOSYSTEM:
+ log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_PLAYBACK;
+ log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_AUDIOSYSTEM;
+ break;
+ case CEC_OP_PRIM_DEVTYPE_SWITCH:
+ log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_UNREGISTERED;
+ log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_SWITCH;
+ break;
+ case CEC_OP_PRIM_DEVTYPE_PROCESSOR:
+ log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_SPECIFIC;
+ log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_SWITCH;
+ break;
+ default:
+ log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_UNREGISTERED;
+ log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_SWITCH;
+ dev_info(pulse8->dev, "Unknown Primary Device Type: %d\n",
+ log_addrs->primary_device_type[0]);
+ break;
+ }
+
+ cmd[0] = MSGCODE_GET_LOGICAL_ADDRESS_MASK;
+ err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 2);
+ if (err)
+ return err;
+ log_addrs->log_addr_mask = (data[0] << 8) | data[1];
+ dev_dbg(pulse8->dev, "Logical address ACK mask: %x\n",
+ log_addrs->log_addr_mask);
+ if (log_addrs->log_addr_mask)
+ log_addrs->num_log_addrs = 1;
+
+ cmd[0] = MSGCODE_GET_PHYSICAL_ADDRESS;
+ err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 1);
+ if (err)
+ return err;
+ *pa = (data[0] << 8) | data[1];
+ dev_dbg(pulse8->dev, "Physical address: %x.%x.%x.%x\n",
+ cec_phys_addr_exp(*pa));
+
+ cmd[0] = MSGCODE_GET_HDMI_VERSION;
+ err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 1);
+ if (err)
+ return err;
+ log_addrs->cec_version = data[0];
+ dev_dbg(pulse8->dev, "CEC version: %d\n", log_addrs->cec_version);
+
+ cmd[0] = MSGCODE_GET_OSD_NAME;
+ err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 0);
+ if (err)
+ return err;
+ strscpy(log_addrs->osd_name, data, sizeof(log_addrs->osd_name));
+ dev_dbg(pulse8->dev, "OSD name: %s\n", log_addrs->osd_name);
+
+ return 0;
+ }
+
+ static int pulse8_apply_persistent_config(struct pulse8 *pulse8,
+ struct cec_log_addrs *log_addrs,
+ u16 pa)
+ {
+ int err;
+
+ err = cec_s_log_addrs(pulse8->adap, log_addrs, false);
+ if (err)
+ return err;
+
+ cec_s_phys_addr(pulse8->adap, pa, false);
+
+ return 0;
+ }
+
+ static void pulse8_ping_eeprom_work_handler(struct work_struct *work)
+ {
+ struct pulse8 *pulse8 =
+ container_of(work, struct pulse8, ping_eeprom_work.work);
+ u8 cmd;
+
+ mutex_lock(&pulse8->lock);
+ cmd = MSGCODE_PING;
+ pulse8_send_and_wait(pulse8, &cmd, 1,
+ MSGCODE_COMMAND_ACCEPTED, 0);
+
+ if (pulse8->vers < 2)
+ goto unlock;
+
+ if (pulse8->config_pending && persistent_config) {
+ dev_dbg(pulse8->dev, "writing pending config to EEPROM\n");
+ cmd = MSGCODE_WRITE_EEPROM;
+ if (pulse8_send_and_wait(pulse8, &cmd, 1,
+ MSGCODE_COMMAND_ACCEPTED, 0))
+ dev_info(pulse8->dev, "failed to write pending config to EEPROM\n");
+ else
+ pulse8->config_pending = false;
+ }
+ unlock:
+ schedule_delayed_work(&pulse8->ping_eeprom_work, PING_PERIOD);
+ mutex_unlock(&pulse8->lock);
+ }
+
+ static int pulse8_connect(struct serio *serio, struct serio_driver *drv)
+ {
+ u32 caps = CEC_CAP_DEFAULTS | CEC_CAP_PHYS_ADDR | CEC_CAP_MONITOR_ALL;
+ struct pulse8 *pulse8;
+ int err = -ENOMEM;
+ struct cec_log_addrs log_addrs = {};
+ u16 pa = CEC_PHYS_ADDR_INVALID;
+
+ pulse8 = kzalloc(sizeof(*pulse8), GFP_KERNEL);
+
+ if (!pulse8)
+ return -ENOMEM;
+
+ pulse8->serio = serio;
+ pulse8->adap = cec_allocate_adapter(&pulse8_cec_adap_ops, pulse8,
+ dev_name(&serio->dev), caps, 1);
+ err = PTR_ERR_OR_ZERO(pulse8->adap);
+ if (err < 0)
+ goto free_device;
+
+ pulse8->dev = &serio->dev;
+ serio_set_drvdata(serio, pulse8);
+ INIT_WORK(&pulse8->irq_work, pulse8_irq_work_handler);
+ INIT_WORK(&pulse8->tx_work, pulse8_tx_work_handler);
+ INIT_DELAYED_WORK(&pulse8->ping_eeprom_work,
+ pulse8_ping_eeprom_work_handler);
+ mutex_init(&pulse8->lock);
+ spin_lock_init(&pulse8->msg_lock);
+ pulse8->config_pending = false;
+
+ err = serio_open(serio, drv);
+ if (err)
+ goto delete_adap;
+
+ err = pulse8_setup(pulse8, serio, &log_addrs, &pa);
+ if (err)
+ goto close_serio;
+
+ err = cec_register_adapter(pulse8->adap, &serio->dev);
+ if (err < 0)
+ goto close_serio;
+
+ pulse8->dev = &pulse8->adap->devnode.dev;
+
+ if (persistent_config && pulse8->autonomous) {
+ err = pulse8_apply_persistent_config(pulse8, &log_addrs, pa);
+ if (err)
+ goto close_serio;
+ pulse8->restoring_config = true;
+ }
+
+ schedule_delayed_work(&pulse8->ping_eeprom_work, PING_PERIOD);
+
+ return 0;
+
+ close_serio:
+ pulse8->serio = NULL;
+ serio_set_drvdata(serio, NULL);
+ serio_close(serio);
+ delete_adap:
+ cec_delete_adapter(pulse8->adap);
+ free_device:
+ kfree(pulse8);
+ return err;
+ }
+
+ static const struct serio_device_id pulse8_serio_ids[] = {
+ {
+ .type = SERIO_RS232,
+ .proto = SERIO_PULSE8_CEC,
+ .id = SERIO_ANY,
+ .extra = SERIO_ANY,
+ },
+ { 0 }
+ };
+
+ MODULE_DEVICE_TABLE(serio, pulse8_serio_ids);
+
+ static struct serio_driver pulse8_drv = {
+ .driver = {
+ .name = "pulse8-cec",
+ },
+ .description = "Pulse Eight HDMI CEC driver",
+ .id_table = pulse8_serio_ids,
+ .interrupt = pulse8_interrupt,
+ .connect = pulse8_connect,
+ .disconnect = pulse8_disconnect,
+ };
+
+ module_serio_driver(pulse8_drv);