lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:   Wed, 27 Mar 2019 14:19:56 +0100
From:   Neil Armstrong <narmstrong@...libre.com>
To:     Hans Verkuil <hverkuil@...all.nl>, mchehab@...nel.org
Cc:     linux-amlogic@...ts.infradead.org, linux-media@...r.kernel.org,
        linux-arm-kernel@...ts.infradead.org, linux-kernel@...r.kernel.org
Subject: Re: [PATCH 2/3] media: platform: meson: Add Amlogic Meson G12A AO CEC
 Controller driver

Hi Hans,

On 27/03/2019 13:52, Hans Verkuil wrote:
> On 3/25/19 6:35 PM, Neil Armstrong wrote:
>> The Amlogic G12A SoC embeds a second CEC controller with a totally
>> different design.
>>
>> The two controller can work in the same time since the CEC line can
>> be set to two different pins on the two controllers.
>>
>> This second CEC controller is documented as "AO-CEC-B", thus the
>> registers will be named "CECB_" to differenciate with the other
>> AO-CEC driver.
>>
>> Unlike the other AO-CEC controller, this one takes the Oscillator
>> clock as input and embeds a dual-divider to provide a precise
>> 32768Hz clock for communication. This is handled by registering
>> a clock in the driver.
>>
>> Unlike the other AO-CEC controller, this controller supports setting
>> up to 15 logical adresses and supports the signal_free_time settings
>> in the transmit function.
>>
>> Unfortunately, this controller does not support "monitor" mode.
>>
>> Signed-off-by: Neil Armstrong <narmstrong@...libre.com>
>> ---
>>  drivers/media/platform/Kconfig             |  13 +
>>  drivers/media/platform/meson/Makefile      |   1 +
>>  drivers/media/platform/meson/ao-cec-g12a.c | 783 +++++++++++++++++++++
>>  3 files changed, 797 insertions(+)
>>  create mode 100644 drivers/media/platform/meson/ao-cec-g12a.c
>>
>> diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
>> index 4acbed189644..92ea07ddc609 100644
>> --- a/drivers/media/platform/Kconfig
>> +++ b/drivers/media/platform/Kconfig
>> @@ -578,6 +578,19 @@ config VIDEO_MESON_AO_CEC
>>  	  generic CEC framework interface.
>>  	  CEC bus is present in the HDMI connector and enables communication
>>  
>> +config VIDEO_MESON_G12A_AO_CEC
>> +	tristate "Amlogic Meson G12A AO CEC driver"
>> +	depends on ARCH_MESON || COMPILE_TEST
>> +	select CEC_CORE
>> +	select CEC_NOTIFIER
>> +	---help---
>> +	  This is a driver for Amlogic Meson G12A SoCs AO CEC interface.
>> +	  This driver if for the new AO-CEC module found in G12A SoCs,
>> +	  usually named AO_CEC_B in documentation.
>> +	  It uses the generic CEC framework interface.
>> +	  CEC bus is present in the HDMI connector and enables communication
>> +	  between compatible devices.
>> +
>>  config CEC_GPIO
>>  	tristate "Generic GPIO-based CEC driver"
>>  	depends on PREEMPT || COMPILE_TEST
>> diff --git a/drivers/media/platform/meson/Makefile b/drivers/media/platform/meson/Makefile
>> index 597beb8f34d1..f611c23c3718 100644
>> --- a/drivers/media/platform/meson/Makefile
>> +++ b/drivers/media/platform/meson/Makefile
>> @@ -1 +1,2 @@
>>  obj-$(CONFIG_VIDEO_MESON_AO_CEC)	+= ao-cec.o
>> +obj-$(CONFIG_VIDEO_MESON_G12A_AO_CEC)	+= ao-cec-g12a.o
>> diff --git a/drivers/media/platform/meson/ao-cec-g12a.c b/drivers/media/platform/meson/ao-cec-g12a.c
>> new file mode 100644
>> index 000000000000..8977ae994164
>> --- /dev/null
>> +++ b/drivers/media/platform/meson/ao-cec-g12a.c
>> @@ -0,0 +1,783 @@
>> +// SPDX-License-Identifier: GPL-2.0+
>> +/*
>> + * Driver for Amlogic Meson AO CEC G12A Controller
>> + *
>> + * Copyright (C) 2017 Amlogic, Inc. All rights reserved
>> + * Copyright (C) 2019 BayLibre, SAS
>> + * Author: Neil Armstrong <narmstrong@...libre.com>
>> + */
>> +

