[media] pulse8-cec: sync configuration with adapter
authorJohan Fjeldtvedt <jaffe1@gmail.com>
Mon, 22 Aug 2016 09:04:54 +0000 (06:04 -0300)
committerMauro Carvalho Chehab <mchehab@s-opensource.com>
Tue, 6 Sep 2016 19:41:32 +0000 (16:41 -0300)
When the configuration is changed, they are also written to the adapter.
This allows the adapter to continue operating in autonomous mode with
the same settings when it is disconnected from the driver (typically by
going into suspend). For adapters with firmware version 2 or greater, the
settings are also persisted in EEPROM.

A new module parameter is added to optionally also use the configuration
already present in the adapter when it is connected. This option is
enabled by default.

When a new configuration is written, the autonomous mode is
automatically enabled. When the device is unconfigured, autonomous mode
is disabled.

Signed-off-by: Johan Fjeldtvedt <jaffe1@gmail.com>
Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@s-opensource.com>
drivers/staging/media/pulse8-cec/pulse8-cec.c

index 3361f4acfdafdfa3ebb7bccc6c0bb42f0c537a36..e27eca307a3847b33d76373abb435a039813e078 100644 (file)
@@ -51,8 +51,11 @@ MODULE_DESCRIPTION("Pulse Eight HDMI CEC driver");
 MODULE_LICENSE("GPL");
 
 static int debug;
+static int persistent_config = 1;
 module_param(debug, int, 0644);
+module_param(persistent_config, int, 0644);
 MODULE_PARM_DESC(debug, "debug level (0-1)");
+MODULE_PARM_DESC(persistent_config, "read config from persistent memory (0-1)");
 
 enum pulse8_msgcodes {
        MSGCODE_NOTHING = 0,
@@ -109,12 +112,16 @@ enum pulse8_msgcodes {
 
 #define DATA_SIZE 256
 
+#define PING_PERIOD    (15 * HZ)
+
 struct pulse8 {
        struct device *dev;
        struct serio *serio;
        struct cec_adapter *adap;
+       unsigned int vers;
        struct completion cmd_done;
        struct work_struct work;
+       struct delayed_work ping_eeprom_work;
        struct cec_msg rx_msg;
        u8 data[DATA_SIZE];
        unsigned int len;
@@ -122,9 +129,15 @@ struct pulse8 {
        unsigned int idx;
        bool escape;
        bool started;
+       struct mutex config_lock;
        struct mutex write_lock;
+       bool config_pending;
+       bool restoring_config;
+       bool autonomous;
 };
 
+static void pulse8_ping_eeprom_work_handler(struct work_struct *work);
+
 static void pulse8_irq_work_handler(struct work_struct *work)
 {
        struct pulse8 *pulse8 =
@@ -229,6 +242,7 @@ static void pulse8_disconnect(struct serio *serio)
        struct pulse8 *pulse8 = serio_get_drvdata(serio);
 
        cec_unregister_adapter(pulse8->adap);
+       cancel_delayed_work_sync(&pulse8->ping_eeprom_work);
        dev_info(&serio->dev, "disconnected\n");
        serio_close(serio);
        serio_set_drvdata(serio, NULL);
@@ -311,14 +325,15 @@ unlock:
        return err == -ENOTTY ? -EIO : err;
 }
 
-static int pulse8_setup(struct pulse8 *pulse8, struct serio *serio)
+static int pulse8_setup(struct pulse8 *pulse8, struct serio *serio,
+                       struct cec_log_addrs *log_addrs, u16 *pa)
 {
        u8 *data = pulse8->data + 1;
-       unsigned int count = 0;
-       unsigned int vers = 0;
        u8 cmd[2];
        int err;
 
+       pulse8->vers = 0;
+
        cmd[0] = MSGCODE_PING;
        err = pulse8_send_and_wait(pulse8, cmd, 1,
                                   MSGCODE_COMMAND_ACCEPTED, 0);
@@ -328,10 +343,10 @@ static int pulse8_setup(struct pulse8 *pulse8, struct serio *serio)
        if (err)
                return err;
 
-       vers = (data[0] << 8) | data[1];
+       pulse8->vers = (data[0] << 8) | data[1];
 
-       dev_info(pulse8->dev, "Firmware version %04x\n", vers);
-       if (vers < 2)
+       dev_info(pulse8->dev, "Firmware version %04x\n", pulse8->vers);
+       if (pulse8->vers < 2)
                return 0;
 
        cmd[0] = MSGCODE_GET_BUILDDATE;
@@ -348,37 +363,103 @@ static int pulse8_setup(struct pulse8 *pulse8, struct serio *serio)
                         tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
                         tm.tm_hour, tm.tm_min, tm.tm_sec);
        }
+       if (err)
+               return err;
 
-       do {
-               if (count)
-                       msleep(500);
-               cmd[0] = MSGCODE_SET_AUTO_ENABLED;
-               cmd[1] = 0;
-               err = pulse8_send_and_wait(pulse8, cmd, 2,
-                                          MSGCODE_COMMAND_ACCEPTED, 1);
-               if (err && count == 0) {
-                       dev_info(pulse8->dev, "No Auto Enabled supported\n");
-                       return 0;
-               }
+       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_AUTO_ENABLED;
-               if (!err)
-                       err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 1);
-               if (!err && !data[0]) {
-                       cmd[0] = MSGCODE_WRITE_EEPROM;
-                       err = pulse8_send_and_wait(pulse8, cmd, 1,
-                                                  MSGCODE_COMMAND_ACCEPTED, 1);
-                       cmd[0] = MSGCODE_GET_AUTO_ENABLED;
-                       if (!err)
-                               err = pulse8_send_and_wait(pulse8, cmd, 1,
-                                                          cmd[0], 1);
-               }
-       } while (!err && data[0] && count++ < 5);
+       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;
+               break;
+       case CEC_OP_PRIM_DEVTYPE_RECORD:
+               log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_RECORD;
+               break;
+       case CEC_OP_PRIM_DEVTYPE_TUNER:
+               log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_TUNER;
+               break;
+       case CEC_OP_PRIM_DEVTYPE_PLAYBACK:
+               log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_PLAYBACK;
+               break;
+       case CEC_OP_PRIM_DEVTYPE_AUDIOSYSTEM:
+               log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_PLAYBACK;
+               break;
+       case CEC_OP_PRIM_DEVTYPE_SWITCH:
+               log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_UNREGISTERED;
+               break;
+       case CEC_OP_PRIM_DEVTYPE_PROCESSOR:
+               log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_SPECIFIC;
+               break;
+       default:
+               log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_UNREGISTERED;
+               dev_info(pulse8->dev, "Unknown Primary Device Type: %d\n",
+                        log_addrs->primary_device_type[0]);
+               break;
+       }
 
