[media] pulse8-cec: sync configuration with adapter

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>
This commit is contained in:
Johan Fjeldtvedt 2016-08-22 06:04:54 -03:00 committed by Mauro Carvalho Chehab
parent 89c2e22871
commit e28a6c8b3f
1 changed files with 228 additions and 38 deletions

View File

@ -51,8 +51,11 @@ MODULE_DESCRIPTION("Pulse Eight HDMI CEC driver");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
static int debug; static int debug;
static int persistent_config = 1;
module_param(debug, int, 0644); module_param(debug, int, 0644);
module_param(persistent_config, int, 0644);
MODULE_PARM_DESC(debug, "debug level (0-1)"); MODULE_PARM_DESC(debug, "debug level (0-1)");
MODULE_PARM_DESC(persistent_config, "read config from persistent memory (0-1)");
enum pulse8_msgcodes { enum pulse8_msgcodes {
MSGCODE_NOTHING = 0, MSGCODE_NOTHING = 0,
@ -109,12 +112,16 @@ enum pulse8_msgcodes {
#define DATA_SIZE 256 #define DATA_SIZE 256
#define PING_PERIOD (15 * HZ)
struct pulse8 { struct pulse8 {
struct device *dev; struct device *dev;
struct serio *serio; struct serio *serio;
struct cec_adapter *adap; struct cec_adapter *adap;
unsigned int vers;
struct completion cmd_done; struct completion cmd_done;
struct work_struct work; struct work_struct work;
struct delayed_work ping_eeprom_work;
struct cec_msg rx_msg; struct cec_msg rx_msg;
u8 data[DATA_SIZE]; u8 data[DATA_SIZE];
unsigned int len; unsigned int len;
@ -122,9 +129,15 @@ struct pulse8 {
unsigned int idx; unsigned int idx;
bool escape; bool escape;
bool started; bool started;
struct mutex config_lock;
struct mutex write_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) static void pulse8_irq_work_handler(struct work_struct *work)
{ {
struct pulse8 *pulse8 = struct pulse8 *pulse8 =
@ -229,6 +242,7 @@ static void pulse8_disconnect(struct serio *serio)
struct pulse8 *pulse8 = serio_get_drvdata(serio); struct pulse8 *pulse8 = serio_get_drvdata(serio);
cec_unregister_adapter(pulse8->adap); cec_unregister_adapter(pulse8->adap);
cancel_delayed_work_sync(&pulse8->ping_eeprom_work);
dev_info(&serio->dev, "disconnected\n"); dev_info(&serio->dev, "disconnected\n");
serio_close(serio); serio_close(serio);
serio_set_drvdata(serio, NULL); serio_set_drvdata(serio, NULL);
@ -311,14 +325,15 @@ unlock:
return err == -ENOTTY ? -EIO : err; 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; u8 *data = pulse8->data + 1;
unsigned int count = 0;
unsigned int vers = 0;
u8 cmd[2]; u8 cmd[2];
int err; int err;
pulse8->vers = 0;
cmd[0] = MSGCODE_PING; cmd[0] = MSGCODE_PING;
err = pulse8_send_and_wait(pulse8, cmd, 1, err = pulse8_send_and_wait(pulse8, cmd, 1,
MSGCODE_COMMAND_ACCEPTED, 0); MSGCODE_COMMAND_ACCEPTED, 0);
@ -328,10 +343,10 @@ static int pulse8_setup(struct pulse8 *pulse8, struct serio *serio)
if (err) if (err)
return 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); dev_info(pulse8->dev, "Firmware version %04x\n", pulse8->vers);
if (vers < 2) if (pulse8->vers < 2)
return 0; return 0;
cmd[0] = MSGCODE_GET_BUILDDATE; 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_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec); tm.tm_hour, tm.tm_min, tm.tm_sec);
} }
if (err)
return err;
do { dev_dbg(pulse8->dev, "Persistent config:\n");
if (count) cmd[0] = MSGCODE_GET_AUTO_ENABLED;
msleep(500); err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 1);
cmd[0] = MSGCODE_SET_AUTO_ENABLED; if (err)
cmd[1] = 0; return err;
err = pulse8_send_and_wait(pulse8, cmd, 2, pulse8->autonomous = data[0];
MSGCODE_COMMAND_ACCEPTED, 1); dev_dbg(pulse8->dev, "Autonomous mode: %s",
if (err && count == 0) { data[0] ? "on" : "off");
dev_info(pulse8->dev, "No Auto Enabled supported\n");
return 0;
}
cmd[0] = MSGCODE_GET_AUTO_ENABLED; cmd[0] = MSGCODE_GET_DEVICE_TYPE;
if (!err) err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 1);
err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 1); if (err)
if (!err && !data[0]) { return err;
cmd[0] = MSGCODE_WRITE_EEPROM; log_addrs->primary_device_type[0] = data[0];
err = pulse8_send_and_wait(pulse8, cmd, 1, dev_dbg(pulse8->dev, "Primary device type: %d\n", data[0]);
MSGCODE_COMMAND_ACCEPTED, 1); switch (log_addrs->primary_device_type[0]) {
cmd[0] = MSGCODE_GET_AUTO_ENABLED; case CEC_OP_PRIM_DEVTYPE_TV:
if (!err) log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_TV;
err = pulse8_send_and_wait(pulse8, cmd, 1, break;
cmd[0], 1); case CEC_OP_PRIM_DEVTYPE_RECORD:
} log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_RECORD;
} while (!err && data[0] && count++ < 5); 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]) cmd[0] = MSGCODE_GET_LOGICAL_ADDRESS_MASK;
err = -EIO; 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;
return err; 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;
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) 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; struct pulse8 *pulse8 = adap->priv;
u16 mask = 0; u16 mask = 0;
u8 cmd[3]; u16 pa = adap->phys_addr;
int err; u8 cmd[16];
int err = 0;
mutex_lock(&pulse8->config_lock);
if (log_addr != CEC_LOG_ADDR_INVALID) if (log_addr != CEC_LOG_ADDR_INVALID)
mask = 1 << log_addr; mask = 1 << log_addr;
cmd[0] = MSGCODE_SET_ACK_MASK; 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; cmd[2] = mask & 0xff;
err = pulse8_send_and_wait(pulse8, cmd, 3, err = pulse8_send_and_wait(pulse8, cmd, 3,
MSGCODE_COMMAND_ACCEPTED, 0); MSGCODE_COMMAND_ACCEPTED, 0);
if (mask == 0) if ((err && mask != 0) || pulse8->restoring_config)
return 0; 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; 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; CEC_CAP_PASSTHROUGH | CEC_CAP_RC | CEC_CAP_MONITOR_ALL;
struct pulse8 *pulse8; struct pulse8 *pulse8;
int err = -ENOMEM; int err = -ENOMEM;
struct cec_log_addrs log_addrs = {};
u16 pa;
pulse8 = kzalloc(sizeof(*pulse8), GFP_KERNEL); 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); serio_set_drvdata(serio, pulse8);
INIT_WORK(&pulse8->work, pulse8_irq_work_handler); INIT_WORK(&pulse8->work, pulse8_irq_work_handler);
mutex_init(&pulse8->write_lock); mutex_init(&pulse8->write_lock);
mutex_init(&pulse8->config_lock);
pulse8->config_pending = false;
err = serio_open(serio, drv); err = serio_open(serio, drv);
if (err) if (err)
goto delete_adap; goto delete_adap;
err = pulse8_setup(pulse8, serio); err = pulse8_setup(pulse8, serio, &log_addrs, &pa);
if (err) if (err)
goto close_serio; goto close_serio;
@ -502,6 +653,18 @@ static int pulse8_connect(struct serio *serio, struct serio_driver *drv)
goto close_serio; goto close_serio;
pulse8->dev = &pulse8->adap->devnode.dev; 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; return 0;
close_serio: close_serio:
@ -514,6 +677,33 @@ free_device:
return err; 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[] = { static struct serio_device_id pulse8_serio_ids[] = {
{ {
.type = SERIO_RS232, .type = SERIO_RS232,