Merge tag 'media/v5.8-1' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab...
authorLinus Torvalds <torvalds@linux-foundation.org>
Thu, 4 Jun 2020 03:59:38 +0000 (20:59 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Thu, 4 Jun 2020 03:59:38 +0000 (20:59 -0700)
Pull media updates from Mauro Carvalho Chehab:

 - Media documentation is now split into admin-guide, driver-api and
   userspace-api books (a longstanding request from Jon);

 - The media Kconfig was reorganized, in order to make easier to select
   drivers and their dependencies;

 - The testing drivers now has a separate directory;

 - added a new driver for Rockchip Video Decoder IP;

 - The atomisp staging driver was resurrected. It is meant to work with
   4 generations of cameras on Atom-based laptops, tablets and cell
   phones. So, it seems worth investing time to cleanup this driver and
   making it in good shape.

 - Added some V4L2 core ancillary routines to help with h264 codecs;

 - Added an ov2740 image sensor driver;

 - The si2157 gained support for Analog TV, which, in turn, added
   support for some cx231xx and cx23885 boards to also support analog
   standards;

 - Added some V4L2 controls (V4L2_CID_CAMERA_ORIENTATION and
   V4L2_CID_CAMERA_SENSOR_ROTATION) to help identifying where the camera
   is located at the device;

 - VIDIOC_ENUM_FMT was extended to support MC-centric devices;

 - Lots of drivers improvements and cleanups.

* tag 'media/v5.8-1' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media: (503 commits)
  media: Documentation: media: Refer to mbus format documentation from CSI-2 docs
  media: s5k5baf: Replace zero-length array with flexible-array
  media: i2c: imx219: Drop <linux/clk-provider.h> and <linux/clkdev.h>
  media: i2c: Add ov2740 image sensor driver
  media: ov8856: Implement sensor module revision identification
  media: ov8856: Add devicetree support
  media: dt-bindings: ov8856: Document YAML bindings
  media: dvb-usb: Add Cinergy S2 PCIe Dual Port support
  media: dvbdev: Fix tuner->demod media controller link
  media: dt-bindings: phy: phy-rockchip-dphy-rx0: move rockchip dphy rx0 bindings out of staging
  media: staging: dt-bindings: phy-rockchip-dphy-rx0: remove non-used reg property
  media: atomisp: unify the version for isp2401 a0 and b0 versions
  media: atomisp: update TODO with the current data
  media: atomisp: adjust some code at sh_css that could be broken
  media: atomisp: don't produce errs for ignored IRQs
  media: atomisp: print IRQ when debugging
  media: atomisp: isp_mmu: don't use kmem_cache
  media: atomisp: add a notice about possible leak resources
  media: atomisp: disable the dynamic and reserved pools
  media: atomisp: turn on camera before setting it
  ...

1  2 
Documentation/Makefile
Documentation/admin-guide/kernel-parameters.txt
Documentation/driver-api/index.rst
Documentation/translations/zh_CN/video4linux/v4l2-framework.txt
MAINTAINERS
drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c
drivers/media/cec/core/cec-notifier.c
drivers/media/cec/usb/pulse8/pulse8-cec.c
drivers/media/v4l2-core/v4l2-fwnode.c
drivers/platform/x86/Kconfig

Simple merge
Simple merge
diff --cc MAINTAINERS
Simple merge
index 0000000000000000000000000000000000000000,e748cd54b45da0b8e6ecda47125c08bd43900dbd..517e0035fc99367baa78bd137c18b0e79f13b971
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,236 +1,236 @@@
 - * 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);
index 0000000000000000000000000000000000000000,0655aa9ecf283a6a73231d12413300a4f7a4c3fa..beae6aa12638a95bcf635a941c4cda7a7c9987c7
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,909 +1,905 @@@
 -      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);
Simple merge
Simple merge