Re: [PATCH v2 1/2] media: cec: add SECO MEC-based cec driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Hans,

Thanks for replying, 

On Fri, Feb 18, 2022 at 09:56:49AM +0100, Hans Verkuil wrote:
> Hi Ettore,
> 
> Some comments below:
> 
> On 15/02/2022 19:13, ektor5 wrote:
> > From: Ettore Chimenti <ek5.chimenti@xxxxxxxxx>
> > 
> > This patch adds support to the CEC device implemented with a Microchip
> > MEC microcontroller in SECO Boards, including UDOO BOLT and UDOO Vision.
> > 
> > The communication is achieved via Mailbox protocol.
> > The driver use direct access to the PCI addresses.
> > The firmware implementation also supports resuming from suspend by
> > sending physical address to EC and waiting for a SET_STREAM_PATH command
> > that matches the provided physical address.
> > 
> > The basic functionalities are tested with success with cec-ctl and
> > cec-compliance.
> 
> I'd like to see the output of cec-compliance --test-adapter.
> 

Here it is:

Driver Info:
	Driver Name                : seco_meccec
	Adapter Name               : CEC00002:00-0
	Capabilities               : 0x0000010e
		Logical Addresses
		Transmit
		Passthrough
		Connector Info
	Driver version             : 5.16.5
	Available Logical Addresses: 1
	DRM Connector Info         : card 0, connector 111
	Physical Address           : 1.0.0.0
	Logical Address Mask       : 0x0010
	CEC Version                : 2.0
	Vendor ID                  : 0x000c03 (HDMI)
	OSD Name                   : 'Playback'
	Logical Addresses          : 1 (Allow RC Passthrough)

	  Logical Address          : 4 (Playback Device 1)
	    Primary Device Type    : Playback
	    Logical Address Type   : Playback
	    All Device Types       : Playback
	    RC TV Profile          : None
	    Device Features        :
		None

Compliance test for seco_meccec device /dev/cec0:

[...]

Find remote devices:
	Polling: OK

CEC API:
	CEC_ADAP_G_CAPS: OK
	Invalid ioctls: OK
	CEC_DQEVENT: OK
	CEC_ADAP_G/S_PHYS_ADDR: OK
	CEC_ADAP_G/S_LOG_ADDRS: OK
		fail: cec-test-adapter.cpp(352): msg.rx_status & CEC_RX_STATUS_TIMEOUT
	CEC_TRANSMIT: FAIL
		fail: cec-test-adapter.cpp(540): doioctl(node, CEC_RECEIVE, &msg)
	CEC_RECEIVE: FAIL
		fail: cec-test-adapter.cpp(783): doioctl(node, CEC_RECEIVE, &msg)
	CEC_TRANSMIT/RECEIVE (non-blocking): FAIL
		fail: cec-test-adapter.cpp(883): doioctl(node, CEC_RECEIVE, &msg)
	CEC_G/S_MODE: FAIL
		Successful transmits: 73
		NACKed transmits: 1
		Received messages: 2 of which 0 were CEC_MSG_CEC_VERSION
		Received 54 messages immediately, and 21 over 7 seconds
		SFTs for repeating messages (>= 7): 6: 71
	warn: cec-test-adapter.cpp(1291): There were 74 CEC_GET_VERSION transmits but only 0 CEC_VERSION receives
	CEC_EVENT_LOST_MSGS: OK

Network topology:
	System Information for device 0 (TV) from device 4 (Playback Device 1):
		CEC Version                : Tx, OK, Rx, Timeout
		Physical Address           : Tx, OK, Rx, Timeout
		Vendor ID                  : 0x00e091 (LG)
		OSD Name                   : Tx, OK, Rx, Timeout
		Menu Language              : kor
		Power Status               : On

Total for seco_meccec device /dev/cec0: 11, Succeeded: 7, Failed: 4, Warnings: 1

> > 
> > Inspired by previous seco-cec implementation, attaches to i915 driver
> > cec-notifier.
> > 
> > Signed-off-by: Ettore Chimenti <ek5.chimenti@xxxxxxxxx>
> > ---
> >  MAINTAINERS                                   |   2 +
> >  drivers/media/cec/platform/Kconfig            |  22 +-
> >  drivers/media/cec/platform/seco/Makefile      |   3 +-
> >  drivers/media/cec/platform/seco/seco-meccec.c | 824 ++++++++++++++++++
> >  drivers/media/cec/platform/seco/seco-meccec.h | 130 +++
> >  5 files changed, 978 insertions(+), 3 deletions(-)
> >  create mode 100644 drivers/media/cec/platform/seco/seco-meccec.c
> >  create mode 100644 drivers/media/cec/platform/seco/seco-meccec.h
> > 
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index fca970a46e77..faa3a19b62eb 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -17303,6 +17303,8 @@ M:	Ettore Chimenti <ek5.chimenti@xxxxxxxxx>
> >  S:	Maintained
> >  F:	drivers/media/cec/platform/seco/seco-cec.c
> >  F:	drivers/media/cec/platform/seco/seco-cec.h
> > +F:	drivers/media/cec/platform/seco/seco-meccec.c
> > +F:	drivers/media/cec/platform/seco/seco-meccec.h
> 
> It's probably easier to just replace these 4 lines with:
> 
> F:	drivers/media/cec/platform/seco/
> 

I agree.

> >  
> >  SECURE COMPUTING
> >  M:	Kees Cook <keescook@xxxxxxxxxxxx>
> > diff --git a/drivers/media/cec/platform/Kconfig b/drivers/media/cec/platform/Kconfig
> > index b672d3142eb7..da92b22d0775 100644
> > --- a/drivers/media/cec/platform/Kconfig
> > +++ b/drivers/media/cec/platform/Kconfig
> > @@ -98,7 +98,11 @@ config CEC_TEGRA
> >  	  between compatible devices.
> >  
> >  config CEC_SECO
> > -	tristate "SECO Boards HDMI CEC driver"
> > +	bool "SECO Boards HDMI CEC drivers"
> > +
> > +config CEC_SECO_LEGACY
> > +	tristate "SECO Legacy Boards HDMI CEC driver"
> 
> I'm not too keen on 'Legacy' since it doesn't tell you what
> boards are considered legacy. Is there a better description?
> 
> E.g.: 'SECO ???-Based Boards'
> 