[...]

>> +
>> +static irqreturn_t meson_ao_cec_g12a_irq_thread(int irq, void *data)
>> +{
>> +	struct meson_ao_cec_g12a_device *ao_cec = data;
>> +	u32 stat;
>> +
>> +	regmap_read(ao_cec->regmap, CECB_INTR_STAT_REG, &stat);
>> +	regmap_write(ao_cec->regmap, CECB_INTR_CLR_REG, stat);
>> +
>> +	if (stat & CECB_INTR_DONE)
>> +		cec_transmit_attempt_done(ao_cec->adap, CEC_TX_STATUS_OK);
>> +
>> +	if (stat & CECB_INTR_EOM)
>> +		meson_ao_cec_g12a_irq_rx(ao_cec);
>> +
>> +	if (stat & CECB_INTR_NACK)
>> +		cec_transmit_attempt_done(ao_cec->adap, CEC_TX_STATUS_NACK);
>> +
>> +	if (stat & CECB_INTR_ARB_LOSS) {
>> +		regmap_write(ao_cec->regmap_cec, CECB_TX_CNT, 0);
>> +		regmap_update_bits(ao_cec->regmap_cec, CECB_CTRL,
>> +				   CECB_CTRL_SEND | CECB_CTRL_TYPE, 0);
>> +		cec_transmit_attempt_done(ao_cec->adap, CEC_TX_STATUS_ARB_LOST);
>> +	}
>> +
>> +	if (stat & CECB_INTR_INITIATOR_ERR)
>> +		cec_transmit_attempt_done(ao_cec->adap, CEC_TX_STATUS_NACK);
>> +
>> +	if (stat & CECB_INTR_FOLLOWER_ERR) {
>> +		regmap_write(ao_cec->regmap_cec, CECB_LOCK_BUF, 0);
>> +		cec_transmit_attempt_done(ao_cec->adap, CEC_TX_STATUS_NACK);
> 
> Any idea what CECB_INTR_INITIATOR_ERR and CECB_INTR_FOLLOWER_ERR actually
> mean? I.e. is returning NACK right here, or would TX_STATUS_ERROR be a
> better choice? I invented that status precisely for cases where there is
> an error, but we don't know what it means.
> 
> Are you sure that CECB_INTR_FOLLOWER_ERR applies to a transmit and not a
> receive? 'Follower' suggests that some error occurred while receiving
> a message. If I am right, then just ignore it.

Vendor describes it as "Follower Error interrupt flag status", indeed it
would apply to a receive nack. I'll ignore it.

> 
> Regarding CECB_INTR_INITIATOR_ERR: I suspect that this indicates a LOW
> DRIVE error situation, in which case you'd return that transmit status.

Vendor describes it as "Initiator Error interrupt flag status", I suspect it
means a generic bus error, and it should certainly be a low drive situation.

Would CEC_TX_STATUS_ERROR be more appropriate since we don't know exactly ?

> 
>> +	}
>> +
>> +	return IRQ_HANDLED;
>> +}
>> +
>> +static int
>> +meson_ao_cec_g12a_set_log_addr(struct cec_adapter *adap, u8 logical_addr)
>> +{
>> +	struct meson_ao_cec_g12a_device *ao_cec = adap->priv;
>> +	int ret = 0;
>> +
>> +	if (logical_addr == CEC_LOG_ADDR_INVALID) {
>> +		ret = regmap_write(ao_cec->regmap_cec, CECB_LADD_LOW, 0);
>> +		ret = regmap_write(ao_cec->regmap_cec, CECB_LADD_HIGH, 0);
> 
> Just ignore the error codes and return 0 here.
> 
> The CEC core will WARN if this function returns anything other than 0
> when invalidating the logical addresses. It assumes this will always
> succeed.

Ok

> 
>> +	} else if (logical_addr < 8) {
>> +		ret = regmap_update_bits(ao_cec->regmap_cec, CECB_LADD_LOW,
>> +					 BIT(logical_addr),
>> +					 BIT(logical_addr));
>> +	} else {
>> +		ret = regmap_update_bits(ao_cec->regmap_cec, CECB_LADD_HIGH,
>> +					 BIT(logical_addr - 8),
>> +					 BIT(logical_addr - 8));
>> +	}
>> +
>> +	/* Always set Broadcast/Unregistered 15 address */
>> +	ret |= regmap_update_bits(ao_cec->regmap_cec, CECB_LADD_HIGH,
> 
> I'd just do:
> 
> 	if (!ret)
> 		ret = regmap_...
> 
> Error codes are not a bitmask after all.
> 
> I see that elsewhere as well.
> 
> It's OK to use |=, but then when you return from the function you
> would have to do something like:
> 
> 	return ret ? -EIO : 0;

I'll do this when errors can only come from regmap, and check each
calls for other situations.

> 
> Regards,
> 
> 	Hans

Thanks,
Neil

> 
>> +				  BIT(CEC_LOG_ADDR_UNREGISTERED - 8),
>> +				  BIT(CEC_LOG_ADDR_UNREGISTERED - 8));
>> +
>> +	return ret;
>> +}
>> +
>> +static int meson_ao_cec_g12a_transmit(struct cec_adapter *adap, u8 attempts,
>> +				 u32 signal_free_time, struct cec_msg *msg)
>> +{
>> +	struct meson_ao_cec_g12a_device *ao_cec = adap->priv;
>> +	unsigned int type;
>> +	int ret = 0;
>> +	u32 val;
>> +	int i;
>> +
>> +	/* Check if RX is in progress */
>> +	ret = regmap_read(ao_cec->regmap_cec, CECB_LOCK_BUF, &val);
>> +	if (ret)
>> +		return ret;
>> +	if (val & CECB_LOCK_BUF_EN)
>> +		return -EBUSY;
>> +
>> +	/* Check if TX Busy */
>> +	ret = regmap_read(ao_cec->regmap_cec, CECB_CTRL, &val);
>> +	if (ret)
>> +		return ret;
>> +	if (val & CECB_CTRL_SEND)
>> +		return -EBUSY;
>> +
>> +	switch (signal_free_time) {
>> +	case CEC_SIGNAL_FREE_TIME_RETRY:
>> +		type = CECB_CTRL_TYPE_RETRY;
>> +		break;
>> +	case CEC_SIGNAL_FREE_TIME_NEXT_XFER:
>> +		type = CECB_CTRL_TYPE_NEXT;
>> +		break;
>> +	case CEC_SIGNAL_FREE_TIME_NEW_INITIATOR:
>> +	default:
>> +		type = CECB_CTRL_TYPE_NEW;
>> +		break;
>> +	}
>> +
>> +	for (i = 0; i < msg->len; i++)
>> +		ret |= regmap_write(ao_cec->regmap_cec, CECB_TX_DATA00 + i,
>> +				    msg->msg[i]);
>> +
>> +	ret |= regmap_write(ao_cec->regmap_cec, CECB_TX_CNT, msg->len);
>> +	ret = regmap_update_bits(ao_cec->regmap_cec, CECB_CTRL,
>> +				 CECB_CTRL_SEND |
>> +				 CECB_CTRL_TYPE,
>> +				 CECB_CTRL_SEND |
>> +				 FIELD_PREP(CECB_CTRL_TYPE, type));
>> +
>> +	return ret;
>> +}
>> +
>> +static int meson_ao_cec_g12a_adap_enable(struct cec_adapter *adap, bool enable)
>> +{
>> +	struct meson_ao_cec_g12a_device *ao_cec = adap->priv;
>> +
>> +	meson_ao_cec_g12a_irq_setup(ao_cec, false);
>> +
>> +	regmap_update_bits(ao_cec->regmap, CECB_GEN_CNTL_REG,
>> +			   CECB_GEN_CNTL_RESET, CECB_GEN_CNTL_RESET);
>> +
>> +	if (!enable)
>> +		return 0;
>> +
>> +	/* Setup Filter */
>> +	regmap_update_bits(ao_cec->regmap, CECB_GEN_CNTL_REG,
>> +			   CECB_GEN_CNTL_FILTER_TICK_SEL |
>> +			   CECB_GEN_CNTL_FILTER_DEL,
>> +			   FIELD_PREP(CECB_GEN_CNTL_FILTER_TICK_SEL,
>> +				      CECB_GEN_CNTL_FILTER_TICK_1US) |
>> +			   FIELD_PREP(CECB_GEN_CNTL_FILTER_DEL, 7));
>> +
>> +	/* Enable System Clock */
>> +	regmap_update_bits(ao_cec->regmap, CECB_GEN_CNTL_REG,
>> +			   CECB_GEN_CNTL_SYS_CLK_EN,
>> +			   CECB_GEN_CNTL_SYS_CLK_EN);
>> +
>> +	/* Enable gated clock (Normal mode). */
>> +	regmap_update_bits(ao_cec->regmap, CECB_GEN_CNTL_REG,
>> +			   CECB_GEN_CNTL_CLK_CTRL_MASK,
>> +			    FIELD_PREP(CECB_GEN_CNTL_CLK_CTRL_MASK,
>> +				       CECB_GEN_CNTL_CLK_ENABLE));
>> +
>> +	/* Release Reset */
>> +	regmap_update_bits(ao_cec->regmap, CECB_GEN_CNTL_REG,
>> +			   CECB_GEN_CNTL_RESET, 0);
>> +
>> +	meson_ao_cec_g12a_irq_setup(ao_cec, true);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct cec_adap_ops meson_ao_cec_g12a_ops = {
>> +	.adap_enable = meson_ao_cec_g12a_adap_enable,
>> +	.adap_log_addr = meson_ao_cec_g12a_set_log_addr,
>> +	.adap_transmit = meson_ao_cec_g12a_transmit,
>> +};
>> +
>> +static int meson_ao_cec_g12a_probe(struct platform_device *pdev)
>> +{
>> +	struct meson_ao_cec_g12a_device *ao_cec;
>> +	struct platform_device *hdmi_dev;
>> +	struct device_node *np;
>> +	struct resource *res;
>> +	void __iomem *base;
>> +	int ret, irq;
>> +
>> +	np = of_parse_phandle(pdev->dev.of_node, "hdmi-phandle", 0);
>> +	if (!np) {
>> +		dev_err(&pdev->dev, "Failed to find hdmi node\n");
>> +		return -ENODEV;
>> +	}
>> +
>> +	hdmi_dev = of_find_device_by_node(np);
>> +	of_node_put(np);
>> +	if (hdmi_dev == NULL)
>> +		return -EPROBE_DEFER;
>> +
>> +	put_device(&hdmi_dev->dev);
>> +	ao_cec = devm_kzalloc(&pdev->dev, sizeof(*ao_cec), GFP_KERNEL);
>> +	if (!ao_cec)
>> +		return -ENOMEM;
>> +
>> +	spin_lock_init(&ao_cec->cec_reg_lock);
>> +	ao_cec->pdev = pdev;
>> +
>> +	ao_cec->notify = cec_notifier_get(&hdmi_dev->dev);
>> +	if (!ao_cec->notify)
>> +		return -ENOMEM;
>> +
>> +	ao_cec->adap = cec_allocate_adapter(&meson_ao_cec_g12a_ops, ao_cec,
>> +					    "meson_g12a_ao_cec",
>> +					    CEC_CAP_DEFAULTS,
>> +					    CEC_MAX_LOG_ADDRS);
>> +	if (IS_ERR(ao_cec->adap)) {
>> +		ret = PTR_ERR(ao_cec->adap);
>> +		goto out_probe_notify;
>> +	}
>> +
>> +	ao_cec->adap->owner = THIS_MODULE;
>> +
>> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> +	base = devm_ioremap_resource(&pdev->dev, res);
>> +	if (IS_ERR(base)) {
>> +		ret = PTR_ERR(base);
>> +		goto out_probe_adapter;
>> +	}
>> +
>> +	ao_cec->regmap = devm_regmap_init_mmio(&pdev->dev, base,
>> +					       &meson_ao_cec_g12a_regmap_conf);
>> +	if (IS_ERR(ao_cec->regmap)) {
>> +		ret = PTR_ERR(ao_cec->regmap);
>> +		goto out_probe_adapter;
>> +	}
>> +
>> +	ao_cec->regmap_cec = devm_regmap_init(&pdev->dev, NULL, ao_cec,
>> +					   &meson_ao_cec_g12a_cec_regmap_conf);
>> +	if (IS_ERR(ao_cec->regmap_cec)) {
>> +		ret = PTR_ERR(ao_cec->regmap_cec);
>> +		goto out_probe_adapter;
>> +	}
>> +
>> +	irq = platform_get_irq(pdev, 0);
>> +	ret = devm_request_threaded_irq(&pdev->dev, irq,
>> +					meson_ao_cec_g12a_irq,
>> +					meson_ao_cec_g12a_irq_thread,
>> +					0, NULL, ao_cec);
>> +	if (ret) {
>> +		dev_err(&pdev->dev, "irq request failed\n");
>> +		goto out_probe_adapter;
>> +	}
>> +
>> +	ao_cec->oscin = devm_clk_get(&pdev->dev, "oscin");
>> +	if (IS_ERR(ao_cec->oscin)) {
>> +		dev_err(&pdev->dev, "oscin clock request failed\n");
>> +		ret = PTR_ERR(ao_cec->oscin);
>> +		goto out_probe_adapter;
>> +	}
>> +
>> +	ret = meson_ao_cec_g12a_setup_clk(ao_cec);
>> +	if (ret)
>> +		goto out_probe_clk;
>> +
>> +	ret = clk_prepare_enable(ao_cec->core);
>> +	if (ret) {
>> +		dev_err(&pdev->dev, "core clock enable failed\n");
>> +		goto out_probe_clk;
>> +	}
>> +
>> +	device_reset_optional(&pdev->dev);
>> +
>> +	platform_set_drvdata(pdev, ao_cec);
>> +
>> +	ret = cec_register_adapter(ao_cec->adap, &pdev->dev);
>> +	if (ret < 0) {
>> +		cec_notifier_put(ao_cec->notify);
>> +		goto out_probe_core_clk;
>> +	}
>> +
>> +	/* Setup Hardware */
>> +	regmap_write(ao_cec->regmap, CECB_GEN_CNTL_REG, CECB_GEN_CNTL_RESET);
>> +
>> +	cec_register_cec_notifier(ao_cec->adap, ao_cec->notify);
>> +
>> +	return 0;
>> +
>> +out_probe_core_clk:
>> +	clk_disable_unprepare(ao_cec->core);
>> +
>> +out_probe_clk:
>> +	clk_disable_unprepare(ao_cec->oscin);
>> +
>> +out_probe_adapter:
>> +	cec_delete_adapter(ao_cec->adap);
>> +
>> +out_probe_notify:
>> +	cec_notifier_put(ao_cec->notify);
>> +
>> +	dev_err(&pdev->dev, "CEC controller registration failed\n");
>> +
>> +	return ret;
>> +}
>> +
>> +static int meson_ao_cec_g12a_remove(struct platform_device *pdev)
>> +{
>> +	struct meson_ao_cec_g12a_device *ao_cec = platform_get_drvdata(pdev);
>> +
>> +	clk_disable_unprepare(ao_cec->oscin);
>> +
>> +	cec_unregister_adapter(ao_cec->adap);
>> +
>> +	cec_notifier_put(ao_cec->notify);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct of_device_id meson_ao_cec_g12a_of_match[] = {
>> +	{ .compatible = "amlogic,meson-g12a-ao-cec-b", },
>> +	{ /* sentinel */ }
>> +};
>> +MODULE_DEVICE_TABLE(of, meson_ao_cec_g12a_of_match);
>> +
>> +static struct platform_driver meson_ao_cec_g12a_driver = {
>> +	.probe   = meson_ao_cec_g12a_probe,
>> +	.remove  = meson_ao_cec_g12a_remove,
>> +	.driver  = {
>> +		.name = "meson-ao-cec-g12a",
>> +		.of_match_table = of_match_ptr(meson_ao_cec_g12a_of_match),
>> +	},
>> +};
>> +
>> +module_platform_driver(meson_ao_cec_g12a_driver);
>> +
>> +MODULE_DESCRIPTION("Meson AO CEC G12A Controller driver");
>> +MODULE_AUTHOR("Neil Armstrong <narmstrong@...libre.com>");
>> +MODULE_LICENSE("GPL");
>>
> 
> Regards,
> 
> 	Hans
> 

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