-       if (!err && data[0])
-               err = -EIO;
+       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));
 
-       return err;
+       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;
+       strncpy(log_addrs->osd_name, data, 13);
+       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);
+       if (err)
+               return err;
+
+       return 0;
 }
 
 static int pulse8_cec_adap_enable(struct cec_adapter *adap, bool enable)
@@ -398,9 +479,11 @@ static int pulse8_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr)
 {
        struct pulse8 *pulse8 = adap->priv;
        u16 mask = 0;
-       u8 cmd[3];
-       int err;
+       u16 pa = adap->phys_addr;
+       u8 cmd[16];
+       int err = 0;
 
+       mutex_lock(&pulse8->config_lock);
        if (log_addr != CEC_LOG_ADDR_INVALID)
                mask = 1 << log_addr;
        cmd[0] = MSGCODE_SET_ACK_MASK;
@@ -408,8 +491,72 @@ static int pulse8_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr)
        cmd[2] = mask & 0xff;
        err = pulse8_send_and_wait(pulse8, cmd, 3,
                                   MSGCODE_COMMAND_ACCEPTED, 0);
-       if (mask == 0)
-               return 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;
+
+       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;
+               strncpy(cmd + 1, adap->log_addrs.osd_name, 13);
+               if (osd_len < 4) {
+                       memset(osd_str + osd_len, ' ', 4 - osd_len);
+                       osd_len = 4;
+                       osd_str[osd_len] = '\0';
+                       strcpy(adap->log_addrs.osd_name, osd_str);
+               }
+               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->config_lock);
        return err;
 }
 
@@ -471,6 +618,8 @@ static int pulse8_connect(struct serio *serio, struct serio_driver *drv)
                CEC_CAP_PASSTHROUGH | CEC_CAP_RC | CEC_CAP_MONITOR_ALL;
        struct pulse8 *pulse8;
        int err = -ENOMEM;
+       struct cec_log_addrs log_addrs = {};
+       u16 pa;
 
        pulse8 = kzalloc(sizeof(*pulse8), GFP_KERNEL);
 
@@ -488,12 +637,14 @@ static int pulse8_connect(struct serio *serio, struct serio_driver *drv)
        serio_set_drvdata(serio, pulse8);
        INIT_WORK(&pulse8->work, pulse8_irq_work_handler);
        mutex_init(&pulse8->write_lock);
+       mutex_init(&pulse8->config_lock);
+       pulse8->config_pending = false;
 
        err = serio_open(serio, drv);
        if (err)
                goto delete_adap;
 
-       err = pulse8_setup(pulse8, serio);
+       err = pulse8_setup(pulse8, serio, &log_addrs, &pa);
        if (err)
                goto close_serio;
 
@@ -502,6 +653,18 @@ static int pulse8_connect(struct serio *serio, struct serio_driver *drv)
                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;
+       }
+
+       INIT_DELAYED_WORK(&pulse8->ping_eeprom_work,
+                         pulse8_ping_eeprom_work_handler);
+       schedule_delayed_work(&pulse8->ping_eeprom_work, PING_PERIOD);
+
        return 0;
 
 close_serio:
@@ -514,6 +677,33 @@ free_device:
        return err;
 }
 
+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;
+
+       schedule_delayed_work(&pulse8->ping_eeprom_work, PING_PERIOD);
+       cmd = MSGCODE_PING;
+       pulse8_send_and_wait(pulse8, &cmd, 1,
+                            MSGCODE_COMMAND_ACCEPTED, 0);
+
+       if (pulse8->vers < 2)
+               return;
+
+       mutex_lock(&pulse8->config_lock);
+       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;
+       }
+       mutex_unlock(&pulse8->config_lock);
+}
+
 static struct serio_device_id pulse8_serio_ids[] = {
        {
                .type   = SERIO_RS232,