diff options
author | Jon Medhurst <tixy@linaro.org> | 2015-12-03 14:47:09 +0000 |
---|---|---|
committer | Jon Medhurst <tixy@linaro.org> | 2015-12-03 14:47:09 +0000 |
commit | 998c02523c03eb4cf590e0dfc48deac84923a9eb (patch) | |
tree | d8a7327aa157ffc225673b9f54a6d70f19bb7118 | |
parent | 33e2b40b8c9afdf18430ec298f78f149f6dd8522 (diff) | |
parent | 5609c1f327d2369ecdeb35e3a45549c8cd552b54 (diff) |
Merge branch 'lsk-3.18-armlt-tda998x' into integration-lsk-3.18-armlt-android
-rw-r--r-- | Documentation/devicetree/bindings/drm/i2c/tda998x.txt | 14 | ||||
-rw-r--r-- | drivers/gpu/drm/i2c/Makefile | 2 | ||||
-rw-r--r-- | drivers/gpu/drm/i2c/tda998x_codec.c | 248 | ||||
-rw-r--r-- | drivers/gpu/drm/i2c/tda998x_drv.c | 148 | ||||
-rw-r--r-- | drivers/gpu/drm/i2c/tda998x_drv.h | 34 | ||||
-rw-r--r-- | include/drm/i2c/tda998x.h | 1 |
6 files changed, 412 insertions, 35 deletions
diff --git a/Documentation/devicetree/bindings/drm/i2c/tda998x.txt b/Documentation/devicetree/bindings/drm/i2c/tda998x.txt index e9e4bce40760..adac4dd2de5f 100644 --- a/Documentation/devicetree/bindings/drm/i2c/tda998x.txt +++ b/Documentation/devicetree/bindings/drm/i2c/tda998x.txt @@ -17,6 +17,15 @@ Optional properties: - video-ports: 24 bits value which defines how the video controller output is wired to the TDA998x input - default: <0x230145> + - audio-ports: one or two values corresponding to entries in + the audio-port-names property. + + - audio-port-names: must contain "i2s", "spdif" entries + matching entries in the audio-ports property. + + - #sound-dai-cells: must be set to <1> for use with the simple-card. + The DAI 0 is the I2S input and the DAI 1 is the S/PDIF input. + Example: tda998x: hdmi-encoder { @@ -26,4 +35,9 @@ Example: interrupts = <27 2>; /* falling edge */ pinctrl-0 = <&pmx_camera>; pinctrl-names = "default"; + + audio-ports = <0x03>, <0x04>; + audio-port-names = "i2s", "spdif"; + #sound-dai-cells = <1>; + }; }; diff --git a/drivers/gpu/drm/i2c/Makefile b/drivers/gpu/drm/i2c/Makefile index 43aa33baebed..f2d625c72329 100644 --- a/drivers/gpu/drm/i2c/Makefile +++ b/drivers/gpu/drm/i2c/Makefile @@ -6,5 +6,5 @@ obj-$(CONFIG_DRM_I2C_CH7006) += ch7006.o sil164-y := sil164_drv.o obj-$(CONFIG_DRM_I2C_SIL164) += sil164.o -tda998x-y := tda998x_drv.o +tda998x-y := tda998x_drv.o tda998x_codec.o obj-$(CONFIG_DRM_I2C_NXP_TDA998X) += tda998x.o diff --git a/drivers/gpu/drm/i2c/tda998x_codec.c b/drivers/gpu/drm/i2c/tda998x_codec.c new file mode 100644 index 000000000000..c84d2b58f42d --- /dev/null +++ b/drivers/gpu/drm/i2c/tda998x_codec.c @@ -0,0 +1,248 @@ +/* + * ALSA SoC TDA998X CODEC + * + * Copyright (C) 2014 Jean-Francois Moine + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <sound/soc.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <linux/of.h> +#include <linux/i2c.h> +#include <drm/drm_encoder_slave.h> +#include <drm/i2c/tda998x.h> + +#include "tda998x_drv.h" + +#define TDA998X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static int tda_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct tda998x_priv *priv = snd_soc_codec_get_drvdata(dai->codec); + u8 *eld = priv->eld; + struct snd_pcm_runtime *runtime = substream->runtime; + u8 *sad; + int sad_count; + unsigned eld_ver, mnl, rate_mask; + unsigned max_channels, fmt; + u64 formats; + struct snd_pcm_hw_constraint_list *rate_constraints = + &priv->rate_constraints; + static const u32 hdmi_rates[] = { + 32000, 44100, 48000, 88200, 96000, 176400, 192000 + }; + + /* check if streaming is already active */ + if (priv->dai_id != AFMT_NO_AUDIO) + return -EBUSY; + priv->dai_id = dai->id; + + if (!eld) + return 0; + + /* adjust the hw params from the ELD (EDID) */ + eld_ver = eld[0] >> 3; + if (eld_ver != 2 && eld_ver != 31) + return 0; + + mnl = eld[4] & 0x1f; + if (mnl > 16) + return 0; + + sad_count = eld[5] >> 4; + sad = eld + 20 + mnl; + + /* Start from the basic audio settings */ + max_channels = 2; + rate_mask = 0; + fmt = 0; + while (sad_count--) { + switch (sad[0] & 0x78) { + case 0x08: /* PCM */ + max_channels = max(max_channels, (sad[0] & 7) + 1u); + rate_mask |= sad[1]; + fmt |= sad[2] & 0x07; + break; + } + sad += 3; + } + + /* set the constraints */ + rate_constraints->list = hdmi_rates; + rate_constraints->count = ARRAY_SIZE(hdmi_rates); + rate_constraints->mask = rate_mask; + snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + rate_constraints); + + formats = 0; + if (fmt & 1) + formats |= SNDRV_PCM_FMTBIT_S16_LE; + if (fmt & 2) + formats |= SNDRV_PCM_FMTBIT_S20_3LE; + if (fmt & 4) + formats |= SNDRV_PCM_FMTBIT_S24_LE; + snd_pcm_hw_constraint_mask64(runtime, + SNDRV_PCM_HW_PARAM_FORMAT, + formats); + + snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, + 1, max_channels); + return 0; +} + +static int tda_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct tda998x_priv *priv = snd_soc_codec_get_drvdata(dai->codec); + + /* Requires an attached display */ + if (!priv->encoder->crtc) + return -ENODEV; + + /* if same input and same parameters, do not do a full switch */ + if (dai->id == priv->params.audio_format && + params_format(params) == priv->audio_sample_format) { + tda998x_audio_start(priv, 0); + return 0; + } + priv->params.audio_sample_rate = params_rate(params); + priv->params.audio_format = dai->id; + priv->audio_sample_format = params_format(params); + priv->params.audio_cfg = + priv->audio_ports[dai->id == AFMT_I2S ? 0 : 1]; + tda998x_audio_start(priv, 1); + return 0; +} + +static void tda_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct tda998x_priv *priv = snd_soc_codec_get_drvdata(dai->codec); + + tda998x_audio_stop(priv); + priv->dai_id = AFMT_NO_AUDIO; +} + +static const struct snd_soc_dai_ops tda_ops = { + .startup = tda_startup, + .hw_params = tda_hw_params, + .shutdown = tda_shutdown, +}; + +static struct snd_soc_dai_driver tda998x_dai[] = { + { + .name = "i2s-hifi", + .id = AFMT_I2S, + .playback = { + .stream_name = "HDMI I2S Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 5512, + .rate_max = 192000, + .formats = TDA998X_FORMATS, + }, + .ops = &tda_ops, + }, + { + .name = "spdif-hifi", + .id = AFMT_SPDIF, + .playback = { + .stream_name = "HDMI SPDIF Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_CONTINUOUS, + .rate_min = 22050, + .rate_max = 192000, + .formats = TDA998X_FORMATS, + }, + .ops = &tda_ops, + }, +}; + +static const struct snd_soc_dapm_widget tda_widgets[] = { + SND_SOC_DAPM_OUTPUT("hdmi-out"), +}; +static const struct snd_soc_dapm_route tda_routes[] = { + { "hdmi-out", NULL, "HDMI I2S Playback" }, + { "hdmi-out", NULL, "HDMI SPDIF Playback" }, +}; + +static int tda_probe(struct snd_soc_codec *codec) +{ + struct i2c_client *i2c_client = to_i2c_client(codec->dev); + struct tda998x_priv *priv = i2c_get_clientdata(i2c_client); + struct device_node *np = codec->dev->of_node; + int i, j, ret; + const char *p; + + if (!priv) + return -ENODEV; + snd_soc_codec_set_drvdata(codec, priv); + + if (!np) + return 0; + + /* get the audio input ports*/ + for (i = 0; i < 2; i++) { + u32 port; + + ret = of_property_read_u32_index(np, "audio-ports", i, &port); + if (ret) { + if (i == 0) + dev_err(codec->dev, + "bad or missing audio-ports\n"); + break; + } + ret = of_property_read_string_index(np, "audio-port-names", + i, &p); + if (ret) { + dev_err(codec->dev, + "missing audio-port-names[%d]\n", i); + break; + } + if (strcmp(p, "i2s") == 0) { + j = 0; + } else if (strcmp(p, "spdif") == 0) { + j = 1; + } else { + dev_err(codec->dev, + "bad audio-port-names '%s'\n", p); + break; + } + priv->audio_ports[j] = port; + } + return 0; +} + +static const struct snd_soc_codec_driver soc_codec_tda998x = { + .probe = tda_probe, + .dapm_widgets = tda_widgets, + .num_dapm_widgets = ARRAY_SIZE(tda_widgets), + .dapm_routes = tda_routes, + .num_dapm_routes = ARRAY_SIZE(tda_routes), +}; + +int tda998x_codec_register(struct device *dev) +{ + return snd_soc_register_codec(dev, + &soc_codec_tda998x, + tda998x_dai, ARRAY_SIZE(tda998x_dai)); +} + +void tda998x_codec_unregister(struct device *dev) +{ + snd_soc_unregister_codec(dev); +} diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c index d4762799351d..ee150c7a9916 100644 --- a/drivers/gpu/drm/i2c/tda998x_drv.c +++ b/drivers/gpu/drm/i2c/tda998x_drv.c @@ -20,6 +20,7 @@ #include <linux/module.h> #include <linux/irq.h> #include <sound/asoundef.h> +#include <sound/pcm_params.h> #include <drm/drmP.h> #include <drm/drm_crtc_helper.h> @@ -27,24 +28,9 @@ #include <drm/drm_edid.h> #include <drm/i2c/tda998x.h> -#define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__) +#include "tda998x_drv.h" -struct tda998x_priv { - struct i2c_client *cec; - struct i2c_client *hdmi; - uint16_t rev; - uint8_t current_page; - int dpms; - bool is_hdmi_sink; - u8 vip_cntrl_0; - u8 vip_cntrl_1; - u8 vip_cntrl_2; - struct tda998x_encoder_params params; - - wait_queue_head_t wq_edid; - volatile int wq_edid_wait; - struct drm_encoder *encoder; -}; +#define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__) #define to_tda998x_priv(x) ((struct tda998x_priv *)to_encoder_slave(x)->slave_priv) @@ -402,9 +388,10 @@ reg_read_range(struct tda998x_priv *priv, uint16_t reg, char *buf, int cnt) uint8_t addr = REG2ADDR(reg); int ret; + mutex_lock(&priv->mutex); ret = set_page(priv, reg); if (ret < 0) - return ret; + goto out; ret = i2c_master_send(client, &addr, sizeof(addr)); if (ret < 0) @@ -414,10 +401,12 @@ reg_read_range(struct tda998x_priv *priv, uint16_t reg, char *buf, int cnt) if (ret < 0) goto fail; - return ret; + goto out; fail: dev_err(&client->dev, "Error %d reading from 0x%x\n", ret, reg); +out: + mutex_unlock(&priv->mutex); return ret; } @@ -431,13 +420,16 @@ reg_write_range(struct tda998x_priv *priv, uint16_t reg, uint8_t *p, int cnt) buf[0] = REG2ADDR(reg); memcpy(&buf[1], p, cnt); + mutex_lock(&priv->mutex); ret = set_page(priv, reg); if (ret < 0) - return; + goto out; ret = i2c_master_send(client, buf, cnt + 1); if (ret < 0) dev_err(&client->dev, "Error %d writing to 0x%x\n", ret, reg); +out: + mutex_unlock(&priv->mutex); } static int @@ -459,13 +451,16 @@ reg_write(struct tda998x_priv *priv, uint16_t reg, uint8_t val) uint8_t buf[] = {REG2ADDR(reg), val}; int ret; + mutex_lock(&priv->mutex); ret = set_page(priv, reg); if (ret < 0) - return; + goto out; ret = i2c_master_send(client, buf, sizeof(buf)); if (ret < 0) dev_err(&client->dev, "Error %d writing to 0x%x\n", ret, reg); +out: + mutex_unlock(&priv->mutex); } static void @@ -475,13 +470,16 @@ reg_write16(struct tda998x_priv *priv, uint16_t reg, uint16_t val) uint8_t buf[] = {REG2ADDR(reg), val >> 8, val}; int ret; + mutex_lock(&priv->mutex); ret = set_page(priv, reg); if (ret < 0) - return; + goto out; ret = i2c_master_send(client, buf, sizeof(buf)); if (ret < 0) dev_err(&client->dev, "Error %d writing to 0x%x\n", ret, reg); +out: + mutex_unlock(&priv->mutex); } static void @@ -536,6 +534,17 @@ tda998x_reset(struct tda998x_priv *priv) reg_write(priv, REG_MUX_VP_VIP_OUT, 0x24); } +/* handle HDMI connect/disconnect */ +static void tda998x_hpd(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct tda998x_priv *priv = + container_of(dwork, struct tda998x_priv, dwork); + + if (priv->encoder && priv->encoder->dev) + drm_kms_helper_hotplug_event(priv->encoder->dev); +} + /* * only 2 interrupts may occur: screen plug/unplug and EDID read */ @@ -559,8 +568,7 @@ static irqreturn_t tda998x_irq_thread(int irq, void *data) priv->wq_edid_wait = 0; wake_up(&priv->wq_edid); } else if (cec != 0) { /* HPD change */ - if (priv->encoder && priv->encoder->dev) - drm_helper_hpd_irq_event(priv->encoder->dev); + schedule_delayed_work(&priv->dwork, HZ/10); } return IRQ_HANDLED; } @@ -639,12 +647,11 @@ static void tda998x_configure_audio(struct tda998x_priv *priv, struct drm_display_mode *mode, struct tda998x_encoder_params *p) { - uint8_t buf[6], clksel_aip, clksel_fs, cts_n, adiv; - uint32_t n; + uint8_t buf[6], clksel_aip, clksel_fs, cts_n, adiv, aclk; + uint32_t n, cts; /* Enable audio ports */ reg_write(priv, REG_ENA_AP, p->audio_cfg); - reg_write(priv, REG_ENA_ACLK, p->audio_clk_cfg); /* Set audio input source */ switch (p->audio_format) { @@ -653,13 +660,28 @@ tda998x_configure_audio(struct tda998x_priv *priv, clksel_aip = AIP_CLKSEL_AIP_SPDIF; clksel_fs = AIP_CLKSEL_FS_FS64SPDIF; cts_n = CTS_N_M(3) | CTS_N_K(3); + aclk = 0; /* no clock */ break; case AFMT_I2S: reg_write(priv, REG_MUX_AP, MUX_AP_SELECT_I2S); clksel_aip = AIP_CLKSEL_AIP_I2S; clksel_fs = AIP_CLKSEL_FS_ACLK; - cts_n = CTS_N_M(3) | CTS_N_K(3); + /* with I2S input, the CTS_N predivider depends on + * the sample width */ + switch (priv->audio_sample_format) { + case SNDRV_PCM_FORMAT_S16_LE: + cts_n = CTS_N_M(3) | CTS_N_K(1); + break; + case SNDRV_PCM_FORMAT_S24_LE: + cts_n = CTS_N_M(3) | CTS_N_K(2); + break; + default: + case SNDRV_PCM_FORMAT_S32_LE: + cts_n = CTS_N_M(3) | CTS_N_K(3); + break; + } + aclk = 1; /* clock enable */ break; default: @@ -671,6 +693,7 @@ tda998x_configure_audio(struct tda998x_priv *priv, reg_clear(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_LAYOUT | AIP_CNTRL_0_ACR_MAN); /* auto CTS */ reg_write(priv, REG_CTS_N, cts_n); + reg_write(priv, REG_ENA_ACLK, aclk); /* * Audio input somehow depends on HDMI line rate which is @@ -696,9 +719,25 @@ tda998x_configure_audio(struct tda998x_priv *priv, n = 128 * p->audio_sample_rate / 1000; /* Write the CTS and N values */ - buf[0] = 0x44; - buf[1] = 0x42; - buf[2] = 0x01; + if ((n > 0) && (mode->clock > 0)) { + /* + * The average CTS value is calculated as: + * + * fTMDS * n / (128 * fs) + * + * which equates to: + * + * fTMDS / 1000 + * + * for non-coherent clocks. + */ + cts = mode->clock; + } else { + cts = 82500; + } + buf[0] = cts; + buf[1] = cts >> 8; + buf[2] = cts >> 16; buf[3] = n; buf[4] = n >> 8; buf[5] = n >> 16; @@ -727,6 +766,24 @@ tda998x_configure_audio(struct tda998x_priv *priv, tda998x_write_aif(priv, p); } +/* tda998x codec interface */ +void tda998x_audio_start(struct tda998x_priv *priv, + int full) +{ + struct tda998x_encoder_params *p = &priv->params; + + if (!full) { + reg_write(priv, REG_ENA_AP, p->audio_cfg); + return; + } + tda998x_configure_audio(priv, &priv->encoder->crtc->hwmode, p); +} + +void tda998x_audio_stop(struct tda998x_priv *priv) +{ + reg_write(priv, REG_ENA_AP, 0); +} + /* DRM encoder functions */ static void tda998x_encoder_set_config(struct tda998x_priv *priv, @@ -1139,6 +1196,11 @@ tda998x_encoder_get_modes(struct tda998x_priv *priv, drm_mode_connector_update_edid_property(connector, edid); n = drm_add_edid_modes(connector, edid); priv->is_hdmi_sink = drm_detect_hdmi_monitor(edid); + + /* keep the EDID as ELD for the audio subsystem */ + drm_edid_to_eld(connector, edid); + priv->eld = connector->eld; + kfree(edid); } @@ -1170,8 +1232,12 @@ static void tda998x_destroy(struct tda998x_priv *priv) /* disable all IRQs and free the IRQ handler */ cec_write(priv, REG_CEC_RXSHPDINTENA, 0); reg_clear(priv, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD); - if (priv->hdmi->irq) + if (priv->hdmi->irq) { free_irq(priv->hdmi->irq, priv); + cancel_delayed_work_sync(&priv->dwork); + } + + tda998x_codec_unregister(&priv->hdmi->dev); i2c_unregister_device(priv->cec); } @@ -1255,19 +1321,29 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) struct device_node *np = client->dev.of_node; u32 video; int rev_lo, rev_hi, ret; + unsigned short cec_addr; 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); priv->vip_cntrl_2 = VIP_CNTRL_2_SWAP_E(4) | VIP_CNTRL_2_SWAP_F(5); + priv->params.audio_frame[1] = 1; /* channels - 1 */ + priv->params.audio_sample_rate = 48000; /* 48kHz */ + priv->current_page = 0xff; priv->hdmi = client; - priv->cec = i2c_new_dummy(client->adapter, 0x34); + /* CEC I2C address bound to TDA998x I2C addr by configuration pins */ + cec_addr = 0x34 + (client->addr & 0x03); + priv->cec = i2c_new_dummy(client->adapter, cec_addr); if (!priv->cec) return -ENODEV; priv->dpms = DRM_MODE_DPMS_OFF; + i2c_set_clientdata(client, priv); + + mutex_init(&priv->mutex); /* protect the page access */ + /* wake up the device: */ cec_write(priv, REG_CEC_ENAMODS, CEC_ENAMODS_EN_RXSENS | CEC_ENAMODS_EN_HDMI); @@ -1323,8 +1399,9 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) if (client->irq) { int irqf_trigger; - /* init read EDID waitqueue */ + /* init read EDID waitqueue and HDP work */ init_waitqueue_head(&priv->wq_edid); + INIT_DELAYED_WORK(&priv->dwork, tda998x_hpd); /* clear pending interrupts */ reg_read(priv, REG_INT_FLAGS_0); @@ -1351,6 +1428,9 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) /* enable EDID read irq: */ reg_set(priv, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD); + /* register the audio CODEC */ + tda998x_codec_register(&client->dev); + if (!np) return 0; /* non-DT */ diff --git a/drivers/gpu/drm/i2c/tda998x_drv.h b/drivers/gpu/drm/i2c/tda998x_drv.h new file mode 100644 index 000000000000..a414654b1ebd --- /dev/null +++ b/drivers/gpu/drm/i2c/tda998x_drv.h @@ -0,0 +1,34 @@ +/* tda998x private data */ + +struct tda998x_priv { + struct i2c_client *cec; + struct i2c_client *hdmi; + struct mutex mutex; + struct delayed_work dwork; + uint16_t rev; + uint8_t current_page; + int dpms; + bool is_hdmi_sink; + u8 vip_cntrl_0; + u8 vip_cntrl_1; + u8 vip_cntrl_2; + struct tda998x_encoder_params params; + + wait_queue_head_t wq_edid; + volatile int wq_edid_wait; + struct drm_encoder *encoder; + + u8 audio_ports[2]; + int audio_sample_format; + int dai_id; /* DAI ID when streaming active */ + + u8 *eld; + + struct snd_pcm_hw_constraint_list rate_constraints; +}; + +int tda998x_codec_register(struct device *dev); +void tda998x_codec_unregister(struct device *dev); + +void tda998x_audio_start(struct tda998x_priv *priv, int full); +void tda998x_audio_stop(struct tda998x_priv *priv); diff --git a/include/drm/i2c/tda998x.h b/include/drm/i2c/tda998x.h index 3e419d92cf5a..31757dff5e91 100644 --- a/include/drm/i2c/tda998x.h +++ b/include/drm/i2c/tda998x.h @@ -20,6 +20,7 @@ struct tda998x_encoder_params { u8 audio_frame[6]; enum { + AFMT_NO_AUDIO = 0, AFMT_SPDIF, AFMT_I2S } audio_format; |