Yes, me neither. What about CEC_SECO_STM32? The former driver boards
were based on that EC.

> > +	depends on CEC_SECO
> >  	depends on (X86 || IA64) || COMPILE_TEST
> >  	depends on PCI && DMI
> >  	select CEC_CORE
> > @@ -109,9 +113,23 @@ config CEC_SECO
> >  	  CEC bus is present in the HDMI connector and enables communication
> >  	  between compatible devices.
> >  
> > +config CEC_SECO_MEC
> > +	tristate "SECO MEC-Based Boards HDMI CEC driver"
> > +	depends on CEC_SECO
> > +	depends on (X86 || IA64) || COMPILE_TEST
> > +	depends on PCI && DMI
> > +	select CEC_CORE
> > +	select CEC_NOTIFIER
> > +	help
> > +	  This is a driver for SECO MEC-Based Boards integrated CEC interface.
> > +	  Selecting it will enable support for this device.
> > +	  CEC bus is present in the HDMI connectors and enables communication
> > +	  between compatible devices.
> > +
> > +
> >  config CEC_SECO_RC
> >  	bool "SECO Boards IR RC5 support"
> > -	depends on CEC_SECO
> > +	depends on CEC_SECO_LEGACY
> >  	depends on RC_CORE=y || RC_CORE = CEC_SECO
> >  	help
> >  	  If you say yes here you will get support for the
> > diff --git a/drivers/media/cec/platform/seco/Makefile b/drivers/media/cec/platform/seco/Makefile
> > index aa1ca8ccdb8b..ccd51dc4c5ac 100644
> > --- a/drivers/media/cec/platform/seco/Makefile
> > +++ b/drivers/media/cec/platform/seco/Makefile
> > @@ -1,2 +1,3 @@
> >  # SPDX-License-Identifier: GPL-2.0-only
> > -obj-$(CONFIG_CEC_SECO) += seco-cec.o
> > +obj-$(CONFIG_CEC_SECO_LEGACY) += seco-cec.o
> > +obj-$(CONFIG_CEC_SECO_MEC) += seco-meccec.o
> > diff --git a/drivers/media/cec/platform/seco/seco-meccec.c b/drivers/media/cec/platform/seco/seco-meccec.c
> > new file mode 100644
> > index 000000000000..eaa0006e1390
> > --- /dev/null
> > +++ b/drivers/media/cec/platform/seco/seco-meccec.c
> > @@ -0,0 +1,824 @@
> > +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
> > +/*
> > + * CEC driver for SECO MEC-based Boards
> > + *
> > + * Author:  Ettore Chimenti <ek5.chimenti@xxxxxxxxx>
> > + * Copyright (C) 2022, SECO SpA.
> > + */
> > +
> > +#include <linux/module.h>
> > +#include <linux/acpi.h>
> > +#include <linux/dmi.h>
> > +#include <linux/gpio/consumer.h>
> > +#include <linux/gpio.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/pci.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/version.h>
> > +
> > +/* CEC Framework */
> > +#include <media/cec-notifier.h>
> > +
> > +#include "seco-meccec.h"
> > +
> > +#define SECO_MECCEC_DEV_NAME "seco_meccec"
> > +#define MECCEC_MAX_CEC_ADAP 4
> > +#define MECCEC_MAX_ADDRS 1
> > +#define MECCEC_MAX_STATUS_RETRIES 10
> > +
> > +static DEFINE_MUTEX(ec_mutex);
> > +
> > +struct seco_meccec_data {
> > +	struct device *dev;
> > +	struct platform_device *pdev;
> > +	struct cec_adapter *cec_adap[MECCEC_MAX_CEC_ADAP];
> > +	struct cec_notifier *notifier[MECCEC_MAX_CEC_ADAP];
> > +	u8 channels;	/* bitmask */
> > +	int irq;
> > +};
> > +
> > +struct seco_meccec_adap_data {
> > +	struct seco_meccec_data *cec;
> > +	int idx;
> > +};
> > +
> > +static int ec_reg_byte_op(u8 reg, u8 operation, u8 data, u8 *result)
> > +{
> > +	int res;
> > +
> > +	/* Check still active */
> > +	res = inb(MBX_RESOURCE_REGISTER) & AGENT_ACTIVE(AGENT_USER);
> > +	if (!res)
> > +		return -EBUSY;
> > +
> > +	/* Set the register index */
> > +	outb(reg, EC_REGISTER_INDEX);
> > +
> > +	/* Check still active */
> > +	res = inb(MBX_RESOURCE_REGISTER) & AGENT_ACTIVE(AGENT_USER);
> > +	if (!res)
> > +		return -EBUSY;
> > +
> > +	if (operation == READ) {
> > +		if (!result)
> > +			return -EINVAL;
> > +
> > +		/* Read the data value */
> > +		*result = inb(EC_REGISTER_DATA);
> > +
> > +	} else if (operation == WRITE) {
> > +		/* Write the data value */
> > +		outb(data, EC_REGISTER_DATA);
> > +	}
> > +
> > +	/* Check still active */
> > +	res = inb(MBX_RESOURCE_REGISTER) & AGENT_ACTIVE(AGENT_USER);
> > +	if (!res)
> > +		return -EBUSY;
> > +
> > +	return 0;
> > +}
> > +
> > +#define ec_reg_byte_rd(reg, res) ec_reg_byte_op(reg, READ, 0, res)
> > +#define ec_reg_byte_wr(reg, val) ec_reg_byte_op(reg, WRITE, val, NULL)
> > +
> > +static int ec_waitstatus(u8 status, u8 cmd)
> > +{
> > +	int idx;
> > +
> > +	/* Loop until time-out or Status */
> > +	for (idx = 0; idx < EC_CMD_TIMEOUT; idx++) {
> > +		u8 res = inb(MBX_RESOURCE_REGISTER);
> > +
> > +		/* If status, done */
> > +		if ((res & AGENT_MASK(AGENT_USER)) == status)
> > +			return 0;
> > +
> > +		/* Send command if needed */
> > +		if (!cmd)
> > +			continue;
> > +
> > +		/* Retry sending command when mailbox is free */
> > +		for ( ; idx < EC_CMD_TIMEOUT; idx++) {
> > +			/* Check busy bit */
> > +			res = inb(MBX_BUSY_REGISTER) & EC_STATUS_REGISTER;
> > +
> > +			if (!res) {
> > +				/* Send command */
> > +				outb_p(cmd, MBX_BUSY_REGISTER);
> > +				break;
> > +			}
> > +		}
> > +	}
> > +
> > +	/* Time-out expired */
> > +	return -EAGAIN;
> > +}
> > +
> > +static int ec_send_command(const struct platform_device *pdev, u8 cmd,
> > +			   void *tx_buf, u8 tx_size,
> > +			   void *rx_buf, u8 rx_size)
> > +{
> > +	struct seco_meccec_data *meccec = platform_get_drvdata(pdev);
> > +	const struct device *dev = meccec->dev;
> > +
> > +	int status;
> > +	u8 *buf;
> > +	u8 idx;
> > +	u8 res;
> > +
> > +	mutex_lock(&ec_mutex);
> > +
> > +	/* Wait for BIOS agent idle */
> > +	status = ec_waitstatus(AGENT_IDLE(AGENT_USER), 0);
> > +	if (status) {
> > +		dev_err(dev, "Mailbox agent not available\n");
> > +		goto err;
> > +	}
> > +
> > +	/* BIOS agent is idle: we can request access */
> > +	status = ec_waitstatus(AGENT_ACTIVE(AGENT_USER),
> > +			       REQUEST_MBX_ACCESS(AGENT_USER));
> > +	if (status) {
> > +		dev_err(dev, "Request mailbox agent failed\n");
> > +		goto err;
> > +	}
> > +
> > +	/* Prepare MBX data */
> > +	for (buf = (uint8_t *)tx_buf, idx = 0; (!status) && idx < tx_size; idx++)
> > +		status = ec_reg_byte_wr(EC_MBX_REGISTER + idx, buf[idx]);
> > +
> > +	if (status) {
> > +		dev_err(dev, "Mailbox buffer write failed\n");
> > +		goto err;
> > +	}
> > +
> > +	/* Send command */
> > +	status = ec_reg_byte_wr(EC_COMMAND_REGISTER, cmd);
> > +	if (status) {
> > +		dev_err(dev, "Command write failed\n");
> > +		goto err;
> > +	}
> > +
> > +	/* Wait for completion */
> > +	status = ec_waitstatus(AGENT_DONE(AGENT_USER), 0);
> > +	if (status) {
> > +		dev_err(dev, "Mailbox did not complete after command write\n");
> > +		goto err;
> > +	}
> > +
> > +	/* Get result code */
> > +	status = ec_reg_byte_rd(EC_RESULT_REGISTER, &res);
> > +	if (status) {
> > +		dev_err(dev, "Result read failed\n");
> > +		goto err;
> > +	}
> > +
> > +	/* Get result code and translate it */
> > +	switch (res) {
> > +	case EC_NO_ERROR:
> > +		status = 0;
> > +		break;
> > +
> > +	case EC_UNKNOWN_COMMAND_ERROR:
> > +		status = -EPERM;
> > +		break;
> > +
> > +	case EC_INVALID_ARGUMENT_ERROR:
> > +		status = -EINVAL;
> > +		break;
> > +
> > +	case EC_TIMEOUT_ERROR:
> > +		status = -EAGAIN;
> > +		break;
> > +
> > +	default:
> > +		status = -EIO;
> > +		break;
> > +	}
> > +	if (status) {
> > +		dev_err(dev, "Command failed\n");
> > +		goto err;
> > +	}
> > +
> > +	/* Read return data */
> > +	for (buf = (uint8_t *)rx_buf, idx = 0; !status && idx < rx_size; idx++)
> > +		status = ec_reg_byte_rd(EC_MBX_REGISTER + idx, &buf[idx]);
> > +
> > +	if (status) {
> > +		dev_err(dev, "Mailbox read failed\n");
> > +		goto err;
> > +	}
> > +
> > +err:
> > +	/* Release access, ignoring eventual time-out */
> > +	ec_waitstatus(AGENT_IDLE(AGENT_USER), RELEASE_MBX_ACCESS(AGENT_USER));
> > +
> > +	mutex_unlock(&ec_mutex);
> > +	return status;
> > +}
> > +
> > +static int ec_get_version(struct seco_meccec_data *cec)
> > +{
> > +	const struct device *dev = cec->dev;
> > +	const struct platform_device *pdev = cec->pdev;
> > +	struct version_msg_t version;
> > +	int status;
> > +
> > +	status = ec_send_command(pdev, GET_FIRMWARE_VERSION_CMD,
> > +				 NULL, 0,
> > +				 &version, sizeof(struct version_msg_t));
> > +
> > +	if (status)
> > +		return status;
> > +
> > +	dev_dbg(dev, "Firmware version %X.%02X / %X.%02X\n",
> > +		version.fw.major,
> > +		version.fw.minor,
> > +		version.lib.major,
> > +		version.lib.minor);
> 
> Consider implementing the adap_status op to show this information in a
> /sys/kernel/debug/cec/cecX/status file. This is useful to expose.
> 

Good to know, I'll try to implement in v3.

> > +
> > +	return 0;
> > +}
> > +
> > +static int ec_cec_status(struct seco_meccec_data *cec,
> > +			 struct seco_meccec_status_t *result)
> > +{
> > +	const struct device *dev = cec->dev;
> > +	const struct platform_device *pdev = cec->pdev;
> > +	struct seco_meccec_status_t buf = { 0 };
> > +	int ret, i;
> > +
> > +	/* retry until get status or interrupt will not reset */
> > +	for (i = 0; i < MECCEC_MAX_STATUS_RETRIES; i++) {
> > +		ret = ec_send_command(pdev, GET_CEC_STATUS_CMD,
> > +				      &buf, sizeof(struct seco_meccec_status_t),
> > +				      &buf, sizeof(struct seco_meccec_status_t));
> > +		if (ret) {
> > +			dev_dbg(dev, "Status: Mailbox is busy. Retrying.\n");
> > +			continue;
> > +		}
> > +		break;
> > +	}
> > +
> > +	if (ret)
> > +		return ret;
> > +
> > +	dev_dbg(dev, "CEC Status:\n");
> > +	dev_dbg(dev, "ch0: 0x%02x\n", buf.status_ch0);
> > +	dev_dbg(dev, "ch1: 0x%02x\n", buf.status_ch1);
> > +	dev_dbg(dev, "ch2: 0x%02x\n", buf.status_ch2);
> > +	dev_dbg(dev, "ch3: 0x%02x\n", buf.status_ch3);
> 
> This might also be useful info for a status file.
> 
> In general I feel that there are too many dev_dbg() calls in this source code.
> Some pruning might be useful, and, as mentioned, some of the info might be
> better for a status file.
> 

Nice, I'll try to convert some of them into adap_status op.

> > +
> > +	if (result)
> > +		*result = buf;
> > +
> > +	return 0;
> > +}
> > +
> > +static int meccec_adap_phys_addr(struct cec_adapter *adap, u16 phys_addr)
> > +{
> > +	struct seco_meccec_adap_data *adap_data = cec_get_drvdata(adap);
> > +	struct seco_meccec_data *cec = adap_data->cec;
> > +	const struct platform_device *pdev = cec->pdev;
> > +	const struct device *dev = cec->dev;
> > +	struct seco_meccec_phyaddr_t buf = { };
> > +	int status;
> > +
> > +	buf.bus = adap_data->idx;
> > +	buf.addr = phys_addr;
> > +
> > +	/* Need to tell physical address to wake up while standby */
> > +	status = ec_send_command(pdev, SET_CEC_PHYADDR_CMD,
> > +				 &buf, sizeof(struct seco_meccec_phyaddr_t),
> > +				 NULL, 0);
> > +	dev_dbg(dev, "Physical address 0x%04x\n", phys_addr);
> > +
> > +	return status;
> > +}
> > +
> > +static int meccec_adap_log_addr(struct cec_adapter *adap, u8 logical_addr)
> > +{
> > +	struct seco_meccec_adap_data *adap_data = cec_get_drvdata(adap);
> > +	struct seco_meccec_data *cec = adap_data->cec;
> > +	struct platform_device *pdev = cec->pdev;
> > +	const struct device *dev = cec->dev;
> > +	struct seco_meccec_logaddr_t buf = { };
> > +	int status;
> > +
> > +	buf.bus = adap_data->idx;
> > +	buf.addr = logical_addr & 0x0f;
> > +
> > +	status = ec_send_command(pdev, SET_CEC_LOGADDR_CMD,
> > +				 &buf, sizeof(struct seco_meccec_logaddr_t),
> > +				 NULL, 0);
> > +	dev_dbg(dev, "Logical address 0x%02x\n", logical_addr);
> > +
> > +	/* Physical address is sent to MEC to be stored for replying
> 
> These multiline comments traditionally start with /* on a line by itself. So:
> 
> /*
>  * long
>  * comment
>  */
> 
> Can you change that in this source?
> 

Ops. I forgot one of them. Yes, no problem.

> > +	 * autonomously to GIVE_PHYSICAL_ADDR and matching SET_STREAM_PATH when
> > +	 * the CPU is sleeping. If PA match with a SET_STREAM_PATH message, it
> > +	 * will resume the CPU.
> > +	 *
> > +	 * When setting LA, adap has valid physical address
> > +	 */
> > +	status = meccec_adap_phys_addr(adap, adap->phys_addr);
> > +	if (status)
> > +		dev_err(dev, "Set physical address failed %d\n", status);
> 
> Should an error from meccec_adap_phys_addr() be considered a failure?
> Or just a warning, and you continue as is. You can argue either way, but I
> am leaning towards just issuing a warning but still return success.
> 

Agree on that. Not a big deal indeed.

> > +
> > +	return status;
> > +}
> > +
> > +static int meccec_adap_enable(struct cec_adapter *adap, bool enable)
> > +{
> > +	struct seco_meccec_adap_data *adap_data = cec_get_drvdata(adap);
> > +	struct seco_meccec_data *cec = adap_data->cec;
> > +	const struct device *dev = cec->dev;
> > +	int ret;
> > +
> > +	/* reset status register */
> > +	ret = ec_cec_status(cec, NULL);
> > +	if (ret)
> > +		dev_err(dev, "enable: status operation failed %d\n", ret);
> > +
> > +	if (enable) {
> > +		dev_dbg(dev, "Device enabled\n");
> > +	} else {
> > +		dev_dbg(dev, "Device disabled\n");
> > +
> > +		/* When the adapter is disabled, setting the physical address to
> > +		 * invalid prevents the MEC firmware to wake up the CPU.
> > +		 */
> > +		ret = meccec_adap_phys_addr(adap, CEC_PHYS_ADDR_INVALID);
> > +		if (ret) {
> > +			dev_err(dev, "enable: set physical address failed %d\n", ret);
> > +			return ret;
> > +		}
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static int meccec_adap_transmit(struct cec_adapter *adap, u8 attempts,
> > +				u32 signal_free_time, struct cec_msg *msg)
> > +{
> > +	struct seco_meccec_adap_data *adap_data = cec_get_drvdata(adap);
> > +	struct seco_meccec_data *cec = adap_data->cec;
> > +	struct platform_device *pdev = cec->pdev;
> > +	const struct device *dev = cec->dev;
> > +	struct seco_meccec_msg_t buf = { };
> > +	int status;
> > +
> > +	dev_dbg(dev, "Device transmitting\n");
> > +
> > +	buf.bus = adap_data->idx;
> > +	buf.send = (msg->msg[0] & 0xf0) >> 4;
> > +	buf.dest = msg->msg[0] & 0x0f;
> > +	buf.size = msg->len - 1;
> > +	memcpy(buf.data, msg->msg + 1, buf.size);
> > +
> > +	status = ec_send_command(pdev, CEC_WRITE_CMD,
> > +				 &buf, sizeof(struct seco_meccec_msg_t),
> > +				 NULL, 0);
> > +
> > +	return status;
> > +}
> > +
> > +static void meccec_tx_done(struct seco_meccec_data *cec, int adap_idx, u8 status_val)
> > +{
> > +	struct cec_adapter *adap = cec->cec_adap[adap_idx];
> > +
> > +	if (status_val & SECOCEC_STATUS_TX_ERROR_MASK) {
> > +		if (status_val & SECOCEC_STATUS_TX_NACK_ERROR)
> > +			cec_transmit_attempt_done(adap, CEC_TX_STATUS_NACK);
> > +		else
> > +			cec_transmit_attempt_done(adap, CEC_TX_STATUS_ERROR);
> > +	} else {
> > +		cec_transmit_attempt_done(adap, CEC_TX_STATUS_OK);
> > +	}
> > +}
> > +
> > +static void meccec_rx_done(struct seco_meccec_data *cec, int adap_idx, u8 status_val)
> > +{
> > +	struct device *dev = cec->dev;
> > +	struct platform_device *pdev = cec->pdev;
> > +	struct cec_adapter *adap = cec->cec_adap[adap_idx];
> > +	struct seco_meccec_msg_t buf = { .bus = adap_idx };
> > +	struct cec_msg msg = { };
> > +	int status;
> > +
> > +	if (status_val & SECOCEC_STATUS_RX_OVERFLOW_MASK)
> > +		dev_warn(dev, "Received more than 16 bytes. Discarding\n");
> 
> Add a period after 'Discarding'.
> 

No problem.

> > +
> > +	if (status_val & SECOCEC_STATUS_RX_ERROR_MASK) {
> > +		dev_warn(dev, "Message received with errors. Discarding\n");
> 
> Ditto.
> 

No problem.

> > +		status = -EIO;
> > +		goto rxerr;
> 
> Huh? Just do 'return -EIO;' here.
> 

Mmmh, something is not right since the function is void. Need to check.

> > +	}
> > +	/* Read message buffer */
> > +	status = ec_send_command(pdev, CEC_READ_CMD,
> > +				 &buf, sizeof(struct seco_meccec_msg_t),
> > +				 &buf, sizeof(struct seco_meccec_msg_t));
> > +	if (status)
> > +		return;
> > +
> > +	/* Device msg len already accounts for the header */
> > +	msg.len = min(buf.size + 1, CEC_MAX_MSG_SIZE);
> > +
> > +	/* Read logical address */
> > +	msg.msg[0]  = buf.dest & 0x0f;
> > +	msg.msg[0] |= (buf.send & 0x0f) << 4;
> > +
> > +	memcpy(msg.msg + 1, buf.data, buf.size);
> > +
> > +	cec_received_msg(adap, &msg);
> > +	dev_dbg(dev, "Message received successfully\n");
> > +
> > +rxerr:
> > +	return;
> > +}
> > +
> > +static int get_status_ch(struct seco_meccec_status_t *s,
> 
> s can be const
> 

Yes, agree.

> > +			 int ch)
> > +{
> > +	if (!s)
> > +		return -1;
> > +
> > +	switch (ch) {
> > +	case 0: return s->status_ch0;
> > +	case 1: return s->status_ch1;
> > +	case 2: return s->status_ch2;
> > +	case 3: return s->status_ch3;
> > +	default: return -1;
> > +	}
> > +}
> > +
> > +static irqreturn_t seco_meccec_irq_handler(int irq, void *priv)
> > +{
> > +	struct seco_meccec_data *cec = priv;
> > +	struct device *dev = cec->dev;
> > +	struct seco_meccec_status_t status;
> > +	bool interrupt_served = false;
> > +	int ret, idx;
> > +
> > +	dev_dbg(dev, "Interrupt Called!\n");
> > +
> > +	ret = ec_cec_status(cec, &status);
> > +	if (ret) {
> > +		dev_warn(dev, "IRQ: status cmd failed %d\n", ret);
> > +		goto err;
> 
> Since there is only one goto, I think it is better to move the
> code after the 'err' label here, and drop the goto.
> 

Yes, way better.

> > +	}
> > +
> > +	for (idx = 0; idx < MECCEC_MAX_CEC_ADAP; idx++) {
> > +		if (cec->channels & BIT_MASK(idx)) {
> 
> I'd invert this:
> 
> 		if (!(cec->channels & BIT_MASK(idx)))
> 			continue;
> 

Yes, I did that somewhere else, and it is more clean indeed.

> > +			int cec_val = get_status_ch(&status, idx);
> 
> Wouldn't cec_status be a better name for this variable?
> 

Yes, since it is the status of a CEC channel, makes sense. 

> > +
> > +			if (cec_val < 0)
> > +				continue;
> > +
> > +			if (cec_val & SECOCEC_STATUS_MSG_RECEIVED_MASK)
> > +				meccec_rx_done(cec, idx, cec_val);
> > +			if (cec_val & SECOCEC_STATUS_MSG_SENT_MASK)
> > +				meccec_tx_done(cec, idx, cec_val);
> > +
> > +			if (cec_val & (SECOCEC_STATUS_MSG_SENT_MASK |
> > +				       SECOCEC_STATUS_MSG_RECEIVED_MASK))
> > +				interrupt_served = true;
> > +		}
> > +	}
> > +	if (!interrupt_served)
> > +		dev_warn(dev, "Message not received or sent, but interrupt fired\n");
> > +
> > +	return IRQ_HANDLED;
> > +err:
> > +	/* reset status register */
> > +	ret = ec_cec_status(cec, NULL);
> > +	if (ret)
> > +		dev_err(dev, "IRQ: status cmd failed twice %d\n", ret);
> > +
> > +	return IRQ_HANDLED;
> > +}
> > +
> > +struct cec_dmi_match {
> > +	const char *sys_vendor;
> > +	const char *product_name;
> > +	const char *devname;
> > +	const char *conn[MECCEC_MAX_CEC_ADAP];
> > +};
> > +
> > +static const struct cec_dmi_match secocec_dmi_match_table[] = {
> > +	/* UDOO BOLT */
> > +	{ "Seco", "0C60", "0000:05:00.0", {"Port B", "Port C"} },
> > +	/* UDOO Vision */
> > +	{ "Seco", "0D02", "0000:00:02.0", {"Port B"} },
> > +	/* SECO SBC-D61 */
> > +	{ "Seco", "0D61", "0000:00:02.0", {"Port B", "Port C"} },
> > +};
> > +
> > +static struct device *seco_meccec_find_hdmi_dev(struct device *dev,
> > +						const char * const **conn_ptr)
> > +{
> > +	int i;
> > +
> > +	for (i = 0 ; i < ARRAY_SIZE(secocec_dmi_match_table) ; ++i) {
> > +		const struct cec_dmi_match *m = &secocec_dmi_match_table[i];
> > +
> > +		if (dmi_match(DMI_SYS_VENDOR, m->sys_vendor) &&
> > +		    dmi_match(DMI_PRODUCT_NAME, m->product_name)) {
> > +			struct device *d;
> > +
> > +			/* Find the device, bail out if not yet registered */
> > +			d = bus_find_device_by_name(&pci_bus_type, NULL,
> > +						    m->devname);
> > +			if (!d)
> > +				return ERR_PTR(-EPROBE_DEFER);
> > +
> > +			put_device(d);
> > +
> > +			if (!conn_ptr)
> > +				return ERR_PTR(-EFAULT);
> > +
> > +			*conn_ptr = m->conn;
> > +
> > +			return d;
> > +		}
> > +	}
> > +
> > +	return ERR_PTR(-EINVAL);
> > +}
> > +
> > +static int seco_meccec_acpi_probe(struct seco_meccec_data *sdev)
> > +{
> > +	struct device *dev = sdev->dev;
> > +	const struct acpi_device *adev = ACPI_COMPANION(dev);
> > +	const union acpi_object *obj;
> > +	struct gpio_desc *gpio;
> > +	int irq = 0;
> > +	int ret;
> > +
> > +	gpio = devm_gpiod_get(dev, "notify", GPIOF_IN);
> > +	if (IS_ERR(gpio)) {
> > +		dev_err(dev, "Cannot request interrupt gpio\n");
> > +		return PTR_ERR(gpio);
> > +	}
> > +
> > +	irq = gpiod_to_irq(gpio);
> > +	if (irq < 0) {
> > +		dev_err(dev, "Cannot find valid irq\n");
> > +		return -ENODEV;
> > +	}
> > +	dev_dbg(dev, "irq-gpio is bound to IRQ %d\n", irq);
> > +	sdev->irq = irq;
> > +
> > +	/* Get info from ACPI about channels capabilities */
> > +	ret = acpi_dev_get_property(adev, "av-channels",  ACPI_TYPE_INTEGER, &obj);
> > +	if (ret < 0) {
> > +		dev_err(dev, "Cannot retrieve channel properties\n");
> > +		return ret;
> > +	}
> > +	dev_dbg(dev, "ACPI property: av-channels -> %x\n", (int)obj->integer.value);
> > +	sdev->channels = (int)obj->integer.value;
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct cec_adap_ops meccec_cec_adap_ops = {
> > +	/* Low-level callbacks */
> > +	.adap_enable = meccec_adap_enable,
> > +	.adap_log_addr = meccec_adap_log_addr,
> > +	.adap_transmit = meccec_adap_transmit,
> > +};
> > +
> > +static int meccec_create_adapter(struct seco_meccec_data *cec, int idx)
> > +{
> > +	struct seco_meccec_adap_data *adap_data;
> > +	struct device *dev = cec->dev;
> > +	struct cec_adapter *acec;
> > +	char adap_name[32];
> > +
> > +	if (!cec)
> > +		return -EINVAL;
> > +
> > +	adap_data = devm_kzalloc(dev, sizeof(*adap_data), GFP_KERNEL);
> > +	if (!adap_data)
> > +		return -ENOMEM;
> > +
> > +	adap_data->cec = cec;
> > +	adap_data->idx = idx;
> > +
> > +	sprintf(adap_name, "%s-%d", dev_name(dev), idx);
> > +
> > +	/* Allocate CEC adapter */
> > +	acec = cec_allocate_adapter(&meccec_cec_adap_ops,
> > +				    adap_data,
> > +				    adap_name,
> > +				    CEC_CAP_DEFAULTS |
> > +				    CEC_CAP_CONNECTOR_INFO,
> > +				    MECCEC_MAX_ADDRS);
> > +
> > +	if (IS_ERR(acec))
> > +		return PTR_ERR(acec);
> > +
> > +	/* Assign to data */
> > +	cec->cec_adap[idx] = acec;
> > +
> > +	return 0;
> > +}
> > +
> > +static int seco_meccec_probe(struct platform_device *pdev)
> > +{
> > +	struct seco_meccec_data *meccec;
> > +	struct device *dev = &pdev->dev;
> > +	struct device *hdmi_dev;
> > +	const char * const *conn;
> > +	int ret, idx;
> > +	int adaps = 0;
> > +	int notifs = 0;
> > +
> > +	meccec = devm_kzalloc(dev, sizeof(*meccec), GFP_KERNEL);
> > +	if (!meccec)
> > +		return -ENOMEM;
> > +
> > +	dev_set_drvdata(dev, meccec);
> > +
> > +	meccec->pdev = pdev;
> > +	meccec->dev = dev;
> > +
> > +	ret = ec_get_version(meccec);
> > +	if (ret) {
> > +		dev_err(dev, "Get version failed\n");
> > +		goto err;
> > +	}
> > +
> > +	if (!has_acpi_companion(dev)) {
> > +		dev_err(dev, "Cannot find any ACPI companion\n");
> > +		ret = -ENODEV;
> > +		goto err;
> > +	}
> > +
> > +	ret = seco_meccec_acpi_probe(meccec);
> > +	if (ret) {
> > +		dev_err(dev, "ACPI probe failed\n");
> > +		goto err;
> > +	}
> > +
> > +	ret = devm_request_threaded_irq(dev,
> > +					meccec->irq,
> > +					NULL,
> > +					seco_meccec_irq_handler,
> > +					IRQF_TRIGGER_RISING | IRQF_ONESHOT,
> > +					dev_name(&pdev->dev), meccec);
> > +
> > +	if (ret) {
> > +		dev_err(dev, "Cannot request IRQ %d\n", meccec->irq);
> > +		ret = -EIO;
> > +		goto err;
> > +	}
> > +
> > +	hdmi_dev = seco_meccec_find_hdmi_dev(&pdev->dev, &conn);
> > +	if (IS_ERR(hdmi_dev)) {
> > +		dev_err(dev, "Cannot find HDMI Device\n");
> > +		return PTR_ERR(hdmi_dev);
> > +	}
> > +	dev_dbg(dev, "HDMI device found\n");
> > +
> > +	for (idx = 0; idx < MECCEC_MAX_CEC_ADAP; idx++) {
> > +		if (meccec->channels & BIT_MASK(idx)) {
> > +			ret = meccec_create_adapter(meccec, idx);
> > +			if (ret)
> > +				goto err_delete_adapter;
> > +
> > +			dev_dbg(dev, "CEC adapter #%d allocated\n", idx);
> > +			adaps++;
> > +		}
> > +	}
> > +
> > +	for (idx = 0; idx < MECCEC_MAX_CEC_ADAP; idx++) {
> > +		if (meccec->channels & BIT_MASK(idx)) {
> > +			struct cec_adapter *acec = meccec->cec_adap[idx];
> > +			struct cec_notifier *ncec;
> > +
> > +			if (!acec) {
> > +				ret = -EINVAL;
> > +				goto err_notifier;
> > +			}
> > +
> > +			ncec = cec_notifier_cec_adap_register(hdmi_dev,
> > +							      conn[idx], acec);
> > +
> > +			dev_dbg(dev, "CEC notifier #%d allocated %s\n", idx, conn[idx]);
> > +
> > +			if (IS_ERR(ncec)) {
> > +				ret = PTR_ERR(ncec);
> > +				goto err_notifier;
> > +			}
> > +
> > +			meccec->notifier[idx] = ncec;
> > +			notifs++;
> > +		}
> > +	}
> > +
> > +	for (idx = 0; idx < MECCEC_MAX_CEC_ADAP; idx++) {
> > +		if (meccec->channels & BIT_MASK(idx)) {
> > +			ret = cec_register_adapter(meccec->cec_adap[idx], dev);
> > +			if (ret)
> > +				goto err_notifier;
> > +
> > +			dev_dbg(dev, "CEC adapter #%d registered\n", idx);
> > +		}
> > +	}
> > +
> > +	platform_set_drvdata(pdev, meccec);
> > +	dev_dbg(dev, "Device registered\n");
> > +
> > +	return ret;
> > +
> > +err_notifier:
> > +	for (idx = 0; idx < MECCEC_MAX_CEC_ADAP; idx++) {
> > +		if (meccec->channels & BIT_MASK(idx)) {
> > +			if (adaps--)
> > +				return ret;
> > +
> > +			cec_notifier_cec_adap_unregister(meccec->notifier[idx],
> > +							 meccec->cec_adap[idx]);
> > +		}
> > +	}
> > +err_delete_adapter:
> > +	for (idx = 0; idx < MECCEC_MAX_CEC_ADAP; idx++) {
> > +		if (meccec->channels & BIT_MASK(idx)) {
> > +			if (notifs--)
> > +				return ret;
> > +
> > +			cec_delete_adapter(meccec->cec_adap[idx]);
> > +		}
> > +	}
> > +err:
> > +	dev_err(dev, "%s device probe failed: %d\n", dev_name(dev), ret);
> > +
> > +	return ret;
> > +}
> > +
> > +static int seco_meccec_remove(struct platform_device *pdev)
> > +{
> > +	struct seco_meccec_data *meccec = platform_get_drvdata(pdev);
> > +	int idx;
> > +
> > +	for (idx = 0; idx < MECCEC_MAX_CEC_ADAP; idx++) {
> > +		if (meccec->channels && BIT_MASK(idx)) {
> > +			cec_notifier_cec_adap_unregister(meccec->notifier[idx],
> > +							 meccec->cec_adap[idx]);
> > +
> > +			cec_unregister_adapter(meccec->cec_adap[idx]);
> > +		}
> > +	}
> > +
> > +	dev_dbg(&pdev->dev, "CEC device removed\n");
> > +
> > +	return 0;
> > +}
> > +
> > +#ifdef CONFIG_PM_SLEEP
> > +static int seco_meccec_resume(struct device *dev)
> > +{
> > +	struct seco_meccec_data *cec = dev_get_drvdata(dev);
> > +	int ret;
> > +
> > +	dev_dbg(dev, "Device resumed from suspend\n");
> > +
> > +	/* reset status register */
> > +	ret = ec_cec_status(cec, NULL);
> > +	if (ret)
> > +		dev_err(dev, "resume: status operation failed %d\n", ret);
> > +
> > +	return ret;
> > +}
> > +
> > +static SIMPLE_DEV_PM_OPS(seco_meccec_pm_ops, NULL, seco_meccec_resume);
> > +#define SECO_MECCEC_PM_OPS (&seco_meccec_pm_ops)
> > +#else
> > +#define SECO_MECCEC_PM_OPS NULL
> > +#endif
> > +
> > +#ifdef CONFIG_ACPI
> > +static const struct acpi_device_id seco_meccec_acpi_match[] = {
> > +	{"CEC00002", 0},
> > +	{},
> > +};
> > +
> > +MODULE_DEVICE_TABLE(acpi, seco_meccec_acpi_match);
> > +#endif
> > +
> > +static struct platform_driver seco_meccec_driver = {
> > +	.driver = {
> > +		.name = SECO_MECCEC_DEV_NAME,
> > +		.acpi_match_table = ACPI_PTR(seco_meccec_acpi_match),
> > +		.pm = SECO_MECCEC_PM_OPS,
> > +	},
> > +	.probe = seco_meccec_probe,
> > +	.remove = seco_meccec_remove,
> > +};
> > +
> > +module_platform_driver(seco_meccec_driver);
> > +
> > +MODULE_DESCRIPTION("SECO MEC CEC Driver");
> > +MODULE_AUTHOR("Ettore Chimenti <ek5.chimenti@xxxxxxxxx>");
> > +MODULE_LICENSE("Dual BSD/GPL");
> > diff --git a/drivers/media/cec/platform/seco/seco-meccec.h b/drivers/media/cec/platform/seco/seco-meccec.h
> > new file mode 100644
> > index 000000000000..6f7fc9e13e30
> > --- /dev/null
> > +++ b/drivers/media/cec/platform/seco/seco-meccec.h
> > @@ -0,0 +1,130 @@
> > +/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
> > + * CEC driver for SECO MEC-based Boards
> > + *
> > + * Author:  Ettore Chimenti <ek5.chimenti@xxxxxxxxx>
> > + * Copyright (C) 2022, SECO SpA.
> > + */
> > +
> > +/* MailBox definitions */
> > +#define MBX_RESERVED_SIZE	0x10
> > +#define MBX_RESERVED_BASE	0x2b0
> > +
> > +#define BAR_FROM_MBX_BASE(x)	(x + MBX_RESERVED_BASE)
> > +
> > +#define RES_BAR_OFFSET		0
> > +#define BSY_BAR_OFFSET		4
> > +#define MBX_BAR_OFFSET		0xc
> > +
> > +#define MBX_RESOURCE_REGISTER	BAR_FROM_MBX_BASE(RES_BAR_OFFSET)
> > +#define MBX_BUSY_REGISTER	BAR_FROM_MBX_BASE(BSY_BAR_OFFSET)
> > +#define MBX_ACCESS_BAR		BAR_FROM_MBX_BASE(MBX_BAR_OFFSET)
> > +
> > +#define EC_REGISTER_INDEX	MBX_ACCESS_BAR
> > +#define EC_REGISTER_DATA	(EC_REGISTER_INDEX + 1)
> > +#define EC_MBX_SIZE		0x20
> > +
> > +#define EC_COMMAND_REGISTER	0
> > +#define EC_RESULT_REGISTER	1
> > +#define EC_STATUS_REGISTER	2
> > +#define EC_MBX_REGISTER		0x10
> > +
> > +#define EC_CMD_TIMEOUT		0x30000 /* Maximum wait loop */
> > +
> > +/* Firmware version data struct and definitions */
> > +#define FIRMWARE_TIME_STAMP_SIZE (EC_MBX_SIZE - sizeof(u32))
> > +
> > +struct version_t {
> > +	u8 minor;
> > +	u8 major;
> > +};
> > +
> > +struct version_msg_t {
> > +	struct version_t fw;
> > +	struct version_t lib;
> > +	u8 firmware_ts[FIRMWARE_TIME_STAMP_SIZE];
> > +};
> > +
> > +/* CEC data structs and constant definitions */
> > +#define MECCEC_MAX_MSG_SIZE 16
> > +
> > +struct seco_meccec_msg_t {
> > +	u8 bus;
> > +	u8 send;
> > +	u8 dest;
> > +	u8 data[MECCEC_MAX_MSG_SIZE];
> > +	u8 size;
> > +};
> > +
> > +struct seco_meccec_logaddr_t {
> > +	u8 bus;
> > +	u8 addr;
> > +};
> > +
> > +struct seco_meccec_phyaddr_t {
> > +	u16 bus;
> > +	u16 addr;
> > +};
> > +
> > +struct seco_meccec_status_t {
> > +	u8 status_ch0;
> > +	u8 status_ch1;
> > +	u8 status_ch2;
> > +	u8 status_ch3;
> > +};
> > +
> > +/* Status data */
> > +#define SECOCEC_STATUS_MSG_RECEIVED_MASK	BIT(0)
> > +#define SECOCEC_STATUS_RX_ERROR_MASK		BIT(1)
> > +#define SECOCEC_STATUS_MSG_SENT_MASK		BIT(2)
> > +#define SECOCEC_STATUS_TX_ERROR_MASK		BIT(3)
> > +
> > +#define SECOCEC_STATUS_TX_NACK_ERROR		BIT(4)
> > +#define SECOCEC_STATUS_RX_OVERFLOW_MASK		BIT(5)
> > +
> > +/* MBX Status bitmap values from EC to Host */
> > +enum MBX_STATUS {
> > +	MBX_OFF     = 0,	/* Disable MBX Interface */
> > +	MBX_ON      = 1,	/* Enable MBX Interface  */
> > +	MBX_ACTIVE0 = (1 << 6),	/* MBX AGENT 0 active    */
> > +	MBX_QUEUED0 = (1 << 7),	/* MBX AGENT 0 idle      */
> > +};
> > +
> > +#define AGENT_IDLE(x)      0
> > +#define AGENT_QUEUED(x)    (MBX_QUEUED0 >> (2 * x))
> > +#define AGENT_ACTIVE(x)    (MBX_ACTIVE0 >> (2 * x))
> > +#define AGENT_MASK(x)      (AGENT_QUEUED(x) + AGENT_ACTIVE(x))
> > +#define AGENT_DONE(x)      AGENT_MASK(x)
> > +#define MBX_STATUS_DEFAULT 0
> > +
> > +/* MBX user IDs */
> > +enum AGENT_IDS {
> > +	AGENT_BIOS, /* BIOS AGENT */
> > +	AGENT_ACPI, /* ACPI AGENT */
> > +	AGENT_EAPI, /* EAPI AGENT */
> > +	AGENT_USER, /* USER AGENT */
> > +	AGENT_NONE, /* No AGENT   */
> > +};
> > +
> > +/* MBX command results */
> > +enum CMD_RESULT {
> > +	EC_NO_ERROR = 0,		/* Success	    */
> > +	EC_UNKNOWN_COMMAND_ERROR,	/* Unknown command  */
> > +	EC_INVALID_ARGUMENT_ERROR,	/* Invalid argument */
> > +	EC_TIMEOUT_ERROR,		/* Waiting Time-out */
> > +	EC_DEVICE_ERROR,		/* Device error     */
> > +};
> > +
> > +/* MBX commands */
> > +enum MBX_CMDS {
> > +	GET_FIRMWARE_VERSION_CMD = 0,    /* Get firmware version record		*/
> > +	CEC_WRITE_CMD		 = 0x80, /* Write CEC command			*/
> > +	CEC_READ_CMD		 = 0x81, /* Read CEC command			*/
> > +	GET_CEC_STATUS_CMD	 = 0x82, /* Get CEC status regisers		*/
> > +	SET_CEC_LOGADDR_CMD	 = 0x83, /* Set CEC Logical Address		*/
> > +	SET_CEC_PHYADDR_CMD	 = 0x84, /* Set CEC Physical Address		*/
> > +	REQUEST_MBX_ACCESS_CMD   = 0xf0, /* First request access command	*/
> > +	RELEASE_MBX_ACCESS_CMD   = 0xf8, /* First release access command	*/
> > +};
> > +
> > +#define REQUEST_MBX_ACCESS(x) (REQUEST_MBX_ACCESS_CMD + x)
> > +#define RELEASE_MBX_ACCESS(x) (RELEASE_MBX_ACCESS_CMD + x)
> 
> Regards,
> 
> 	Hans



[Index of Archives]     [Linux Input]     [Video for Linux]     [Gstreamer Embedded]     [Mplayer Users]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]

  Powered by Linux