Message ID | 20170919073331.29007-3-hverkuil@xs4all.nl (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Hi Hans, On 09/19/2017 01:03 PM, Hans Verkuil wrote: > From: Hans Verkuil <hans.verkuil@cisco.com> > > Add support for HDMI CEC to the drm adv7511/adv7533 drivers. > > The CEC registers that we need to use are identical for both drivers, > but they appear at different offsets in the register map. The patch looks good to me. I can go ahead an queue it to drm-misc-next. There were some minor comments on the DT bindings patch. Are you planning to send a new patch for that? Thanks, Archit > > Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com> > --- > drivers/gpu/drm/bridge/adv7511/Kconfig | 8 + > drivers/gpu/drm/bridge/adv7511/Makefile | 1 + > drivers/gpu/drm/bridge/adv7511/adv7511.h | 43 +++- > drivers/gpu/drm/bridge/adv7511/adv7511_cec.c | 337 +++++++++++++++++++++++++++ > drivers/gpu/drm/bridge/adv7511/adv7511_drv.c | 116 +++++++-- > drivers/gpu/drm/bridge/adv7511/adv7533.c | 38 +-- > 6 files changed, 485 insertions(+), 58 deletions(-) > create mode 100644 drivers/gpu/drm/bridge/adv7511/adv7511_cec.c > > diff --git a/drivers/gpu/drm/bridge/adv7511/Kconfig b/drivers/gpu/drm/bridge/adv7511/Kconfig > index 2fed567f9943..592b9d2ec034 100644 > --- a/drivers/gpu/drm/bridge/adv7511/Kconfig > +++ b/drivers/gpu/drm/bridge/adv7511/Kconfig > @@ -21,3 +21,11 @@ config DRM_I2C_ADV7533 > default y > help > Support for the Analog Devices ADV7533 DSI to HDMI encoder. > + > +config DRM_I2C_ADV7511_CEC > + bool "ADV7511/33 HDMI CEC driver" > + depends on DRM_I2C_ADV7511 > + select CEC_CORE > + default y > + help > + When selected the HDMI transmitter will support the CEC feature. > diff --git a/drivers/gpu/drm/bridge/adv7511/Makefile b/drivers/gpu/drm/bridge/adv7511/Makefile > index 5ba675534f6e..5bb384938a71 100644 > --- a/drivers/gpu/drm/bridge/adv7511/Makefile > +++ b/drivers/gpu/drm/bridge/adv7511/Makefile > @@ -1,4 +1,5 @@ > adv7511-y := adv7511_drv.o > adv7511-$(CONFIG_DRM_I2C_ADV7511_AUDIO) += adv7511_audio.o > +adv7511-$(CONFIG_DRM_I2C_ADV7511_CEC) += adv7511_cec.o > adv7511-$(CONFIG_DRM_I2C_ADV7533) += adv7533.o > obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511.o > diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511.h b/drivers/gpu/drm/bridge/adv7511/adv7511.h > index fe18a5d2d84b..543a5eb91624 100644 > --- a/drivers/gpu/drm/bridge/adv7511/adv7511.h > +++ b/drivers/gpu/drm/bridge/adv7511/adv7511.h > @@ -195,6 +195,25 @@ > #define ADV7511_PACKET_GM(x) ADV7511_PACKET(5, x) > #define ADV7511_PACKET_SPARE(x) ADV7511_PACKET(6, x) > > +#define ADV7511_REG_CEC_TX_FRAME_HDR 0x00 > +#define ADV7511_REG_CEC_TX_FRAME_DATA0 0x01 > +#define ADV7511_REG_CEC_TX_FRAME_LEN 0x10 > +#define ADV7511_REG_CEC_TX_ENABLE 0x11 > +#define ADV7511_REG_CEC_TX_RETRY 0x12 > +#define ADV7511_REG_CEC_TX_LOW_DRV_CNT 0x14 > +#define ADV7511_REG_CEC_RX_FRAME_HDR 0x15 > +#define ADV7511_REG_CEC_RX_FRAME_DATA0 0x16 > +#define ADV7511_REG_CEC_RX_FRAME_LEN 0x25 > +#define ADV7511_REG_CEC_RX_ENABLE 0x26 > +#define ADV7511_REG_CEC_RX_BUFFERS 0x4a > +#define ADV7511_REG_CEC_LOG_ADDR_MASK 0x4b > +#define ADV7511_REG_CEC_LOG_ADDR_0_1 0x4c > +#define ADV7511_REG_CEC_LOG_ADDR_2 0x4d > +#define ADV7511_REG_CEC_CLK_DIV 0x4e > +#define ADV7511_REG_CEC_SOFT_RESET 0x50 > + > +#define ADV7533_REG_CEC_OFFSET 0x70 > + > enum adv7511_input_clock { > ADV7511_INPUT_CLOCK_1X, > ADV7511_INPUT_CLOCK_2X, > @@ -297,6 +316,8 @@ enum adv7511_type { > ADV7533, > }; > > +#define ADV7511_MAX_ADDRS 3 > + > struct adv7511 { > struct i2c_client *i2c_main; > struct i2c_client *i2c_edid; > @@ -343,15 +364,27 @@ struct adv7511 { > > enum adv7511_type type; > struct platform_device *audio_pdev; > + > + struct cec_adapter *cec_adap; > + u8 cec_addr[ADV7511_MAX_ADDRS]; > + u8 cec_valid_addrs; > + bool cec_enabled_adap; > + struct clk *cec_clk; > + u32 cec_clk_freq; > }; > > +#ifdef CONFIG_DRM_I2C_ADV7511_CEC > +int adv7511_cec_init(struct device *dev, struct adv7511 *adv7511, > + unsigned int offset); > +void adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1); > +#endif > + > #ifdef CONFIG_DRM_I2C_ADV7533 > void adv7533_dsi_power_on(struct adv7511 *adv); > void adv7533_dsi_power_off(struct adv7511 *adv); > void adv7533_mode_set(struct adv7511 *adv, struct drm_display_mode *mode); > int adv7533_patch_registers(struct adv7511 *adv); > -void adv7533_uninit_cec(struct adv7511 *adv); > -int adv7533_init_cec(struct adv7511 *adv); > +int adv7533_patch_cec_registers(struct adv7511 *adv); > int adv7533_attach_dsi(struct adv7511 *adv); > void adv7533_detach_dsi(struct adv7511 *adv); > int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv); > @@ -374,11 +407,7 @@ static inline int adv7533_patch_registers(struct adv7511 *adv) > return -ENODEV; > } > > -static inline void adv7533_uninit_cec(struct adv7511 *adv) > -{ > -} > - > -static inline int adv7533_init_cec(struct adv7511 *adv) > +static inline int adv7533_patch_cec_registers(struct adv7511 *adv) > { > return -ENODEV; > } > diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c b/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c > new file mode 100644 > index 000000000000..b33d730e4d73 > --- /dev/null > +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c > @@ -0,0 +1,337 @@ > +/* > + * adv7511_cec.c - Analog Devices ADV7511/33 cec driver > + * > + * Copyright 2017 Cisco Systems, Inc. and/or its affiliates. All rights reserved. > + * > + * This program is free software; you may redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; version 2 of the License. > + * > + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, > + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF > + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND > + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS > + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN > + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN > + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE > + * SOFTWARE. > + * > + */ > + > +#include <linux/device.h> > +#include <linux/module.h> > +#include <linux/of_device.h> > +#include <linux/slab.h> > +#include <linux/clk.h> > + > +#include <media/cec.h> > + > +#include "adv7511.h" > + > +#define ADV7511_INT1_CEC_MASK \ > + (ADV7511_INT1_CEC_TX_READY | ADV7511_INT1_CEC_TX_ARBIT_LOST | \ > + ADV7511_INT1_CEC_TX_RETRY_TIMEOUT | ADV7511_INT1_CEC_RX_READY1) > + > +static void adv_cec_tx_raw_status(struct adv7511 *adv7511, u8 tx_raw_status) > +{ > + unsigned int offset = adv7511->type == ADV7533 ? > + ADV7533_REG_CEC_OFFSET : 0; > + unsigned int val; > + > + if (regmap_read(adv7511->regmap_cec, > + ADV7511_REG_CEC_TX_ENABLE + offset, &val)) > + return; > + > + if ((val & 0x01) == 0) > + return; > + > + if (tx_raw_status & ADV7511_INT1_CEC_TX_ARBIT_LOST) { > + cec_transmit_attempt_done(adv7511->cec_adap, > + CEC_TX_STATUS_ARB_LOST); > + return; > + } > + if (tx_raw_status & ADV7511_INT1_CEC_TX_RETRY_TIMEOUT) { > + u8 status; > + u8 err_cnt = 0; > + u8 nack_cnt = 0; > + u8 low_drive_cnt = 0; > + unsigned int cnt; > + > + /* > + * We set this status bit since this hardware performs > + * retransmissions. > + */ > + status = CEC_TX_STATUS_MAX_RETRIES; > + if (regmap_read(adv7511->regmap_cec, > + ADV7511_REG_CEC_TX_LOW_DRV_CNT + offset, &cnt)) { > + err_cnt = 1; > + status |= CEC_TX_STATUS_ERROR; > + } else { > + nack_cnt = cnt & 0xf; > + if (nack_cnt) > + status |= CEC_TX_STATUS_NACK; > + low_drive_cnt = cnt >> 4; > + if (low_drive_cnt) > + status |= CEC_TX_STATUS_LOW_DRIVE; > + } > + cec_transmit_done(adv7511->cec_adap, status, > + 0, nack_cnt, low_drive_cnt, err_cnt); > + return; > + } > + if (tx_raw_status & ADV7511_INT1_CEC_TX_READY) { > + cec_transmit_attempt_done(adv7511->cec_adap, CEC_TX_STATUS_OK); > + return; > + } > +} > + > +void adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1) > +{ > + unsigned int offset = adv7511->type == ADV7533 ? > + ADV7533_REG_CEC_OFFSET : 0; > + const u32 irq_tx_mask = ADV7511_INT1_CEC_TX_READY | > + ADV7511_INT1_CEC_TX_ARBIT_LOST | > + ADV7511_INT1_CEC_TX_RETRY_TIMEOUT; > + struct cec_msg msg = {}; > + unsigned int len; > + unsigned int val; > + u8 i; > + > + if (irq1 & irq_tx_mask) > + adv_cec_tx_raw_status(adv7511, irq1); > + > + if (!(irq1 & ADV7511_INT1_CEC_RX_READY1)) > + return; > + > + if (regmap_read(adv7511->regmap_cec, > + ADV7511_REG_CEC_RX_FRAME_LEN + offset, &len)) > + return; > + > + msg.len = len & 0x1f; > + > + if (msg.len > 16) > + msg.len = 16; > + > + if (!msg.len) > + return; > + > + for (i = 0; i < msg.len; i++) { > + regmap_read(adv7511->regmap_cec, > + i + ADV7511_REG_CEC_RX_FRAME_HDR + offset, &val); > + msg.msg[i] = val; > + } > + > + /* toggle to re-enable rx 1 */ > + regmap_write(adv7511->regmap_cec, > + ADV7511_REG_CEC_RX_BUFFERS + offset, 1); > + regmap_write(adv7511->regmap_cec, > + ADV7511_REG_CEC_RX_BUFFERS + offset, 0); > + cec_received_msg(adv7511->cec_adap, &msg); > +} > + > +static int adv7511_cec_adap_enable(struct cec_adapter *adap, bool enable) > +{ > + struct adv7511 *adv7511 = cec_get_drvdata(adap); > + unsigned int offset = adv7511->type == ADV7533 ? > + ADV7533_REG_CEC_OFFSET : 0; > + > + if (adv7511->i2c_cec == NULL) > + return -EIO; > + > + if (!adv7511->cec_enabled_adap && enable) { > + /* power up cec section */ > + regmap_update_bits(adv7511->regmap_cec, > + ADV7511_REG_CEC_CLK_DIV + offset, > + 0x03, 0x01); > + /* legacy mode and clear all rx buffers */ > + regmap_write(adv7511->regmap_cec, > + ADV7511_REG_CEC_RX_BUFFERS + offset, 0x07); > + regmap_write(adv7511->regmap_cec, > + ADV7511_REG_CEC_RX_BUFFERS + offset, 0); > + /* initially disable tx */ > + regmap_update_bits(adv7511->regmap_cec, > + ADV7511_REG_CEC_TX_ENABLE + offset, 1, 0); > + /* enabled irqs: */ > + /* tx: ready */ > + /* tx: arbitration lost */ > + /* tx: retry timeout */ > + /* rx: ready 1 */ > + regmap_update_bits(adv7511->regmap, > + ADV7511_REG_INT_ENABLE(1), 0x3f, > + ADV7511_INT1_CEC_MASK); > + } else if (adv7511->cec_enabled_adap && !enable) { > + regmap_update_bits(adv7511->regmap, > + ADV7511_REG_INT_ENABLE(1), 0x3f, 0); > + /* disable address mask 1-3 */ > + regmap_update_bits(adv7511->regmap_cec, > + ADV7511_REG_CEC_LOG_ADDR_MASK + offset, > + 0x70, 0x00); > + /* power down cec section */ > + regmap_update_bits(adv7511->regmap_cec, > + ADV7511_REG_CEC_CLK_DIV + offset, > + 0x03, 0x00); > + adv7511->cec_valid_addrs = 0; > + } > + adv7511->cec_enabled_adap = enable; > + return 0; > +} > + > +static int adv7511_cec_adap_log_addr(struct cec_adapter *adap, u8 addr) > +{ > + struct adv7511 *adv7511 = cec_get_drvdata(adap); > + unsigned int offset = adv7511->type == ADV7533 ? > + ADV7533_REG_CEC_OFFSET : 0; > + unsigned int i, free_idx = ADV7511_MAX_ADDRS; > + > + if (!adv7511->cec_enabled_adap) > + return addr == CEC_LOG_ADDR_INVALID ? 0 : -EIO; > + > + if (addr == CEC_LOG_ADDR_INVALID) { > + regmap_update_bits(adv7511->regmap_cec, > + ADV7511_REG_CEC_LOG_ADDR_MASK + offset, > + 0x70, 0); > + adv7511->cec_valid_addrs = 0; > + return 0; > + } > + > + for (i = 0; i < ADV7511_MAX_ADDRS; i++) { > + bool is_valid = adv7511->cec_valid_addrs & (1 << i); > + > + if (free_idx == ADV7511_MAX_ADDRS && !is_valid) > + free_idx = i; > + if (is_valid && adv7511->cec_addr[i] == addr) > + return 0; > + } > + if (i == ADV7511_MAX_ADDRS) { > + i = free_idx; > + if (i == ADV7511_MAX_ADDRS) > + return -ENXIO; > + } > + adv7511->cec_addr[i] = addr; > + adv7511->cec_valid_addrs |= 1 << i; > + > + switch (i) { > + case 0: > + /* enable address mask 0 */ > + regmap_update_bits(adv7511->regmap_cec, > + ADV7511_REG_CEC_LOG_ADDR_MASK + offset, > + 0x10, 0x10); > + /* set address for mask 0 */ > + regmap_update_bits(adv7511->regmap_cec, > + ADV7511_REG_CEC_LOG_ADDR_0_1 + offset, > + 0x0f, addr); > + break; > + case 1: > + /* enable address mask 1 */ > + regmap_update_bits(adv7511->regmap_cec, > + ADV7511_REG_CEC_LOG_ADDR_MASK + offset, > + 0x20, 0x20); > + /* set address for mask 1 */ > + regmap_update_bits(adv7511->regmap_cec, > + ADV7511_REG_CEC_LOG_ADDR_0_1 + offset, > + 0xf0, addr << 4); > + break; > + case 2: > + /* enable address mask 2 */ > + regmap_update_bits(adv7511->regmap_cec, > + ADV7511_REG_CEC_LOG_ADDR_MASK + offset, > + 0x40, 0x40); > + /* set address for mask 1 */ > + regmap_update_bits(adv7511->regmap_cec, > + ADV7511_REG_CEC_LOG_ADDR_2 + offset, > + 0x0f, addr); > + break; > + } > + return 0; > +} > + > +static int adv7511_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, > + u32 signal_free_time, struct cec_msg *msg) > +{ > + struct adv7511 *adv7511 = cec_get_drvdata(adap); > + unsigned int offset = adv7511->type == ADV7533 ? > + ADV7533_REG_CEC_OFFSET : 0; > + u8 len = msg->len; > + unsigned int i; > + > + /* > + * The number of retries is the number of attempts - 1, but retry > + * at least once. It's not clear if a value of 0 is allowed, so > + * let's do at least one retry. > + */ > + regmap_update_bits(adv7511->regmap_cec, > + ADV7511_REG_CEC_TX_RETRY + offset, > + 0x70, max(1, attempts - 1) << 4); > + > + /* blocking, clear cec tx irq status */ > + regmap_update_bits(adv7511->regmap, ADV7511_REG_INT(1), 0x38, 0x38); > + > + /* write data */ > + for (i = 0; i < len; i++) > + regmap_write(adv7511->regmap_cec, > + i + ADV7511_REG_CEC_TX_FRAME_HDR + offset, > + msg->msg[i]); > + > + /* set length (data + header) */ > + regmap_write(adv7511->regmap_cec, > + ADV7511_REG_CEC_TX_FRAME_LEN + offset, len); > + /* start transmit, enable tx */ > + regmap_write(adv7511->regmap_cec, > + ADV7511_REG_CEC_TX_ENABLE + offset, 0x01); > + return 0; > +} > + > +static const struct cec_adap_ops adv7511_cec_adap_ops = { > + .adap_enable = adv7511_cec_adap_enable, > + .adap_log_addr = adv7511_cec_adap_log_addr, > + .adap_transmit = adv7511_cec_adap_transmit, > +}; > + > +static int adv7511_cec_parse_dt(struct device *dev, struct adv7511 *adv7511) > +{ > + adv7511->cec_clk = devm_clk_get(dev, "cec"); > + if (IS_ERR(adv7511->cec_clk)) { > + int ret = PTR_ERR(adv7511->cec_clk); > + > + adv7511->cec_clk = NULL; > + return ret; > + } > + clk_prepare_enable(adv7511->cec_clk); > + adv7511->cec_clk_freq = clk_get_rate(adv7511->cec_clk); > + return 0; > +} > + > +int adv7511_cec_init(struct device *dev, struct adv7511 *adv7511, > + unsigned int offset) > +{ > + int ret = adv7511_cec_parse_dt(dev, adv7511); > + > + if (ret) > + return ret; > + > + adv7511->cec_adap = cec_allocate_adapter(&adv7511_cec_adap_ops, > + adv7511, dev_name(dev), CEC_CAP_DEFAULTS, ADV7511_MAX_ADDRS); > + if (IS_ERR(adv7511->cec_adap)) > + return PTR_ERR(adv7511->cec_adap); > + > + regmap_write(adv7511->regmap, ADV7511_REG_CEC_CTRL + offset, 0); > + /* cec soft reset */ > + regmap_write(adv7511->regmap_cec, > + ADV7511_REG_CEC_SOFT_RESET + offset, 0x01); > + regmap_write(adv7511->regmap_cec, > + ADV7511_REG_CEC_SOFT_RESET + offset, 0x00); > + > + /* legacy mode */ > + regmap_write(adv7511->regmap_cec, > + ADV7511_REG_CEC_RX_BUFFERS + offset, 0x00); > + > + regmap_write(adv7511->regmap_cec, > + ADV7511_REG_CEC_CLK_DIV + offset, > + ((adv7511->cec_clk_freq / 750000) - 1) << 2); > + > + ret = cec_register_adapter(adv7511->cec_adap, dev); > + if (ret) { > + cec_delete_adapter(adv7511->cec_adap); > + adv7511->cec_adap = NULL; > + } > + return ret; > +} > diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c > index b2431aee7887..3a33075dbb22 100644 > --- a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c > +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c > @@ -11,12 +11,15 @@ > #include <linux/module.h> > #include <linux/of_device.h> > #include <linux/slab.h> > +#include <linux/clk.h> > > #include <drm/drmP.h> > #include <drm/drm_atomic.h> > #include <drm/drm_atomic_helper.h> > #include <drm/drm_edid.h> > > +#include <media/cec.h> > + > #include "adv7511.h" > > /* ADI recommended values for proper operation. */ > @@ -339,8 +342,10 @@ static void __adv7511_power_on(struct adv7511 *adv7511) > */ > regmap_write(adv7511->regmap, ADV7511_REG_INT_ENABLE(0), > ADV7511_INT0_EDID_READY | ADV7511_INT0_HPD); > - regmap_write(adv7511->regmap, ADV7511_REG_INT_ENABLE(1), > - ADV7511_INT1_DDC_ERROR); > + regmap_update_bits(adv7511->regmap, > + ADV7511_REG_INT_ENABLE(1), > + ADV7511_INT1_DDC_ERROR, > + ADV7511_INT1_DDC_ERROR); > } > > /* > @@ -376,6 +381,9 @@ static void __adv7511_power_off(struct adv7511 *adv7511) > regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER, > ADV7511_POWER_POWER_DOWN, > ADV7511_POWER_POWER_DOWN); > + regmap_update_bits(adv7511->regmap, > + ADV7511_REG_INT_ENABLE(1), > + ADV7511_INT1_DDC_ERROR, 0); > regcache_mark_dirty(adv7511->regmap); > } > > @@ -426,6 +434,8 @@ static void adv7511_hpd_work(struct work_struct *work) > > if (adv7511->connector.status != status) { > adv7511->connector.status = status; > + if (status == connector_status_disconnected) > + cec_phys_addr_invalidate(adv7511->cec_adap); > drm_kms_helper_hotplug_event(adv7511->connector.dev); > } > } > @@ -456,6 +466,10 @@ static int adv7511_irq_process(struct adv7511 *adv7511, bool process_hpd) > wake_up_all(&adv7511->wq); > } > > +#ifdef CONFIG_DRM_I2C_ADV7511_CEC > + adv7511_cec_irq_process(adv7511, irq1); > +#endif > + > return 0; > } > > @@ -599,6 +613,8 @@ static int adv7511_get_modes(struct adv7511 *adv7511, > > adv7511_set_config_csc(adv7511, connector, adv7511->rgb); > > + cec_s_phys_addr_from_edid(adv7511->cec_adap, edid); > + > return count; > } > > @@ -919,6 +935,65 @@ static void adv7511_uninit_regulators(struct adv7511 *adv) > regulator_bulk_disable(adv->num_supplies, adv->supplies); > } > > +static bool adv7511_cec_register_volatile(struct device *dev, unsigned int reg) > +{ > + struct i2c_client *i2c = to_i2c_client(dev); > + struct adv7511 *adv7511 = i2c_get_clientdata(i2c); > + > + if (adv7511->type == ADV7533) > + reg -= ADV7533_REG_CEC_OFFSET; > + > + switch (reg) { > + case ADV7511_REG_CEC_RX_FRAME_HDR: > + case ADV7511_REG_CEC_RX_FRAME_DATA0... > + ADV7511_REG_CEC_RX_FRAME_DATA0 + 14: > + case ADV7511_REG_CEC_RX_FRAME_LEN: > + case ADV7511_REG_CEC_RX_BUFFERS: > + case ADV7511_REG_CEC_TX_LOW_DRV_CNT: > + return true; > + } > + > + return false; > +} > + > +static const struct regmap_config adv7511_cec_regmap_config = { > + .reg_bits = 8, > + .val_bits = 8, > + > + .max_register = 0xff, > + .cache_type = REGCACHE_RBTREE, > + .volatile_reg = adv7511_cec_register_volatile, > +}; > + > +static int adv7511_init_cec_regmap(struct adv7511 *adv) > +{ > + int ret; > + > + adv->i2c_cec = i2c_new_dummy(adv->i2c_main->adapter, > + adv->i2c_main->addr - 1); > + if (!adv->i2c_cec) > + return -ENOMEM; > + i2c_set_clientdata(adv->i2c_cec, adv); > + > + adv->regmap_cec = devm_regmap_init_i2c(adv->i2c_cec, > + &adv7511_cec_regmap_config); > + if (IS_ERR(adv->regmap_cec)) { > + ret = PTR_ERR(adv->regmap_cec); > + goto err; > + } > + > + if (adv->type == ADV7533) { > + ret = adv7533_patch_cec_registers(adv); > + if (ret) > + goto err; > + } > + > + return 0; > +err: > + i2c_unregister_device(adv->i2c_cec); > + return ret; > +} > + > static int adv7511_parse_dt(struct device_node *np, > struct adv7511_link_config *config) > { > @@ -1009,6 +1084,7 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) > struct device *dev = &i2c->dev; > unsigned int main_i2c_addr = i2c->addr << 1; > unsigned int edid_i2c_addr = main_i2c_addr + 4; > + unsigned int offset; > unsigned int val; > int ret; > > @@ -1092,11 +1168,9 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) > goto uninit_regulators; > } > > - if (adv7511->type == ADV7533) { > - ret = adv7533_init_cec(adv7511); > - if (ret) > - goto err_i2c_unregister_edid; > - } > + ret = adv7511_init_cec_regmap(adv7511); > + if (ret) > + goto err_i2c_unregister_edid; > > INIT_WORK(&adv7511->hpd_work, adv7511_hpd_work); > > @@ -1111,10 +1185,6 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) > goto err_unregister_cec; > } > > - /* CEC is unused for now */ > - regmap_write(adv7511->regmap, ADV7511_REG_CEC_CTRL, > - ADV7511_CEC_CTRL_POWER_DOWN); > - > adv7511_power_off(adv7511); > > i2c_set_clientdata(i2c, adv7511); > @@ -1129,10 +1199,23 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) > > adv7511_audio_init(dev, adv7511); > > + offset = adv7511->type == ADV7533 ? ADV7533_REG_CEC_OFFSET : 0; > + > +#ifdef CONFIG_DRM_I2C_ADV7511_CEC > + ret = adv7511_cec_init(dev, adv7511, offset); > + if (ret) > + goto err_unregister_cec; > +#else > + regmap_write(adv7511->regmap, ADV7511_REG_CEC_CTRL + offset, > + ADV7511_CEC_CTRL_POWER_DOWN); > +#endif > + > return 0; > > err_unregister_cec: > - adv7533_uninit_cec(adv7511); > + i2c_unregister_device(adv7511->i2c_cec); > + if (adv7511->cec_clk) > + clk_disable_unprepare(adv7511->cec_clk); > err_i2c_unregister_edid: > i2c_unregister_device(adv7511->i2c_edid); > uninit_regulators: > @@ -1145,10 +1228,11 @@ static int adv7511_remove(struct i2c_client *i2c) > { > struct adv7511 *adv7511 = i2c_get_clientdata(i2c); > > - if (adv7511->type == ADV7533) { > + if (adv7511->type == ADV7533) > adv7533_detach_dsi(adv7511); > - adv7533_uninit_cec(adv7511); > - } > + i2c_unregister_device(adv7511->i2c_cec); > + if (adv7511->cec_clk) > + clk_disable_unprepare(adv7511->cec_clk); > > adv7511_uninit_regulators(adv7511); > > @@ -1156,6 +1240,8 @@ static int adv7511_remove(struct i2c_client *i2c) > > adv7511_audio_exit(adv7511); > > + cec_unregister_adapter(adv7511->cec_adap); > + > i2c_unregister_device(adv7511->i2c_edid); > > kfree(adv7511->edid); > diff --git a/drivers/gpu/drm/bridge/adv7511/adv7533.c b/drivers/gpu/drm/bridge/adv7511/adv7533.c > index ac804f81e2f6..185b6d842166 100644 > --- a/drivers/gpu/drm/bridge/adv7511/adv7533.c > +++ b/drivers/gpu/drm/bridge/adv7511/adv7533.c > @@ -32,14 +32,6 @@ static const struct reg_sequence adv7533_cec_fixed_registers[] = { > { 0x05, 0xc8 }, > }; > > -static const struct regmap_config adv7533_cec_regmap_config = { > - .reg_bits = 8, > - .val_bits = 8, > - > - .max_register = 0xff, > - .cache_type = REGCACHE_RBTREE, > -}; > - > static void adv7511_dsi_config_timing_gen(struct adv7511 *adv) > { > struct mipi_dsi_device *dsi = adv->dsi; > @@ -145,37 +137,11 @@ int adv7533_patch_registers(struct adv7511 *adv) > ARRAY_SIZE(adv7533_fixed_registers)); > } > > -void adv7533_uninit_cec(struct adv7511 *adv) > -{ > - i2c_unregister_device(adv->i2c_cec); > -} > - > -int adv7533_init_cec(struct adv7511 *adv) > +int adv7533_patch_cec_registers(struct adv7511 *adv) > { > - int ret; > - > - adv->i2c_cec = i2c_new_dummy(adv->i2c_main->adapter, > - adv->i2c_main->addr - 1); > - if (!adv->i2c_cec) > - return -ENOMEM; > - > - adv->regmap_cec = devm_regmap_init_i2c(adv->i2c_cec, > - &adv7533_cec_regmap_config); > - if (IS_ERR(adv->regmap_cec)) { > - ret = PTR_ERR(adv->regmap_cec); > - goto err; > - } > - > - ret = regmap_register_patch(adv->regmap_cec, > + return regmap_register_patch(adv->regmap_cec, > adv7533_cec_fixed_registers, > ARRAY_SIZE(adv7533_cec_fixed_registers)); > - if (ret) > - goto err; > - > - return 0; > -err: > - adv7533_uninit_cec(adv); > - return ret; > } > > int adv7533_attach_dsi(struct adv7511 *adv) >
On 06/10/17 06:26, Archit Taneja wrote: > Hi Hans, > > On 09/19/2017 01:03 PM, Hans Verkuil wrote: >> From: Hans Verkuil <hans.verkuil@cisco.com> >> >> Add support for HDMI CEC to the drm adv7511/adv7533 drivers. >> >> The CEC registers that we need to use are identical for both drivers, >> but they appear at different offsets in the register map. > > The patch looks good to me. I can go ahead an queue it to drm-misc-next. > > There were some minor comments on the DT bindings patch. Are you planning > to send a new patch for that? Yes. Now that this patch has been reviewed I'll post a v3 for the first fixing those comments. Expect to see it Monday at the latest. Regards, Hans
diff --git a/drivers/gpu/drm/bridge/adv7511/Kconfig b/drivers/gpu/drm/bridge/adv7511/Kconfig index 2fed567f9943..592b9d2ec034 100644 --- a/drivers/gpu/drm/bridge/adv7511/Kconfig +++ b/drivers/gpu/drm/bridge/adv7511/Kconfig @@ -21,3 +21,11 @@ config DRM_I2C_ADV7533 default y help Support for the Analog Devices ADV7533 DSI to HDMI encoder. + +config DRM_I2C_ADV7511_CEC + bool "ADV7511/33 HDMI CEC driver" + depends on DRM_I2C_ADV7511 + select CEC_CORE + default y + help + When selected the HDMI transmitter will support the CEC feature. diff --git a/drivers/gpu/drm/bridge/adv7511/Makefile b/drivers/gpu/drm/bridge/adv7511/Makefile index 5ba675534f6e..5bb384938a71 100644 --- a/drivers/gpu/drm/bridge/adv7511/Makefile +++ b/drivers/gpu/drm/bridge/adv7511/Makefile @@ -1,4 +1,5 @@ adv7511-y := adv7511_drv.o adv7511-$(CONFIG_DRM_I2C_ADV7511_AUDIO) += adv7511_audio.o +adv7511-$(CONFIG_DRM_I2C_ADV7511_CEC) += adv7511_cec.o adv7511-$(CONFIG_DRM_I2C_ADV7533) += adv7533.o obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511.o diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511.h b/drivers/gpu/drm/bridge/adv7511/adv7511.h index fe18a5d2d84b..543a5eb91624 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511.h +++ b/drivers/gpu/drm/bridge/adv7511/adv7511.h @@ -195,6 +195,25 @@ #define ADV7511_PACKET_GM(x) ADV7511_PACKET(5, x) #define ADV7511_PACKET_SPARE(x) ADV7511_PACKET(6, x) +#define ADV7511_REG_CEC_TX_FRAME_HDR 0x00 +#define ADV7511_REG_CEC_TX_FRAME_DATA0 0x01 +#define ADV7511_REG_CEC_TX_FRAME_LEN 0x10 +#define ADV7511_REG_CEC_TX_ENABLE 0x11 +#define ADV7511_REG_CEC_TX_RETRY 0x12 +#define ADV7511_REG_CEC_TX_LOW_DRV_CNT 0x14 +#define ADV7511_REG_CEC_RX_FRAME_HDR 0x15 +#define ADV7511_REG_CEC_RX_FRAME_DATA0 0x16 +#define ADV7511_REG_CEC_RX_FRAME_LEN 0x25 +#define ADV7511_REG_CEC_RX_ENABLE 0x26 +#define ADV7511_REG_CEC_RX_BUFFERS 0x4a +#define ADV7511_REG_CEC_LOG_ADDR_MASK 0x4b +#define ADV7511_REG_CEC_LOG_ADDR_0_1 0x4c +#define ADV7511_REG_CEC_LOG_ADDR_2 0x4d +#define ADV7511_REG_CEC_CLK_DIV 0x4e +#define ADV7511_REG_CEC_SOFT_RESET 0x50 + +#define ADV7533_REG_CEC_OFFSET 0x70 + enum adv7511_input_clock { ADV7511_INPUT_CLOCK_1X, ADV7511_INPUT_CLOCK_2X, @@ -297,6 +316,8 @@ enum adv7511_type { ADV7533, }; +#define ADV7511_MAX_ADDRS 3 + struct adv7511 { struct i2c_client *i2c_main; struct i2c_client *i2c_edid; @@ -343,15 +364,27 @@ struct adv7511 { enum adv7511_type type; struct platform_device *audio_pdev; + + struct cec_adapter *cec_adap; + u8 cec_addr[ADV7511_MAX_ADDRS]; + u8 cec_valid_addrs; + bool cec_enabled_adap; + struct clk *cec_clk; + u32 cec_clk_freq; }; +#ifdef CONFIG_DRM_I2C_ADV7511_CEC +int adv7511_cec_init(struct device *dev, struct adv7511 *adv7511, + unsigned int offset); +void adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1); +#endif + #ifdef CONFIG_DRM_I2C_ADV7533 void adv7533_dsi_power_on(struct adv7511 *adv); void adv7533_dsi_power_off(struct adv7511 *adv); void adv7533_mode_set(struct adv7511 *adv, struct drm_display_mode *mode); int adv7533_patch_registers(struct adv7511 *adv); -void adv7533_uninit_cec(struct adv7511 *adv); -int adv7533_init_cec(struct adv7511 *adv); +int adv7533_patch_cec_registers(struct adv7511 *adv); int adv7533_attach_dsi(struct adv7511 *adv); void adv7533_detach_dsi(struct adv7511 *adv); int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv); @@ -374,11 +407,7 @@ static inline int adv7533_patch_registers(struct adv7511 *adv) return -ENODEV; } -static inline void adv7533_uninit_cec(struct adv7511 *adv) -{ -} - -static inline int adv7533_init_cec(struct adv7511 *adv) +static inline int adv7533_patch_cec_registers(struct adv7511 *adv) { return -ENODEV; } diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c b/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c new file mode 100644 index 000000000000..b33d730e4d73 --- /dev/null +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c @@ -0,0 +1,337 @@ +/* + * adv7511_cec.c - Analog Devices ADV7511/33 cec driver + * + * Copyright 2017 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/slab.h> +#include <linux/clk.h> + +#include <media/cec.h> + +#include "adv7511.h" + +#define ADV7511_INT1_CEC_MASK \ + (ADV7511_INT1_CEC_TX_READY | ADV7511_INT1_CEC_TX_ARBIT_LOST | \ + ADV7511_INT1_CEC_TX_RETRY_TIMEOUT | ADV7511_INT1_CEC_RX_READY1) + +static void adv_cec_tx_raw_status(struct adv7511 *adv7511, u8 tx_raw_status) +{ + unsigned int offset = adv7511->type == ADV7533 ? + ADV7533_REG_CEC_OFFSET : 0; + unsigned int val; + + if (regmap_read(adv7511->regmap_cec, + ADV7511_REG_CEC_TX_ENABLE + offset, &val)) + return; + + if ((val & 0x01) == 0) + return; + + if (tx_raw_status & ADV7511_INT1_CEC_TX_ARBIT_LOST) { + cec_transmit_attempt_done(adv7511->cec_adap, + CEC_TX_STATUS_ARB_LOST); + return; + } + if (tx_raw_status & ADV7511_INT1_CEC_TX_RETRY_TIMEOUT) { + u8 status; + u8 err_cnt = 0; + u8 nack_cnt = 0; + u8 low_drive_cnt = 0; + unsigned int cnt; + + /* + * We set this status bit since this hardware performs + * retransmissions. + */ + status = CEC_TX_STATUS_MAX_RETRIES; + if (regmap_read(adv7511->regmap_cec, + ADV7511_REG_CEC_TX_LOW_DRV_CNT + offset, &cnt)) { + err_cnt = 1; + status |= CEC_TX_STATUS_ERROR; + } else { + nack_cnt = cnt & 0xf; + if (nack_cnt) + status |= CEC_TX_STATUS_NACK; + low_drive_cnt = cnt >> 4; + if (low_drive_cnt) + status |= CEC_TX_STATUS_LOW_DRIVE; + } + cec_transmit_done(adv7511->cec_adap, status, + 0, nack_cnt, low_drive_cnt, err_cnt); + return; + } + if (tx_raw_status & ADV7511_INT1_CEC_TX_READY) { + cec_transmit_attempt_done(adv7511->cec_adap, CEC_TX_STATUS_OK); + return; + } +} + +void adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1) +{ + unsigned int offset = adv7511->type == ADV7533 ? + ADV7533_REG_CEC_OFFSET : 0; + const u32 irq_tx_mask = ADV7511_INT1_CEC_TX_READY | + ADV7511_INT1_CEC_TX_ARBIT_LOST | + ADV7511_INT1_CEC_TX_RETRY_TIMEOUT; + struct cec_msg msg = {}; + unsigned int len; + unsigned int val; + u8 i; + + if (irq1 & irq_tx_mask) + adv_cec_tx_raw_status(adv7511, irq1); + + if (!(irq1 & ADV7511_INT1_CEC_RX_READY1)) + return; + + if (regmap_read(adv7511->regmap_cec, + ADV7511_REG_CEC_RX_FRAME_LEN + offset, &len)) + return; + + msg.len = len & 0x1f; + + if (msg.len > 16) + msg.len = 16; + + if (!msg.len) + return; + + for (i = 0; i < msg.len; i++) { + regmap_read(adv7511->regmap_cec, + i + ADV7511_REG_CEC_RX_FRAME_HDR + offset, &val); + msg.msg[i] = val; + } + + /* toggle to re-enable rx 1 */ + regmap_write(adv7511->regmap_cec, + ADV7511_REG_CEC_RX_BUFFERS + offset, 1); + regmap_write(adv7511->regmap_cec, + ADV7511_REG_CEC_RX_BUFFERS + offset, 0); + cec_received_msg(adv7511->cec_adap, &msg); +} + +static int adv7511_cec_adap_enable(struct cec_adapter *adap, bool enable) +{ + struct adv7511 *adv7511 = cec_get_drvdata(adap); + unsigned int offset = adv7511->type == ADV7533 ? + ADV7533_REG_CEC_OFFSET : 0; + + if (adv7511->i2c_cec == NULL) + return -EIO; + + if (!adv7511->cec_enabled_adap && enable) { + /* power up cec section */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_CLK_DIV + offset, + 0x03, 0x01); + /* legacy mode and clear all rx buffers */ + regmap_write(adv7511->regmap_cec, + ADV7511_REG_CEC_RX_BUFFERS + offset, 0x07); + regmap_write(adv7511->regmap_cec, + ADV7511_REG_CEC_RX_BUFFERS + offset, 0); + /* initially disable tx */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_TX_ENABLE + offset, 1, 0); + /* enabled irqs: */ + /* tx: ready */ + /* tx: arbitration lost */ + /* tx: retry timeout */ + /* rx: ready 1 */ + regmap_update_bits(adv7511->regmap, + ADV7511_REG_INT_ENABLE(1), 0x3f, + ADV7511_INT1_CEC_MASK); + } else if (adv7511->cec_enabled_adap && !enable) { + regmap_update_bits(adv7511->regmap, + ADV7511_REG_INT_ENABLE(1), 0x3f, 0); + /* disable address mask 1-3 */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_LOG_ADDR_MASK + offset, + 0x70, 0x00); + /* power down cec section */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_CLK_DIV + offset, + 0x03, 0x00); + adv7511->cec_valid_addrs = 0; + } + adv7511->cec_enabled_adap = enable; + return 0; +} + +static int adv7511_cec_adap_log_addr(struct cec_adapter *adap, u8 addr) +{ + struct adv7511 *adv7511 = cec_get_drvdata(adap); + unsigned int offset = adv7511->type == ADV7533 ? + ADV7533_REG_CEC_OFFSET : 0; + unsigned int i, free_idx = ADV7511_MAX_ADDRS; + + if (!adv7511->cec_enabled_adap) + return addr == CEC_LOG_ADDR_INVALID ? 0 : -EIO; + + if (addr == CEC_LOG_ADDR_INVALID) { + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_LOG_ADDR_MASK + offset, + 0x70, 0); + adv7511->cec_valid_addrs = 0; + return 0; + } + + for (i = 0; i < ADV7511_MAX_ADDRS; i++) { + bool is_valid = adv7511->cec_valid_addrs & (1 << i); + + if (free_idx == ADV7511_MAX_ADDRS && !is_valid) + free_idx = i; + if (is_valid && adv7511->cec_addr[i] == addr) + return 0; + } + if (i == ADV7511_MAX_ADDRS) { + i = free_idx; + if (i == ADV7511_MAX_ADDRS) + return -ENXIO; + } + adv7511->cec_addr[i] = addr; + adv7511->cec_valid_addrs |= 1 << i; + + switch (i) { + case 0: + /* enable address mask 0 */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_LOG_ADDR_MASK + offset, + 0x10, 0x10); + /* set address for mask 0 */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_LOG_ADDR_0_1 + offset, + 0x0f, addr); + break; + case 1: + /* enable address mask 1 */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_LOG_ADDR_MASK + offset, + 0x20, 0x20); + /* set address for mask 1 */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_LOG_ADDR_0_1 + offset, + 0xf0, addr << 4); + break; + case 2: + /* enable address mask 2 */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_LOG_ADDR_MASK + offset, + 0x40, 0x40); + /* set address for mask 1 */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_LOG_ADDR_2 + offset, + 0x0f, addr); + break; + } + return 0; +} + +static int adv7511_cec_adap_transmit(struct cec_adapter *adap, u8 attempts, + u32 signal_free_time, struct cec_msg *msg) +{ + struct adv7511 *adv7511 = cec_get_drvdata(adap); + unsigned int offset = adv7511->type == ADV7533 ? + ADV7533_REG_CEC_OFFSET : 0; + u8 len = msg->len; + unsigned int i; + + /* + * The number of retries is the number of attempts - 1, but retry + * at least once. It's not clear if a value of 0 is allowed, so + * let's do at least one retry. + */ + regmap_update_bits(adv7511->regmap_cec, + ADV7511_REG_CEC_TX_RETRY + offset, + 0x70, max(1, attempts - 1) << 4); + + /* blocking, clear cec tx irq status */ + regmap_update_bits(adv7511->regmap, ADV7511_REG_INT(1), 0x38, 0x38); + + /* write data */ + for (i = 0; i < len; i++) + regmap_write(adv7511->regmap_cec, + i + ADV7511_REG_CEC_TX_FRAME_HDR + offset, + msg->msg[i]); + + /* set length (data + header) */ + regmap_write(adv7511->regmap_cec, + ADV7511_REG_CEC_TX_FRAME_LEN + offset, len); + /* start transmit, enable tx */ + regmap_write(adv7511->regmap_cec, + ADV7511_REG_CEC_TX_ENABLE + offset, 0x01); + return 0; +} + +static const struct cec_adap_ops adv7511_cec_adap_ops = { + .adap_enable = adv7511_cec_adap_enable, + .adap_log_addr = adv7511_cec_adap_log_addr, + .adap_transmit = adv7511_cec_adap_transmit, +}; + +static int adv7511_cec_parse_dt(struct device *dev, struct adv7511 *adv7511) +{ + adv7511->cec_clk = devm_clk_get(dev, "cec"); + if (IS_ERR(adv7511->cec_clk)) { + int ret = PTR_ERR(adv7511->cec_clk); + + adv7511->cec_clk = NULL; + return ret; + } + clk_prepare_enable(adv7511->cec_clk); + adv7511->cec_clk_freq = clk_get_rate(adv7511->cec_clk); + return 0; +} + +int adv7511_cec_init(struct device *dev, struct adv7511 *adv7511, + unsigned int offset) +{ + int ret = adv7511_cec_parse_dt(dev, adv7511); + + if (ret) + return ret; + + adv7511->cec_adap = cec_allocate_adapter(&adv7511_cec_adap_ops, + adv7511, dev_name(dev), CEC_CAP_DEFAULTS, ADV7511_MAX_ADDRS); + if (IS_ERR(adv7511->cec_adap)) + return PTR_ERR(adv7511->cec_adap); + + regmap_write(adv7511->regmap, ADV7511_REG_CEC_CTRL + offset, 0); + /* cec soft reset */ + regmap_write(adv7511->regmap_cec, + ADV7511_REG_CEC_SOFT_RESET + offset, 0x01); + regmap_write(adv7511->regmap_cec, + ADV7511_REG_CEC_SOFT_RESET + offset, 0x00); + + /* legacy mode */ + regmap_write(adv7511->regmap_cec, + ADV7511_REG_CEC_RX_BUFFERS + offset, 0x00); + + regmap_write(adv7511->regmap_cec, + ADV7511_REG_CEC_CLK_DIV + offset, + ((adv7511->cec_clk_freq / 750000) - 1) << 2); + + ret = cec_register_adapter(adv7511->cec_adap, dev); + if (ret) { + cec_delete_adapter(adv7511->cec_adap); + adv7511->cec_adap = NULL; + } + return ret; +} diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c index b2431aee7887..3a33075dbb22 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c @@ -11,12 +11,15 @@ #include <linux/module.h> #include <linux/of_device.h> #include <linux/slab.h> +#include <linux/clk.h> #include <drm/drmP.h> #include <drm/drm_atomic.h> #include <drm/drm_atomic_helper.h> #include <drm/drm_edid.h> +#include <media/cec.h> + #include "adv7511.h" /* ADI recommended values for proper operation. */ @@ -339,8 +342,10 @@ static void __adv7511_power_on(struct adv7511 *adv7511) */ regmap_write(adv7511->regmap, ADV7511_REG_INT_ENABLE(0), ADV7511_INT0_EDID_READY | ADV7511_INT0_HPD); - regmap_write(adv7511->regmap, ADV7511_REG_INT_ENABLE(1), - ADV7511_INT1_DDC_ERROR); + regmap_update_bits(adv7511->regmap, + ADV7511_REG_INT_ENABLE(1), + ADV7511_INT1_DDC_ERROR, + ADV7511_INT1_DDC_ERROR); } /* @@ -376,6 +381,9 @@ static void __adv7511_power_off(struct adv7511 *adv7511) regmap_update_bits(adv7511->regmap, ADV7511_REG_POWER, ADV7511_POWER_POWER_DOWN, ADV7511_POWER_POWER_DOWN); + regmap_update_bits(adv7511->regmap, + ADV7511_REG_INT_ENABLE(1), + ADV7511_INT1_DDC_ERROR, 0); regcache_mark_dirty(adv7511->regmap); } @@ -426,6 +434,8 @@ static void adv7511_hpd_work(struct work_struct *work) if (adv7511->connector.status != status) { adv7511->connector.status = status; + if (status == connector_status_disconnected) + cec_phys_addr_invalidate(adv7511->cec_adap); drm_kms_helper_hotplug_event(adv7511->connector.dev); } } @@ -456,6 +466,10 @@ static int adv7511_irq_process(struct adv7511 *adv7511, bool process_hpd) wake_up_all(&adv7511->wq); } +#ifdef CONFIG_DRM_I2C_ADV7511_CEC + adv7511_cec_irq_process(adv7511, irq1); +#endif + return 0; } @@ -599,6 +613,8 @@ static int adv7511_get_modes(struct adv7511 *adv7511, adv7511_set_config_csc(adv7511, connector, adv7511->rgb); + cec_s_phys_addr_from_edid(adv7511->cec_adap, edid); + return count; } @@ -919,6 +935,65 @@ static void adv7511_uninit_regulators(struct adv7511 *adv) regulator_bulk_disable(adv->num_supplies, adv->supplies); } +static bool adv7511_cec_register_volatile(struct device *dev, unsigned int reg) +{ + struct i2c_client *i2c = to_i2c_client(dev); + struct adv7511 *adv7511 = i2c_get_clientdata(i2c); + + if (adv7511->type == ADV7533) + reg -= ADV7533_REG_CEC_OFFSET; + + switch (reg) { + case ADV7511_REG_CEC_RX_FRAME_HDR: + case ADV7511_REG_CEC_RX_FRAME_DATA0... + ADV7511_REG_CEC_RX_FRAME_DATA0 + 14: + case ADV7511_REG_CEC_RX_FRAME_LEN: + case ADV7511_REG_CEC_RX_BUFFERS: + case ADV7511_REG_CEC_TX_LOW_DRV_CNT: + return true; + } + + return false; +} + +static const struct regmap_config adv7511_cec_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = adv7511_cec_register_volatile, +}; + +static int adv7511_init_cec_regmap(struct adv7511 *adv) +{ + int ret; + + adv->i2c_cec = i2c_new_dummy(adv->i2c_main->adapter, + adv->i2c_main->addr - 1); + if (!adv->i2c_cec) + return -ENOMEM; + i2c_set_clientdata(adv->i2c_cec, adv); + + adv->regmap_cec = devm_regmap_init_i2c(adv->i2c_cec, + &adv7511_cec_regmap_config); + if (IS_ERR(adv->regmap_cec)) { + ret = PTR_ERR(adv->regmap_cec); + goto err; + } + + if (adv->type == ADV7533) { + ret = adv7533_patch_cec_registers(adv); + if (ret) + goto err; + } + + return 0; +err: + i2c_unregister_device(adv->i2c_cec); + return ret; +} + static int adv7511_parse_dt(struct device_node *np, struct adv7511_link_config *config) { @@ -1009,6 +1084,7 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) struct device *dev = &i2c->dev; unsigned int main_i2c_addr = i2c->addr << 1; unsigned int edid_i2c_addr = main_i2c_addr + 4; + unsigned int offset; unsigned int val; int ret; @@ -1092,11 +1168,9 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) goto uninit_regulators; } - if (adv7511->type == ADV7533) { - ret = adv7533_init_cec(adv7511); - if (ret) - goto err_i2c_unregister_edid; - } + ret = adv7511_init_cec_regmap(adv7511); + if (ret) + goto err_i2c_unregister_edid; INIT_WORK(&adv7511->hpd_work, adv7511_hpd_work); @@ -1111,10 +1185,6 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) goto err_unregister_cec; } - /* CEC is unused for now */ - regmap_write(adv7511->regmap, ADV7511_REG_CEC_CTRL, - ADV7511_CEC_CTRL_POWER_DOWN); - adv7511_power_off(adv7511); i2c_set_clientdata(i2c, adv7511); @@ -1129,10 +1199,23 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id) adv7511_audio_init(dev, adv7511); + offset = adv7511->type == ADV7533 ? ADV7533_REG_CEC_OFFSET : 0; + +#ifdef CONFIG_DRM_I2C_ADV7511_CEC + ret = adv7511_cec_init(dev, adv7511, offset); + if (ret) + goto err_unregister_cec; +#else + regmap_write(adv7511->regmap, ADV7511_REG_CEC_CTRL + offset, + ADV7511_CEC_CTRL_POWER_DOWN); +#endif + return 0; err_unregister_cec: - adv7533_uninit_cec(adv7511); + i2c_unregister_device(adv7511->i2c_cec); + if (adv7511->cec_clk) + clk_disable_unprepare(adv7511->cec_clk); err_i2c_unregister_edid: i2c_unregister_device(adv7511->i2c_edid); uninit_regulators: @@ -1145,10 +1228,11 @@ static int adv7511_remove(struct i2c_client *i2c) { struct adv7511 *adv7511 = i2c_get_clientdata(i2c); - if (adv7511->type == ADV7533) { + if (adv7511->type == ADV7533) adv7533_detach_dsi(adv7511); - adv7533_uninit_cec(adv7511); - } + i2c_unregister_device(adv7511->i2c_cec); + if (adv7511->cec_clk) + clk_disable_unprepare(adv7511->cec_clk); adv7511_uninit_regulators(adv7511); @@ -1156,6 +1240,8 @@ static int adv7511_remove(struct i2c_client *i2c) adv7511_audio_exit(adv7511); + cec_unregister_adapter(adv7511->cec_adap); + i2c_unregister_device(adv7511->i2c_edid); kfree(adv7511->edid); diff --git a/drivers/gpu/drm/bridge/adv7511/adv7533.c b/drivers/gpu/drm/bridge/adv7511/adv7533.c index ac804f81e2f6..185b6d842166 100644 --- a/drivers/gpu/drm/bridge/adv7511/adv7533.c +++ b/drivers/gpu/drm/bridge/adv7511/adv7533.c @@ -32,14 +32,6 @@ static const struct reg_sequence adv7533_cec_fixed_registers[] = { { 0x05, 0xc8 }, }; -static const struct regmap_config adv7533_cec_regmap_config = { - .reg_bits = 8, - .val_bits = 8, - - .max_register = 0xff, - .cache_type = REGCACHE_RBTREE, -}; - static void adv7511_dsi_config_timing_gen(struct adv7511 *adv) { struct mipi_dsi_device *dsi = adv->dsi; @@ -145,37 +137,11 @@ int adv7533_patch_registers(struct adv7511 *adv) ARRAY_SIZE(adv7533_fixed_registers)); } -void adv7533_uninit_cec(struct adv7511 *adv) -{ - i2c_unregister_device(adv->i2c_cec); -} - -int adv7533_init_cec(struct adv7511 *adv) +int adv7533_patch_cec_registers(struct adv7511 *adv) { - int ret; - - adv->i2c_cec = i2c_new_dummy(adv->i2c_main->adapter, - adv->i2c_main->addr - 1); - if (!adv->i2c_cec) - return -ENOMEM; - - adv->regmap_cec = devm_regmap_init_i2c(adv->i2c_cec, - &adv7533_cec_regmap_config); - if (IS_ERR(adv->regmap_cec)) { - ret = PTR_ERR(adv->regmap_cec); - goto err; - } - - ret = regmap_register_patch(adv->regmap_cec, + return regmap_register_patch(adv->regmap_cec, adv7533_cec_fixed_registers, ARRAY_SIZE(adv7533_cec_fixed_registers)); - if (ret) - goto err; - - return 0; -err: - adv7533_uninit_cec(adv); - return ret; } int adv7533_attach_dsi(struct adv7511 *adv)