Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,120 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Platform drivers
config CEC_CROS_EC
tristate "ChromeOS EC CEC driver"
depends on CROS_EC
select CEC_CORE
select CEC_NOTIFIER
select CROS_EC_PROTO
help
If you say yes here you will get support for the
ChromeOS Embedded Controller's CEC.
The CEC bus is present in the HDMI connector and enables communication
between compatible devices.
config CEC_MESON_AO
tristate "Amlogic Meson AO CEC driver"
depends on ARCH_MESON || COMPILE_TEST
select CEC_CORE
select CEC_NOTIFIER
help
This is a driver for Amlogic Meson SoCs AO CEC interface. It uses the
generic CEC framework interface.
CEC bus is present in the HDMI connector and enables communication
config CEC_MESON_G12A_AO
tristate "Amlogic Meson G12A AO CEC driver"
depends on ARCH_MESON || COMPILE_TEST
depends on COMMON_CLK && OF
select REGMAP
select REGMAP_MMIO
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 PREEMPTION || COMPILE_TEST
select CEC_CORE
select CEC_PIN
select CEC_NOTIFIER
select GPIOLIB
help
This is a generic GPIO-based CEC driver.
The CEC bus is present in the HDMI connector and enables communication
between compatible devices.
config CEC_SAMSUNG_S5P
tristate "Samsung S5P CEC driver"
depends on ARCH_EXYNOS || COMPILE_TEST
select CEC_CORE
select CEC_NOTIFIER
help
This is a driver for Samsung S5P HDMI CEC interface. It uses the
generic CEC framework interface.
CEC bus is present in the HDMI connector and enables communication
between compatible devices.
config CEC_STI
tristate "STMicroelectronics STiH4xx HDMI CEC driver"
depends on ARCH_STI || COMPILE_TEST
select CEC_CORE
select CEC_NOTIFIER
help
This is a driver for STIH4xx HDMI CEC interface. It uses the
generic CEC framework interface.
CEC bus is present in the HDMI connector and enables communication
between compatible devices.
config CEC_STM32
tristate "STMicroelectronics STM32 HDMI CEC driver"
depends on ARCH_STM32 || COMPILE_TEST
select REGMAP
select REGMAP_MMIO
select CEC_CORE
help
This is a driver for STM32 interface. It uses the
generic CEC framework interface.
CEC bus is present in the HDMI connector and enables communication
between compatible devices.
config CEC_TEGRA
tristate "Tegra HDMI CEC driver"
depends on ARCH_TEGRA || COMPILE_TEST
select CEC_CORE
select CEC_NOTIFIER
help
This is a driver for the Tegra HDMI CEC interface. It uses the
generic CEC framework interface.
The CEC bus is present in the HDMI connector and enables communication
between compatible devices.
config CEC_SECO
tristate "SECO Boards HDMI CEC driver"
depends on X86 || (COMPILE_TEST && HAS_IOPORT)
depends on PCI && DMI
select CEC_CORE
select CEC_NOTIFIER
help
This is a driver for SECO Boards integrated CEC interface.
Selecting it will enable support for this device.
CEC bus is present in the HDMI connector and enables communication
between compatible devices.
config CEC_SECO_RC
bool "SECO Boards IR RC5 support"
depends on CEC_SECO
depends on RC_CORE=y || RC_CORE = CEC_SECO
help
If you say yes here you will get support for the
SECO Boards Consumer-IR in seco-cec driver.
The embedded controller supports RC5 protocol only, default mapping
is set to rc-hauppauge.
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the CEC platform device drivers.
#
# Please keep it in alphabetic order
obj-$(CONFIG_CEC_CROS_EC) += cros-ec/
obj-$(CONFIG_CEC_GPIO) += cec-gpio/
obj-y += meson/
obj-$(CONFIG_CEC_SAMSUNG_S5P) += s5p/
obj-$(CONFIG_CEC_SECO) += seco/
obj-$(CONFIG_CEC_STI) += sti/
obj-$(CONFIG_CEC_STM32) += stm32/
obj-$(CONFIG_CEC_TEGRA) += tegra/
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_CEC_GPIO) += cec-gpio.o
@@ -0,0 +1,296 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2017 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/seq_file.h>
#include <media/cec-notifier.h>
#include <media/cec-pin.h>
struct cec_gpio {
struct cec_adapter *adap;
struct cec_notifier *notifier;
struct device *dev;
struct gpio_desc *cec_gpio;
int cec_irq;
bool cec_is_low;
struct gpio_desc *hpd_gpio;
int hpd_irq;
bool hpd_is_high;
ktime_t hpd_ts;
struct gpio_desc *v5_gpio;
int v5_irq;
bool v5_is_high;
ktime_t v5_ts;
};
static int cec_gpio_read(struct cec_adapter *adap)
{
struct cec_gpio *cec = cec_get_drvdata(adap);
if (cec->cec_is_low)
return 0;
return gpiod_get_value(cec->cec_gpio);
}
static void cec_gpio_high(struct cec_adapter *adap)
{
struct cec_gpio *cec = cec_get_drvdata(adap);
if (!cec->cec_is_low)
return;
cec->cec_is_low = false;
gpiod_set_value(cec->cec_gpio, 1);
}
static void cec_gpio_low(struct cec_adapter *adap)
{
struct cec_gpio *cec = cec_get_drvdata(adap);
if (cec->cec_is_low)
return;
cec->cec_is_low = true;
gpiod_set_value(cec->cec_gpio, 0);
}
static irqreturn_t cec_gpio_5v_irq_handler_thread(int irq, void *priv)
{
struct cec_gpio *cec = priv;
int val = gpiod_get_value_cansleep(cec->v5_gpio);
bool is_high = val > 0;
if (val < 0 || is_high == cec->v5_is_high)
return IRQ_HANDLED;
cec->v5_is_high = is_high;
cec_queue_pin_5v_event(cec->adap, cec->v5_is_high, cec->v5_ts);
return IRQ_HANDLED;
}
static irqreturn_t cec_gpio_5v_irq_handler(int irq, void *priv)
{
struct cec_gpio *cec = priv;
cec->v5_ts = ktime_get();
return IRQ_WAKE_THREAD;
}
static irqreturn_t cec_gpio_hpd_irq_handler_thread(int irq, void *priv)
{
struct cec_gpio *cec = priv;
int val = gpiod_get_value_cansleep(cec->hpd_gpio);
bool is_high = val > 0;
if (val < 0 || is_high == cec->hpd_is_high)
return IRQ_HANDLED;
cec->hpd_is_high = is_high;
cec_queue_pin_hpd_event(cec->adap, cec->hpd_is_high, cec->hpd_ts);
return IRQ_HANDLED;
}
static irqreturn_t cec_gpio_hpd_irq_handler(int irq, void *priv)
{
struct cec_gpio *cec = priv;
cec->hpd_ts = ktime_get();
return IRQ_WAKE_THREAD;
}
static irqreturn_t cec_gpio_cec_irq_handler(int irq, void *priv)
{
struct cec_gpio *cec = priv;
int val = gpiod_get_value(cec->cec_gpio);
if (val >= 0)
cec_pin_changed(cec->adap, val > 0);
return IRQ_HANDLED;
}
static bool cec_gpio_cec_enable_irq(struct cec_adapter *adap)
{
struct cec_gpio *cec = cec_get_drvdata(adap);
enable_irq(cec->cec_irq);
return true;
}
static void cec_gpio_cec_disable_irq(struct cec_adapter *adap)
{
struct cec_gpio *cec = cec_get_drvdata(adap);
disable_irq(cec->cec_irq);
}
static void cec_gpio_status(struct cec_adapter *adap, struct seq_file *file)
{
struct cec_gpio *cec = cec_get_drvdata(adap);
seq_printf(file, "mode: %s\n", cec->cec_is_low ? "low-drive" : "read");
seq_printf(file, "using irq: %d\n", cec->cec_irq);
if (cec->hpd_gpio)
seq_printf(file, "hpd: %s\n",
cec->hpd_is_high ? "high" : "low");
if (cec->v5_gpio)
seq_printf(file, "5V: %s\n",
cec->v5_is_high ? "high" : "low");
}
static int cec_gpio_read_hpd(struct cec_adapter *adap)
{
struct cec_gpio *cec = cec_get_drvdata(adap);
if (!cec->hpd_gpio)
return -ENOTTY;
return gpiod_get_value_cansleep(cec->hpd_gpio);
}
static int cec_gpio_read_5v(struct cec_adapter *adap)
{
struct cec_gpio *cec = cec_get_drvdata(adap);
if (!cec->v5_gpio)
return -ENOTTY;
return gpiod_get_value_cansleep(cec->v5_gpio);
}
static const struct cec_pin_ops cec_gpio_pin_ops = {
.read = cec_gpio_read,
.low = cec_gpio_low,
.high = cec_gpio_high,
.enable_irq = cec_gpio_cec_enable_irq,
.disable_irq = cec_gpio_cec_disable_irq,
.status = cec_gpio_status,
.read_hpd = cec_gpio_read_hpd,
.read_5v = cec_gpio_read_5v,
};
static int cec_gpio_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device *hdmi_dev;
struct cec_gpio *cec;
u32 caps = CEC_CAP_DEFAULTS | CEC_CAP_MONITOR_ALL | CEC_CAP_MONITOR_PIN;
int ret;
hdmi_dev = cec_notifier_parse_hdmi_phandle(dev);
if (PTR_ERR(hdmi_dev) == -EPROBE_DEFER)
return PTR_ERR(hdmi_dev);
if (IS_ERR(hdmi_dev))
caps |= CEC_CAP_PHYS_ADDR;
cec = devm_kzalloc(dev, sizeof(*cec), GFP_KERNEL);
if (!cec)
return -ENOMEM;
cec->dev = dev;
cec->cec_gpio = devm_gpiod_get(dev, "cec", GPIOD_OUT_HIGH_OPEN_DRAIN);
if (IS_ERR(cec->cec_gpio))
return PTR_ERR(cec->cec_gpio);
cec->cec_irq = gpiod_to_irq(cec->cec_gpio);
cec->hpd_gpio = devm_gpiod_get_optional(dev, "hpd", GPIOD_IN);
if (IS_ERR(cec->hpd_gpio))
return PTR_ERR(cec->hpd_gpio);
cec->v5_gpio = devm_gpiod_get_optional(dev, "v5", GPIOD_IN);
if (IS_ERR(cec->v5_gpio))
return PTR_ERR(cec->v5_gpio);
cec->adap = cec_pin_allocate_adapter(&cec_gpio_pin_ops,
cec, pdev->name, caps);
if (IS_ERR(cec->adap))
return PTR_ERR(cec->adap);
ret = devm_request_irq(dev, cec->cec_irq, cec_gpio_cec_irq_handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_NO_AUTOEN,
cec->adap->name, cec);
if (ret)
goto del_adap;
if (cec->hpd_gpio) {
cec->hpd_irq = gpiod_to_irq(cec->hpd_gpio);
ret = devm_request_threaded_irq(dev, cec->hpd_irq,
cec_gpio_hpd_irq_handler,
cec_gpio_hpd_irq_handler_thread,
IRQF_ONESHOT |
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
"hpd-gpio", cec);
if (ret)
goto del_adap;
}
if (cec->v5_gpio) {
cec->v5_irq = gpiod_to_irq(cec->v5_gpio);
ret = devm_request_threaded_irq(dev, cec->v5_irq,
cec_gpio_5v_irq_handler,
cec_gpio_5v_irq_handler_thread,
IRQF_ONESHOT |
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
"v5-gpio", cec);
if (ret)
goto del_adap;
}
if (!IS_ERR(hdmi_dev)) {
cec->notifier = cec_notifier_cec_adap_register(hdmi_dev, NULL,
cec->adap);
if (!cec->notifier) {
ret = -ENOMEM;
goto del_adap;
}
}
ret = cec_register_adapter(cec->adap, &pdev->dev);
if (ret)
goto unreg_notifier;
platform_set_drvdata(pdev, cec);
return 0;
unreg_notifier:
cec_notifier_cec_adap_unregister(cec->notifier, cec->adap);
del_adap:
cec_delete_adapter(cec->adap);
return ret;
}
static void cec_gpio_remove(struct platform_device *pdev)
{
struct cec_gpio *cec = platform_get_drvdata(pdev);
cec_notifier_cec_adap_unregister(cec->notifier, cec->adap);
cec_unregister_adapter(cec->adap);
}
static const struct of_device_id cec_gpio_match[] = {
{
.compatible = "cec-gpio",
},
{},
};
MODULE_DEVICE_TABLE(of, cec_gpio_match);
static struct platform_driver cec_gpio_pdrv = {
.probe = cec_gpio_probe,
.remove = cec_gpio_remove,
.driver = {
.name = "cec-gpio",
.of_match_table = cec_gpio_match,
},
};
module_platform_driver(cec_gpio_pdrv);
MODULE_AUTHOR("Hans Verkuil <hverkuil@kernel.org>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("CEC GPIO driver");
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_CEC_CROS_EC) += cros-ec-cec.o
@@ -0,0 +1,607 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* CEC driver for ChromeOS Embedded Controller
*
* Copyright (c) 2018 BayLibre, SAS
* Author: Neil Armstrong <narmstrong@baylibre.com>
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/dmi.h>
#include <linux/pci.h>
#include <linux/cec.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/platform_data/cros_ec_commands.h>
#include <linux/platform_data/cros_ec_proto.h>
#include <media/cec.h>
#include <media/cec-notifier.h>
#define DRV_NAME "cros-ec-cec"
/**
* struct cros_ec_cec_port - Driver data for a single EC CEC port
*
* @port_num: port number
* @adap: CEC adapter
* @notify: CEC notifier pointer
* @rx_msg: storage for a received message
* @cros_ec_cec: pointer to the parent struct
*/
struct cros_ec_cec_port {
int port_num;
struct cec_adapter *adap;
struct cec_notifier *notify;
struct cec_msg rx_msg;
struct cros_ec_cec *cros_ec_cec;
};
/**
* struct cros_ec_cec - Driver data for EC CEC
*
* @cros_ec: Pointer to EC device
* @notifier: Notifier info for responding to EC events
* @write_cmd_version: Highest supported version of EC_CMD_CEC_WRITE_MSG.
* @num_ports: Number of CEC ports
* @ports: Array of ports
*/
struct cros_ec_cec {
struct cros_ec_device *cros_ec;
struct notifier_block notifier;
int write_cmd_version;
int num_ports;
struct cros_ec_cec_port *ports[EC_CEC_MAX_PORTS];
};
static void cros_ec_cec_received_message(struct cros_ec_cec_port *port,
uint8_t *msg, uint8_t len)
{
if (len > CEC_MAX_MSG_SIZE)
len = CEC_MAX_MSG_SIZE;
port->rx_msg.len = len;
memcpy(port->rx_msg.msg, msg, len);
cec_received_msg(port->adap, &port->rx_msg);
}
static void handle_cec_message(struct cros_ec_cec *cros_ec_cec)
{
struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec;
uint8_t *cec_message = cros_ec->event_data.data.cec_message;
unsigned int len = cros_ec->event_size;
struct cros_ec_cec_port *port;
/*
* There are two ways of receiving CEC messages:
* 1. Old EC firmware which only supports one port sends the data in a
* cec_message MKBP event.
* 2. New EC firmware which supports multiple ports uses
* EC_MKBP_CEC_HAVE_DATA to notify that data is ready and
* EC_CMD_CEC_READ_MSG to read it.
* Check that the EC only has one CEC port, and then we can assume the
* message is from port 0.
*/
if (cros_ec_cec->num_ports != 1) {
dev_err(cros_ec->dev,
"received cec_message on device with %d ports\n",
cros_ec_cec->num_ports);
return;
}
port = cros_ec_cec->ports[0];
cros_ec_cec_received_message(port, cec_message, len);
}
static void cros_ec_cec_read_message(struct cros_ec_cec_port *port)
{
struct cros_ec_device *cros_ec = port->cros_ec_cec->cros_ec;
struct ec_params_cec_read params = {
.port = port->port_num,
};
struct ec_response_cec_read response;
int ret;
ret = cros_ec_cmd(cros_ec, 0, EC_CMD_CEC_READ_MSG, &params,
sizeof(params), &response, sizeof(response));
if (ret < 0) {
dev_err(cros_ec->dev,
"error reading CEC message on EC: %d\n", ret);
return;
}
cros_ec_cec_received_message(port, response.msg, response.msg_len);
}
static void handle_cec_event(struct cros_ec_cec *cros_ec_cec)
{
struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec;
uint32_t cec_events = cros_ec->event_data.data.cec_events;
uint32_t port_num = EC_MKBP_EVENT_CEC_GET_PORT(cec_events);
uint32_t events = EC_MKBP_EVENT_CEC_GET_EVENTS(cec_events);
struct cros_ec_cec_port *port;
if (port_num >= cros_ec_cec->num_ports) {
dev_err(cros_ec->dev,
"received CEC event for invalid port %d\n", port_num);
return;
}
port = cros_ec_cec->ports[port_num];
if (events & EC_MKBP_CEC_SEND_OK)
cec_transmit_attempt_done(port->adap, CEC_TX_STATUS_OK);
/* FW takes care of all retries, tell core to avoid more retries */
if (events & EC_MKBP_CEC_SEND_FAILED)
cec_transmit_attempt_done(port->adap,
CEC_TX_STATUS_MAX_RETRIES |
CEC_TX_STATUS_NACK);
if (events & EC_MKBP_CEC_HAVE_DATA)
cros_ec_cec_read_message(port);
}
static int cros_ec_cec_event(struct notifier_block *nb,
unsigned long queued_during_suspend,
void *_notify)
{
struct cros_ec_cec *cros_ec_cec;
struct cros_ec_device *cros_ec;
cros_ec_cec = container_of(nb, struct cros_ec_cec, notifier);
cros_ec = cros_ec_cec->cros_ec;
if (cros_ec->event_data.event_type == EC_MKBP_EVENT_CEC_EVENT) {
handle_cec_event(cros_ec_cec);
return NOTIFY_OK;
}
if (cros_ec->event_data.event_type == EC_MKBP_EVENT_CEC_MESSAGE) {
handle_cec_message(cros_ec_cec);
return NOTIFY_OK;
}
return NOTIFY_DONE;
}
static int cros_ec_cec_set_log_addr(struct cec_adapter *adap, u8 logical_addr)
{
struct cros_ec_cec_port *port = adap->priv;
struct cros_ec_cec *cros_ec_cec = port->cros_ec_cec;
struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec;
struct ec_params_cec_set params = {
.cmd = CEC_CMD_LOGICAL_ADDRESS,
.port = port->port_num,
.val = logical_addr,
};
int ret;
ret = cros_ec_cmd(cros_ec, 0, EC_CMD_CEC_SET, &params, sizeof(params),
NULL, 0);
if (ret < 0) {
dev_err(cros_ec->dev,
"error setting CEC logical address on EC: %d\n", ret);
return ret;
}
return 0;
}
static int cros_ec_cec_transmit(struct cec_adapter *adap, u8 attempts,
u32 signal_free_time, struct cec_msg *cec_msg)
{
struct cros_ec_cec_port *port = adap->priv;
struct cros_ec_cec *cros_ec_cec = port->cros_ec_cec;
struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec;
struct ec_params_cec_write params;
struct ec_params_cec_write_v1 params_v1;
int ret;
if (cros_ec_cec->write_cmd_version == 0) {
memcpy(params.msg, cec_msg->msg, cec_msg->len);
ret = cros_ec_cmd(cros_ec, 0, EC_CMD_CEC_WRITE_MSG, &params,
cec_msg->len, NULL, 0);
} else {
params_v1.port = port->port_num;
params_v1.msg_len = cec_msg->len;
memcpy(params_v1.msg, cec_msg->msg, cec_msg->len);
ret = cros_ec_cmd(cros_ec, cros_ec_cec->write_cmd_version,
EC_CMD_CEC_WRITE_MSG, &params_v1,
sizeof(params_v1), NULL, 0);
}
if (ret < 0) {
dev_err(cros_ec->dev,
"error writing CEC msg on EC: %d\n", ret);
return ret;
}
return 0;
}
static int cros_ec_cec_adap_enable(struct cec_adapter *adap, bool enable)
{
struct cros_ec_cec_port *port = adap->priv;
struct cros_ec_cec *cros_ec_cec = port->cros_ec_cec;
struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec;
struct ec_params_cec_set params = {
.cmd = CEC_CMD_ENABLE,
.port = port->port_num,
.val = enable,
};
int ret;
ret = cros_ec_cmd(cros_ec, 0, EC_CMD_CEC_SET, &params, sizeof(params),
NULL, 0);
if (ret < 0) {
dev_err(cros_ec->dev,
"error %sabling CEC on EC: %d\n",
(enable ? "en" : "dis"), ret);
return ret;
}
return 0;
}
static const struct cec_adap_ops cros_ec_cec_ops = {
.adap_enable = cros_ec_cec_adap_enable,
.adap_log_addr = cros_ec_cec_set_log_addr,
.adap_transmit = cros_ec_cec_transmit,
};
#ifdef CONFIG_PM_SLEEP
static int cros_ec_cec_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct cros_ec_cec *cros_ec_cec = dev_get_drvdata(&pdev->dev);
if (device_may_wakeup(dev))
enable_irq_wake(cros_ec_cec->cros_ec->irq);
return 0;
}
static int cros_ec_cec_resume(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct cros_ec_cec *cros_ec_cec = dev_get_drvdata(&pdev->dev);
if (device_may_wakeup(dev))
disable_irq_wake(cros_ec_cec->cros_ec->irq);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(cros_ec_cec_pm_ops,
cros_ec_cec_suspend, cros_ec_cec_resume);
#if IS_ENABLED(CONFIG_PCI) && IS_ENABLED(CONFIG_DMI)
/*
* Specify the DRM device name handling the HDMI output and the HDMI connector
* corresponding to each CEC port. The order of connectors must match the order
* in the EC (first connector is EC port 0, ...), and the number of connectors
* must match the number of ports in the EC (which can be queried using the
* EC_CMD_CEC_PORT_COUNT host command).
*/
struct cec_dmi_match {
const char *sys_vendor;
const char *product_name;
const char *devname;
const char *const *conns;
};
static const char *const port_b_conns[] = { "Port B", NULL };
static const char *const port_db_conns[] = { "Port D", "Port B", NULL };
static const char *const port_ba_conns[] = { "Port B", "Port A", NULL };
static const char *const port_ab_conns[] = { "Port A", "Port B", NULL };
static const char *const port_d_conns[] = { "Port D", NULL };
static const struct cec_dmi_match cec_dmi_match_table[] = {
/* Google Fizz */
{ "Google", "Fizz", "0000:00:02.0", port_b_conns },
/* Google Brask */
{ "Google", "Brask", "0000:00:02.0", port_b_conns },
/* Google Moli */
{ "Google", "Moli", "0000:00:02.0", port_b_conns },
/* Google Kinox */
{ "Google", "Kinox", "0000:00:02.0", port_b_conns },
/* Google Kuldax */
{ "Google", "Kuldax", "0000:00:02.0", port_b_conns },
/* Google Aurash */
{ "Google", "Aurash", "0000:00:02.0", port_b_conns },
/* Google Gladios */
{ "Google", "Gladios", "0000:00:02.0", port_b_conns },
/* Google Lisbon */
{ "Google", "Lisbon", "0000:00:02.0", port_b_conns },
/* Google Dibbi */
{ "Google", "Dibbi", "0000:00:02.0", port_db_conns },
/* Google Constitution */
{ "Google", "Constitution", "0000:00:02.0", port_ba_conns },
/* Google Boxy */
{ "Google", "Boxy", "0000:00:02.0", port_d_conns },
/* Google Taranza */
{ "Google", "Taranza", "0000:00:02.0", port_db_conns },
/* Google Dexi */
{ "Google", "Dexi", "0000:00:02.0", port_db_conns },
/* Google Dita */
{ "Google", "Dita", "0000:00:02.0", port_db_conns },
/* Google Dirks */
{ "Google", "Dirks", "0000:00:02.0", port_ab_conns },
/* Google Moxie */
{ "Google", "Moxie", "0000:00:02.0", port_b_conns },
/* Google Kulnex */
{ "Google", "Kulnex", "0000:00:02.0", port_b_conns },
/* Google Moxoe */
{ "Google", "Moxoe", "0000:00:02.0", port_b_conns },
/* Google Dirkson */
{ "Google", "Dirkson", "0000:00:02.0", port_ab_conns },
};
static struct device *cros_ec_cec_find_hdmi_dev(struct device *dev,
const char * const **conns)
{
int i;
for (i = 0 ; i < ARRAY_SIZE(cec_dmi_match_table) ; ++i) {
const struct cec_dmi_match *m = &cec_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);
*conns = m->conns;
return d;
}
}
/* Hardware support must be added in the cec_dmi_match_table */
dev_warn(dev, "CEC notifier not configured for this hardware\n");
return ERR_PTR(-ENODEV);
}
#else
static struct device *cros_ec_cec_find_hdmi_dev(struct device *dev,
const char * const **conns)
{
return ERR_PTR(-ENODEV);
}
#endif
static int cros_ec_cec_get_num_ports(struct cros_ec_cec *cros_ec_cec)
{
struct ec_response_cec_port_count response;
int ret;
ret = cros_ec_cmd(cros_ec_cec->cros_ec, 0, EC_CMD_CEC_PORT_COUNT, NULL,
0, &response, sizeof(response));
if (ret < 0) {
/*
* Old EC firmware only supports one port and does not support
* the port count command, so fall back to assuming one port.
*/
cros_ec_cec->num_ports = 1;
return 0;
}
if (response.port_count == 0) {
dev_err(cros_ec_cec->cros_ec->dev,
"EC reports 0 CEC ports\n");
return -ENODEV;
}
if (response.port_count > EC_CEC_MAX_PORTS) {
dev_err(cros_ec_cec->cros_ec->dev,
"EC reports too many ports: %d\n", response.port_count);
return -EINVAL;
}
cros_ec_cec->num_ports = response.port_count;
return 0;
}
static int cros_ec_cec_get_write_cmd_version(struct cros_ec_cec *cros_ec_cec)
{
struct cros_ec_device *cros_ec = cros_ec_cec->cros_ec;
struct ec_params_get_cmd_versions_v1 params = {
.cmd = EC_CMD_CEC_WRITE_MSG,
};
struct ec_response_get_cmd_versions response;
int ret;
ret = cros_ec_cmd(cros_ec, 1, EC_CMD_GET_CMD_VERSIONS, &params,
sizeof(params), &response, sizeof(response));
if (ret < 0) {
dev_err(cros_ec->dev,
"error getting CEC write command version: %d\n", ret);
return ret;
}
if (response.version_mask & EC_VER_MASK(1)) {
cros_ec_cec->write_cmd_version = 1;
} else {
if (cros_ec_cec->num_ports != 1) {
dev_err(cros_ec->dev,
"v0 write command only supports 1 port, %d reported\n",
cros_ec_cec->num_ports);
return -EINVAL;
}
cros_ec_cec->write_cmd_version = 0;
}
return 0;
}
static int cros_ec_cec_init_port(struct device *dev,
struct cros_ec_cec *cros_ec_cec,
int port_num, struct device *hdmi_dev,
const char * const *conns)
{
struct cros_ec_cec_port *port;
int ret;
port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
if (!port)
return -ENOMEM;
port->cros_ec_cec = cros_ec_cec;
port->port_num = port_num;
port->adap = cec_allocate_adapter(&cros_ec_cec_ops, port, DRV_NAME,
CEC_CAP_DEFAULTS |
CEC_CAP_CONNECTOR_INFO, 1);
if (IS_ERR(port->adap))
return PTR_ERR(port->adap);
if (!conns[port_num]) {
dev_err(dev, "no conn for port %d\n", port_num);
ret = -ENODEV;
goto out_probe_adapter;
}
port->notify = cec_notifier_cec_adap_register(hdmi_dev, conns[port_num],
port->adap);
if (!port->notify) {
ret = -ENOMEM;
goto out_probe_adapter;
}
ret = cec_register_adapter(port->adap, dev);
if (ret < 0)
goto out_probe_notify;
cros_ec_cec->ports[port_num] = port;
return 0;
out_probe_notify:
cec_notifier_cec_adap_unregister(port->notify, port->adap);
out_probe_adapter:
cec_delete_adapter(port->adap);
return ret;
}
static int cros_ec_cec_probe(struct platform_device *pdev)
{
struct cros_ec_dev *ec_dev = dev_get_drvdata(pdev->dev.parent);
struct cros_ec_device *cros_ec = ec_dev->ec_dev;
struct cros_ec_cec *cros_ec_cec;
struct cros_ec_cec_port *port;
struct device *hdmi_dev;
const char * const *conns = NULL;
int ret;
hdmi_dev = cros_ec_cec_find_hdmi_dev(&pdev->dev, &conns);
if (IS_ERR(hdmi_dev))
return PTR_ERR(hdmi_dev);
cros_ec_cec = devm_kzalloc(&pdev->dev, sizeof(*cros_ec_cec),
GFP_KERNEL);
if (!cros_ec_cec)
return -ENOMEM;
platform_set_drvdata(pdev, cros_ec_cec);
cros_ec_cec->cros_ec = cros_ec;
device_init_wakeup(&pdev->dev, 1);
ret = cros_ec_cec_get_num_ports(cros_ec_cec);
if (ret)
return ret;
ret = cros_ec_cec_get_write_cmd_version(cros_ec_cec);
if (ret)
return ret;
for (int i = 0; i < cros_ec_cec->num_ports; i++) {
ret = cros_ec_cec_init_port(&pdev->dev, cros_ec_cec, i,
hdmi_dev, conns);
if (ret)
goto unregister_ports;
}
/* Get CEC events from the EC. */
cros_ec_cec->notifier.notifier_call = cros_ec_cec_event;
ret = blocking_notifier_chain_register(&cros_ec->event_notifier,
&cros_ec_cec->notifier);
if (ret) {
dev_err(&pdev->dev, "failed to register notifier\n");
goto unregister_ports;
}
return 0;
unregister_ports:
/*
* Unregister any adapters which have been registered. We don't add the
* port to the array until the adapter has been registered successfully,
* so any non-NULL ports must have been registered.
*/
for (int i = 0; i < cros_ec_cec->num_ports; i++) {
port = cros_ec_cec->ports[i];
if (!port)
break;
cec_notifier_cec_adap_unregister(port->notify, port->adap);
cec_unregister_adapter(port->adap);
}
return ret;
}
static void cros_ec_cec_remove(struct platform_device *pdev)
{
struct cros_ec_cec *cros_ec_cec = platform_get_drvdata(pdev);
struct device *dev = &pdev->dev;
struct cros_ec_cec_port *port;
int ret;
/*
* blocking_notifier_chain_unregister() only fails if the notifier isn't
* in the list. We know it was added to it by .probe(), so there should
* be no need for error checking. Be cautious and still check.
*/
ret = blocking_notifier_chain_unregister(
&cros_ec_cec->cros_ec->event_notifier,
&cros_ec_cec->notifier);
if (ret)
dev_err(dev, "failed to unregister notifier\n");
for (int i = 0; i < cros_ec_cec->num_ports; i++) {
port = cros_ec_cec->ports[i];
cec_notifier_cec_adap_unregister(port->notify, port->adap);
cec_unregister_adapter(port->adap);
}
}
static const struct platform_device_id cros_ec_cec_id[] = {
{ DRV_NAME, 0 },
{}
};
MODULE_DEVICE_TABLE(platform, cros_ec_cec_id);
static struct platform_driver cros_ec_cec_driver = {
.probe = cros_ec_cec_probe,
.remove = cros_ec_cec_remove,
.driver = {
.name = DRV_NAME,
.pm = &cros_ec_cec_pm_ops,
},
.id_table = cros_ec_cec_id,
};
module_platform_driver(cros_ec_cec_driver);
MODULE_DESCRIPTION("CEC driver for ChromeOS ECs");
MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
MODULE_LICENSE("GPL");
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_CEC_MESON_AO) += ao-cec.o
obj-$(CONFIG_CEC_MESON_G12A_AO) += ao-cec-g12a.o
@@ -0,0 +1,792 @@
// 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@baylibre.com>
*/
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/reset.h>
#include <linux/slab.h>
#include <linux/regmap.h>
#include <media/cec.h>
#include <media/cec-notifier.h>
#include <linux/clk-provider.h>
/* CEC Registers */
#define CECB_CLK_CNTL_REG0 0x00
#define CECB_CLK_CNTL_N1 GENMASK(11, 0)
#define CECB_CLK_CNTL_N2 GENMASK(23, 12)
#define CECB_CLK_CNTL_DUAL_EN BIT(28)
#define CECB_CLK_CNTL_OUTPUT_EN BIT(30)
#define CECB_CLK_CNTL_INPUT_EN BIT(31)
#define CECB_CLK_CNTL_REG1 0x04
#define CECB_CLK_CNTL_M1 GENMASK(11, 0)
#define CECB_CLK_CNTL_M2 GENMASK(23, 12)
#define CECB_CLK_CNTL_BYPASS_EN BIT(24)
/*
* [14:12] Filter_del. For glitch-filtering CEC line, ignore signal
* change pulse width < filter_del * T(filter_tick) * 3.
* [9:8] Filter_tick_sel: Select which periodical pulse for
* glitch-filtering CEC line signal.
* - 0=Use T(xtal)*3 = 125ns;
* - 1=Use once-per-1us pulse;
* - 2=Use once-per-10us pulse;
* - 3=Use once-per-100us pulse.
* [3] Sysclk_en. 0=Disable system clock; 1=Enable system clock.
* [2:1] cntl_clk
* - 0 = Disable clk (Power-off mode)
* - 1 = Enable gated clock (Normal mode)
* - 2 = Enable free-run clk (Debug mode)
* [0] SW_RESET 1=Apply reset; 0=No reset.
*/
#define CECB_GEN_CNTL_REG 0x08
#define CECB_GEN_CNTL_RESET BIT(0)
#define CECB_GEN_CNTL_CLK_DISABLE 0
#define CECB_GEN_CNTL_CLK_ENABLE 1
#define CECB_GEN_CNTL_CLK_ENABLE_DBG 2
#define CECB_GEN_CNTL_CLK_CTRL_MASK GENMASK(2, 1)
#define CECB_GEN_CNTL_SYS_CLK_EN BIT(3)
#define CECB_GEN_CNTL_FILTER_TICK_125NS 0
#define CECB_GEN_CNTL_FILTER_TICK_1US 1
#define CECB_GEN_CNTL_FILTER_TICK_10US 2
#define CECB_GEN_CNTL_FILTER_TICK_100US 3
#define CECB_GEN_CNTL_FILTER_TICK_SEL GENMASK(9, 8)
#define CECB_GEN_CNTL_FILTER_DEL GENMASK(14, 12)
/*
* [7:0] cec_reg_addr
* [15:8] cec_reg_wrdata
* [16] cec_reg_wr
* - 0 = Read
* - 1 = Write
* [31:24] cec_reg_rddata
*/
#define CECB_RW_REG 0x0c
#define CECB_RW_ADDR GENMASK(7, 0)
#define CECB_RW_WR_DATA GENMASK(15, 8)
#define CECB_RW_WRITE_EN BIT(16)
#define CECB_RW_BUS_BUSY BIT(23)
#define CECB_RW_RD_DATA GENMASK(31, 24)
/*
* [0] DONE Interrupt
* [1] End Of Message Interrupt
* [2] Not Acknowlegde Interrupt
* [3] Arbitration Loss Interrupt
* [4] Initiator Error Interrupt
* [5] Follower Error Interrupt
* [6] Wake-Up Interrupt
*/
#define CECB_INTR_MASKN_REG 0x10
#define CECB_INTR_CLR_REG 0x14
#define CECB_INTR_STAT_REG 0x18
#define CECB_INTR_DONE BIT(0)
#define CECB_INTR_EOM BIT(1)
#define CECB_INTR_NACK BIT(2)
#define CECB_INTR_ARB_LOSS BIT(3)
#define CECB_INTR_INITIATOR_ERR BIT(4)
#define CECB_INTR_FOLLOWER_ERR BIT(5)
#define CECB_INTR_WAKE_UP BIT(6)
/* CEC Commands */
#define CECB_CTRL 0x00
#define CECB_CTRL_SEND BIT(0)
#define CECB_CTRL_TYPE GENMASK(2, 1)
#define CECB_CTRL_TYPE_RETRY 0
#define CECB_CTRL_TYPE_NEW 1
#define CECB_CTRL_TYPE_NEXT 2
#define CECB_CTRL2 0x01
#define CECB_CTRL2_RISE_DEL_MAX GENMASK(4, 0)
#define CECB_INTR_MASK 0x02
#define CECB_LADD_LOW 0x05
#define CECB_LADD_HIGH 0x06
#define CECB_TX_CNT 0x07
#define CECB_RX_CNT 0x08
#define CECB_STAT0 0x09
#define CECB_TX_DATA00 0x10
#define CECB_TX_DATA01 0x11
#define CECB_TX_DATA02 0x12
#define CECB_TX_DATA03 0x13
#define CECB_TX_DATA04 0x14
#define CECB_TX_DATA05 0x15
#define CECB_TX_DATA06 0x16
#define CECB_TX_DATA07 0x17
#define CECB_TX_DATA08 0x18
#define CECB_TX_DATA09 0x19
#define CECB_TX_DATA10 0x1A
#define CECB_TX_DATA11 0x1B
#define CECB_TX_DATA12 0x1C
#define CECB_TX_DATA13 0x1D
#define CECB_TX_DATA14 0x1E
#define CECB_TX_DATA15 0x1F
#define CECB_RX_DATA00 0x20
#define CECB_RX_DATA01 0x21
#define CECB_RX_DATA02 0x22
#define CECB_RX_DATA03 0x23
#define CECB_RX_DATA04 0x24
#define CECB_RX_DATA05 0x25
#define CECB_RX_DATA06 0x26
#define CECB_RX_DATA07 0x27
#define CECB_RX_DATA08 0x28
#define CECB_RX_DATA09 0x29
#define CECB_RX_DATA10 0x2A
#define CECB_RX_DATA11 0x2B
#define CECB_RX_DATA12 0x2C
#define CECB_RX_DATA13 0x2D
#define CECB_RX_DATA14 0x2E
#define CECB_RX_DATA15 0x2F
#define CECB_LOCK_BUF 0x30
#define CECB_LOCK_BUF_EN BIT(0)
#define CECB_WAKEUPCTRL 0x31
struct meson_ao_cec_g12a_data {
/* Setup the internal CECB_CTRL2 register */
bool ctrl2_setup;
};
struct meson_ao_cec_g12a_device {
struct platform_device *pdev;
struct regmap *regmap;
struct regmap *regmap_cec;
spinlock_t cec_reg_lock;
struct cec_notifier *notify;
struct cec_adapter *adap;
struct cec_msg rx_msg;
struct clk *oscin;
struct clk *core;
const struct meson_ao_cec_g12a_data *data;
};
static const struct regmap_config meson_ao_cec_g12a_regmap_conf = {
.reg_bits = 8,
.val_bits = 32,
.reg_stride = 4,
.max_register = CECB_INTR_STAT_REG,
};
/*
* The AO-CECB embeds a dual/divider to generate a more precise
* 32,768KHz clock for CEC core clock.
* ______ ______
* | | | |
* ______ | Div1 |-| Cnt1 | ______
* | | /|______| |______|\ | |
* Xtal-->| Gate |---| ______ ______ X-X--| Gate |-->
* |______| | \| | | |/ | |______|
* | | Div2 |-| Cnt2 | |
* | |______| |______| |
* |_______________________|
*
* The dividing can be switched to single or dual, with a counter
* for each divider to set when the switching is done.
* The entire dividing mechanism can be also bypassed.
*/
struct meson_ao_cec_g12a_dualdiv_clk {
struct clk_hw hw;
struct regmap *regmap;
};
#define hw_to_meson_ao_cec_g12a_dualdiv_clk(_hw) \
container_of(_hw, struct meson_ao_cec_g12a_dualdiv_clk, hw) \
static unsigned long
meson_ao_cec_g12a_dualdiv_clk_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct meson_ao_cec_g12a_dualdiv_clk *dualdiv_clk =
hw_to_meson_ao_cec_g12a_dualdiv_clk(hw);
unsigned long n1;
u32 reg0, reg1;
regmap_read(dualdiv_clk->regmap, CECB_CLK_CNTL_REG0, &reg0);
regmap_read(dualdiv_clk->regmap, CECB_CLK_CNTL_REG0, &reg1);
if (reg1 & CECB_CLK_CNTL_BYPASS_EN)
return parent_rate;
if (reg0 & CECB_CLK_CNTL_DUAL_EN) {
unsigned long n2, m1, m2, f1, f2, p1, p2;
n1 = FIELD_GET(CECB_CLK_CNTL_N1, reg0) + 1;
n2 = FIELD_GET(CECB_CLK_CNTL_N2, reg0) + 1;
m1 = FIELD_GET(CECB_CLK_CNTL_M1, reg1) + 1;
m2 = FIELD_GET(CECB_CLK_CNTL_M1, reg1) + 1;
f1 = DIV_ROUND_CLOSEST(parent_rate, n1);
f2 = DIV_ROUND_CLOSEST(parent_rate, n2);
p1 = DIV_ROUND_CLOSEST(100000000 * m1, f1 * (m1 + m2));
p2 = DIV_ROUND_CLOSEST(100000000 * m2, f2 * (m1 + m2));
return DIV_ROUND_UP(100000000, p1 + p2);
}
n1 = FIELD_GET(CECB_CLK_CNTL_N1, reg0) + 1;
return DIV_ROUND_CLOSEST(parent_rate, n1);
}
static int meson_ao_cec_g12a_dualdiv_clk_enable(struct clk_hw *hw)
{
struct meson_ao_cec_g12a_dualdiv_clk *dualdiv_clk =
hw_to_meson_ao_cec_g12a_dualdiv_clk(hw);
/* Disable Input & Output */
regmap_update_bits(dualdiv_clk->regmap, CECB_CLK_CNTL_REG0,
CECB_CLK_CNTL_INPUT_EN | CECB_CLK_CNTL_OUTPUT_EN,
0);
/* Set N1 & N2 */
regmap_update_bits(dualdiv_clk->regmap, CECB_CLK_CNTL_REG0,
CECB_CLK_CNTL_N1,
FIELD_PREP(CECB_CLK_CNTL_N1, 733 - 1));
regmap_update_bits(dualdiv_clk->regmap, CECB_CLK_CNTL_REG0,
CECB_CLK_CNTL_N2,
FIELD_PREP(CECB_CLK_CNTL_N2, 732 - 1));
/* Set M1 & M2 */
regmap_update_bits(dualdiv_clk->regmap, CECB_CLK_CNTL_REG1,
CECB_CLK_CNTL_M1,
FIELD_PREP(CECB_CLK_CNTL_M1, 8 - 1));
regmap_update_bits(dualdiv_clk->regmap, CECB_CLK_CNTL_REG1,
CECB_CLK_CNTL_M2,
FIELD_PREP(CECB_CLK_CNTL_M2, 11 - 1));
/* Enable Dual divisor */
regmap_update_bits(dualdiv_clk->regmap, CECB_CLK_CNTL_REG0,
CECB_CLK_CNTL_DUAL_EN, CECB_CLK_CNTL_DUAL_EN);
/* Disable divisor bypass */
regmap_update_bits(dualdiv_clk->regmap, CECB_CLK_CNTL_REG1,
CECB_CLK_CNTL_BYPASS_EN, 0);
/* Enable Input & Output */
regmap_update_bits(dualdiv_clk->regmap, CECB_CLK_CNTL_REG0,
CECB_CLK_CNTL_INPUT_EN | CECB_CLK_CNTL_OUTPUT_EN,
CECB_CLK_CNTL_INPUT_EN | CECB_CLK_CNTL_OUTPUT_EN);
return 0;
}
static void meson_ao_cec_g12a_dualdiv_clk_disable(struct clk_hw *hw)
{
struct meson_ao_cec_g12a_dualdiv_clk *dualdiv_clk =
hw_to_meson_ao_cec_g12a_dualdiv_clk(hw);
regmap_update_bits(dualdiv_clk->regmap, CECB_CLK_CNTL_REG0,
CECB_CLK_CNTL_INPUT_EN | CECB_CLK_CNTL_OUTPUT_EN,
0);
}
static int meson_ao_cec_g12a_dualdiv_clk_is_enabled(struct clk_hw *hw)
{
struct meson_ao_cec_g12a_dualdiv_clk *dualdiv_clk =
hw_to_meson_ao_cec_g12a_dualdiv_clk(hw);
int val;
regmap_read(dualdiv_clk->regmap, CECB_CLK_CNTL_REG0, &val);
return !!(val & (CECB_CLK_CNTL_INPUT_EN | CECB_CLK_CNTL_OUTPUT_EN));
}
static const struct clk_ops meson_ao_cec_g12a_dualdiv_clk_ops = {
.recalc_rate = meson_ao_cec_g12a_dualdiv_clk_recalc_rate,
.is_enabled = meson_ao_cec_g12a_dualdiv_clk_is_enabled,
.enable = meson_ao_cec_g12a_dualdiv_clk_enable,
.disable = meson_ao_cec_g12a_dualdiv_clk_disable,
};
static int meson_ao_cec_g12a_setup_clk(struct meson_ao_cec_g12a_device *ao_cec)
{
struct meson_ao_cec_g12a_dualdiv_clk *dualdiv_clk;
struct device *dev = &ao_cec->pdev->dev;
struct clk_init_data init;
const char *parent_name;
struct clk *clk;
char *name;
dualdiv_clk = devm_kzalloc(dev, sizeof(*dualdiv_clk), GFP_KERNEL);
if (!dualdiv_clk)
return -ENOMEM;
name = kasprintf(GFP_KERNEL, "%s#dualdiv_clk", dev_name(dev));
if (!name)
return -ENOMEM;
parent_name = __clk_get_name(ao_cec->oscin);
init.name = name;
init.ops = &meson_ao_cec_g12a_dualdiv_clk_ops;
init.flags = 0;
init.parent_names = &parent_name;
init.num_parents = 1;
dualdiv_clk->regmap = ao_cec->regmap;
dualdiv_clk->hw.init = &init;
clk = devm_clk_register(dev, &dualdiv_clk->hw);
kfree(name);
if (IS_ERR(clk)) {
dev_err(dev, "failed to register clock\n");
return PTR_ERR(clk);
}
ao_cec->core = clk;
return 0;
}
static int meson_ao_cec_g12a_read(void *context, unsigned int addr,
unsigned int *data)
{
struct meson_ao_cec_g12a_device *ao_cec = context;
u32 reg = FIELD_PREP(CECB_RW_ADDR, addr);
int ret = 0;
ret = regmap_write(ao_cec->regmap, CECB_RW_REG, reg);
if (ret)
return ret;
ret = regmap_read_poll_timeout(ao_cec->regmap, CECB_RW_REG, reg,
!(reg & CECB_RW_BUS_BUSY),
5, 1000);
if (ret)
return ret;
ret = regmap_read(ao_cec->regmap, CECB_RW_REG, &reg);
*data = FIELD_GET(CECB_RW_RD_DATA, reg);
return ret;
}
static int meson_ao_cec_g12a_write(void *context, unsigned int addr,
unsigned int data)
{
struct meson_ao_cec_g12a_device *ao_cec = context;
u32 reg = FIELD_PREP(CECB_RW_ADDR, addr) |
FIELD_PREP(CECB_RW_WR_DATA, data) |
CECB_RW_WRITE_EN;
return regmap_write(ao_cec->regmap, CECB_RW_REG, reg);
}
static const struct regmap_config meson_ao_cec_g12a_cec_regmap_conf = {
.reg_bits = 8,
.val_bits = 8,
.reg_read = meson_ao_cec_g12a_read,
.reg_write = meson_ao_cec_g12a_write,
.max_register = 0xffff,
};
static inline void
meson_ao_cec_g12a_irq_setup(struct meson_ao_cec_g12a_device *ao_cec,
bool enable)
{
u32 cfg = CECB_INTR_DONE | CECB_INTR_EOM | CECB_INTR_NACK |
CECB_INTR_ARB_LOSS | CECB_INTR_INITIATOR_ERR |
CECB_INTR_FOLLOWER_ERR;
regmap_write(ao_cec->regmap, CECB_INTR_MASKN_REG,
enable ? cfg : 0);
}
static void meson_ao_cec_g12a_irq_rx(struct meson_ao_cec_g12a_device *ao_cec)
{
int i, ret = 0;
u32 val;
ret = regmap_read(ao_cec->regmap_cec, CECB_RX_CNT, &val);
ao_cec->rx_msg.len = val;
if (ao_cec->rx_msg.len > CEC_MAX_MSG_SIZE)
ao_cec->rx_msg.len = CEC_MAX_MSG_SIZE;
for (i = 0; i < ao_cec->rx_msg.len; i++) {
ret |= regmap_read(ao_cec->regmap_cec,
CECB_RX_DATA00 + i, &val);
ao_cec->rx_msg.msg[i] = val & 0xff;
}
ret |= regmap_write(ao_cec->regmap_cec, CECB_LOCK_BUF, 0);
if (ret)
return;
cec_received_msg(ao_cec->adap, &ao_cec->rx_msg);
}
static irqreturn_t meson_ao_cec_g12a_irq(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);
if (stat)
return IRQ_WAKE_THREAD;
return IRQ_NONE;
}
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);
}
/* Initiator reports an error on the CEC bus */
if (stat & CECB_INTR_INITIATOR_ERR)
cec_transmit_attempt_done(ao_cec->adap, CEC_TX_STATUS_ERROR);
/* Follower reports a receive error, just reset RX buffer */
if (stat & CECB_INTR_FOLLOWER_ERR)
regmap_write(ao_cec->regmap_cec, CECB_LOCK_BUF, 0);
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) {
/* Assume this will allways succeed */
regmap_write(ao_cec->regmap_cec, CECB_LADD_LOW, 0);
regmap_write(ao_cec->regmap_cec, CECB_LADD_HIGH, 0);
return 0;
} 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,
BIT(CEC_LOG_ADDR_UNREGISTERED - 8),
BIT(CEC_LOG_ADDR_UNREGISTERED - 8));
return ret ? -EIO : 0;
}
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);
if (ret)
return -EIO;
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);
if (ao_cec->data->ctrl2_setup)
regmap_write(ao_cec->regmap_cec, CECB_CTRL2,
FIELD_PREP(CECB_CTRL2_RISE_DEL_MAX, 2));
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 device *hdmi_dev;
void __iomem *base;
int ret, irq;
hdmi_dev = cec_notifier_parse_hdmi_phandle(&pdev->dev);
if (IS_ERR(hdmi_dev))
return PTR_ERR(hdmi_dev);
ao_cec = devm_kzalloc(&pdev->dev, sizeof(*ao_cec), GFP_KERNEL);
if (!ao_cec)
return -ENOMEM;
ao_cec->data = of_device_get_match_data(&pdev->dev);
if (!ao_cec->data) {
dev_err(&pdev->dev, "failed to get match data\n");
return -ENODEV;
}
spin_lock_init(&ao_cec->cec_reg_lock);
ao_cec->pdev = pdev;
ao_cec->adap = cec_allocate_adapter(&meson_ao_cec_g12a_ops, ao_cec,
"meson_g12a_ao_cec",
CEC_CAP_DEFAULTS |
CEC_CAP_CONNECTOR_INFO,
CEC_MAX_LOG_ADDRS);
if (IS_ERR(ao_cec->adap))
return PTR_ERR(ao_cec->adap);
ao_cec->adap->owner = THIS_MODULE;
base = devm_platform_ioremap_resource(pdev, 0);
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_adapter;
ret = clk_prepare_enable(ao_cec->core);
if (ret) {
dev_err(&pdev->dev, "core clock enable failed\n");
goto out_probe_adapter;
}
device_reset_optional(&pdev->dev);
platform_set_drvdata(pdev, ao_cec);
ao_cec->notify = cec_notifier_cec_adap_register(hdmi_dev, NULL,
ao_cec->adap);
if (!ao_cec->notify) {
ret = -ENOMEM;
goto out_probe_core_clk;
}
ret = cec_register_adapter(ao_cec->adap, &pdev->dev);
if (ret < 0)
goto out_probe_notify;
/* Setup Hardware */
regmap_write(ao_cec->regmap, CECB_GEN_CNTL_REG, CECB_GEN_CNTL_RESET);
return 0;
out_probe_notify:
cec_notifier_cec_adap_unregister(ao_cec->notify, ao_cec->adap);
out_probe_core_clk:
clk_disable_unprepare(ao_cec->core);
out_probe_adapter:
cec_delete_adapter(ao_cec->adap);
dev_err(&pdev->dev, "CEC controller registration failed\n");
return ret;
}
static void 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->core);
cec_notifier_cec_adap_unregister(ao_cec->notify, ao_cec->adap);
cec_unregister_adapter(ao_cec->adap);
}
static const struct meson_ao_cec_g12a_data ao_cec_g12a_data = {
.ctrl2_setup = false,
};
static const struct meson_ao_cec_g12a_data ao_cec_sm1_data = {
.ctrl2_setup = true,
};
static const struct of_device_id meson_ao_cec_g12a_of_match[] = {
{
.compatible = "amlogic,meson-g12a-ao-cec",
.data = &ao_cec_g12a_data,
},
{
.compatible = "amlogic,meson-sm1-ao-cec",
.data = &ao_cec_sm1_data,
},
{ /* 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@baylibre.com>");
MODULE_LICENSE("GPL");
@@ -0,0 +1,728 @@
/*
* Driver for Amlogic Meson AO CEC Controller
*
* Copyright (C) 2015 Amlogic, Inc. All rights reserved
* Copyright (C) 2017 BayLibre, SAS
* Author: Neil Armstrong <narmstrong@baylibre.com>
*
* SPDX-License-Identifier: GPL-2.0+
*/
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/reset.h>
#include <media/cec.h>
#include <media/cec-notifier.h>
/* CEC Registers */
/*
* [2:1] cntl_clk
* - 0 = Disable clk (Power-off mode)
* - 1 = Enable gated clock (Normal mode)
* - 2 = Enable free-run clk (Debug mode)
*/
#define CEC_GEN_CNTL_REG 0x00
#define CEC_GEN_CNTL_RESET BIT(0)
#define CEC_GEN_CNTL_CLK_DISABLE 0
#define CEC_GEN_CNTL_CLK_ENABLE 1
#define CEC_GEN_CNTL_CLK_ENABLE_DBG 2
#define CEC_GEN_CNTL_CLK_CTRL_MASK GENMASK(2, 1)
/*
* [7:0] cec_reg_addr
* [15:8] cec_reg_wrdata
* [16] cec_reg_wr
* - 0 = Read
* - 1 = Write
* [23] bus free
* [31:24] cec_reg_rddata
*/
#define CEC_RW_REG 0x04
#define CEC_RW_ADDR GENMASK(7, 0)
#define CEC_RW_WR_DATA GENMASK(15, 8)
#define CEC_RW_WRITE_EN BIT(16)
#define CEC_RW_BUS_BUSY BIT(23)
#define CEC_RW_RD_DATA GENMASK(31, 24)
/*
* [1] tx intr
* [2] rx intr
*/
#define CEC_INTR_MASKN_REG 0x08
#define CEC_INTR_CLR_REG 0x0c
#define CEC_INTR_STAT_REG 0x10
#define CEC_INTR_TX BIT(1)
#define CEC_INTR_RX BIT(2)
/* CEC Commands */
#define CEC_TX_MSG_0_HEADER 0x00
#define CEC_TX_MSG_1_OPCODE 0x01
#define CEC_TX_MSG_2_OP1 0x02
#define CEC_TX_MSG_3_OP2 0x03
#define CEC_TX_MSG_4_OP3 0x04
#define CEC_TX_MSG_5_OP4 0x05
#define CEC_TX_MSG_6_OP5 0x06
#define CEC_TX_MSG_7_OP6 0x07
#define CEC_TX_MSG_8_OP7 0x08
#define CEC_TX_MSG_9_OP8 0x09
#define CEC_TX_MSG_A_OP9 0x0A
#define CEC_TX_MSG_B_OP10 0x0B
#define CEC_TX_MSG_C_OP11 0x0C
#define CEC_TX_MSG_D_OP12 0x0D
#define CEC_TX_MSG_E_OP13 0x0E
#define CEC_TX_MSG_F_OP14 0x0F
#define CEC_TX_MSG_LENGTH 0x10
#define CEC_TX_MSG_CMD 0x11
#define CEC_TX_WRITE_BUF 0x12
#define CEC_TX_CLEAR_BUF 0x13
#define CEC_RX_MSG_CMD 0x14
#define CEC_RX_CLEAR_BUF 0x15
#define CEC_LOGICAL_ADDR0 0x16
#define CEC_LOGICAL_ADDR1 0x17
#define CEC_LOGICAL_ADDR2 0x18
#define CEC_LOGICAL_ADDR3 0x19
#define CEC_LOGICAL_ADDR4 0x1A
#define CEC_CLOCK_DIV_H 0x1B
#define CEC_CLOCK_DIV_L 0x1C
#define CEC_QUIESCENT_25MS_BIT7_0 0x20
#define CEC_QUIESCENT_25MS_BIT11_8 0x21
#define CEC_STARTBITMINL2H_3MS5_BIT7_0 0x22
#define CEC_STARTBITMINL2H_3MS5_BIT8 0x23
#define CEC_STARTBITMAXL2H_3MS9_BIT7_0 0x24
#define CEC_STARTBITMAXL2H_3MS9_BIT8 0x25
#define CEC_STARTBITMINH_0MS6_BIT7_0 0x26
#define CEC_STARTBITMINH_0MS6_BIT8 0x27
#define CEC_STARTBITMAXH_1MS0_BIT7_0 0x28
#define CEC_STARTBITMAXH_1MS0_BIT8 0x29
#define CEC_STARTBITMINTOT_4MS3_BIT7_0 0x2A
#define CEC_STARTBITMINTOT_4MS3_BIT9_8 0x2B
#define CEC_STARTBITMAXTOT_4MS7_BIT7_0 0x2C
#define CEC_STARTBITMAXTOT_4MS7_BIT9_8 0x2D
#define CEC_LOGIC1MINL2H_0MS4_BIT7_0 0x2E
#define CEC_LOGIC1MINL2H_0MS4_BIT8 0x2F
#define CEC_LOGIC1MAXL2H_0MS8_BIT7_0 0x30
#define CEC_LOGIC1MAXL2H_0MS8_BIT8 0x31
#define CEC_LOGIC0MINL2H_1MS3_BIT7_0 0x32
#define CEC_LOGIC0MINL2H_1MS3_BIT8 0x33
#define CEC_LOGIC0MAXL2H_1MS7_BIT7_0 0x34
#define CEC_LOGIC0MAXL2H_1MS7_BIT8 0x35
#define CEC_LOGICMINTOTAL_2MS05_BIT7_0 0x36
#define CEC_LOGICMINTOTAL_2MS05_BIT9_8 0x37
#define CEC_LOGICMAXHIGH_2MS8_BIT7_0 0x38
#define CEC_LOGICMAXHIGH_2MS8_BIT8 0x39
#define CEC_LOGICERRLOW_3MS4_BIT7_0 0x3A
#define CEC_LOGICERRLOW_3MS4_BIT8 0x3B
#define CEC_NOMSMPPOINT_1MS05 0x3C
#define CEC_DELCNTR_LOGICERR 0x3E
#define CEC_TXTIME_17MS_BIT7_0 0x40
#define CEC_TXTIME_17MS_BIT10_8 0x41
#define CEC_TXTIME_2BIT_BIT7_0 0x42
#define CEC_TXTIME_2BIT_BIT10_8 0x43
#define CEC_TXTIME_4BIT_BIT7_0 0x44
#define CEC_TXTIME_4BIT_BIT10_8 0x45
#define CEC_STARTBITNOML2H_3MS7_BIT7_0 0x46
#define CEC_STARTBITNOML2H_3MS7_BIT8 0x47
#define CEC_STARTBITNOMH_0MS8_BIT7_0 0x48
#define CEC_STARTBITNOMH_0MS8_BIT8 0x49
#define CEC_LOGIC1NOML2H_0MS6_BIT7_0 0x4A
#define CEC_LOGIC1NOML2H_0MS6_BIT8 0x4B
#define CEC_LOGIC0NOML2H_1MS5_BIT7_0 0x4C
#define CEC_LOGIC0NOML2H_1MS5_BIT8 0x4D
#define CEC_LOGIC1NOMH_1MS8_BIT7_0 0x4E
#define CEC_LOGIC1NOMH_1MS8_BIT8 0x4F
#define CEC_LOGIC0NOMH_0MS9_BIT7_0 0x50
#define CEC_LOGIC0NOMH_0MS9_BIT8 0x51
#define CEC_LOGICERRLOW_3MS6_BIT7_0 0x52
#define CEC_LOGICERRLOW_3MS6_BIT8 0x53
#define CEC_CHKCONTENTION_0MS1 0x54
#define CEC_PREPARENXTBIT_0MS05_BIT7_0 0x56
#define CEC_PREPARENXTBIT_0MS05_BIT8 0x57
#define CEC_NOMSMPACKPOINT_0MS45 0x58
#define CEC_ACK0NOML2H_1MS5_BIT7_0 0x5A
#define CEC_ACK0NOML2H_1MS5_BIT8 0x5B
#define CEC_BUGFIX_DISABLE_0 0x60
#define CEC_BUGFIX_DISABLE_1 0x61
#define CEC_RX_MSG_0_HEADER 0x80
#define CEC_RX_MSG_1_OPCODE 0x81
#define CEC_RX_MSG_2_OP1 0x82
#define CEC_RX_MSG_3_OP2 0x83
#define CEC_RX_MSG_4_OP3 0x84
#define CEC_RX_MSG_5_OP4 0x85
#define CEC_RX_MSG_6_OP5 0x86
#define CEC_RX_MSG_7_OP6 0x87
#define CEC_RX_MSG_8_OP7 0x88
#define CEC_RX_MSG_9_OP8 0x89
#define CEC_RX_MSG_A_OP9 0x8A
#define CEC_RX_MSG_B_OP10 0x8B
#define CEC_RX_MSG_C_OP11 0x8C
#define CEC_RX_MSG_D_OP12 0x8D
#define CEC_RX_MSG_E_OP13 0x8E
#define CEC_RX_MSG_F_OP14 0x8F
#define CEC_RX_MSG_LENGTH 0x90
#define CEC_RX_MSG_STATUS 0x91
#define CEC_RX_NUM_MSG 0x92
#define CEC_TX_MSG_STATUS 0x93
#define CEC_TX_NUM_MSG 0x94
/* CEC_TX_MSG_CMD definition */
#define TX_NO_OP 0 /* No transaction */
#define TX_REQ_CURRENT 1 /* Transmit earliest message in buffer */
#define TX_ABORT 2 /* Abort transmitting earliest message */
#define TX_REQ_NEXT 3 /* Overwrite earliest msg, transmit next */
/* tx_msg_status definition */
#define TX_IDLE 0 /* No transaction */
#define TX_BUSY 1 /* Transmitter is busy */
#define TX_DONE 2 /* Message successfully transmitted */
#define TX_ERROR 3 /* Message transmitted with error */
/* rx_msg_cmd */
#define RX_NO_OP 0 /* No transaction */
#define RX_ACK_CURRENT 1 /* Read earliest message in buffer */
#define RX_DISABLE 2 /* Disable receiving latest message */
#define RX_ACK_NEXT 3 /* Clear earliest msg, read next */
/* rx_msg_status */
#define RX_IDLE 0 /* No transaction */
#define RX_BUSY 1 /* Receiver is busy */
#define RX_DONE 2 /* Message has been received successfully */
#define RX_ERROR 3 /* Message has been received with error */
/* RX_CLEAR_BUF options */
#define CLEAR_START 1
#define CLEAR_STOP 0
/* CEC_LOGICAL_ADDRx options */
#define LOGICAL_ADDR_MASK 0xf
#define LOGICAL_ADDR_VALID BIT(4)
#define LOGICAL_ADDR_DISABLE 0
#define CEC_CLK_RATE 32768
struct meson_ao_cec_device {
struct platform_device *pdev;
void __iomem *base;
struct clk *core;
spinlock_t cec_reg_lock;
struct cec_notifier *notify;
struct cec_adapter *adap;
struct cec_msg rx_msg;
};
#define writel_bits_relaxed(mask, val, addr) \
writel_relaxed((readl_relaxed(addr) & ~(mask)) | (val), addr)
static inline int meson_ao_cec_wait_busy(struct meson_ao_cec_device *ao_cec)
{
ktime_t timeout = ktime_add_us(ktime_get(), 5000);
while (readl_relaxed(ao_cec->base + CEC_RW_REG) & CEC_RW_BUS_BUSY) {
if (ktime_compare(ktime_get(), timeout) > 0)
return -ETIMEDOUT;
}
return 0;
}
static void meson_ao_cec_read(struct meson_ao_cec_device *ao_cec,
unsigned long address, u8 *data,
int *res)
{
unsigned long flags;
u32 reg = FIELD_PREP(CEC_RW_ADDR, address);
int ret = 0;
if (res && *res)
return;
spin_lock_irqsave(&ao_cec->cec_reg_lock, flags);
ret = meson_ao_cec_wait_busy(ao_cec);
if (ret)
goto read_out;
writel_relaxed(reg, ao_cec->base + CEC_RW_REG);
ret = meson_ao_cec_wait_busy(ao_cec);
if (ret)
goto read_out;
*data = FIELD_GET(CEC_RW_RD_DATA,
readl_relaxed(ao_cec->base + CEC_RW_REG));
read_out:
spin_unlock_irqrestore(&ao_cec->cec_reg_lock, flags);
if (res)
*res = ret;
}
static void meson_ao_cec_write(struct meson_ao_cec_device *ao_cec,
unsigned long address, u8 data,
int *res)
{
unsigned long flags;
u32 reg = FIELD_PREP(CEC_RW_ADDR, address) |
FIELD_PREP(CEC_RW_WR_DATA, data) |
CEC_RW_WRITE_EN;
int ret = 0;
if (res && *res)
return;
spin_lock_irqsave(&ao_cec->cec_reg_lock, flags);
ret = meson_ao_cec_wait_busy(ao_cec);
if (ret)
goto write_out;
writel_relaxed(reg, ao_cec->base + CEC_RW_REG);
write_out:
spin_unlock_irqrestore(&ao_cec->cec_reg_lock, flags);
if (res)
*res = ret;
}
static inline void meson_ao_cec_irq_setup(struct meson_ao_cec_device *ao_cec,
bool enable)
{
u32 cfg = CEC_INTR_TX | CEC_INTR_RX;
writel_bits_relaxed(cfg, enable ? cfg : 0,
ao_cec->base + CEC_INTR_MASKN_REG);
}
static inline int meson_ao_cec_clear(struct meson_ao_cec_device *ao_cec)
{
int ret = 0;
meson_ao_cec_write(ao_cec, CEC_RX_MSG_CMD, RX_DISABLE, &ret);
meson_ao_cec_write(ao_cec, CEC_TX_MSG_CMD, TX_ABORT, &ret);
meson_ao_cec_write(ao_cec, CEC_RX_CLEAR_BUF, 1, &ret);
meson_ao_cec_write(ao_cec, CEC_TX_CLEAR_BUF, 1, &ret);
if (ret)
return ret;
udelay(100);
meson_ao_cec_write(ao_cec, CEC_RX_CLEAR_BUF, 0, &ret);
meson_ao_cec_write(ao_cec, CEC_TX_CLEAR_BUF, 0, &ret);
if (ret)
return ret;
udelay(100);
meson_ao_cec_write(ao_cec, CEC_RX_MSG_CMD, RX_NO_OP, &ret);
meson_ao_cec_write(ao_cec, CEC_TX_MSG_CMD, TX_NO_OP, &ret);
return ret;
}
static int meson_ao_cec_arbit_bit_time_set(struct meson_ao_cec_device *ao_cec,
unsigned int bit_set,
unsigned int time_set)
{
int ret = 0;
switch (bit_set) {
case CEC_SIGNAL_FREE_TIME_RETRY:
meson_ao_cec_write(ao_cec, CEC_TXTIME_4BIT_BIT7_0,
time_set & 0xff, &ret);
meson_ao_cec_write(ao_cec, CEC_TXTIME_4BIT_BIT10_8,
(time_set >> 8) & 0x7, &ret);
break;
case CEC_SIGNAL_FREE_TIME_NEW_INITIATOR:
meson_ao_cec_write(ao_cec, CEC_TXTIME_2BIT_BIT7_0,
time_set & 0xff, &ret);
meson_ao_cec_write(ao_cec, CEC_TXTIME_2BIT_BIT10_8,
(time_set >> 8) & 0x7, &ret);
break;
case CEC_SIGNAL_FREE_TIME_NEXT_XFER:
meson_ao_cec_write(ao_cec, CEC_TXTIME_17MS_BIT7_0,
time_set & 0xff, &ret);
meson_ao_cec_write(ao_cec, CEC_TXTIME_17MS_BIT10_8,
(time_set >> 8) & 0x7, &ret);
break;
}
return ret;
}
static irqreturn_t meson_ao_cec_irq(int irq, void *data)
{
struct meson_ao_cec_device *ao_cec = data;
u32 stat = readl_relaxed(ao_cec->base + CEC_INTR_STAT_REG);
if (stat)
return IRQ_WAKE_THREAD;
return IRQ_NONE;
}
static void meson_ao_cec_irq_tx(struct meson_ao_cec_device *ao_cec)
{
unsigned long tx_status = 0;
u8 stat;
int ret = 0;
meson_ao_cec_read(ao_cec, CEC_TX_MSG_STATUS, &stat, &ret);
if (ret)
goto tx_reg_err;
switch (stat) {
case TX_DONE:
tx_status = CEC_TX_STATUS_OK;
break;
case TX_BUSY:
tx_status = CEC_TX_STATUS_ARB_LOST;
break;
case TX_IDLE:
tx_status = CEC_TX_STATUS_LOW_DRIVE;
break;
case TX_ERROR:
default:
tx_status = CEC_TX_STATUS_NACK;
break;
}
/* Clear Interruption */
writel_relaxed(CEC_INTR_TX, ao_cec->base + CEC_INTR_CLR_REG);
/* Stop TX */
meson_ao_cec_write(ao_cec, CEC_TX_MSG_CMD, TX_NO_OP, &ret);
if (ret)
goto tx_reg_err;
cec_transmit_attempt_done(ao_cec->adap, tx_status);
return;
tx_reg_err:
cec_transmit_attempt_done(ao_cec->adap, CEC_TX_STATUS_ERROR);
}
static void meson_ao_cec_irq_rx(struct meson_ao_cec_device *ao_cec)
{
int i, ret = 0;
u8 reg;
meson_ao_cec_read(ao_cec, CEC_RX_MSG_STATUS, &reg, &ret);
if (reg != RX_DONE)
goto rx_out;
meson_ao_cec_read(ao_cec, CEC_RX_NUM_MSG, &reg, &ret);
if (reg != 1)
goto rx_out;
meson_ao_cec_read(ao_cec, CEC_RX_MSG_LENGTH, &reg, &ret);
ao_cec->rx_msg.len = reg + 1;
if (ao_cec->rx_msg.len > CEC_MAX_MSG_SIZE)
ao_cec->rx_msg.len = CEC_MAX_MSG_SIZE;
for (i = 0; i < ao_cec->rx_msg.len; i++) {
u8 byte;
meson_ao_cec_read(ao_cec, CEC_RX_MSG_0_HEADER + i, &byte, &ret);
ao_cec->rx_msg.msg[i] = byte;
}
if (ret)
goto rx_out;
cec_received_msg(ao_cec->adap, &ao_cec->rx_msg);
rx_out:
/* Clear Interruption */
writel_relaxed(CEC_INTR_RX, ao_cec->base + CEC_INTR_CLR_REG);
/* Ack RX message */
meson_ao_cec_write(ao_cec, CEC_RX_MSG_CMD, RX_ACK_CURRENT, &ret);
meson_ao_cec_write(ao_cec, CEC_RX_MSG_CMD, RX_NO_OP, &ret);
/* Clear RX buffer */
meson_ao_cec_write(ao_cec, CEC_RX_CLEAR_BUF, CLEAR_START, &ret);
meson_ao_cec_write(ao_cec, CEC_RX_CLEAR_BUF, CLEAR_STOP, &ret);
}
static irqreturn_t meson_ao_cec_irq_thread(int irq, void *data)
{
struct meson_ao_cec_device *ao_cec = data;
u32 stat = readl_relaxed(ao_cec->base + CEC_INTR_STAT_REG);
if (stat & CEC_INTR_TX)
meson_ao_cec_irq_tx(ao_cec);
meson_ao_cec_irq_rx(ao_cec);
return IRQ_HANDLED;
}
static int meson_ao_cec_set_log_addr(struct cec_adapter *adap, u8 logical_addr)
{
struct meson_ao_cec_device *ao_cec = adap->priv;
int ret = 0;
meson_ao_cec_write(ao_cec, CEC_LOGICAL_ADDR0,
LOGICAL_ADDR_DISABLE, &ret);
if (ret)
return ret;
ret = meson_ao_cec_clear(ao_cec);
if (ret)
return ret;
if (logical_addr == CEC_LOG_ADDR_INVALID)
return 0;
meson_ao_cec_write(ao_cec, CEC_LOGICAL_ADDR0,
logical_addr & LOGICAL_ADDR_MASK, &ret);
if (ret)
return ret;
udelay(100);
meson_ao_cec_write(ao_cec, CEC_LOGICAL_ADDR0,
(logical_addr & LOGICAL_ADDR_MASK) |
LOGICAL_ADDR_VALID, &ret);
return ret;
}
static int meson_ao_cec_transmit(struct cec_adapter *adap, u8 attempts,
u32 signal_free_time, struct cec_msg *msg)
{
struct meson_ao_cec_device *ao_cec = adap->priv;
int i, ret = 0;
u8 reg;
meson_ao_cec_read(ao_cec, CEC_TX_MSG_STATUS, &reg, &ret);
if (ret)
return ret;
if (reg == TX_BUSY) {
dev_dbg(&ao_cec->pdev->dev, "%s: busy TX: aborting\n",
__func__);
meson_ao_cec_write(ao_cec, CEC_TX_MSG_CMD, TX_ABORT, &ret);
}
for (i = 0; i < msg->len; i++) {
meson_ao_cec_write(ao_cec, CEC_TX_MSG_0_HEADER + i,
msg->msg[i], &ret);
}
meson_ao_cec_write(ao_cec, CEC_TX_MSG_LENGTH, msg->len - 1, &ret);
meson_ao_cec_write(ao_cec, CEC_TX_MSG_CMD, TX_REQ_CURRENT, &ret);
return ret;
}
static int meson_ao_cec_adap_enable(struct cec_adapter *adap, bool enable)
{
struct meson_ao_cec_device *ao_cec = adap->priv;
int ret;
meson_ao_cec_irq_setup(ao_cec, false);
writel_bits_relaxed(CEC_GEN_CNTL_RESET, CEC_GEN_CNTL_RESET,
ao_cec->base + CEC_GEN_CNTL_REG);
if (!enable)
return 0;
/* Enable gated clock (Normal mode). */
writel_bits_relaxed(CEC_GEN_CNTL_CLK_CTRL_MASK,
FIELD_PREP(CEC_GEN_CNTL_CLK_CTRL_MASK,
CEC_GEN_CNTL_CLK_ENABLE),
ao_cec->base + CEC_GEN_CNTL_REG);
udelay(100);
/* Release Reset */
writel_bits_relaxed(CEC_GEN_CNTL_RESET, 0,
ao_cec->base + CEC_GEN_CNTL_REG);
/* Clear buffers */
ret = meson_ao_cec_clear(ao_cec);
if (ret)
return ret;
/* CEC arbitration 3/5/7 bit time set. */
ret = meson_ao_cec_arbit_bit_time_set(ao_cec,
CEC_SIGNAL_FREE_TIME_RETRY,
0x118);
if (ret)
return ret;
ret = meson_ao_cec_arbit_bit_time_set(ao_cec,
CEC_SIGNAL_FREE_TIME_NEW_INITIATOR,
0x000);
if (ret)
return ret;
ret = meson_ao_cec_arbit_bit_time_set(ao_cec,
CEC_SIGNAL_FREE_TIME_NEXT_XFER,
0x2aa);
if (ret)
return ret;
meson_ao_cec_irq_setup(ao_cec, true);
return 0;
}
static const struct cec_adap_ops meson_ao_cec_ops = {
.adap_enable = meson_ao_cec_adap_enable,
.adap_log_addr = meson_ao_cec_set_log_addr,
.adap_transmit = meson_ao_cec_transmit,
};
static int meson_ao_cec_probe(struct platform_device *pdev)
{
struct meson_ao_cec_device *ao_cec;
struct device *hdmi_dev;
int ret, irq;
hdmi_dev = cec_notifier_parse_hdmi_phandle(&pdev->dev);
if (IS_ERR(hdmi_dev))
return PTR_ERR(hdmi_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->adap = cec_allocate_adapter(&meson_ao_cec_ops, ao_cec,
"meson_ao_cec",
CEC_CAP_DEFAULTS |
CEC_CAP_CONNECTOR_INFO,
1); /* Use 1 for now */
if (IS_ERR(ao_cec->adap))
return PTR_ERR(ao_cec->adap);
ao_cec->adap->owner = THIS_MODULE;
ao_cec->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(ao_cec->base)) {
ret = PTR_ERR(ao_cec->base);
goto out_probe_adapter;
}
irq = platform_get_irq(pdev, 0);
ret = devm_request_threaded_irq(&pdev->dev, irq,
meson_ao_cec_irq,
meson_ao_cec_irq_thread,
0, NULL, ao_cec);
if (ret) {
dev_err(&pdev->dev, "irq request failed\n");
goto out_probe_adapter;
}
ao_cec->core = devm_clk_get(&pdev->dev, "core");
if (IS_ERR(ao_cec->core)) {
dev_err(&pdev->dev, "core clock request failed\n");
ret = PTR_ERR(ao_cec->core);
goto out_probe_adapter;
}
ret = clk_prepare_enable(ao_cec->core);
if (ret) {
dev_err(&pdev->dev, "core clock enable failed\n");
goto out_probe_adapter;
}
ret = clk_set_rate(ao_cec->core, CEC_CLK_RATE);
if (ret) {
dev_err(&pdev->dev, "core clock set rate failed\n");
goto out_probe_clk;
}
device_reset_optional(&pdev->dev);
ao_cec->pdev = pdev;
platform_set_drvdata(pdev, ao_cec);
ao_cec->notify = cec_notifier_cec_adap_register(hdmi_dev, NULL,
ao_cec->adap);
if (!ao_cec->notify) {
ret = -ENOMEM;
goto out_probe_clk;
}
ret = cec_register_adapter(ao_cec->adap, &pdev->dev);
if (ret < 0)
goto out_probe_notify;
/* Setup Hardware */
writel_relaxed(CEC_GEN_CNTL_RESET,
ao_cec->base + CEC_GEN_CNTL_REG);
return 0;
out_probe_notify:
cec_notifier_cec_adap_unregister(ao_cec->notify, ao_cec->adap);
out_probe_clk:
clk_disable_unprepare(ao_cec->core);
out_probe_adapter:
cec_delete_adapter(ao_cec->adap);
dev_err(&pdev->dev, "CEC controller registration failed\n");
return ret;
}
static void meson_ao_cec_remove(struct platform_device *pdev)
{
struct meson_ao_cec_device *ao_cec = platform_get_drvdata(pdev);
clk_disable_unprepare(ao_cec->core);
cec_notifier_cec_adap_unregister(ao_cec->notify, ao_cec->adap);
cec_unregister_adapter(ao_cec->adap);
}
static const struct of_device_id meson_ao_cec_of_match[] = {
{ .compatible = "amlogic,meson-gx-ao-cec", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, meson_ao_cec_of_match);
static struct platform_driver meson_ao_cec_driver = {
.probe = meson_ao_cec_probe,
.remove = meson_ao_cec_remove,
.driver = {
.name = "meson-ao-cec",
.of_match_table = meson_ao_cec_of_match,
},
};
module_platform_driver(meson_ao_cec_driver);
MODULE_DESCRIPTION("Meson AO CEC Controller driver");
MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
MODULE_LICENSE("GPL");
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_CEC_SAMSUNG_S5P) += s5p-cec.o
s5p-cec-y += s5p_cec.o exynos_hdmi_cecctrl.o
@@ -0,0 +1,34 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* drivers/media/platform/s5p-cec/exynos_hdmi_cec.h
*
* Copyright (c) 2010, 2014 Samsung Electronics
* http://www.samsung.com/
*
* Header file for interface of Samsung Exynos hdmi cec hardware
*/
#ifndef _EXYNOS_HDMI_CEC_H_
#define _EXYNOS_HDMI_CEC_H_ __FILE__
#include <linux/regmap.h>
#include "s5p_cec.h"
void s5p_cec_set_divider(struct s5p_cec_dev *cec);
void s5p_cec_enable_rx(struct s5p_cec_dev *cec);
void s5p_cec_mask_rx_interrupts(struct s5p_cec_dev *cec);
void s5p_cec_unmask_rx_interrupts(struct s5p_cec_dev *cec);
void s5p_cec_mask_tx_interrupts(struct s5p_cec_dev *cec);
void s5p_cec_unmask_tx_interrupts(struct s5p_cec_dev *cec);
void s5p_cec_reset(struct s5p_cec_dev *cec);
void s5p_cec_tx_reset(struct s5p_cec_dev *cec);
void s5p_cec_rx_reset(struct s5p_cec_dev *cec);
void s5p_cec_threshold(struct s5p_cec_dev *cec);
void s5p_cec_copy_packet(struct s5p_cec_dev *cec, char *data,
size_t count, u8 retries);
void s5p_cec_set_addr(struct s5p_cec_dev *cec, u32 addr);
u32 s5p_cec_get_status(struct s5p_cec_dev *cec);
void s5p_clr_pending_tx(struct s5p_cec_dev *cec);
void s5p_clr_pending_rx(struct s5p_cec_dev *cec);
void s5p_cec_get_rx_buf(struct s5p_cec_dev *cec, u32 size, u8 *buffer);
#endif /* _EXYNOS_HDMI_CEC_H_ */
@@ -0,0 +1,206 @@
// SPDX-License-Identifier: GPL-2.0-only
/* drivers/media/platform/s5p-cec/exynos_hdmi_cecctrl.c
*
* Copyright (c) 2009, 2014 Samsung Electronics
* http://www.samsung.com/
*
* cec ftn file for Samsung TVOUT driver
*/
#include <linux/io.h>
#include <linux/device.h>
#include "exynos_hdmi_cec.h"
#include "regs-cec.h"
#define S5P_HDMI_FIN 24000000
#define CEC_DIV_RATIO 320000
#define CEC_MESSAGE_BROADCAST_MASK 0x0F
#define CEC_MESSAGE_BROADCAST 0x0F
#define CEC_FILTER_THRESHOLD 0x15
void s5p_cec_set_divider(struct s5p_cec_dev *cec)
{
u32 div_ratio, div_val;
unsigned int reg;
div_ratio = S5P_HDMI_FIN / CEC_DIV_RATIO - 1;
if (regmap_read(cec->pmu, EXYNOS_HDMI_PHY_CONTROL, &reg)) {
dev_err(cec->dev, "failed to read phy control\n");
return;
}
reg = (reg & ~(0x3FF << 16)) | (div_ratio << 16);
if (regmap_write(cec->pmu, EXYNOS_HDMI_PHY_CONTROL, reg)) {
dev_err(cec->dev, "failed to write phy control\n");
return;
}
div_val = CEC_DIV_RATIO * 0.00005 - 1;
writeb(0x0, cec->reg + S5P_CEC_DIVISOR_3);
writeb(0x0, cec->reg + S5P_CEC_DIVISOR_2);
writeb(0x0, cec->reg + S5P_CEC_DIVISOR_1);
writeb(div_val, cec->reg + S5P_CEC_DIVISOR_0);
}
void s5p_cec_enable_rx(struct s5p_cec_dev *cec)
{
u8 reg;
reg = readb(cec->reg + S5P_CEC_RX_CTRL);
reg |= S5P_CEC_RX_CTRL_ENABLE;
writeb(reg, cec->reg + S5P_CEC_RX_CTRL);
}
void s5p_cec_mask_rx_interrupts(struct s5p_cec_dev *cec)
{
u8 reg;
reg = readb(cec->reg + S5P_CEC_IRQ_MASK);
reg |= S5P_CEC_IRQ_RX_DONE;
reg |= S5P_CEC_IRQ_RX_ERROR;
writeb(reg, cec->reg + S5P_CEC_IRQ_MASK);
}
void s5p_cec_unmask_rx_interrupts(struct s5p_cec_dev *cec)
{
u8 reg;
reg = readb(cec->reg + S5P_CEC_IRQ_MASK);
reg &= ~S5P_CEC_IRQ_RX_DONE;
reg &= ~S5P_CEC_IRQ_RX_ERROR;
writeb(reg, cec->reg + S5P_CEC_IRQ_MASK);
}
void s5p_cec_mask_tx_interrupts(struct s5p_cec_dev *cec)
{
u8 reg;
reg = readb(cec->reg + S5P_CEC_IRQ_MASK);
reg |= S5P_CEC_IRQ_TX_DONE;
reg |= S5P_CEC_IRQ_TX_ERROR;
writeb(reg, cec->reg + S5P_CEC_IRQ_MASK);
}
void s5p_cec_unmask_tx_interrupts(struct s5p_cec_dev *cec)
{
u8 reg;
reg = readb(cec->reg + S5P_CEC_IRQ_MASK);
reg &= ~S5P_CEC_IRQ_TX_DONE;
reg &= ~S5P_CEC_IRQ_TX_ERROR;
writeb(reg, cec->reg + S5P_CEC_IRQ_MASK);
}
void s5p_cec_reset(struct s5p_cec_dev *cec)
{
u8 reg;
writeb(S5P_CEC_RX_CTRL_RESET, cec->reg + S5P_CEC_RX_CTRL);
writeb(S5P_CEC_TX_CTRL_RESET, cec->reg + S5P_CEC_TX_CTRL);
reg = readb(cec->reg + 0xc4);
reg &= ~0x1;
writeb(reg, cec->reg + 0xc4);
}
void s5p_cec_tx_reset(struct s5p_cec_dev *cec)
{
writeb(S5P_CEC_TX_CTRL_RESET, cec->reg + S5P_CEC_TX_CTRL);
}
void s5p_cec_rx_reset(struct s5p_cec_dev *cec)
{
u8 reg;
writeb(S5P_CEC_RX_CTRL_RESET, cec->reg + S5P_CEC_RX_CTRL);
reg = readb(cec->reg + 0xc4);
reg &= ~0x1;
writeb(reg, cec->reg + 0xc4);
}
void s5p_cec_threshold(struct s5p_cec_dev *cec)
{
writeb(CEC_FILTER_THRESHOLD, cec->reg + S5P_CEC_RX_FILTER_TH);
writeb(0, cec->reg + S5P_CEC_RX_FILTER_CTRL);
}
void s5p_cec_copy_packet(struct s5p_cec_dev *cec, char *data,
size_t count, u8 retries)
{
int i = 0;
u8 reg;
while (i < count) {
writeb(data[i], cec->reg + (S5P_CEC_TX_BUFF0 + (i * 4)));
i++;
}
writeb(count, cec->reg + S5P_CEC_TX_BYTES);
reg = readb(cec->reg + S5P_CEC_TX_CTRL);
reg |= S5P_CEC_TX_CTRL_START;
reg &= ~0x70;
reg |= retries << 4;
if ((data[0] & CEC_MESSAGE_BROADCAST_MASK) == CEC_MESSAGE_BROADCAST) {
dev_dbg(cec->dev, "Broadcast");
reg |= S5P_CEC_TX_CTRL_BCAST;
} else {
dev_dbg(cec->dev, "No Broadcast");
reg &= ~S5P_CEC_TX_CTRL_BCAST;
}
writeb(reg, cec->reg + S5P_CEC_TX_CTRL);
dev_dbg(cec->dev, "cec-tx: cec count (%zu): %*ph", count,
(int)count, data);
}
void s5p_cec_set_addr(struct s5p_cec_dev *cec, u32 addr)
{
writeb(addr & 0x0F, cec->reg + S5P_CEC_LOGIC_ADDR);
}
u32 s5p_cec_get_status(struct s5p_cec_dev *cec)
{
u32 status = 0;
status = readb(cec->reg + S5P_CEC_STATUS_0) & 0xf;
status |= (readb(cec->reg + S5P_CEC_TX_STAT1) & 0xf) << 4;
status |= readb(cec->reg + S5P_CEC_STATUS_1) << 8;
status |= readb(cec->reg + S5P_CEC_STATUS_2) << 16;
status |= readb(cec->reg + S5P_CEC_STATUS_3) << 24;
dev_dbg(cec->dev, "status = 0x%x!\n", status);
return status;
}
void s5p_clr_pending_tx(struct s5p_cec_dev *cec)
{
writeb(S5P_CEC_IRQ_TX_DONE | S5P_CEC_IRQ_TX_ERROR,
cec->reg + S5P_CEC_IRQ_CLEAR);
}
void s5p_clr_pending_rx(struct s5p_cec_dev *cec)
{
writeb(S5P_CEC_IRQ_RX_DONE | S5P_CEC_IRQ_RX_ERROR,
cec->reg + S5P_CEC_IRQ_CLEAR);
}
void s5p_cec_get_rx_buf(struct s5p_cec_dev *cec, u32 size, u8 *buffer)
{
u32 i = 0;
char debug[40];
while (i < size) {
buffer[i] = readb(cec->reg + S5P_CEC_RX_BUFF0 + (i * 4));
sprintf(debug + i * 2, "%02x ", buffer[i]);
i++;
}
dev_dbg(cec->dev, "cec-rx: cec size(%d): %s", size, debug);
}
@@ -0,0 +1,93 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* drivers/media/platform/s5p-cec/regs-cec.h
*
* Copyright (c) 2010 Samsung Electronics
* http://www.samsung.com/
*
* register header file for Samsung TVOUT driver
*/
#ifndef __EXYNOS_REGS__H
#define __EXYNOS_REGS__H
/*
* Register part
*/
#define S5P_CEC_STATUS_0 (0x0000)
#define S5P_CEC_STATUS_1 (0x0004)
#define S5P_CEC_STATUS_2 (0x0008)
#define S5P_CEC_STATUS_3 (0x000C)
#define S5P_CEC_IRQ_MASK (0x0010)
#define S5P_CEC_IRQ_CLEAR (0x0014)
#define S5P_CEC_LOGIC_ADDR (0x0020)
#define S5P_CEC_DIVISOR_0 (0x0030)
#define S5P_CEC_DIVISOR_1 (0x0034)
#define S5P_CEC_DIVISOR_2 (0x0038)
#define S5P_CEC_DIVISOR_3 (0x003C)
#define S5P_CEC_TX_CTRL (0x0040)
#define S5P_CEC_TX_BYTES (0x0044)
#define S5P_CEC_TX_STAT0 (0x0060)
#define S5P_CEC_TX_STAT1 (0x0064)
#define S5P_CEC_TX_BUFF0 (0x0080)
#define S5P_CEC_TX_BUFF1 (0x0084)
#define S5P_CEC_TX_BUFF2 (0x0088)
#define S5P_CEC_TX_BUFF3 (0x008C)
#define S5P_CEC_TX_BUFF4 (0x0090)
#define S5P_CEC_TX_BUFF5 (0x0094)
#define S5P_CEC_TX_BUFF6 (0x0098)
#define S5P_CEC_TX_BUFF7 (0x009C)
#define S5P_CEC_TX_BUFF8 (0x00A0)
#define S5P_CEC_TX_BUFF9 (0x00A4)
#define S5P_CEC_TX_BUFF10 (0x00A8)
#define S5P_CEC_TX_BUFF11 (0x00AC)
#define S5P_CEC_TX_BUFF12 (0x00B0)
#define S5P_CEC_TX_BUFF13 (0x00B4)
#define S5P_CEC_TX_BUFF14 (0x00B8)
#define S5P_CEC_TX_BUFF15 (0x00BC)
#define S5P_CEC_RX_CTRL (0x00C0)
#define S5P_CEC_RX_STAT0 (0x00E0)
#define S5P_CEC_RX_STAT1 (0x00E4)
#define S5P_CEC_RX_BUFF0 (0x0100)
#define S5P_CEC_RX_BUFF1 (0x0104)
#define S5P_CEC_RX_BUFF2 (0x0108)
#define S5P_CEC_RX_BUFF3 (0x010C)
#define S5P_CEC_RX_BUFF4 (0x0110)
#define S5P_CEC_RX_BUFF5 (0x0114)
#define S5P_CEC_RX_BUFF6 (0x0118)
#define S5P_CEC_RX_BUFF7 (0x011C)
#define S5P_CEC_RX_BUFF8 (0x0120)
#define S5P_CEC_RX_BUFF9 (0x0124)
#define S5P_CEC_RX_BUFF10 (0x0128)
#define S5P_CEC_RX_BUFF11 (0x012C)
#define S5P_CEC_RX_BUFF12 (0x0130)
#define S5P_CEC_RX_BUFF13 (0x0134)
#define S5P_CEC_RX_BUFF14 (0x0138)
#define S5P_CEC_RX_BUFF15 (0x013C)
#define S5P_CEC_RX_FILTER_CTRL (0x0180)
#define S5P_CEC_RX_FILTER_TH (0x0184)
/*
* Bit definition part
*/
#define S5P_CEC_IRQ_TX_DONE (1<<0)
#define S5P_CEC_IRQ_TX_ERROR (1<<1)
#define S5P_CEC_IRQ_RX_DONE (1<<4)
#define S5P_CEC_IRQ_RX_ERROR (1<<5)
#define S5P_CEC_TX_CTRL_START (1<<0)
#define S5P_CEC_TX_CTRL_BCAST (1<<1)
#define S5P_CEC_TX_CTRL_RETRY (0x04<<4)
#define S5P_CEC_TX_CTRL_RESET (1<<7)
#define S5P_CEC_RX_CTRL_ENABLE (1<<0)
#define S5P_CEC_RX_CTRL_RESET (1<<7)
#define S5P_CEC_LOGIC_ADDR_MASK (0xF)
/* PMU Registers for PHY */
#define EXYNOS_HDMI_PHY_CONTROL 0x700
#endif /* __EXYNOS_REGS__H */
@@ -0,0 +1,309 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/* drivers/media/platform/s5p-cec/s5p_cec.c
*
* Samsung S5P CEC driver
*
* Copyright (c) 2014 Samsung Electronics Co., Ltd.
*
* This driver is based on the "cec interface driver for exynos soc" by
* SangPil Moon.
*/
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/timer.h>
#include <linux/workqueue.h>
#include <media/cec.h>
#include <media/cec-notifier.h>
#include "exynos_hdmi_cec.h"
#include "regs-cec.h"
#include "s5p_cec.h"
#define CEC_NAME "s5p-cec"
static int debug;
module_param(debug, int, 0644);
MODULE_PARM_DESC(debug, "debug level (0-2)");
static int s5p_cec_adap_enable(struct cec_adapter *adap, bool enable)
{
int ret;
struct s5p_cec_dev *cec = cec_get_drvdata(adap);
if (enable) {
ret = pm_runtime_resume_and_get(cec->dev);
if (ret < 0)
return ret;
s5p_cec_reset(cec);
s5p_cec_set_divider(cec);
s5p_cec_threshold(cec);
s5p_cec_unmask_tx_interrupts(cec);
s5p_cec_unmask_rx_interrupts(cec);
s5p_cec_enable_rx(cec);
} else {
s5p_cec_mask_tx_interrupts(cec);
s5p_cec_mask_rx_interrupts(cec);
pm_runtime_put(cec->dev);
}
return 0;
}
static int s5p_cec_adap_log_addr(struct cec_adapter *adap, u8 addr)
{
struct s5p_cec_dev *cec = cec_get_drvdata(adap);
s5p_cec_set_addr(cec, addr);
return 0;
}
static int s5p_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
u32 signal_free_time, struct cec_msg *msg)
{
struct s5p_cec_dev *cec = cec_get_drvdata(adap);
/*
* Unclear if 0 retries are allowed by the hardware, so have 1 as
* the minimum.
*/
s5p_cec_copy_packet(cec, msg->msg, msg->len, max(1, attempts - 1));
return 0;
}
static irqreturn_t s5p_cec_irq_handler(int irq, void *priv)
{
struct s5p_cec_dev *cec = priv;
u32 status = 0;
status = s5p_cec_get_status(cec);
dev_dbg(cec->dev, "irq received\n");
if (status & CEC_STATUS_TX_DONE) {
if (status & CEC_STATUS_TX_NACK) {
dev_dbg(cec->dev, "CEC_STATUS_TX_NACK set\n");
cec->tx = STATE_NACK;
} else if (status & CEC_STATUS_TX_ERROR) {
dev_dbg(cec->dev, "CEC_STATUS_TX_ERROR set\n");
cec->tx = STATE_ERROR;
} else {
dev_dbg(cec->dev, "CEC_STATUS_TX_DONE\n");
cec->tx = STATE_DONE;
}
s5p_clr_pending_tx(cec);
}
if (status & CEC_STATUS_RX_DONE) {
if (status & CEC_STATUS_RX_ERROR) {
dev_dbg(cec->dev, "CEC_STATUS_RX_ERROR set\n");
s5p_cec_rx_reset(cec);
s5p_cec_enable_rx(cec);
} else {
dev_dbg(cec->dev, "CEC_STATUS_RX_DONE set\n");
if (cec->rx != STATE_IDLE)
dev_dbg(cec->dev, "Buffer overrun (worker did not process previous message)\n");
cec->rx = STATE_BUSY;
cec->msg.len = status >> 24;
if (cec->msg.len > CEC_MAX_MSG_SIZE)
cec->msg.len = CEC_MAX_MSG_SIZE;
cec->msg.rx_status = CEC_RX_STATUS_OK;
s5p_cec_get_rx_buf(cec, cec->msg.len,
cec->msg.msg);
cec->rx = STATE_DONE;
s5p_cec_enable_rx(cec);
}
/* Clear interrupt pending bit */
s5p_clr_pending_rx(cec);
}
return IRQ_WAKE_THREAD;
}
static irqreturn_t s5p_cec_irq_handler_thread(int irq, void *priv)
{
struct s5p_cec_dev *cec = priv;
dev_dbg(cec->dev, "irq processing thread\n");
switch (cec->tx) {
case STATE_DONE:
cec_transmit_done(cec->adap, CEC_TX_STATUS_OK, 0, 0, 0, 0);
cec->tx = STATE_IDLE;
break;
case STATE_NACK:
cec_transmit_done(cec->adap,
CEC_TX_STATUS_MAX_RETRIES | CEC_TX_STATUS_NACK,
0, 1, 0, 0);
cec->tx = STATE_IDLE;
break;
case STATE_ERROR:
cec_transmit_done(cec->adap,
CEC_TX_STATUS_MAX_RETRIES | CEC_TX_STATUS_ERROR,
0, 0, 0, 1);
cec->tx = STATE_IDLE;
break;
case STATE_BUSY:
dev_err(cec->dev, "state set to busy, this should not occur here\n");
break;
default:
break;
}
switch (cec->rx) {
case STATE_DONE:
cec_received_msg(cec->adap, &cec->msg);
cec->rx = STATE_IDLE;
break;
default:
break;
}
return IRQ_HANDLED;
}
static const struct cec_adap_ops s5p_cec_adap_ops = {
.adap_enable = s5p_cec_adap_enable,
.adap_log_addr = s5p_cec_adap_log_addr,
.adap_transmit = s5p_cec_adap_transmit,
};
static int s5p_cec_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device *hdmi_dev;
struct s5p_cec_dev *cec;
bool needs_hpd = of_property_read_bool(pdev->dev.of_node, "needs-hpd");
int ret;
hdmi_dev = cec_notifier_parse_hdmi_phandle(dev);
if (IS_ERR(hdmi_dev))
return PTR_ERR(hdmi_dev);
cec = devm_kzalloc(&pdev->dev, sizeof(*cec), GFP_KERNEL);
if (!cec)
return -ENOMEM;
cec->dev = dev;
cec->irq = platform_get_irq(pdev, 0);
if (cec->irq < 0)
return cec->irq;
ret = devm_request_threaded_irq(dev, cec->irq, s5p_cec_irq_handler,
s5p_cec_irq_handler_thread, 0, pdev->name, cec);
if (ret)
return ret;
cec->clk = devm_clk_get(dev, "hdmicec");
if (IS_ERR(cec->clk))
return PTR_ERR(cec->clk);
cec->pmu = syscon_regmap_lookup_by_phandle(dev->of_node,
"samsung,syscon-phandle");
if (IS_ERR(cec->pmu))
return -EPROBE_DEFER;
cec->reg = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(cec->reg))
return PTR_ERR(cec->reg);
cec->adap = cec_allocate_adapter(&s5p_cec_adap_ops, cec, CEC_NAME,
CEC_CAP_DEFAULTS | (needs_hpd ? CEC_CAP_NEEDS_HPD : 0) |
CEC_CAP_CONNECTOR_INFO, 1);
ret = PTR_ERR_OR_ZERO(cec->adap);
if (ret)
return ret;
cec->notifier = cec_notifier_cec_adap_register(hdmi_dev, NULL,
cec->adap);
if (!cec->notifier) {
ret = -ENOMEM;
goto err_delete_adapter;
}
ret = cec_register_adapter(cec->adap, &pdev->dev);
if (ret)
goto err_notifier;
platform_set_drvdata(pdev, cec);
pm_runtime_enable(dev);
dev_dbg(dev, "successfully probed\n");
return 0;
err_notifier:
cec_notifier_cec_adap_unregister(cec->notifier, cec->adap);
err_delete_adapter:
cec_delete_adapter(cec->adap);
return ret;
}
static void s5p_cec_remove(struct platform_device *pdev)
{
struct s5p_cec_dev *cec = platform_get_drvdata(pdev);
cec_notifier_cec_adap_unregister(cec->notifier, cec->adap);
cec_unregister_adapter(cec->adap);
pm_runtime_disable(&pdev->dev);
}
static int __maybe_unused s5p_cec_runtime_suspend(struct device *dev)
{
struct s5p_cec_dev *cec = dev_get_drvdata(dev);
clk_disable_unprepare(cec->clk);
return 0;
}
static int __maybe_unused s5p_cec_runtime_resume(struct device *dev)
{
struct s5p_cec_dev *cec = dev_get_drvdata(dev);
int ret;
ret = clk_prepare_enable(cec->clk);
if (ret < 0)
return ret;
return 0;
}
static const struct dev_pm_ops s5p_cec_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
pm_runtime_force_resume)
SET_RUNTIME_PM_OPS(s5p_cec_runtime_suspend, s5p_cec_runtime_resume,
NULL)
};
static const struct of_device_id s5p_cec_match[] = {
{
.compatible = "samsung,s5p-cec",
},
{},
};
MODULE_DEVICE_TABLE(of, s5p_cec_match);
static struct platform_driver s5p_cec_pdrv = {
.probe = s5p_cec_probe,
.remove = s5p_cec_remove,
.driver = {
.name = CEC_NAME,
.of_match_table = s5p_cec_match,
.pm = &s5p_cec_pm_ops,
},
};
module_platform_driver(s5p_cec_pdrv);
MODULE_AUTHOR("Kamil Debski <kamil@wypas.org>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Samsung S5P CEC driver");
@@ -0,0 +1,76 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/* drivers/media/platform/s5p-cec/s5p_cec.h
*
* Samsung S5P HDMI CEC driver
*
* Copyright (c) 2014 Samsung Electronics Co., Ltd.
*/
#ifndef _S5P_CEC_H_
#define _S5P_CEC_H_ __FILE__
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/timer.h>
#include <linux/workqueue.h>
#include <media/cec.h>
#include "exynos_hdmi_cec.h"
#include "regs-cec.h"
#include "s5p_cec.h"
#define CEC_NAME "s5p-cec"
#define CEC_STATUS_TX_RUNNING (1 << 0)
#define CEC_STATUS_TX_TRANSFERRING (1 << 1)
#define CEC_STATUS_TX_DONE (1 << 2)
#define CEC_STATUS_TX_ERROR (1 << 3)
#define CEC_STATUS_TX_NACK (1 << 4)
#define CEC_STATUS_TX_BYTES (0xFF << 8)
#define CEC_STATUS_RX_RUNNING (1 << 16)
#define CEC_STATUS_RX_RECEIVING (1 << 17)
#define CEC_STATUS_RX_DONE (1 << 18)
#define CEC_STATUS_RX_ERROR (1 << 19)
#define CEC_STATUS_RX_BCAST (1 << 20)
#define CEC_STATUS_RX_BYTES (0xFF << 24)
#define CEC_WORKER_TX_DONE (1 << 0)
#define CEC_WORKER_RX_MSG (1 << 1)
/* CEC Rx buffer size */
#define CEC_RX_BUFF_SIZE 16
/* CEC Tx buffer size */
#define CEC_TX_BUFF_SIZE 16
enum cec_state {
STATE_IDLE,
STATE_BUSY,
STATE_DONE,
STATE_NACK,
STATE_ERROR
};
struct cec_notifier;
struct s5p_cec_dev {
struct cec_adapter *adap;
struct clk *clk;
struct device *dev;
struct mutex lock;
struct regmap *pmu;
struct cec_notifier *notifier;
int irq;
void __iomem *reg;
enum cec_state rx;
enum cec_state tx;
struct cec_msg msg;
};
#endif /* _S5P_CEC_H_ */
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_CEC_SECO) += seco-cec.o
@@ -0,0 +1,792 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* CEC driver for SECO X86 Boards
*
* Author: Ettore Chimenti <ek5.chimenti@gmail.com>
* Copyright (C) 2018, SECO SpA.
* Copyright (C) 2018, Aidilab Srl.
*/
#include <linux/module.h>
#include <linux/acpi.h>
#include <linux/delay.h>
#include <linux/dmi.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
/* CEC Framework */
#include <media/cec-notifier.h>
#include "seco-cec.h"
struct secocec_data {
struct device *dev;
struct platform_device *pdev;
struct cec_adapter *cec_adap;
struct cec_notifier *notifier;
struct rc_dev *ir;
char ir_input_phys[32];
int irq;
};
#define smb_wr16(cmd, data) smb_word_op(SECOCEC_MICRO_ADDRESS, \
cmd, data, SMBUS_WRITE, NULL)
#define smb_rd16(cmd, res) smb_word_op(SECOCEC_MICRO_ADDRESS, \
cmd, 0, SMBUS_READ, res)
static int smb_word_op(u16 slave_addr, u8 cmd, u16 data,
u8 operation, u16 *result)
{
unsigned int count;
int status = 0;
/* Active wait until ready */
for (count = 0; count <= SMBTIMEOUT; ++count) {
if (!(inb(HSTS) & BRA_INUSE_STS))
break;
udelay(SMB_POLL_UDELAY);
}
if (count > SMBTIMEOUT)
/* Reset the lock instead of failing */
outb(0xff, HSTS);
outb(0x00, HCNT);
outb((u8)(slave_addr & 0xfe) | operation, XMIT_SLVA);
outb(cmd, HCMD);
inb(HCNT);
if (operation == SMBUS_WRITE) {
outb((u8)data, HDAT0);
outb((u8)(data >> 8), HDAT1);
}
outb(BRA_START + BRA_SMB_CMD_WORD_DATA, HCNT);
for (count = 0; count <= SMBTIMEOUT; count++) {
if (!(inb(HSTS) & BRA_HOST_BUSY))
break;
udelay(SMB_POLL_UDELAY);
}
if (count > SMBTIMEOUT) {
status = -EBUSY;
goto err;
}
if (inb(HSTS) & BRA_HSTS_ERR_MASK) {
status = -EIO;
goto err;
}
if (operation == SMBUS_READ)
*result = ((inb(HDAT0) & 0xff) + ((inb(HDAT1) & 0xff) << 8));
err:
outb(0xff, HSTS);
return status;
}
static int secocec_adap_enable(struct cec_adapter *adap, bool enable)
{
struct secocec_data *cec = cec_get_drvdata(adap);
struct device *dev = cec->dev;
u16 val = 0;
int status;
if (enable) {
/* Clear the status register */
status = smb_rd16(SECOCEC_STATUS_REG_1, &val);
if (status)
goto err;
status = smb_wr16(SECOCEC_STATUS_REG_1, val);
if (status)
goto err;
/* Enable the interrupts */
status = smb_rd16(SECOCEC_ENABLE_REG_1, &val);
if (status)
goto err;
status = smb_wr16(SECOCEC_ENABLE_REG_1,
val | SECOCEC_ENABLE_REG_1_CEC);
if (status)
goto err;
dev_dbg(dev, "Device enabled\n");
} else {
/* Clear the status register */
status = smb_rd16(SECOCEC_STATUS_REG_1, &val);
status = smb_wr16(SECOCEC_STATUS_REG_1, val);
/* Disable the interrupts */
status = smb_rd16(SECOCEC_ENABLE_REG_1, &val);
status = smb_wr16(SECOCEC_ENABLE_REG_1, val &
~SECOCEC_ENABLE_REG_1_CEC &
~SECOCEC_ENABLE_REG_1_IR);
dev_dbg(dev, "Device disabled\n");
}
return 0;
err:
return status;
}
static int secocec_adap_log_addr(struct cec_adapter *adap, u8 logical_addr)
{
u16 enable_val = 0;
int status;
/* Disable device */
status = smb_rd16(SECOCEC_ENABLE_REG_1, &enable_val);
if (status)
return status;
status = smb_wr16(SECOCEC_ENABLE_REG_1,
enable_val & ~SECOCEC_ENABLE_REG_1_CEC);
if (status)
return status;
/* Write logical address
* NOTE: CEC_LOG_ADDR_INVALID is mapped to the 'Unregistered' LA
*/
status = smb_wr16(SECOCEC_DEVICE_LA, logical_addr & 0xf);
if (status)
return status;
/* Re-enable device */
status = smb_wr16(SECOCEC_ENABLE_REG_1,
enable_val | SECOCEC_ENABLE_REG_1_CEC);
if (status)
return status;
return 0;
}
static int secocec_adap_transmit(struct cec_adapter *adap, u8 attempts,
u32 signal_free_time, struct cec_msg *msg)
{
u16 payload_len, payload_id_len, destination, val = 0;
u8 *payload_msg;
int status;
u8 i;
/* Device msg len already accounts for header */
payload_id_len = msg->len - 1;
/* Send data length */
status = smb_wr16(SECOCEC_WRITE_DATA_LENGTH, payload_id_len);
if (status)
goto err;
/* Send Operation ID if present */
if (payload_id_len > 0) {
status = smb_wr16(SECOCEC_WRITE_OPERATION_ID, msg->msg[1]);
if (status)
goto err;
}
/* Send data if present */
if (payload_id_len > 1) {
/* Only data; */
payload_len = msg->len - 2;
payload_msg = &msg->msg[2];
/* Copy message into registers */
for (i = 0; i < payload_len; i += 2) {
/* hi byte */
val = payload_msg[i + 1] << 8;
/* lo byte */
val |= payload_msg[i];
status = smb_wr16(SECOCEC_WRITE_DATA_00 + i / 2, val);
if (status)
goto err;
}
}
/* Send msg source/destination and fire msg */
destination = msg->msg[0];
status = smb_wr16(SECOCEC_WRITE_BYTE0, destination);
if (status)
goto err;
return 0;
err:
return status;
}
static void secocec_tx_done(struct cec_adapter *adap, u16 status_val)
{
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);
}
/* Reset status reg */
status_val = SECOCEC_STATUS_TX_ERROR_MASK |
SECOCEC_STATUS_MSG_SENT_MASK |
SECOCEC_STATUS_TX_NACK_ERROR;
smb_wr16(SECOCEC_STATUS, status_val);
}
static void secocec_rx_done(struct cec_adapter *adap, u16 status_val)
{
struct secocec_data *cec = cec_get_drvdata(adap);
struct device *dev = cec->dev;
struct cec_msg msg = { };
bool flag_overflow = false;
u8 payload_len, i = 0;
u8 *payload_msg;
u16 val = 0;
int status;
if (status_val & SECOCEC_STATUS_RX_OVERFLOW_MASK) {
/* NOTE: Untested, it also might not be necessary */
dev_warn(dev, "Received more than 16 bytes. Discarding\n");
flag_overflow = true;
}
if (status_val & SECOCEC_STATUS_RX_ERROR_MASK) {
dev_warn(dev, "Message received with errors. Discarding\n");
status = -EIO;
goto rxerr;
}
/* Read message length */
status = smb_rd16(SECOCEC_READ_DATA_LENGTH, &val);
if (status)
return;
/* Device msg len already accounts for the header */
msg.len = min(val + 1, CEC_MAX_MSG_SIZE);
/* Read logical address */
status = smb_rd16(SECOCEC_READ_BYTE0, &val);
if (status)
return;
/* device stores source LA and destination */
msg.msg[0] = val;
/* Read operation ID */
status = smb_rd16(SECOCEC_READ_OPERATION_ID, &val);
if (status)
return;
msg.msg[1] = val;
/* Read data if present */
if (msg.len > 1) {
payload_len = msg.len - 2;
payload_msg = &msg.msg[2];
/* device stores 2 bytes in every 16-bit val */
for (i = 0; i < payload_len; i += 2) {
status = smb_rd16(SECOCEC_READ_DATA_00 + i / 2, &val);
if (status)
return;
/* low byte, skipping header */
payload_msg[i] = val & 0x00ff;
/* hi byte */
payload_msg[i + 1] = (val & 0xff00) >> 8;
}
}
cec_received_msg(cec->cec_adap, &msg);
/* Reset status reg */
status_val = SECOCEC_STATUS_MSG_RECEIVED_MASK;
if (flag_overflow)
status_val |= SECOCEC_STATUS_RX_OVERFLOW_MASK;
status = smb_wr16(SECOCEC_STATUS, status_val);
return;
rxerr:
/* Reset error reg */
status_val = SECOCEC_STATUS_MSG_RECEIVED_MASK |
SECOCEC_STATUS_RX_ERROR_MASK;
if (flag_overflow)
status_val |= SECOCEC_STATUS_RX_OVERFLOW_MASK;
smb_wr16(SECOCEC_STATUS, status_val);
}
static const struct cec_adap_ops secocec_cec_adap_ops = {
/* Low-level callbacks */
.adap_enable = secocec_adap_enable,
.adap_log_addr = secocec_adap_log_addr,
.adap_transmit = secocec_adap_transmit,
};
#ifdef CONFIG_CEC_SECO_RC
static int secocec_ir_probe(void *priv)
{
struct secocec_data *cec = priv;
struct device *dev = cec->dev;
int status;
u16 val;
/* Prepare the RC input device */
cec->ir = devm_rc_allocate_device(dev, RC_DRIVER_SCANCODE);
if (!cec->ir)
return -ENOMEM;
snprintf(cec->ir_input_phys, sizeof(cec->ir_input_phys),
"%s/input0", dev_name(dev));
cec->ir->device_name = dev_name(dev);
cec->ir->input_phys = cec->ir_input_phys;
cec->ir->input_id.bustype = BUS_HOST;
cec->ir->input_id.vendor = 0;
cec->ir->input_id.product = 0;
cec->ir->input_id.version = 1;
cec->ir->driver_name = SECOCEC_DEV_NAME;
cec->ir->allowed_protocols = RC_PROTO_BIT_RC5;
cec->ir->priv = cec;
cec->ir->map_name = RC_MAP_HAUPPAUGE;
cec->ir->timeout = MS_TO_US(100);
/* Clear the status register */
status = smb_rd16(SECOCEC_STATUS_REG_1, &val);
if (status != 0)
goto err;
status = smb_wr16(SECOCEC_STATUS_REG_1, val);
if (status != 0)
goto err;
/* Enable the interrupts */
status = smb_rd16(SECOCEC_ENABLE_REG_1, &val);
if (status != 0)
goto err;
status = smb_wr16(SECOCEC_ENABLE_REG_1,
val | SECOCEC_ENABLE_REG_1_IR);
if (status != 0)
goto err;
dev_dbg(dev, "IR enabled\n");
status = devm_rc_register_device(dev, cec->ir);
if (status) {
dev_err(dev, "Failed to prepare input device\n");
cec->ir = NULL;
goto err;
}
return 0;
err:
smb_rd16(SECOCEC_ENABLE_REG_1, &val);
smb_wr16(SECOCEC_ENABLE_REG_1,
val & ~SECOCEC_ENABLE_REG_1_IR);
dev_dbg(dev, "IR disabled\n");
return status;
}
static int secocec_ir_rx(struct secocec_data *priv)
{
struct secocec_data *cec = priv;
struct device *dev = cec->dev;
u16 val, status, key, addr, toggle;
if (!cec->ir)
return -ENODEV;
status = smb_rd16(SECOCEC_IR_READ_DATA, &val);
if (status != 0)
goto err;
key = val & SECOCEC_IR_COMMAND_MASK;
addr = (val & SECOCEC_IR_ADDRESS_MASK) >> SECOCEC_IR_ADDRESS_SHL;
toggle = (val & SECOCEC_IR_TOGGLE_MASK) >> SECOCEC_IR_TOGGLE_SHL;
rc_keydown(cec->ir, RC_PROTO_RC5, RC_SCANCODE_RC5(addr, key), toggle);
dev_dbg(dev, "IR key pressed: 0x%02x addr 0x%02x toggle 0x%02x\n", key,
addr, toggle);
return 0;
err:
dev_err(dev, "IR Receive message failed (%d)\n", status);
return -EIO;
}
#else
static void secocec_ir_rx(struct secocec_data *priv)
{
}
static int secocec_ir_probe(void *priv)
{
return 0;
}
#endif
static irqreturn_t secocec_irq_handler(int irq, void *priv)
{
struct secocec_data *cec = priv;
struct device *dev = cec->dev;
u16 status_val, cec_val, val = 0;
int status;
/* Read status register */
status = smb_rd16(SECOCEC_STATUS_REG_1, &status_val);
if (status)
goto err;
if (status_val & SECOCEC_STATUS_REG_1_CEC) {
/* Read CEC status register */
status = smb_rd16(SECOCEC_STATUS, &cec_val);
if (status)
goto err;
if (cec_val & SECOCEC_STATUS_MSG_RECEIVED_MASK)
secocec_rx_done(cec->cec_adap, cec_val);
if (cec_val & SECOCEC_STATUS_MSG_SENT_MASK)
secocec_tx_done(cec->cec_adap, cec_val);
if ((~cec_val & SECOCEC_STATUS_MSG_SENT_MASK) &&
(~cec_val & SECOCEC_STATUS_MSG_RECEIVED_MASK))
dev_warn_once(dev,
"Message not received or sent, but interrupt fired");
val = SECOCEC_STATUS_REG_1_CEC;
}
if (status_val & SECOCEC_STATUS_REG_1_IR) {
val |= SECOCEC_STATUS_REG_1_IR;
secocec_ir_rx(cec);
}
/* Reset status register */
status = smb_wr16(SECOCEC_STATUS_REG_1, val);
if (status)
goto err;
return IRQ_HANDLED;
err:
dev_err_once(dev, "IRQ: R/W SMBus operation failed %d\n", status);
/* Reset status register */
val = SECOCEC_STATUS_REG_1_CEC | SECOCEC_STATUS_REG_1_IR;
smb_wr16(SECOCEC_STATUS_REG_1, val);
return IRQ_HANDLED;
}
struct cec_dmi_match {
const char *sys_vendor;
const char *product_name;
const char *devname;
const char *conn;
};
static const struct cec_dmi_match secocec_dmi_match_table[] = {
/* UDOO X86 */
{ "SECO", "UDOO x86", "0000:00:02.0", "Port B" },
};
static struct device *secocec_cec_find_hdmi_dev(struct device *dev,
const char **conn)
{
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);
*conn = m->conn;
return d;
}
}
return ERR_PTR(-EINVAL);
}
static int secocec_acpi_probe(struct secocec_data *sdev)
{
struct device *dev = sdev->dev;
struct gpio_desc *gpio;
int irq = 0;
gpio = devm_gpiod_get(dev, NULL, GPIOD_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;
return 0;
}
static int secocec_probe(struct platform_device *pdev)
{
struct secocec_data *secocec;
struct device *dev = &pdev->dev;
struct device *hdmi_dev;
const char *conn = NULL;
int ret;
u16 val;
hdmi_dev = secocec_cec_find_hdmi_dev(&pdev->dev, &conn);
if (IS_ERR(hdmi_dev))
return PTR_ERR(hdmi_dev);
secocec = devm_kzalloc(dev, sizeof(*secocec), GFP_KERNEL);
if (!secocec)
return -ENOMEM;
dev_set_drvdata(dev, secocec);
/* Request SMBus regions */
if (!request_muxed_region(BRA_SMB_BASE_ADDR, 7, "CEC00001")) {
dev_err(dev, "Request memory region failed\n");
return -ENXIO;
}
secocec->pdev = pdev;
secocec->dev = dev;
if (!has_acpi_companion(dev)) {
dev_dbg(dev, "Cannot find any ACPI companion\n");
ret = -ENODEV;
goto err;
}
ret = secocec_acpi_probe(secocec);
if (ret) {
dev_err(dev, "Cannot assign gpio to IRQ\n");
ret = -ENODEV;
goto err;
}
/* Firmware version check */
ret = smb_rd16(SECOCEC_VERSION, &val);
if (ret) {
dev_err(dev, "Cannot check fw version\n");
goto err;
}
if (val < SECOCEC_LATEST_FW) {
dev_err(dev, "CEC Firmware not supported (v.%04x). Use ver > v.%04x\n",
val, SECOCEC_LATEST_FW);
ret = -EINVAL;
goto err;
}
ret = devm_request_threaded_irq(dev,
secocec->irq,
NULL,
secocec_irq_handler,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
dev_name(&pdev->dev), secocec);
if (ret) {
dev_err(dev, "Cannot request IRQ %d\n", secocec->irq);
ret = -EIO;
goto err;
}
/* Allocate CEC adapter */
secocec->cec_adap = cec_allocate_adapter(&secocec_cec_adap_ops,
secocec,
dev_name(dev),
CEC_CAP_DEFAULTS |
CEC_CAP_CONNECTOR_INFO,
SECOCEC_MAX_ADDRS);
if (IS_ERR(secocec->cec_adap)) {
ret = PTR_ERR(secocec->cec_adap);
goto err;
}
secocec->notifier = cec_notifier_cec_adap_register(hdmi_dev, conn,
secocec->cec_adap);
if (!secocec->notifier) {
ret = -ENOMEM;
goto err_delete_adapter;
}
ret = cec_register_adapter(secocec->cec_adap, dev);
if (ret)
goto err_notifier;
ret = secocec_ir_probe(secocec);
if (ret)
goto err_unregister_adapter;
platform_set_drvdata(pdev, secocec);
dev_dbg(dev, "Device registered\n");
return ret;
err_unregister_adapter:
cec_notifier_cec_adap_unregister(secocec->notifier, secocec->cec_adap);
cec_unregister_adapter(secocec->cec_adap);
goto err;
err_notifier:
cec_notifier_cec_adap_unregister(secocec->notifier, secocec->cec_adap);
err_delete_adapter:
cec_delete_adapter(secocec->cec_adap);
err:
release_region(BRA_SMB_BASE_ADDR, 7);
dev_err(dev, "%s device probe failed\n", dev_name(dev));
return ret;
}
static void secocec_remove(struct platform_device *pdev)
{
struct secocec_data *secocec = platform_get_drvdata(pdev);
u16 val;
if (secocec->ir) {
smb_rd16(SECOCEC_ENABLE_REG_1, &val);
smb_wr16(SECOCEC_ENABLE_REG_1, val & ~SECOCEC_ENABLE_REG_1_IR);
dev_dbg(&pdev->dev, "IR disabled\n");
}
cec_notifier_cec_adap_unregister(secocec->notifier, secocec->cec_adap);
cec_unregister_adapter(secocec->cec_adap);
release_region(BRA_SMB_BASE_ADDR, 7);
dev_dbg(&pdev->dev, "CEC device removed\n");
}
#ifdef CONFIG_PM_SLEEP
static int secocec_suspend(struct device *dev)
{
int status;
u16 val;
dev_dbg(dev, "Device going to suspend, disabling\n");
/* Clear the status register */
status = smb_rd16(SECOCEC_STATUS_REG_1, &val);
if (status)
goto err;
status = smb_wr16(SECOCEC_STATUS_REG_1, val);
if (status)
goto err;
/* Disable the interrupts */
status = smb_rd16(SECOCEC_ENABLE_REG_1, &val);
if (status)
goto err;
status = smb_wr16(SECOCEC_ENABLE_REG_1, val &
~SECOCEC_ENABLE_REG_1_CEC & ~SECOCEC_ENABLE_REG_1_IR);
if (status)
goto err;
return 0;
err:
dev_err(dev, "Suspend failed: %d\n", status);
return status;
}
static int secocec_resume(struct device *dev)
{
int status;
u16 val;
dev_dbg(dev, "Resuming device from suspend\n");
/* Clear the status register */
status = smb_rd16(SECOCEC_STATUS_REG_1, &val);
if (status)
goto err;
status = smb_wr16(SECOCEC_STATUS_REG_1, val);
if (status)
goto err;
/* Enable the interrupts */
status = smb_rd16(SECOCEC_ENABLE_REG_1, &val);
if (status)
goto err;
status = smb_wr16(SECOCEC_ENABLE_REG_1, val | SECOCEC_ENABLE_REG_1_CEC);
if (status)
goto err;
dev_dbg(dev, "Device resumed from suspend\n");
return 0;
err:
dev_err(dev, "Resume failed: %d\n", status);
return status;
}
static SIMPLE_DEV_PM_OPS(secocec_pm_ops, secocec_suspend, secocec_resume);
#define SECOCEC_PM_OPS (&secocec_pm_ops)
#else
#define SECOCEC_PM_OPS NULL
#endif
#ifdef CONFIG_ACPI
static const struct acpi_device_id secocec_acpi_match[] = {
{"CEC00001", 0},
{},
};
MODULE_DEVICE_TABLE(acpi, secocec_acpi_match);
#endif
static struct platform_driver secocec_driver = {
.driver = {
.name = SECOCEC_DEV_NAME,
.acpi_match_table = ACPI_PTR(secocec_acpi_match),
.pm = SECOCEC_PM_OPS,
},
.probe = secocec_probe,
.remove = secocec_remove,
};
module_platform_driver(secocec_driver);
MODULE_DESCRIPTION("SECO CEC X86 Driver");
MODULE_AUTHOR("Ettore Chimenti <ek5.chimenti@gmail.com>");
MODULE_LICENSE("Dual BSD/GPL");
@@ -0,0 +1,141 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
/*
* SECO X86 Boards CEC register defines
*
* Author: Ettore Chimenti <ek5.chimenti@gmail.com>
* Copyright (C) 2018, SECO Spa.
* Copyright (C) 2018, Aidilab Srl.
*/
#ifndef __SECO_CEC_H__
#define __SECO_CEC_H__
#define SECOCEC_MAX_ADDRS 1
#define SECOCEC_DEV_NAME "secocec"
#define SECOCEC_LATEST_FW 0x0f0b
#define SMBTIMEOUT 0xfff
#define SMB_POLL_UDELAY 10
#define SMBUS_WRITE 0
#define SMBUS_READ 1
#define CMD_BYTE_DATA 0
#define CMD_WORD_DATA 1
/*
* SMBus definitons for Braswell
*/
#define BRA_DONE_STATUS BIT(7)
#define BRA_INUSE_STS BIT(6)
#define BRA_FAILED_OP BIT(4)
#define BRA_BUS_ERR BIT(3)
#define BRA_DEV_ERR BIT(2)
#define BRA_INTR BIT(1)
#define BRA_HOST_BUSY BIT(0)
#define BRA_HSTS_ERR_MASK (BRA_FAILED_OP | BRA_BUS_ERR | BRA_DEV_ERR)
#define BRA_PEC_EN BIT(7)
#define BRA_START BIT(6)
#define BRA_LAST__BYTE BIT(5)
#define BRA_INTREN BIT(0)
#define BRA_SMB_CMD (7 << 2)
#define BRA_SMB_CMD_QUICK (0 << 2)
#define BRA_SMB_CMD_BYTE (1 << 2)
#define BRA_SMB_CMD_BYTE_DATA (2 << 2)
#define BRA_SMB_CMD_WORD_DATA (3 << 2)
#define BRA_SMB_CMD_PROCESS_CALL (4 << 2)
#define BRA_SMB_CMD_BLOCK (5 << 2)
#define BRA_SMB_CMD_I2CREAD (6 << 2)
#define BRA_SMB_CMD_BLOCK_PROCESS (7 << 2)
#define BRA_SMB_BASE_ADDR 0x2040
#define HSTS (BRA_SMB_BASE_ADDR + 0)
#define HCNT (BRA_SMB_BASE_ADDR + 2)
#define HCMD (BRA_SMB_BASE_ADDR + 3)
#define XMIT_SLVA (BRA_SMB_BASE_ADDR + 4)
#define HDAT0 (BRA_SMB_BASE_ADDR + 5)
#define HDAT1 (BRA_SMB_BASE_ADDR + 6)
/*
* Microcontroller Address
*/
#define SECOCEC_MICRO_ADDRESS 0x40
/*
* STM32 SMBus Registers
*/
#define SECOCEC_VERSION 0x00
#define SECOCEC_ENABLE_REG_1 0x01
#define SECOCEC_ENABLE_REG_2 0x02
#define SECOCEC_STATUS_REG_1 0x03
#define SECOCEC_STATUS_REG_2 0x04
#define SECOCEC_STATUS 0x28
#define SECOCEC_DEVICE_LA 0x29
#define SECOCEC_READ_OPERATION_ID 0x2a
#define SECOCEC_READ_DATA_LENGTH 0x2b
#define SECOCEC_READ_DATA_00 0x2c
#define SECOCEC_READ_DATA_02 0x2d
#define SECOCEC_READ_DATA_04 0x2e
#define SECOCEC_READ_DATA_06 0x2f
#define SECOCEC_READ_DATA_08 0x30
#define SECOCEC_READ_DATA_10 0x31
#define SECOCEC_READ_DATA_12 0x32
#define SECOCEC_READ_BYTE0 0x33
#define SECOCEC_WRITE_OPERATION_ID 0x34
#define SECOCEC_WRITE_DATA_LENGTH 0x35
#define SECOCEC_WRITE_DATA_00 0x36
#define SECOCEC_WRITE_DATA_02 0x37
#define SECOCEC_WRITE_DATA_04 0x38
#define SECOCEC_WRITE_DATA_06 0x39
#define SECOCEC_WRITE_DATA_08 0x3a
#define SECOCEC_WRITE_DATA_10 0x3b
#define SECOCEC_WRITE_DATA_12 0x3c
#define SECOCEC_WRITE_BYTE0 0x3d
#define SECOCEC_IR_READ_DATA 0x3e
/*
* IR
*/
#define SECOCEC_IR_COMMAND_MASK 0x007F
#define SECOCEC_IR_COMMAND_SHL 0
#define SECOCEC_IR_ADDRESS_MASK 0x1F00
#define SECOCEC_IR_ADDRESS_SHL 8
#define SECOCEC_IR_TOGGLE_MASK 0x8000
#define SECOCEC_IR_TOGGLE_SHL 15
/*
* Enabling register
*/
#define SECOCEC_ENABLE_REG_1_CEC 0x1000
#define SECOCEC_ENABLE_REG_1_IR 0x2000
#define SECOCEC_ENABLE_REG_1_IR_PASSTHROUGH 0x4000
/*
* Status register
*/
#define SECOCEC_STATUS_REG_1_CEC SECOCEC_ENABLE_REG_1_CEC
#define SECOCEC_STATUS_REG_1_IR SECOCEC_ENABLE_REG_1_IR
#define SECOCEC_STATUS_REG_1_IR_PASSTHR SECOCEC_ENABLE_REG_1_IR_PASSTHR
/*
* 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)
#endif /* __SECO_CEC_H__ */
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_CEC_STI) += stih-cec.o
@@ -0,0 +1,397 @@
// SPDX-License-Identifier: GPL-2.0
/*
* STIH4xx CEC driver
* Copyright (C) STMicroelectronics SA 2016
*
*/
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <media/cec.h>
#include <media/cec-notifier.h>
#define CEC_NAME "stih-cec"
/* CEC registers */
#define CEC_CLK_DIV 0x0
#define CEC_CTRL 0x4
#define CEC_IRQ_CTRL 0x8
#define CEC_STATUS 0xC
#define CEC_EXT_STATUS 0x10
#define CEC_TX_CTRL 0x14
#define CEC_FREE_TIME_THRESH 0x18
#define CEC_BIT_TOUT_THRESH 0x1C
#define CEC_BIT_PULSE_THRESH 0x20
#define CEC_DATA 0x24
#define CEC_TX_ARRAY_CTRL 0x28
#define CEC_CTRL2 0x2C
#define CEC_TX_ERROR_STS 0x30
#define CEC_ADDR_TABLE 0x34
#define CEC_DATA_ARRAY_CTRL 0x38
#define CEC_DATA_ARRAY_STATUS 0x3C
#define CEC_TX_DATA_BASE 0x40
#define CEC_TX_DATA_TOP 0x50
#define CEC_TX_DATA_SIZE 0x1
#define CEC_RX_DATA_BASE 0x54
#define CEC_RX_DATA_TOP 0x64
#define CEC_RX_DATA_SIZE 0x1
/* CEC_CTRL2 */
#define CEC_LINE_INACTIVE_EN BIT(0)
#define CEC_AUTO_BUS_ERR_EN BIT(1)
#define CEC_STOP_ON_ARB_ERR_EN BIT(2)
#define CEC_TX_REQ_WAIT_EN BIT(3)
/* CEC_DATA_ARRAY_CTRL */
#define CEC_TX_ARRAY_EN BIT(0)
#define CEC_RX_ARRAY_EN BIT(1)
#define CEC_TX_ARRAY_RESET BIT(2)
#define CEC_RX_ARRAY_RESET BIT(3)
#define CEC_TX_N_OF_BYTES_IRQ_EN BIT(4)
#define CEC_TX_STOP_ON_NACK BIT(7)
/* CEC_TX_ARRAY_CTRL */
#define CEC_TX_N_OF_BYTES 0x1F
#define CEC_TX_START BIT(5)
#define CEC_TX_AUTO_SOM_EN BIT(6)
#define CEC_TX_AUTO_EOM_EN BIT(7)
/* CEC_IRQ_CTRL */
#define CEC_TX_DONE_IRQ_EN BIT(0)
#define CEC_ERROR_IRQ_EN BIT(2)
#define CEC_RX_DONE_IRQ_EN BIT(3)
#define CEC_RX_SOM_IRQ_EN BIT(4)
#define CEC_RX_EOM_IRQ_EN BIT(5)
#define CEC_FREE_TIME_IRQ_EN BIT(6)
#define CEC_PIN_STS_IRQ_EN BIT(7)
/* CEC_CTRL */
#define CEC_IN_FILTER_EN BIT(0)
#define CEC_PWR_SAVE_EN BIT(1)
#define CEC_EN BIT(4)
#define CEC_ACK_CTRL BIT(5)
#define CEC_RX_RESET_EN BIT(6)
#define CEC_IGNORE_RX_ERROR BIT(7)
/* CEC_STATUS */
#define CEC_TX_DONE_STS BIT(0)
#define CEC_TX_ACK_GET_STS BIT(1)
#define CEC_ERROR_STS BIT(2)
#define CEC_RX_DONE_STS BIT(3)
#define CEC_RX_SOM_STS BIT(4)
#define CEC_RX_EOM_STS BIT(5)
#define CEC_FREE_TIME_IRQ_STS BIT(6)
#define CEC_PIN_STS BIT(7)
#define CEC_SBIT_TOUT_STS BIT(8)
#define CEC_DBIT_TOUT_STS BIT(9)
#define CEC_LPULSE_ERROR_STS BIT(10)
#define CEC_HPULSE_ERROR_STS BIT(11)
#define CEC_TX_ERROR BIT(12)
#define CEC_TX_ARB_ERROR BIT(13)
#define CEC_RX_ERROR_MIN BIT(14)
#define CEC_RX_ERROR_MAX BIT(15)
/* Signal free time in bit periods (2.4ms) */
#define CEC_PRESENT_INIT_SFT 7
#define CEC_NEW_INIT_SFT 5
#define CEC_RETRANSMIT_SFT 3
/* Constants for CEC_BIT_TOUT_THRESH register */
#define CEC_SBIT_TOUT_47MS BIT(1)
#define CEC_SBIT_TOUT_48MS (BIT(0) | BIT(1))
#define CEC_SBIT_TOUT_50MS BIT(2)
#define CEC_DBIT_TOUT_27MS BIT(0)
#define CEC_DBIT_TOUT_28MS BIT(1)
#define CEC_DBIT_TOUT_29MS (BIT(0) | BIT(1))
/* Constants for CEC_BIT_PULSE_THRESH register */
#define CEC_BIT_LPULSE_03MS BIT(1)
#define CEC_BIT_HPULSE_03MS BIT(3)
/* Constants for CEC_DATA_ARRAY_STATUS register */
#define CEC_RX_N_OF_BYTES 0x1F
#define CEC_TX_N_OF_BYTES_SENT BIT(5)
#define CEC_RX_OVERRUN BIT(6)
struct stih_cec {
struct cec_adapter *adap;
struct device *dev;
struct clk *clk;
void __iomem *regs;
int irq;
u32 irq_status;
struct cec_notifier *notifier;
};
static int stih_cec_adap_enable(struct cec_adapter *adap, bool enable)
{
struct stih_cec *cec = cec_get_drvdata(adap);
if (enable) {
/* The doc says (input TCLK_PERIOD * CEC_CLK_DIV) = 0.1ms */
unsigned long clk_freq = clk_get_rate(cec->clk);
u32 cec_clk_div = clk_freq / 10000;
writel(cec_clk_div, cec->regs + CEC_CLK_DIV);
/* Configuration of the durations activating a timeout */
writel(CEC_SBIT_TOUT_47MS | (CEC_DBIT_TOUT_28MS << 4),
cec->regs + CEC_BIT_TOUT_THRESH);
/* Configuration of the smallest allowed duration for pulses */
writel(CEC_BIT_LPULSE_03MS | CEC_BIT_HPULSE_03MS,
cec->regs + CEC_BIT_PULSE_THRESH);
/* Minimum received bit period threshold */
writel(BIT(5) | BIT(7), cec->regs + CEC_TX_CTRL);
/* Configuration of transceiver data arrays */
writel(CEC_TX_ARRAY_EN | CEC_RX_ARRAY_EN | CEC_TX_STOP_ON_NACK,
cec->regs + CEC_DATA_ARRAY_CTRL);
/* Configuration of the control bits for CEC Transceiver */
writel(CEC_IN_FILTER_EN | CEC_EN | CEC_RX_RESET_EN,
cec->regs + CEC_CTRL);
/* Clear logical addresses */
writel(0, cec->regs + CEC_ADDR_TABLE);
/* Clear the status register */
writel(0x0, cec->regs + CEC_STATUS);
/* Enable the interrupts */
writel(CEC_TX_DONE_IRQ_EN | CEC_RX_DONE_IRQ_EN |
CEC_RX_SOM_IRQ_EN | CEC_RX_EOM_IRQ_EN |
CEC_ERROR_IRQ_EN,
cec->regs + CEC_IRQ_CTRL);
} else {
/* Clear logical addresses */
writel(0, cec->regs + CEC_ADDR_TABLE);
/* Clear the status register */
writel(0x0, cec->regs + CEC_STATUS);
/* Disable the interrupts */
writel(0, cec->regs + CEC_IRQ_CTRL);
}
return 0;
}
static int stih_cec_adap_log_addr(struct cec_adapter *adap, u8 logical_addr)
{
struct stih_cec *cec = cec_get_drvdata(adap);
u32 reg = readl(cec->regs + CEC_ADDR_TABLE);
reg |= 1 << logical_addr;
if (logical_addr == CEC_LOG_ADDR_INVALID)
reg = 0;
writel(reg, cec->regs + CEC_ADDR_TABLE);
return 0;
}
static int stih_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
u32 signal_free_time, struct cec_msg *msg)
{
struct stih_cec *cec = cec_get_drvdata(adap);
int i;
/* Copy message into registers */
for (i = 0; i < msg->len; i++)
writeb(msg->msg[i], cec->regs + CEC_TX_DATA_BASE + i);
/*
* Start transmission, configure hardware to add start and stop bits
* Signal free time is handled by the hardware
*/
writel(CEC_TX_AUTO_SOM_EN | CEC_TX_AUTO_EOM_EN | CEC_TX_START |
msg->len, cec->regs + CEC_TX_ARRAY_CTRL);
return 0;
}
static void stih_tx_done(struct stih_cec *cec, u32 status)
{
if (status & CEC_TX_ERROR) {
cec_transmit_attempt_done(cec->adap, CEC_TX_STATUS_ERROR);
return;
}
if (status & CEC_TX_ARB_ERROR) {
cec_transmit_attempt_done(cec->adap, CEC_TX_STATUS_ARB_LOST);
return;
}
if (!(status & CEC_TX_ACK_GET_STS)) {
cec_transmit_attempt_done(cec->adap, CEC_TX_STATUS_NACK);
return;
}
cec_transmit_attempt_done(cec->adap, CEC_TX_STATUS_OK);
}
static void stih_rx_done(struct stih_cec *cec, u32 status)
{
struct cec_msg msg = {};
u8 i;
if (status & CEC_RX_ERROR_MIN)
return;
if (status & CEC_RX_ERROR_MAX)
return;
msg.len = readl(cec->regs + CEC_DATA_ARRAY_STATUS) & 0x1f;
if (!msg.len)
return;
if (msg.len > CEC_MAX_MSG_SIZE)
msg.len = CEC_MAX_MSG_SIZE;
for (i = 0; i < msg.len; i++)
msg.msg[i] = readl(cec->regs + CEC_RX_DATA_BASE + i);
cec_received_msg(cec->adap, &msg);
}
static irqreturn_t stih_cec_irq_handler_thread(int irq, void *priv)
{
struct stih_cec *cec = priv;
if (cec->irq_status & CEC_TX_DONE_STS)
stih_tx_done(cec, cec->irq_status);
if (cec->irq_status & CEC_RX_DONE_STS)
stih_rx_done(cec, cec->irq_status);
cec->irq_status = 0;
return IRQ_HANDLED;
}
static irqreturn_t stih_cec_irq_handler(int irq, void *priv)
{
struct stih_cec *cec = priv;
cec->irq_status = readl(cec->regs + CEC_STATUS);
writel(cec->irq_status, cec->regs + CEC_STATUS);
return IRQ_WAKE_THREAD;
}
static const struct cec_adap_ops sti_cec_adap_ops = {
.adap_enable = stih_cec_adap_enable,
.adap_log_addr = stih_cec_adap_log_addr,
.adap_transmit = stih_cec_adap_transmit,
};
static int stih_cec_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct stih_cec *cec;
struct device *hdmi_dev;
int ret;
hdmi_dev = cec_notifier_parse_hdmi_phandle(dev);
if (IS_ERR(hdmi_dev))
return PTR_ERR(hdmi_dev);
cec = devm_kzalloc(dev, sizeof(*cec), GFP_KERNEL);
if (!cec)
return -ENOMEM;
cec->dev = dev;
cec->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(cec->regs))
return PTR_ERR(cec->regs);
cec->irq = platform_get_irq(pdev, 0);
if (cec->irq < 0)
return cec->irq;
ret = devm_request_threaded_irq(dev, cec->irq, stih_cec_irq_handler,
stih_cec_irq_handler_thread, 0,
pdev->name, cec);
if (ret)
return ret;
cec->clk = devm_clk_get(dev, "cec-clk");
if (IS_ERR(cec->clk)) {
dev_err(dev, "Cannot get cec clock\n");
return PTR_ERR(cec->clk);
}
cec->adap = cec_allocate_adapter(&sti_cec_adap_ops, cec, CEC_NAME,
CEC_CAP_DEFAULTS |
CEC_CAP_CONNECTOR_INFO,
CEC_MAX_LOG_ADDRS);
ret = PTR_ERR_OR_ZERO(cec->adap);
if (ret)
return ret;
cec->notifier = cec_notifier_cec_adap_register(hdmi_dev, NULL,
cec->adap);
if (!cec->notifier) {
ret = -ENOMEM;
goto err_delete_adapter;
}
ret = cec_register_adapter(cec->adap, &pdev->dev);
if (ret)
goto err_notifier;
platform_set_drvdata(pdev, cec);
return 0;
err_notifier:
cec_notifier_cec_adap_unregister(cec->notifier, cec->adap);
err_delete_adapter:
cec_delete_adapter(cec->adap);
return ret;
}
static void stih_cec_remove(struct platform_device *pdev)
{
struct stih_cec *cec = platform_get_drvdata(pdev);
cec_notifier_cec_adap_unregister(cec->notifier, cec->adap);
cec_unregister_adapter(cec->adap);
}
static const struct of_device_id stih_cec_match[] = {
{
.compatible = "st,stih-cec",
},
{},
};
MODULE_DEVICE_TABLE(of, stih_cec_match);
static struct platform_driver stih_cec_pdrv = {
.probe = stih_cec_probe,
.remove = stih_cec_remove,
.driver = {
.name = CEC_NAME,
.of_match_table = stih_cec_match,
},
};
module_platform_driver(stih_cec_pdrv);
MODULE_AUTHOR("Benjamin Gaignard <benjamin.gaignard@linaro.org>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("STIH4xx CEC driver");
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_CEC_STM32) += stm32-cec.o
@@ -0,0 +1,375 @@
// SPDX-License-Identifier: GPL-2.0
/*
* STM32 CEC driver
* Copyright (C) STMicroelectronics SA 2017
*
*/
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <media/cec.h>
#define CEC_NAME "stm32-cec"
/* CEC registers */
#define CEC_CR 0x0000 /* Control Register */
#define CEC_CFGR 0x0004 /* ConFiGuration Register */
#define CEC_TXDR 0x0008 /* Rx data Register */
#define CEC_RXDR 0x000C /* Rx data Register */
#define CEC_ISR 0x0010 /* Interrupt and status Register */
#define CEC_IER 0x0014 /* Interrupt enable Register */
#define TXEOM BIT(2)
#define TXSOM BIT(1)
#define CECEN BIT(0)
#define LSTN BIT(31)
#define OAR GENMASK(30, 16)
#define SFTOP BIT(8)
#define BRDNOGEN BIT(7)
#define LBPEGEN BIT(6)
#define BREGEN BIT(5)
#define BRESTP BIT(4)
#define RXTOL BIT(3)
#define SFT GENMASK(2, 0)
#define FULL_CFG (LSTN | SFTOP | BRDNOGEN | LBPEGEN | BREGEN | BRESTP \
| RXTOL)
#define TXACKE BIT(12)
#define TXERR BIT(11)
#define TXUDR BIT(10)
#define TXEND BIT(9)
#define TXBR BIT(8)
#define ARBLST BIT(7)
#define RXACKE BIT(6)
#define RXOVR BIT(2)
#define RXEND BIT(1)
#define RXBR BIT(0)
#define ALL_TX_IT (TXEND | TXBR | TXACKE | TXERR | TXUDR | ARBLST)
#define ALL_RX_IT (RXEND | RXBR | RXACKE | RXOVR)
/*
* 400 ms is the time it takes for one 16 byte message to be
* transferred and 5 is the maximum number of retries. Add
* another 100 ms as a margin.
*/
#define CEC_XFER_TIMEOUT_MS (5 * 400 + 100)
struct stm32_cec {
struct cec_adapter *adap;
struct device *dev;
struct clk *clk_cec;
struct clk *clk_hdmi_cec;
struct reset_control *rstc;
struct regmap *regmap;
int irq;
u32 irq_status;
struct cec_msg rx_msg;
struct cec_msg tx_msg;
int tx_cnt;
};
static void cec_hw_init(struct stm32_cec *cec)
{
regmap_update_bits(cec->regmap, CEC_CR, TXEOM | TXSOM | CECEN, 0);
regmap_update_bits(cec->regmap, CEC_IER, ALL_TX_IT | ALL_RX_IT,
ALL_TX_IT | ALL_RX_IT);
regmap_update_bits(cec->regmap, CEC_CFGR, FULL_CFG, FULL_CFG);
}
static void stm32_tx_done(struct stm32_cec *cec, u32 status)
{
if (status & (TXERR | TXUDR)) {
cec_transmit_done(cec->adap, CEC_TX_STATUS_ERROR,
0, 0, 0, 1);
return;
}
if (status & ARBLST) {
cec_transmit_done(cec->adap, CEC_TX_STATUS_ARB_LOST,
1, 0, 0, 0);
return;
}
if (status & TXACKE) {
cec_transmit_done(cec->adap, CEC_TX_STATUS_NACK,
0, 1, 0, 0);
return;
}
if (cec->irq_status & TXBR) {
/* send next byte */
if (cec->tx_cnt < cec->tx_msg.len)
regmap_write(cec->regmap, CEC_TXDR,
cec->tx_msg.msg[cec->tx_cnt++]);
/* TXEOM is set to command transmission of the last byte */
if (cec->tx_cnt == cec->tx_msg.len)
regmap_update_bits(cec->regmap, CEC_CR, TXEOM, TXEOM);
}
if (cec->irq_status & TXEND)
cec_transmit_done(cec->adap, CEC_TX_STATUS_OK, 0, 0, 0, 0);
}
static void stm32_rx_done(struct stm32_cec *cec, u32 status)
{
if (cec->irq_status & (RXACKE | RXOVR)) {
cec->rx_msg.len = 0;
return;
}
if (cec->irq_status & RXBR) {
u32 val;
regmap_read(cec->regmap, CEC_RXDR, &val);
cec->rx_msg.msg[cec->rx_msg.len++] = val & 0xFF;
}
if (cec->irq_status & RXEND) {
cec_received_msg(cec->adap, &cec->rx_msg);
cec->rx_msg.len = 0;
}
}
static irqreturn_t stm32_cec_irq_thread(int irq, void *arg)
{
struct stm32_cec *cec = arg;
if (cec->irq_status & ALL_TX_IT)
stm32_tx_done(cec, cec->irq_status);
if (cec->irq_status & ALL_RX_IT)
stm32_rx_done(cec, cec->irq_status);
cec->irq_status = 0;
return IRQ_HANDLED;
}
static irqreturn_t stm32_cec_irq_handler(int irq, void *arg)
{
struct stm32_cec *cec = arg;
regmap_read(cec->regmap, CEC_ISR, &cec->irq_status);
regmap_update_bits(cec->regmap, CEC_ISR,
ALL_TX_IT | ALL_RX_IT,
ALL_TX_IT | ALL_RX_IT);
return IRQ_WAKE_THREAD;
}
static int stm32_cec_adap_enable(struct cec_adapter *adap, bool enable)
{
struct stm32_cec *cec = adap->priv;
int ret = 0;
if (enable) {
ret = clk_enable(cec->clk_cec);
if (ret)
dev_err(cec->dev, "fail to enable cec clock\n");
clk_enable(cec->clk_hdmi_cec);
regmap_update_bits(cec->regmap, CEC_CR, CECEN, CECEN);
} else {
clk_disable(cec->clk_cec);
clk_disable(cec->clk_hdmi_cec);
regmap_update_bits(cec->regmap, CEC_CR, CECEN, 0);
}
return ret;
}
static int stm32_cec_adap_log_addr(struct cec_adapter *adap, u8 logical_addr)
{
struct stm32_cec *cec = adap->priv;
u32 oar = (1 << logical_addr) << 16;
u32 val;
/* Poll every 100µs the register CEC_CR to wait end of transmission */
regmap_read_poll_timeout(cec->regmap, CEC_CR, val, !(val & TXSOM),
100, CEC_XFER_TIMEOUT_MS * 1000);
regmap_update_bits(cec->regmap, CEC_CR, CECEN, 0);
if (logical_addr == CEC_LOG_ADDR_INVALID)
regmap_update_bits(cec->regmap, CEC_CFGR, OAR, 0);
else
regmap_update_bits(cec->regmap, CEC_CFGR, oar, oar);
regmap_update_bits(cec->regmap, CEC_CR, CECEN, CECEN);
return 0;
}
static int stm32_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
u32 signal_free_time, struct cec_msg *msg)
{
struct stm32_cec *cec = adap->priv;
/* Copy message */
cec->tx_msg = *msg;
cec->tx_cnt = 0;
/*
* If the CEC message consists of only one byte,
* TXEOM must be set before of TXSOM.
*/
if (cec->tx_msg.len == 1)
regmap_update_bits(cec->regmap, CEC_CR, TXEOM, TXEOM);
/* TXSOM is set to command transmission of the first byte */
regmap_update_bits(cec->regmap, CEC_CR, TXSOM, TXSOM);
/* Write the header (first byte of message) */
regmap_write(cec->regmap, CEC_TXDR, cec->tx_msg.msg[0]);
cec->tx_cnt++;
return 0;
}
static const struct cec_adap_ops stm32_cec_adap_ops = {
.adap_enable = stm32_cec_adap_enable,
.adap_log_addr = stm32_cec_adap_log_addr,
.adap_transmit = stm32_cec_adap_transmit,
};
static const struct regmap_config stm32_cec_regmap_cfg = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = sizeof(u32),
.max_register = 0x14,
};
static int stm32_cec_probe(struct platform_device *pdev)
{
u32 caps = CEC_CAP_DEFAULTS | CEC_CAP_PHYS_ADDR | CEC_MODE_MONITOR_ALL;
struct stm32_cec *cec;
void __iomem *mmio;
int ret;
cec = devm_kzalloc(&pdev->dev, sizeof(*cec), GFP_KERNEL);
if (!cec)
return -ENOMEM;
cec->dev = &pdev->dev;
mmio = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(mmio))
return PTR_ERR(mmio);
cec->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "cec", mmio,
&stm32_cec_regmap_cfg);
if (IS_ERR(cec->regmap))
return PTR_ERR(cec->regmap);
cec->irq = platform_get_irq(pdev, 0);
if (cec->irq < 0)
return cec->irq;
ret = devm_request_threaded_irq(&pdev->dev, cec->irq,
stm32_cec_irq_handler,
stm32_cec_irq_thread,
0,
pdev->name, cec);
if (ret)
return ret;
cec->clk_cec = devm_clk_get(&pdev->dev, "cec");
if (IS_ERR(cec->clk_cec))
return dev_err_probe(&pdev->dev, PTR_ERR(cec->clk_cec),
"Cannot get cec clock\n");
ret = clk_prepare(cec->clk_cec);
if (ret) {
dev_err(&pdev->dev, "Unable to prepare cec clock\n");
return ret;
}
cec->clk_hdmi_cec = devm_clk_get(&pdev->dev, "hdmi-cec");
if (IS_ERR(cec->clk_hdmi_cec) &&
PTR_ERR(cec->clk_hdmi_cec) == -EPROBE_DEFER) {
ret = -EPROBE_DEFER;
goto err_unprepare_cec_clk;
}
if (!IS_ERR(cec->clk_hdmi_cec)) {
ret = clk_prepare(cec->clk_hdmi_cec);
if (ret) {
dev_err(&pdev->dev, "Can't prepare hdmi-cec clock\n");
goto err_unprepare_cec_clk;
}
}
/*
* CEC_CAP_PHYS_ADDR caps should be removed when a cec notifier is
* available for example when a drm driver can provide edid
*/
cec->adap = cec_allocate_adapter(&stm32_cec_adap_ops, cec,
CEC_NAME, caps, CEC_MAX_LOG_ADDRS);
ret = PTR_ERR_OR_ZERO(cec->adap);
if (ret)
goto err_unprepare_hdmi_cec_clk;
ret = cec_register_adapter(cec->adap, &pdev->dev);
if (ret)
goto err_delete_adapter;
cec_hw_init(cec);
platform_set_drvdata(pdev, cec);
return 0;
err_delete_adapter:
cec_delete_adapter(cec->adap);
err_unprepare_hdmi_cec_clk:
clk_unprepare(cec->clk_hdmi_cec);
err_unprepare_cec_clk:
clk_unprepare(cec->clk_cec);
return ret;
}
static void stm32_cec_remove(struct platform_device *pdev)
{
struct stm32_cec *cec = platform_get_drvdata(pdev);
clk_unprepare(cec->clk_cec);
clk_unprepare(cec->clk_hdmi_cec);
cec_unregister_adapter(cec->adap);
}
static const struct of_device_id stm32_cec_of_match[] = {
{ .compatible = "st,stm32-cec" },
{ /* end node */ }
};
MODULE_DEVICE_TABLE(of, stm32_cec_of_match);
static struct platform_driver stm32_cec_driver = {
.probe = stm32_cec_probe,
.remove = stm32_cec_remove,
.driver = {
.name = CEC_NAME,
.of_match_table = stm32_cec_of_match,
},
};
module_platform_driver(stm32_cec_driver);
MODULE_AUTHOR("Benjamin Gaignard <benjamin.gaignard@st.com>");
MODULE_AUTHOR("Yannick Fertre <yannick.fertre@st.com>");
MODULE_DESCRIPTION("STMicroelectronics STM32 Consumer Electronics Control");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_CEC_TEGRA) += tegra_cec.o
@@ -0,0 +1,481 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Tegra CEC implementation
*
* The original 3.10 CEC driver using a custom API:
*
* Copyright (c) 2012-2015, NVIDIA CORPORATION. All rights reserved.
*
* Conversion to the CEC framework and to the mainline kernel:
*
* Copyright 2016-2017 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/clk/tegra.h>
#include <media/cec-notifier.h>
#include "tegra_cec.h"
#define TEGRA_CEC_NAME "tegra-cec"
struct tegra_cec {
struct cec_adapter *adap;
struct device *dev;
struct clk *clk;
void __iomem *cec_base;
struct cec_notifier *notifier;
int tegra_cec_irq;
bool rx_done;
bool tx_done;
int tx_status;
u8 rx_buf[CEC_MAX_MSG_SIZE];
u8 rx_buf_cnt;
u32 tx_buf[CEC_MAX_MSG_SIZE];
u8 tx_buf_cur;
u8 tx_buf_cnt;
};
static inline u32 cec_read(struct tegra_cec *cec, u32 reg)
{
return readl(cec->cec_base + reg);
}
static inline void cec_write(struct tegra_cec *cec, u32 reg, u32 val)
{
writel(val, cec->cec_base + reg);
}
static void tegra_cec_error_recovery(struct tegra_cec *cec)
{
u32 hw_ctrl;
hw_ctrl = cec_read(cec, TEGRA_CEC_HW_CONTROL);
cec_write(cec, TEGRA_CEC_HW_CONTROL, 0);
cec_write(cec, TEGRA_CEC_INT_STAT, 0xffffffff);
cec_write(cec, TEGRA_CEC_HW_CONTROL, hw_ctrl);
}
static irqreturn_t tegra_cec_irq_thread_handler(int irq, void *data)
{
struct device *dev = data;
struct tegra_cec *cec = dev_get_drvdata(dev);
if (cec->tx_done) {
cec_transmit_attempt_done(cec->adap, cec->tx_status);
cec->tx_done = false;
}
if (cec->rx_done) {
struct cec_msg msg = {};
msg.len = cec->rx_buf_cnt;
memcpy(msg.msg, cec->rx_buf, msg.len);
cec_received_msg(cec->adap, &msg);
cec->rx_done = false;
cec->rx_buf_cnt = 0;
}
return IRQ_HANDLED;
}
static irqreturn_t tegra_cec_irq_handler(int irq, void *data)
{
struct device *dev = data;
struct tegra_cec *cec = dev_get_drvdata(dev);
u32 status, mask;
status = cec_read(cec, TEGRA_CEC_INT_STAT);
mask = cec_read(cec, TEGRA_CEC_INT_MASK);
status &= mask;
if (!status)
return IRQ_HANDLED;
if (status & TEGRA_CEC_INT_STAT_TX_REGISTER_UNDERRUN) {
dev_err(dev, "TX underrun, interrupt timing issue!\n");
tegra_cec_error_recovery(cec);
cec_write(cec, TEGRA_CEC_INT_MASK,
mask & ~TEGRA_CEC_INT_MASK_TX_REGISTER_EMPTY);
cec->tx_done = true;
cec->tx_status = CEC_TX_STATUS_ERROR;
return IRQ_WAKE_THREAD;
}
if ((status & TEGRA_CEC_INT_STAT_TX_ARBITRATION_FAILED) ||
(status & TEGRA_CEC_INT_STAT_TX_BUS_ANOMALY_DETECTED)) {
tegra_cec_error_recovery(cec);
cec_write(cec, TEGRA_CEC_INT_MASK,
mask & ~TEGRA_CEC_INT_MASK_TX_REGISTER_EMPTY);
cec->tx_done = true;
if (status & TEGRA_CEC_INT_STAT_TX_BUS_ANOMALY_DETECTED)
cec->tx_status = CEC_TX_STATUS_LOW_DRIVE;
else
cec->tx_status = CEC_TX_STATUS_ARB_LOST;
return IRQ_WAKE_THREAD;
}
if (status & TEGRA_CEC_INT_STAT_TX_FRAME_TRANSMITTED) {
cec_write(cec, TEGRA_CEC_INT_STAT,
TEGRA_CEC_INT_STAT_TX_FRAME_TRANSMITTED);
if (status & TEGRA_CEC_INT_STAT_TX_FRAME_OR_BLOCK_NAKD) {
tegra_cec_error_recovery(cec);
cec->tx_done = true;
cec->tx_status = CEC_TX_STATUS_NACK;
} else {
cec->tx_done = true;
cec->tx_status = CEC_TX_STATUS_OK;
}
return IRQ_WAKE_THREAD;
}
if (status & TEGRA_CEC_INT_STAT_TX_FRAME_OR_BLOCK_NAKD)
dev_warn(dev, "TX NAKed on the fly!\n");
if (status & TEGRA_CEC_INT_STAT_TX_REGISTER_EMPTY) {
if (cec->tx_buf_cur == cec->tx_buf_cnt) {
cec_write(cec, TEGRA_CEC_INT_MASK,
mask & ~TEGRA_CEC_INT_MASK_TX_REGISTER_EMPTY);
} else {
cec_write(cec, TEGRA_CEC_TX_REGISTER,
cec->tx_buf[cec->tx_buf_cur++]);
cec_write(cec, TEGRA_CEC_INT_STAT,
TEGRA_CEC_INT_STAT_TX_REGISTER_EMPTY);
}
}
if (status & TEGRA_CEC_INT_STAT_RX_START_BIT_DETECTED) {
cec_write(cec, TEGRA_CEC_INT_STAT,
TEGRA_CEC_INT_STAT_RX_START_BIT_DETECTED);
cec->rx_done = false;
cec->rx_buf_cnt = 0;
}
if (status & TEGRA_CEC_INT_STAT_RX_REGISTER_FULL) {
u32 v;
cec_write(cec, TEGRA_CEC_INT_STAT,
TEGRA_CEC_INT_STAT_RX_REGISTER_FULL);
v = cec_read(cec, TEGRA_CEC_RX_REGISTER);
if (cec->rx_buf_cnt < CEC_MAX_MSG_SIZE)
cec->rx_buf[cec->rx_buf_cnt++] = v & 0xff;
if (v & TEGRA_CEC_RX_REGISTER_EOM) {
cec->rx_done = true;
return IRQ_WAKE_THREAD;
}
}
return IRQ_HANDLED;
}
static int tegra_cec_adap_enable(struct cec_adapter *adap, bool enable)
{
struct tegra_cec *cec = adap->priv;
cec->rx_buf_cnt = 0;
cec->tx_buf_cnt = 0;
cec->tx_buf_cur = 0;
cec_write(cec, TEGRA_CEC_HW_CONTROL, 0);
cec_write(cec, TEGRA_CEC_INT_MASK, 0);
cec_write(cec, TEGRA_CEC_INT_STAT, 0xffffffff);
cec_write(cec, TEGRA_CEC_SW_CONTROL, 0);
if (!enable)
return 0;
cec_write(cec, TEGRA_CEC_INPUT_FILTER, (1U << 31) | 0x20);
cec_write(cec, TEGRA_CEC_RX_TIMING_0,
(0x7a << TEGRA_CEC_RX_TIM0_START_BIT_MAX_LO_TIME_SHIFT) |
(0x6d << TEGRA_CEC_RX_TIM0_START_BIT_MIN_LO_TIME_SHIFT) |
(0x93 << TEGRA_CEC_RX_TIM0_START_BIT_MAX_DURATION_SHIFT) |
(0x86 << TEGRA_CEC_RX_TIM0_START_BIT_MIN_DURATION_SHIFT));
cec_write(cec, TEGRA_CEC_RX_TIMING_1,
(0x35 << TEGRA_CEC_RX_TIM1_DATA_BIT_MAX_LO_TIME_SHIFT) |
(0x21 << TEGRA_CEC_RX_TIM1_DATA_BIT_SAMPLE_TIME_SHIFT) |
(0x56 << TEGRA_CEC_RX_TIM1_DATA_BIT_MAX_DURATION_SHIFT) |
(0x40 << TEGRA_CEC_RX_TIM1_DATA_BIT_MIN_DURATION_SHIFT));
cec_write(cec, TEGRA_CEC_RX_TIMING_2,
(0x50 << TEGRA_CEC_RX_TIM2_END_OF_BLOCK_TIME_SHIFT));
cec_write(cec, TEGRA_CEC_TX_TIMING_0,
(0x74 << TEGRA_CEC_TX_TIM0_START_BIT_LO_TIME_SHIFT) |
(0x8d << TEGRA_CEC_TX_TIM0_START_BIT_DURATION_SHIFT) |
(0x08 << TEGRA_CEC_TX_TIM0_BUS_XITION_TIME_SHIFT) |
(0x71 << TEGRA_CEC_TX_TIM0_BUS_ERROR_LO_TIME_SHIFT));
cec_write(cec, TEGRA_CEC_TX_TIMING_1,
(0x2f << TEGRA_CEC_TX_TIM1_LO_DATA_BIT_LO_TIME_SHIFT) |
(0x13 << TEGRA_CEC_TX_TIM1_HI_DATA_BIT_LO_TIME_SHIFT) |
(0x4b << TEGRA_CEC_TX_TIM1_DATA_BIT_DURATION_SHIFT) |
(0x21 << TEGRA_CEC_TX_TIM1_ACK_NAK_BIT_SAMPLE_TIME_SHIFT));
cec_write(cec, TEGRA_CEC_TX_TIMING_2,
(0x07 << TEGRA_CEC_TX_TIM2_BUS_IDLE_TIME_ADDITIONAL_FRAME_SHIFT) |
(0x05 << TEGRA_CEC_TX_TIM2_BUS_IDLE_TIME_NEW_FRAME_SHIFT) |
(0x03 << TEGRA_CEC_TX_TIM2_BUS_IDLE_TIME_RETRY_FRAME_SHIFT));
cec_write(cec, TEGRA_CEC_INT_MASK,
TEGRA_CEC_INT_MASK_TX_REGISTER_UNDERRUN |
TEGRA_CEC_INT_MASK_TX_FRAME_OR_BLOCK_NAKD |
TEGRA_CEC_INT_MASK_TX_ARBITRATION_FAILED |
TEGRA_CEC_INT_MASK_TX_BUS_ANOMALY_DETECTED |
TEGRA_CEC_INT_MASK_TX_FRAME_TRANSMITTED |
TEGRA_CEC_INT_MASK_RX_REGISTER_FULL |
TEGRA_CEC_INT_MASK_RX_START_BIT_DETECTED);
cec_write(cec, TEGRA_CEC_HW_CONTROL, TEGRA_CEC_HWCTRL_TX_RX_MODE);
return 0;
}
static int tegra_cec_adap_log_addr(struct cec_adapter *adap, u8 logical_addr)
{
struct tegra_cec *cec = adap->priv;
u32 state = cec_read(cec, TEGRA_CEC_HW_CONTROL);
if (logical_addr == CEC_LOG_ADDR_INVALID)
state &= ~TEGRA_CEC_HWCTRL_RX_LADDR_MASK;
else
state |= TEGRA_CEC_HWCTRL_RX_LADDR((1 << logical_addr));
cec_write(cec, TEGRA_CEC_HW_CONTROL, state);
return 0;
}
static int tegra_cec_adap_monitor_all_enable(struct cec_adapter *adap,
bool enable)
{
struct tegra_cec *cec = adap->priv;
u32 reg = cec_read(cec, TEGRA_CEC_HW_CONTROL);
if (enable)
reg |= TEGRA_CEC_HWCTRL_RX_SNOOP;
else
reg &= ~TEGRA_CEC_HWCTRL_RX_SNOOP;
cec_write(cec, TEGRA_CEC_HW_CONTROL, reg);
return 0;
}
static int tegra_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
u32 signal_free_time_ms, struct cec_msg *msg)
{
bool retry_xfer = signal_free_time_ms == CEC_SIGNAL_FREE_TIME_RETRY;
struct tegra_cec *cec = adap->priv;
unsigned int i;
u32 mode = 0;
u32 mask;
if (cec_msg_is_broadcast(msg))
mode = TEGRA_CEC_TX_REG_BCAST;
cec->tx_buf_cur = 0;
cec->tx_buf_cnt = msg->len;
for (i = 0; i < msg->len; i++) {
cec->tx_buf[i] = mode | msg->msg[i];
if (i == 0)
cec->tx_buf[i] |= TEGRA_CEC_TX_REG_START_BIT;
if (i == msg->len - 1)
cec->tx_buf[i] |= TEGRA_CEC_TX_REG_EOM;
if (i == 0 && retry_xfer)
cec->tx_buf[i] |= TEGRA_CEC_TX_REG_RETRY;
}
mask = cec_read(cec, TEGRA_CEC_INT_MASK);
cec_write(cec, TEGRA_CEC_INT_MASK,
mask | TEGRA_CEC_INT_MASK_TX_REGISTER_EMPTY);
return 0;
}
static const struct cec_adap_ops tegra_cec_ops = {
.adap_enable = tegra_cec_adap_enable,
.adap_log_addr = tegra_cec_adap_log_addr,
.adap_transmit = tegra_cec_adap_transmit,
.adap_monitor_all_enable = tegra_cec_adap_monitor_all_enable,
};
static int tegra_cec_probe(struct platform_device *pdev)
{
struct device *hdmi_dev;
struct tegra_cec *cec;
struct resource *res;
int ret = 0;
hdmi_dev = cec_notifier_parse_hdmi_phandle(&pdev->dev);
if (IS_ERR(hdmi_dev))
return PTR_ERR(hdmi_dev);
cec = devm_kzalloc(&pdev->dev, sizeof(struct tegra_cec), GFP_KERNEL);
if (!cec)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev,
"Unable to allocate resources for device\n");
return -EBUSY;
}
if (!devm_request_mem_region(&pdev->dev, res->start, resource_size(res),
pdev->name)) {
dev_err(&pdev->dev,
"Unable to request mem region for device\n");
return -EBUSY;
}
cec->tegra_cec_irq = platform_get_irq(pdev, 0);
if (cec->tegra_cec_irq < 0)
return cec->tegra_cec_irq;
cec->cec_base = devm_ioremap(&pdev->dev, res->start,
resource_size(res));
if (!cec->cec_base) {
dev_err(&pdev->dev, "Unable to grab IOs for device\n");
return -EBUSY;
}
cec->clk = devm_clk_get(&pdev->dev, "cec");
if (IS_ERR_OR_NULL(cec->clk)) {
dev_err(&pdev->dev, "Can't get clock for CEC\n");
return -ENOENT;
}
ret = clk_prepare_enable(cec->clk);
if (ret) {
dev_err(&pdev->dev, "Unable to prepare clock for CEC\n");
return ret;
}
/* set context info. */
cec->dev = &pdev->dev;
platform_set_drvdata(pdev, cec);
ret = devm_request_threaded_irq(&pdev->dev, cec->tegra_cec_irq,
tegra_cec_irq_handler, tegra_cec_irq_thread_handler,
0, "cec_irq", &pdev->dev);
if (ret) {
dev_err(&pdev->dev,
"Unable to request interrupt for device\n");
goto err_clk;
}
cec->adap = cec_allocate_adapter(&tegra_cec_ops, cec, TEGRA_CEC_NAME,
CEC_CAP_DEFAULTS | CEC_CAP_MONITOR_ALL |
CEC_CAP_CONNECTOR_INFO,
CEC_MAX_LOG_ADDRS);
if (IS_ERR(cec->adap)) {
ret = -ENOMEM;
dev_err(&pdev->dev, "Couldn't create cec adapter\n");
goto err_clk;
}
cec->notifier = cec_notifier_cec_adap_register(hdmi_dev, NULL,
cec->adap);
if (!cec->notifier) {
ret = -ENOMEM;
goto err_adapter;
}
ret = cec_register_adapter(cec->adap, &pdev->dev);
if (ret) {
dev_err(&pdev->dev, "Couldn't register device\n");
goto err_notifier;
}
return 0;
err_notifier:
cec_notifier_cec_adap_unregister(cec->notifier, cec->adap);
err_adapter:
cec_delete_adapter(cec->adap);
err_clk:
clk_disable_unprepare(cec->clk);
return ret;
}
static void tegra_cec_remove(struct platform_device *pdev)
{
struct tegra_cec *cec = platform_get_drvdata(pdev);
clk_disable_unprepare(cec->clk);
cec_notifier_cec_adap_unregister(cec->notifier, cec->adap);
cec_unregister_adapter(cec->adap);
}
#ifdef CONFIG_PM
static int tegra_cec_suspend(struct platform_device *pdev, pm_message_t state)
{
struct tegra_cec *cec = platform_get_drvdata(pdev);
clk_disable_unprepare(cec->clk);
dev_notice(&pdev->dev, "suspended\n");
return 0;
}
static int tegra_cec_resume(struct platform_device *pdev)
{
struct tegra_cec *cec = platform_get_drvdata(pdev);
dev_notice(&pdev->dev, "Resuming\n");
return clk_prepare_enable(cec->clk);
}
#endif
static const struct of_device_id tegra_cec_of_match[] = {
{ .compatible = "nvidia,tegra114-cec", },
{ .compatible = "nvidia,tegra124-cec", },
{ .compatible = "nvidia,tegra210-cec", },
{},
};
static struct platform_driver tegra_cec_driver = {
.driver = {
.name = TEGRA_CEC_NAME,
.of_match_table = tegra_cec_of_match,
},
.probe = tegra_cec_probe,
.remove = tegra_cec_remove,
#ifdef CONFIG_PM
.suspend = tegra_cec_suspend,
.resume = tegra_cec_resume,
#endif
};
module_platform_driver(tegra_cec_driver);
MODULE_DESCRIPTION("Tegra HDMI CEC driver");
MODULE_AUTHOR("NVIDIA CORPORATION");
MODULE_AUTHOR("Cisco Systems, Inc. and/or its affiliates");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,116 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Tegra CEC register definitions
*
* The original 3.10 CEC driver using a custom API:
*
* Copyright (c) 2012-2015, NVIDIA CORPORATION. All rights reserved.
*
* Conversion to the CEC framework and to the mainline kernel:
*
* Copyright 2016-2017 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
*/
#ifndef TEGRA_CEC_H
#define TEGRA_CEC_H
/* CEC registers */
#define TEGRA_CEC_SW_CONTROL 0x000
#define TEGRA_CEC_HW_CONTROL 0x004
#define TEGRA_CEC_INPUT_FILTER 0x008
#define TEGRA_CEC_TX_REGISTER 0x010
#define TEGRA_CEC_RX_REGISTER 0x014
#define TEGRA_CEC_RX_TIMING_0 0x018
#define TEGRA_CEC_RX_TIMING_1 0x01c
#define TEGRA_CEC_RX_TIMING_2 0x020
#define TEGRA_CEC_TX_TIMING_0 0x024
#define TEGRA_CEC_TX_TIMING_1 0x028
#define TEGRA_CEC_TX_TIMING_2 0x02c
#define TEGRA_CEC_INT_STAT 0x030
#define TEGRA_CEC_INT_MASK 0x034
#define TEGRA_CEC_HW_DEBUG_RX 0x038
#define TEGRA_CEC_HW_DEBUG_TX 0x03c
#define TEGRA_CEC_HWCTRL_RX_LADDR_MASK 0x7fff
#define TEGRA_CEC_HWCTRL_RX_LADDR(x) \
((x) & TEGRA_CEC_HWCTRL_RX_LADDR_MASK)
#define TEGRA_CEC_HWCTRL_RX_SNOOP BIT(15)
#define TEGRA_CEC_HWCTRL_RX_NAK_MODE BIT(16)
#define TEGRA_CEC_HWCTRL_TX_NAK_MODE BIT(24)
#define TEGRA_CEC_HWCTRL_FAST_SIM_MODE BIT(30)
#define TEGRA_CEC_HWCTRL_TX_RX_MODE BIT(31)
#define TEGRA_CEC_INPUT_FILTER_MODE BIT(31)
#define TEGRA_CEC_INPUT_FILTER_FIFO_LENGTH_SHIFT 0
#define TEGRA_CEC_TX_REG_DATA_SHIFT 0
#define TEGRA_CEC_TX_REG_EOM BIT(8)
#define TEGRA_CEC_TX_REG_BCAST BIT(12)
#define TEGRA_CEC_TX_REG_START_BIT BIT(16)
#define TEGRA_CEC_TX_REG_RETRY BIT(17)
#define TEGRA_CEC_RX_REGISTER_SHIFT 0
#define TEGRA_CEC_RX_REGISTER_EOM BIT(8)
#define TEGRA_CEC_RX_REGISTER_ACK BIT(9)
#define TEGRA_CEC_RX_TIM0_START_BIT_MAX_LO_TIME_SHIFT 0
#define TEGRA_CEC_RX_TIM0_START_BIT_MIN_LO_TIME_SHIFT 8
#define TEGRA_CEC_RX_TIM0_START_BIT_MAX_DURATION_SHIFT 16
#define TEGRA_CEC_RX_TIM0_START_BIT_MIN_DURATION_SHIFT 24
#define TEGRA_CEC_RX_TIM1_DATA_BIT_MAX_LO_TIME_SHIFT 0
#define TEGRA_CEC_RX_TIM1_DATA_BIT_SAMPLE_TIME_SHIFT 8
#define TEGRA_CEC_RX_TIM1_DATA_BIT_MAX_DURATION_SHIFT 16
#define TEGRA_CEC_RX_TIM1_DATA_BIT_MIN_DURATION_SHIFT 24
#define TEGRA_CEC_RX_TIM2_END_OF_BLOCK_TIME_SHIFT 0
#define TEGRA_CEC_TX_TIM0_START_BIT_LO_TIME_SHIFT 0
#define TEGRA_CEC_TX_TIM0_START_BIT_DURATION_SHIFT 8
#define TEGRA_CEC_TX_TIM0_BUS_XITION_TIME_SHIFT 16
#define TEGRA_CEC_TX_TIM0_BUS_ERROR_LO_TIME_SHIFT 24
#define TEGRA_CEC_TX_TIM1_LO_DATA_BIT_LO_TIME_SHIFT 0
#define TEGRA_CEC_TX_TIM1_HI_DATA_BIT_LO_TIME_SHIFT 8
#define TEGRA_CEC_TX_TIM1_DATA_BIT_DURATION_SHIFT 16
#define TEGRA_CEC_TX_TIM1_ACK_NAK_BIT_SAMPLE_TIME_SHIFT 24
#define TEGRA_CEC_TX_TIM2_BUS_IDLE_TIME_ADDITIONAL_FRAME_SHIFT 0
#define TEGRA_CEC_TX_TIM2_BUS_IDLE_TIME_NEW_FRAME_SHIFT 4
#define TEGRA_CEC_TX_TIM2_BUS_IDLE_TIME_RETRY_FRAME_SHIFT 8
#define TEGRA_CEC_INT_STAT_TX_REGISTER_EMPTY BIT(0)
#define TEGRA_CEC_INT_STAT_TX_REGISTER_UNDERRUN BIT(1)
#define TEGRA_CEC_INT_STAT_TX_FRAME_OR_BLOCK_NAKD BIT(2)
#define TEGRA_CEC_INT_STAT_TX_ARBITRATION_FAILED BIT(3)
#define TEGRA_CEC_INT_STAT_TX_BUS_ANOMALY_DETECTED BIT(4)
#define TEGRA_CEC_INT_STAT_TX_FRAME_TRANSMITTED BIT(5)
#define TEGRA_CEC_INT_STAT_RX_REGISTER_FULL BIT(8)
#define TEGRA_CEC_INT_STAT_RX_REGISTER_OVERRUN BIT(9)
#define TEGRA_CEC_INT_STAT_RX_START_BIT_DETECTED BIT(10)
#define TEGRA_CEC_INT_STAT_RX_BUS_ANOMALY_DETECTED BIT(11)
#define TEGRA_CEC_INT_STAT_RX_BUS_ERROR_DETECTED BIT(12)
#define TEGRA_CEC_INT_STAT_FILTERED_RX_DATA_PIN_TRANSITION_H2L BIT(13)
#define TEGRA_CEC_INT_STAT_FILTERED_RX_DATA_PIN_TRANSITION_L2H BIT(14)
#define TEGRA_CEC_INT_MASK_TX_REGISTER_EMPTY BIT(0)
#define TEGRA_CEC_INT_MASK_TX_REGISTER_UNDERRUN BIT(1)
#define TEGRA_CEC_INT_MASK_TX_FRAME_OR_BLOCK_NAKD BIT(2)
#define TEGRA_CEC_INT_MASK_TX_ARBITRATION_FAILED BIT(3)
#define TEGRA_CEC_INT_MASK_TX_BUS_ANOMALY_DETECTED BIT(4)
#define TEGRA_CEC_INT_MASK_TX_FRAME_TRANSMITTED BIT(5)
#define TEGRA_CEC_INT_MASK_RX_REGISTER_FULL BIT(8)
#define TEGRA_CEC_INT_MASK_RX_REGISTER_OVERRUN BIT(9)
#define TEGRA_CEC_INT_MASK_RX_START_BIT_DETECTED BIT(10)
#define TEGRA_CEC_INT_MASK_RX_BUS_ANOMALY_DETECTED BIT(11)
#define TEGRA_CEC_INT_MASK_RX_BUS_ERROR_DETECTED BIT(12)
#define TEGRA_CEC_INT_MASK_FILTERED_RX_DATA_PIN_TRANSITION_H2L BIT(13)
#define TEGRA_CEC_INT_MASK_FILTERED_RX_DATA_PIN_TRANSITION_L2H BIT(14)
#define TEGRA_CEC_HW_DEBUG_TX_DURATION_COUNT_SHIFT 0
#define TEGRA_CEC_HW_DEBUG_TX_TXBIT_COUNT_SHIFT 17
#define TEGRA_CEC_HW_DEBUG_TX_STATE_SHIFT 21
#define TEGRA_CEC_HW_DEBUG_TX_FORCELOOUT BIT(25)
#define TEGRA_CEC_HW_DEBUG_TX_TXDATABIT_SAMPLE_TIMER BIT(26)
#endif /* TEGRA_CEC_H */