diff options
author | Dave Airlie <airlied@redhat.com> | 2018-05-22 03:20:45 +0300 |
---|---|---|
committer | Dave Airlie <airlied@redhat.com> | 2018-05-22 03:20:45 +0300 |
commit | 1dd6eb88af7a511b090fa847ed826bf3accf3bce (patch) | |
tree | 34c8b7c0da62e78d301946ba325b54f2e89a898f /drivers/gpu/drm/i2c/tda998x_drv.c | |
parent | 3b064e6f7be75efbb5d8ec5991cd64c8ed09e0dd (diff) | |
parent | ba52762fb1430b2a2ea8127c1a292c15f13b8dac (diff) | |
download | linux-1dd6eb88af7a511b090fa847ed826bf3accf3bce.tar.xz |
Merge branch 'drm-tda998x-devel' of git://git.armlinux.org.uk/~rmk/linux-arm into drm-next
Please incorporate support for TDA998x I2C driver CEC
Signed-off-by: Dave Airlie <airlied@redhat.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20180424095456.GA32460@rmk-PC.armlinux.org.uk
Diffstat (limited to 'drivers/gpu/drm/i2c/tda998x_drv.c')
-rw-r--r-- | drivers/gpu/drm/i2c/tda998x_drv.c | 242 |
1 files changed, 215 insertions, 27 deletions
diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c index 421c8a72369e..6ebd8842dbcc 100644 --- a/drivers/gpu/drm/i2c/tda998x_drv.c +++ b/drivers/gpu/drm/i2c/tda998x_drv.c @@ -16,8 +16,10 @@ */ #include <linux/component.h> +#include <linux/gpio/consumer.h> #include <linux/hdmi.h> #include <linux/module.h> +#include <linux/platform_data/tda9950.h> #include <linux/irq.h> #include <sound/asoundef.h> #include <sound/hdmi-codec.h> @@ -29,6 +31,8 @@ #include <drm/drm_of.h> #include <drm/i2c/tda998x.h> +#include <media/cec-notifier.h> + #define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__) struct tda998x_audio_port { @@ -55,6 +59,7 @@ struct tda998x_priv { struct platform_device *audio_pdev; struct mutex audio_mutex; + struct mutex edid_mutex; wait_queue_head_t wq_edid; volatile int wq_edid_wait; @@ -67,6 +72,9 @@ struct tda998x_priv { struct drm_connector connector; struct tda998x_audio_port audio_port[2]; + struct tda9950_glue cec_glue; + struct gpio_desc *calib; + struct cec_notifier *cec_notify; }; #define conn_to_tda998x_priv(x) \ @@ -345,6 +353,12 @@ struct tda998x_priv { #define REG_CEC_INTSTATUS 0xee /* read */ # define CEC_INTSTATUS_CEC (1 << 0) # define CEC_INTSTATUS_HDMI (1 << 1) +#define REG_CEC_CAL_XOSC_CTRL1 0xf2 +# define CEC_CAL_XOSC_CTRL1_ENA_CAL BIT(0) +#define REG_CEC_DES_FREQ2 0xf5 +# define CEC_DES_FREQ2_DIS_AUTOCAL BIT(7) +#define REG_CEC_CLK 0xf6 +# define CEC_CLK_FRO 0x11 #define REG_CEC_FRO_IM_CLK_CTRL 0xfb /* read/write */ # define CEC_FRO_IM_CLK_CTRL_GHOST_DIS (1 << 7) # define CEC_FRO_IM_CLK_CTRL_ENA_OTP (1 << 6) @@ -359,6 +373,7 @@ struct tda998x_priv { # define CEC_RXSHPDLEV_HPD (1 << 1) #define REG_CEC_ENAMODS 0xff /* read/write */ +# define CEC_ENAMODS_EN_CEC_CLK (1 << 7) # define CEC_ENAMODS_DIS_FRO (1 << 6) # define CEC_ENAMODS_DIS_CCLK (1 << 5) # define CEC_ENAMODS_EN_RXSENS (1 << 2) @@ -417,6 +432,114 @@ cec_read(struct tda998x_priv *priv, u8 addr) return val; } +static void cec_enamods(struct tda998x_priv *priv, u8 mods, bool enable) +{ + int val = cec_read(priv, REG_CEC_ENAMODS); + + if (val < 0) + return; + + if (enable) + val |= mods; + else + val &= ~mods; + + cec_write(priv, REG_CEC_ENAMODS, val); +} + +static void tda998x_cec_set_calibration(struct tda998x_priv *priv, bool enable) +{ + if (enable) { + u8 val; + + cec_write(priv, 0xf3, 0xc0); + cec_write(priv, 0xf4, 0xd4); + + /* Enable automatic calibration mode */ + val = cec_read(priv, REG_CEC_DES_FREQ2); + val &= ~CEC_DES_FREQ2_DIS_AUTOCAL; + cec_write(priv, REG_CEC_DES_FREQ2, val); + + /* Enable free running oscillator */ + cec_write(priv, REG_CEC_CLK, CEC_CLK_FRO); + cec_enamods(priv, CEC_ENAMODS_DIS_FRO, false); + + cec_write(priv, REG_CEC_CAL_XOSC_CTRL1, + CEC_CAL_XOSC_CTRL1_ENA_CAL); + } else { + cec_write(priv, REG_CEC_CAL_XOSC_CTRL1, 0); + } +} + +/* + * Calibration for the internal oscillator: we need to set calibration mode, + * and then pulse the IRQ line low for a 10ms ± 1% period. + */ +static void tda998x_cec_calibration(struct tda998x_priv *priv) +{ + struct gpio_desc *calib = priv->calib; + + mutex_lock(&priv->edid_mutex); + if (priv->hdmi->irq > 0) + disable_irq(priv->hdmi->irq); + gpiod_direction_output(calib, 1); + tda998x_cec_set_calibration(priv, true); + + local_irq_disable(); + gpiod_set_value(calib, 0); + mdelay(10); + gpiod_set_value(calib, 1); + local_irq_enable(); + + tda998x_cec_set_calibration(priv, false); + gpiod_direction_input(calib); + if (priv->hdmi->irq > 0) + enable_irq(priv->hdmi->irq); + mutex_unlock(&priv->edid_mutex); +} + +static int tda998x_cec_hook_init(void *data) +{ + struct tda998x_priv *priv = data; + struct gpio_desc *calib; + + calib = gpiod_get(&priv->hdmi->dev, "nxp,calib", GPIOD_ASIS); + if (IS_ERR(calib)) { + dev_warn(&priv->hdmi->dev, "failed to get calibration gpio: %ld\n", + PTR_ERR(calib)); + return PTR_ERR(calib); + } + + priv->calib = calib; + + return 0; +} + +static void tda998x_cec_hook_exit(void *data) +{ + struct tda998x_priv *priv = data; + + gpiod_put(priv->calib); + priv->calib = NULL; +} + +static int tda998x_cec_hook_open(void *data) +{ + struct tda998x_priv *priv = data; + + cec_enamods(priv, CEC_ENAMODS_EN_CEC_CLK | CEC_ENAMODS_EN_CEC, true); + tda998x_cec_calibration(priv); + + return 0; +} + +static void tda998x_cec_hook_release(void *data) +{ + struct tda998x_priv *priv = data; + + cec_enamods(priv, CEC_ENAMODS_EN_CEC_CLK | CEC_ENAMODS_EN_CEC, false); +} + static int set_page(struct tda998x_priv *priv, u16 reg) { @@ -657,10 +780,13 @@ static irqreturn_t tda998x_irq_thread(int irq, void *data) sta, cec, lvl, flag0, flag1, flag2); if (cec & CEC_RXSHPDINT_HPD) { - if (lvl & CEC_RXSHPDLEV_HPD) + if (lvl & CEC_RXSHPDLEV_HPD) { tda998x_edid_delay_start(priv); - else + } else { schedule_work(&priv->detect_work); + cec_notifier_set_phys_addr(priv->cec_notify, + CEC_PHYS_ADDR_INVALID); + } handled = true; } @@ -981,6 +1107,8 @@ static int tda998x_connector_fill_modes(struct drm_connector *connector, if (connector->edid_blob_ptr) { struct edid *edid = (void *)connector->edid_blob_ptr->data; + cec_notifier_set_phys_addr_from_edid(priv->cec_notify, edid); + priv->sink_has_audio = drm_detect_monitor_audio(edid); } else { priv->sink_has_audio = false; @@ -1024,6 +1152,8 @@ static int read_edid_block(void *data, u8 *buf, unsigned int blk, size_t length) offset = (blk & 1) ? 128 : 0; segptr = blk / 2; + mutex_lock(&priv->edid_mutex); + reg_write(priv, REG_DDC_ADDR, 0xa0); reg_write(priv, REG_DDC_OFFS, offset); reg_write(priv, REG_DDC_SEGM_ADDR, 0x60); @@ -1043,14 +1173,15 @@ static int read_edid_block(void *data, u8 *buf, unsigned int blk, size_t length) msecs_to_jiffies(100)); if (i < 0) { dev_err(&priv->hdmi->dev, "read edid wait err %d\n", i); - return i; + ret = i; + goto failed; } } else { for (i = 100; i > 0; i--) { msleep(1); ret = reg_read(priv, REG_INT_FLAGS_2); if (ret < 0) - return ret; + goto failed; if (ret & INT_FLAGS_2_EDID_BLK_RD) break; } @@ -1058,17 +1189,22 @@ static int read_edid_block(void *data, u8 *buf, unsigned int blk, size_t length) if (i == 0) { dev_err(&priv->hdmi->dev, "read edid timeout\n"); - return -ETIMEDOUT; + ret = -ETIMEDOUT; + goto failed; } ret = reg_read_range(priv, REG_EDID_DATA_0, buf, length); if (ret != length) { dev_err(&priv->hdmi->dev, "failed to read edid block %d: %d\n", blk, ret); - return ret; + goto failed; } - return 0; + ret = 0; + + failed: + mutex_unlock(&priv->edid_mutex); + return ret; } static int tda998x_connector_get_modes(struct drm_connector *connector) @@ -1423,6 +1559,9 @@ static void tda998x_destroy(struct tda998x_priv *priv) cancel_work_sync(&priv->detect_work); i2c_unregister_device(priv->cec); + + if (priv->cec_notify) + cec_notifier_put(priv->cec_notify); } /* I2C driver functions */ @@ -1472,10 +1611,16 @@ static int tda998x_get_audio_ports(struct tda998x_priv *priv, static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) { struct device_node *np = client->dev.of_node; + struct i2c_board_info cec_info; u32 video; int rev_lo, rev_hi, ret; - mutex_init(&priv->audio_mutex); /* Protect access from audio thread */ + mutex_init(&priv->mutex); /* protect the page access */ + mutex_init(&priv->audio_mutex); /* protect access from audio thread */ + mutex_init(&priv->edid_mutex); + init_waitqueue_head(&priv->edid_delay_waitq); + timer_setup(&priv->edid_delay_timer, tda998x_edid_delay_done, 0); + INIT_WORK(&priv->detect_work, tda998x_detect_work); priv->vip_cntrl_0 = VIP_CNTRL_0_SWAP_A(2) | VIP_CNTRL_0_SWAP_B(3); priv->vip_cntrl_1 = VIP_CNTRL_1_SWAP_C(0) | VIP_CNTRL_1_SWAP_D(1); @@ -1485,14 +1630,6 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) priv->cec_addr = 0x34 + (client->addr & 0x03); priv->current_page = 0xff; priv->hdmi = client; - priv->cec = i2c_new_dummy(client->adapter, priv->cec_addr); - if (!priv->cec) - return -ENODEV; - - mutex_init(&priv->mutex); /* protect the page access */ - init_waitqueue_head(&priv->edid_delay_waitq); - timer_setup(&priv->edid_delay_timer, tda998x_edid_delay_done, 0); - INIT_WORK(&priv->detect_work, tda998x_detect_work); /* wake up the device: */ cec_write(priv, REG_CEC_ENAMODS, @@ -1502,10 +1639,15 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) /* read version: */ rev_lo = reg_read(priv, REG_VERSION_LSB); + if (rev_lo < 0) { + dev_err(&client->dev, "failed to read version: %d\n", rev_lo); + return rev_lo; + } + rev_hi = reg_read(priv, REG_VERSION_MSB); - if (rev_lo < 0 || rev_hi < 0) { - ret = rev_lo < 0 ? rev_lo : rev_hi; - goto fail; + if (rev_hi < 0) { + dev_err(&client->dev, "failed to read version: %d\n", rev_hi); + return rev_hi; } priv->rev = rev_lo | rev_hi << 8; @@ -1529,7 +1671,7 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) default: dev_err(&client->dev, "found unsupported device: %04x\n", priv->rev); - goto fail; + return -ENXIO; } /* after reset, enable DDC: */ @@ -1545,6 +1687,15 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) cec_write(priv, REG_CEC_FRO_IM_CLK_CTRL, CEC_FRO_IM_CLK_CTRL_GHOST_DIS | CEC_FRO_IM_CLK_CTRL_IMCLK_SEL); + /* ensure interrupts are disabled */ + cec_write(priv, REG_CEC_RXSHPDINTENA, 0); + + /* clear pending interrupts */ + cec_read(priv, REG_CEC_RXSHPDINT); + reg_read(priv, REG_INT_FLAGS_0); + reg_read(priv, REG_INT_FLAGS_1); + reg_read(priv, REG_INT_FLAGS_2); + /* initialize the optional IRQ */ if (client->irq) { unsigned long irq_flags; @@ -1552,13 +1703,11 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) /* init read EDID waitqueue and HDP work */ init_waitqueue_head(&priv->wq_edid); - /* clear pending interrupts */ - reg_read(priv, REG_INT_FLAGS_0); - reg_read(priv, REG_INT_FLAGS_1); - reg_read(priv, REG_INT_FLAGS_2); - irq_flags = irqd_get_trigger_type(irq_get_irq_data(client->irq)); + + priv->cec_glue.irq_flags = irq_flags; + irq_flags |= IRQF_SHARED | IRQF_ONESHOT; ret = request_threaded_irq(client->irq, NULL, tda998x_irq_thread, irq_flags, @@ -1567,13 +1716,46 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) dev_err(&client->dev, "failed to request IRQ#%u: %d\n", client->irq, ret); - goto fail; + goto err_irq; } /* enable HPD irq */ cec_write(priv, REG_CEC_RXSHPDINTENA, CEC_RXSHPDLEV_HPD); } + priv->cec_notify = cec_notifier_get(&client->dev); + if (!priv->cec_notify) { + ret = -ENOMEM; + goto fail; + } + + priv->cec_glue.parent = &client->dev; + priv->cec_glue.data = priv; + priv->cec_glue.init = tda998x_cec_hook_init; + priv->cec_glue.exit = tda998x_cec_hook_exit; + priv->cec_glue.open = tda998x_cec_hook_open; + priv->cec_glue.release = tda998x_cec_hook_release; + + /* + * Some TDA998x are actually two I2C devices merged onto one piece + * of silicon: TDA9989 and TDA19989 combine the HDMI transmitter + * with a slightly modified TDA9950 CEC device. The CEC device + * is at the TDA9950 address, with the address pins strapped across + * to the TDA998x address pins. Hence, it always has the same + * offset. + */ + memset(&cec_info, 0, sizeof(cec_info)); + strlcpy(cec_info.type, "tda9950", sizeof(cec_info.type)); + cec_info.addr = priv->cec_addr; + cec_info.platform_data = &priv->cec_glue; + cec_info.irq = client->irq; + + priv->cec = i2c_new_device(client->adapter, &cec_info); + if (!priv->cec) { + ret = -ENODEV; + goto fail; + } + /* enable EDID read irq: */ reg_set(priv, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD); @@ -1596,12 +1778,18 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) tda998x_audio_codec_init(priv, &client->dev); return 0; + fail: /* if encoder_init fails, the encoder slave is never registered, * so cleanup here: */ i2c_unregister_device(priv->cec); - return -ENXIO; + if (priv->cec_notify) + cec_notifier_put(priv->cec_notify); + if (client->irq) + free_irq(client->irq, priv); +err_irq: + return ret; } static void tda998x_encoder_prepare(struct drm_encoder *encoder) |