.../devicetree/bindings/drm/i2c/tda998x.txt | 52 +++++ arch/arm/boot/dts/dove-cubox.dts | 84 +++++++- arch/arm/boot/dts/dove.dtsi | 19 ++ drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/i2c/tda998x_drv.c | 221 +++++++++++++++++++-- drivers/video/hdmi.c | 6 +- include/sound/soc.h | 2 + sound/soc/codecs/Kconfig | 6 + sound/soc/codecs/Makefile | 2 + sound/soc/generic/Kconfig | 2 + sound/soc/generic/Makefile | 2 + sound/soc/kirkwood/Kconfig | 1 + sound/soc/kirkwood/kirkwood-i2s.c | 173 +++++++++------- sound/soc/kirkwood/kirkwood.h | 5 +- sound/soc/soc-core.c | 5 +- 16 files changed, 488 insertions(+), 95 deletions(-) diff --git a/Documentation/devicetree/bindings/drm/i2c/tda998x.txt b/Documentation/devicetree/bindings/drm/i2c/tda998x.txt index e9e4bce..f3ced4b 100644 --- a/Documentation/devicetree/bindings/drm/i2c/tda998x.txt +++ b/Documentation/devicetree/bindings/drm/i2c/tda998x.txt @@ -17,6 +17,36 @@ Optional properties: - video-ports: 24 bits value which defines how the video controller output is wired to the TDA998x input - default: <0x230145> + This property is not used when ports are defined. + +Optional nodes: + + - port: up to three ports. + The ports are defined according to [1]. + + Video port. + There may be only one video port. + This one must contain the following property: + + - port-type: must be "rgb" + + and may contain the optional property: + + - reg: 24 bits value which defines how the video controller + output is wired to the TDA998x input (video pins) + When absent, the default value is <0x230145>. + + Audio ports. + There may be one or two audio ports. + These ones must contain the following properties: + + - port-type: must be "i2s" or "spdif" + + - reg: 8 bits value which defines how the audio controller + output is wired to the TDA998x input (audio pins) + +[1] Documentation/devicetree/bindings/graph.txt + Example: tda998x: hdmi-encoder { @@ -26,4 +56,26 @@ Example: interrupts = <27 2>; /* falling edge */ pinctrl-0 = <&pmx_camera>; pinctrl-names = "default"; + + port@230145 { + port-type = "rgb"; + reg = <0x230145>; + hdmi_0: endpoint { + remote-endpoint = <&lcd0_0>; + }; + }; + port@3 { /* AP1 = I2S */ + port-type = "i2s"; + reg = <0x03>; + tda998x_i2s: endpoint { + remote-endpoint = <&audio1_i2s>; + }; + }; + port@4 { /* AP2 = S/PDIF */ + port-type = "spdif"; + reg = <0x04>; + tda998x_spdif: endpoint { + remote-endpoint = <&audio1_spdif1>; + }; + }; }; diff --git a/arch/arm/boot/dts/dove-cubox.dts b/arch/arm/boot/dts/dove-cubox.dts index e6fa251..5600cfd 100644 --- a/arch/arm/boot/dts/dove-cubox.dts +++ b/arch/arm/boot/dts/dove-cubox.dts @@ -1,6 +1,7 @@ /dts-v1/; #include "dove.dtsi" +#include / { model = "SolidRun CuBox"; @@ -62,6 +63,20 @@ pinctrl-0 = <&pmx_gpio_19>; pinctrl-names = "default"; }; + spdif_codec: spdif-codec { /* optical output */ + compatible = "linux,spdif-dit"; + port { + port-type = "spdif"; + spdif_out: endpoint { + remote-endpoint = <&audio1_spdif0>; + }; + }; + }; + + video { + compatible = "marvell,dove-video"; + marvell,video-devices = <&lcd0>; + }; }; &uart0 { status = "okay"; }; @@ -74,8 +89,8 @@ reg = <1>; }; -&i2c0 { - status = "okay"; +&i2c { +// status = "okay"; clock-frequency = <100000>; si5351: clock-generator { @@ -108,6 +123,37 @@ silabs,pll-master; }; }; + hdmi: hdmi-encoder { + compatible = "nxp,tda998x"; + reg = <0x70>; + interrupt-parent = <&gpio0>; + interrupts = <27 IRQ_TYPE_EDGE_FALLING>; + pinctrl-0 = <&pmx_camera>; + pinctrl-names = "default"; + #address-cells = <1>; + #size-cells = <0>; + port@230145 { + port-type = "rgb"; + reg = <0x230145>; + hdmi_0: endpoint { + remote-endpoint = <&lcd0_0>; + }; + }; + port@3 { /* AP1 = I2S */ + port-type = "i2s"; + reg = <0x03>; + tda998x_i2s: endpoint { + remote-endpoint = <&audio1_i2s>; + }; + }; + port@4 { /* AP2 = S/PDIF */ + port-type = "spdif"; + reg = <0x04>; + tda998x_spdif: endpoint { + remote-endpoint = <&audio1_spdif1>; + }; + }; + }; }; &sdio0 { @@ -129,6 +175,40 @@ status = "okay"; clocks = <&gate_clk 13>, <&si5351 2>; clock-names = "internal", "extclk"; +// clocks = <&gate_clk 13>; +// clock-names = "internal"; pinctrl-0 = <&pmx_audio1_i2s1_spdifo &pmx_audio1_extclk>; pinctrl-names = "default"; + #address-cells = <1>; + #size-cells = <0>; + dt-audio-card,format = "i2s"; + port@0 { + port-type = "spdif"; + audio1_spdif0: endpoint@0 { + remote-endpoint = <&spdif_out>; + }; + audio1_spdif1: endpoint@1 { + remote-endpoint = <&tda998x_spdif>; + }; + }; + port@1 { + port-type = "i2s"; + audio1_i2s: endpoint { + remote-endpoint = <&tda998x_i2s>; + }; + }; +}; + +&lcd0 { + status = "okay"; + clocks = <&si5351 0>; + clock-names = "ext_ref_clk1"; +// (same as plldivider) +// clocks = <&core_clk 3>; +// clock-names = "axibus"; + port { + lcd0_0: endpoint { + remote-endpoint = <&hdmi_0>; + }; + }; }; diff --git a/arch/arm/boot/dts/dove.dtsi b/arch/arm/boot/dts/dove.dtsi index 1791216..f40f92e 100644 --- a/arch/arm/boot/dts/dove.dtsi +++ b/arch/arm/boot/dts/dove.dtsi @@ -14,6 +14,8 @@ gpio0 = &gpio0; gpio1 = &gpio1; gpio2 = &gpio2; + lcd0 = &lcd0; + lcd1 = &lcd1; }; cpus { @@ -754,10 +756,18 @@ ngpios = <8>; }; + corepll: fixed-clock { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <400000000>; + }; + lcd1: lcd-controller@810000 { compatible = "marvell,dove-lcd"; reg = <0x810000 0x1000>; interrupts = <46>; + clocks = <&corepll>; + clock-names = "plldivider"; status = "disabled"; }; @@ -765,8 +775,17 @@ compatible = "marvell,dove-lcd"; reg = <0x820000 0x1000>; interrupts = <47>; + clocks = <&corepll>; + clock-names = "plldivider"; status = "disabled"; }; + /* display controller and image rotation engine */ +// dcon: display-controller@830000 { +// compatible = "marvell,dove-dcon"; +// reg = <0x830000 0xc4>, <0x831000 0x8c>; +// interrupts = <45>; +// status = "disabled"; +// }; }; }; }; diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 1a0a8df..e5cb188 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -264,3 +264,5 @@ source "drivers/gpu/drm/sti/Kconfig" source "drivers/gpu/drm/amd/amdkfd/Kconfig" source "drivers/gpu/drm/imx/Kconfig" + +source "drivers/gpu/drm/dove/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 45e7719..1c281c9 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_DRM_MSM) += msm/ obj-$(CONFIG_DRM_TEGRA) += tegra/ obj-$(CONFIG_DRM_STI) += sti/ obj-$(CONFIG_DRM_IMX) += imx/ +obj-$(CONFIG_DRM_DOVE) += dove/ obj-y += i2c/ obj-y += panel/ obj-y += bridge/ diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c index 424228b..972733d 100644 --- a/drivers/gpu/drm/i2c/tda998x_drv.c +++ b/drivers/gpu/drm/i2c/tda998x_drv.c @@ -27,10 +27,18 @@ #include #include #include +#include + +#define SYSRQ_HDMI 1 + +#ifdef SYSRQ_HDMI +#include +#endif #define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__) struct tda998x_priv { + struct tda998x_audio audio; /* audio and video common field */ struct i2c_client *cec; struct i2c_client *hdmi; struct mutex mutex; @@ -345,7 +353,7 @@ struct tda998x_priv { #define TDA19988 0x0301 static void -cec_write(struct tda998x_priv *priv, uint16_t addr, uint8_t val) +cec_write(struct tda998x_priv *priv, uint8_t addr, uint8_t val) { struct i2c_client *client = priv->cec; uint8_t buf[] = {addr, val}; @@ -452,7 +460,7 @@ out: static int reg_read(struct tda998x_priv *priv, uint16_t reg) { - uint8_t val = 0; + uint8_t val; int ret; ret = reg_read_range(priv, reg, &val, sizeof(val)); @@ -549,6 +557,9 @@ tda998x_reset(struct tda998x_priv *priv) /* Write the default value MUX register */ reg_write(priv, REG_MUX_VP_VIP_OUT, 0x24); + + /* disable audio input */ + reg_write(priv, REG_ENA_AP, 0); } /* handle HDMI connect/disconnect */ @@ -755,6 +766,78 @@ tda998x_configure_audio(struct tda998x_priv *priv, tda998x_write_aif(priv, p); } +#if IS_ENABLED(CONFIG_SND_SOC_TDA998X) +/* tda998x audio codec interface */ + +/* switch the audio port and initialize the audio parameters for streaming */ +static int tda998x_set_audio_input(struct device *dev, + int port_index, + unsigned sample_rate) +{ + struct tda998x_priv *priv = dev_get_drvdata(dev); + struct tda998x_encoder_params *p = &priv->params; + + if (!priv->encoder->crtc) + return -ENODEV; + + /* if no port, just disable the audio port */ + if (port_index == PORT_NONE) { + reg_write(priv, REG_ENA_AP, 0); + return 0; + } + + if (reg_read(priv, REG_ENA_AP) != 0) +{ +pr_info("tda ap %02x\n", reg_read(priv, REG_ENA_AP)); + return -EBUSY; +} + + /* if same audio parameters, just enable the audio port */ + if (p->audio_cfg == priv->audio.ports[port_index] && + p->audio_sample_rate == sample_rate) { +// priv->audio.sample_format == sample_format) { + reg_write(priv, REG_ENA_AP, p->audio_cfg); + return 0; + } + + p->audio_format = priv->audio.port_types[port_index]; + p->audio_clk_cfg = p->audio_format == AFMT_SPDIF ? 0 : 1; + p->audio_cfg = priv->audio.ports[port_index]; + p->audio_sample_rate = sample_rate; + tda998x_configure_audio(priv, &priv->encoder->crtc->hwmode, p); + return 0; +} +#endif /* SND_SOC */ + +#ifdef SYSRQ_HDMI +static struct tda998x_priv *global_priv; +static struct work_struct hdmi_work; + +static void hdmi_toggle(struct work_struct *work) +{ + struct tda998x_priv *priv = global_priv; + u8 reg; + + if (!priv || !priv->is_hdmi_sink) + return; + reg = reg_read(priv, REG_TX33); + reg ^= TX33_HDMI; + reg_write(priv, REG_TX33, reg); +} + +static void sysrq_handle_hdmi_toggle(int key) +{ + schedule_work(&hdmi_work); +} + +static struct sysrq_key_op hdmi_toggle_op = { + .handler = sysrq_handle_hdmi_toggle, + .help_msg = "hdmi-toggle(x)", + .action_msg = "tda998x HDMI toggle", + .enable_mask = SYSRQ_ENABLE_KEYBOARD, +}; +#endif + /* DRM encoder functions */ static void tda998x_encoder_set_config(struct tda998x_priv *priv, @@ -774,6 +857,10 @@ static void tda998x_encoder_set_config(struct tda998x_priv *priv, (p->mirr_f ? VIP_CNTRL_2_MIRR_F : 0); priv->params = *p; +#if IS_ENABLED(CONFIG_SND_SOC_TDA998X) + priv->audio.port_types[0] = p->audio_format; + priv->audio.ports[0] = p->audio_cfg; +#endif } static void tda998x_encoder_dpms(struct tda998x_priv *priv, int mode) @@ -1024,9 +1111,11 @@ tda998x_encoder_mode_set(struct tda998x_priv *priv, tda998x_write_avi(priv, adjusted_mode); +#if !IS_ENABLED(CONFIG_SND_SOC_TDA998X) if (priv->params.audio_cfg) tda998x_configure_audio(priv, adjusted_mode, &priv->params); +#endif } } @@ -1102,6 +1191,9 @@ tda998x_encoder_get_modes(struct tda998x_priv *priv, struct edid *edid; int n; +#if IS_ENABLED(CONFIG_SND_SOC_TDA998X) + priv->audio.eld = NULL; +#endif if (priv->rev == TDA19988) reg_clear(priv, REG_TX4, TX4_PD_RAM); @@ -1118,6 +1210,14 @@ 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); + +#if IS_ENABLED(CONFIG_SND_SOC_TDA998X) + if (priv->is_hdmi_sink) { + drm_edid_to_eld(connector, edid); + priv->audio.eld = connector->eld; + } +#endif + kfree(edid); return n; @@ -1145,6 +1245,10 @@ tda998x_encoder_set_property(struct drm_encoder *encoder, static void tda998x_destroy(struct tda998x_priv *priv) { +#ifdef SYSRQ_HDMI + unregister_sysrq_key('x', &hdmi_toggle_op); +#endif + /* 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); @@ -1153,6 +1257,10 @@ static void tda998x_destroy(struct tda998x_priv *priv) cancel_delayed_work_sync(&priv->dwork); } +#if IS_ENABLED(CONFIG_SND_SOC_TDA998X) + if (priv->audio.ports[0]) + tda9998x_codec_unregister(&priv->hdmi->dev); +#endif i2c_unregister_device(priv->cec); } @@ -1230,9 +1338,57 @@ static struct drm_encoder_slave_funcs tda998x_encoder_slave_funcs = { /* I2C driver functions */ +static int tda998x_parse_ports(struct tda998x_priv *priv, + struct device_node *np) +{ + struct device_node *of_port; + const char *port_type; + int ret, audio_index, reg, afmt; + + audio_index = 0; + for_each_child_of_node(np, of_port) { + if (!of_port->name + || of_node_cmp(of_port->name, "port") != 0) + continue; + ret = of_property_read_string(of_port, "port-type", + &port_type); + if (ret < 0) + continue; + ret = of_property_read_u32(of_port, "reg", ®); + if (strcmp(port_type, "rgb") == 0) { + if (!ret) { /* video reg is optional */ + priv->vip_cntrl_0 = reg >> 16; + priv->vip_cntrl_1 = reg >> 8; + priv->vip_cntrl_2 = reg; + } + continue; + } + if (strcmp(port_type, "i2s") == 0) + afmt = AFMT_I2S; + else if (strcmp(port_type, "spdif") == 0) + afmt = AFMT_SPDIF; + else + continue; + if (ret < 0) { + dev_err(&priv->hdmi->dev, "missing reg for %s\n", + port_type); + return ret; + } + if (audio_index >= ARRAY_SIZE(priv->audio.ports)) { + dev_err(&priv->hdmi->dev, "too many audio ports\n"); + break; + } + priv->audio.ports[audio_index] = reg; + priv->audio.port_types[audio_index] = afmt; + audio_index++; + } + return 0; +} + static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) { struct device_node *np = client->dev.of_node; + struct device_node *of_port; u32 video; int rev_lo, rev_hi, ret; unsigned short cec_addr; @@ -1241,6 +1397,10 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) 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->audio.sample_format = SNDRV_PCM_FORMAT_S24_LE; + priv->current_page = 0xff; priv->hdmi = client; /* CEC I2C address bound to TDA998x I2C addr by configuration pins */ @@ -1251,6 +1411,12 @@ static int tda998x_create(struct i2c_client *client, struct tda998x_priv *priv) priv->dpms = DRM_MODE_DPMS_OFF; +#ifdef SYSRQ_HDMI + global_priv = priv; + INIT_WORK(&hdmi_work, hdmi_toggle); + register_sysrq_key('x', &hdmi_toggle_op); +#endif + mutex_init(&priv->mutex); /* protect the page access */ /* wake up the device: */ @@ -1337,17 +1503,40 @@ 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); - if (!np) - return 0; /* non-DT */ - - /* get the optional video properties */ - ret = of_property_read_u32(np, "video-ports", &video); - if (ret == 0) { - priv->vip_cntrl_0 = video >> 16; - priv->vip_cntrl_1 = video >> 8; - priv->vip_cntrl_2 = video; + /* get the device tree parameters */ + if (np) { + of_port = of_get_child_by_name(np, "port"); + if (of_port) { /* graph of ports */ + of_node_put(of_port); + ret = tda998x_parse_ports(priv, np); + if (ret < 0) + goto fail; + + /* initialize the audio configuration */ + if (priv->audio.ports[0]) { +#if IS_ENABLED(CONFIG_SND_SOC_TDA998X) + priv->audio.set_audio_input = tda998x_set_audio_input; + tda9998x_codec_register(&client->dev); +#else + priv->params.audio_cfg = priv->audio.ports[0]; + priv->params.audio_format = + priv->audio.port_types[0]; + priv->params.audio_clk_cfg = + priv->params.audio_format == + AFMT_SPDIF ? 0 : 1; +#endif + } + } else { + + /* optional video properties */ + ret = of_property_read_u32(np, "video-ports", &video); + if (ret == 0) { + priv->vip_cntrl_0 = video >> 16; + priv->vip_cntrl_1 = video >> 8; + priv->vip_cntrl_2 = video; + } + } } - return 0; fail: @@ -1381,6 +1570,8 @@ static int tda998x_encoder_init(struct i2c_client *client, encoder_slave->slave_priv = priv; encoder_slave->slave_funcs = &tda998x_encoder_slave_funcs; + dev_set_drvdata(&client->dev, priv); + return 0; } @@ -1508,7 +1699,7 @@ static int tda998x_bind(struct device *dev, struct device *master, void *data) if (!priv) return -ENOMEM; - dev_set_drvdata(dev, priv); + dev_set_drvdata(dev, &priv->base); if (dev->of_node) crtcs = drm_of_find_possible_crtcs(drm, dev->of_node); @@ -1567,7 +1758,9 @@ err_encoder: static void tda998x_unbind(struct device *dev, struct device *master, void *data) { - struct tda998x_priv2 *priv = dev_get_drvdata(dev); + struct tda998x_priv *priv_s = dev_get_drvdata(dev); + struct tda998x_priv2 *priv = + container_of(priv_s, struct tda998x_priv2, base); drm_connector_cleanup(&priv->connector); drm_encoder_cleanup(&priv->encoder); diff --git a/drivers/video/hdmi.c b/drivers/video/hdmi.c index 1626892..6beee48 100644 --- a/drivers/video/hdmi.c +++ b/drivers/video/hdmi.c @@ -33,14 +33,14 @@ static u8 hdmi_infoframe_checksum(u8 *ptr, size_t size) { - u8 csum = 0; + int csum = 0; size_t i; /* compute checksum */ for (i = 0; i < size; i++) - csum += ptr[i]; + csum -= ptr[i]; - return 256 - csum; + return csum; } static void hdmi_infoframe_set_checksum(void *buffer, size_t size) diff --git a/include/sound/soc.h b/include/sound/soc.h index 884e728..3e5b7ce 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -1618,6 +1618,8 @@ int snd_soc_of_get_dai_name(struct device_node *of_node, int snd_soc_of_get_dai_link_codecs(struct device *dev, struct device_node *of_node, struct snd_soc_dai_link *dai_link); +int snd_soc_get_dai_name(struct of_phandle_args *args, + const char **dai_name); #include diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 0c9733e..42bbbf1 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -110,6 +110,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_TAS2552 if I2C select SND_SOC_TAS5086 if I2C select SND_SOC_TAS571X if I2C + select SND_SOC_TDA998X if DRM_I2C_NXP_TDA998X select SND_SOC_TFA9879 if I2C select SND_SOC_TLV320AIC23_I2C if I2C select SND_SOC_TLV320AIC23_SPI if SPI_MASTER @@ -647,6 +648,11 @@ config SND_SOC_TAS571X tristate "Texas Instruments TAS5711/TAS5717/TAS5719 power amplifiers" depends on I2C +config SND_SOC_TDA998X + def_tristate y + select SND_PCM_ELD + depends on DRM_I2C_NXP_TDA998X + config SND_SOC_TFA9879 tristate "NXP Semiconductors TFA9879 amplifier" depends on I2C diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 4a32077..110ef52 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -113,6 +113,7 @@ snd-soc-stac9766-objs := stac9766.o snd-soc-sti-sas-objs := sti-sas.o snd-soc-tas5086-objs := tas5086.o snd-soc-tas571x-objs := tas571x.o +snd-soc-tda998x-objs := tda998x.o snd-soc-tfa9879-objs := tfa9879.o snd-soc-tlv320aic23-objs := tlv320aic23.o snd-soc-tlv320aic23-i2c-objs := tlv320aic23-i2c.o @@ -302,6 +303,7 @@ obj-$(CONFIG_SND_SOC_STI_SAS) += snd-soc-sti-sas.o obj-$(CONFIG_SND_SOC_TAS2552) += snd-soc-tas2552.o obj-$(CONFIG_SND_SOC_TAS5086) += snd-soc-tas5086.o obj-$(CONFIG_SND_SOC_TAS571X) += snd-soc-tas571x.o +obj-$(CONFIG_SND_SOC_TDA998X) += snd-soc-tda998x.o obj-$(CONFIG_SND_SOC_TFA9879) += snd-soc-tfa9879.o obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o obj-$(CONFIG_SND_SOC_TLV320AIC23_I2C) += snd-soc-tlv320aic23-i2c.o diff --git a/sound/soc/generic/Kconfig b/sound/soc/generic/Kconfig index 610f612..9c5e1e2 100644 --- a/sound/soc/generic/Kconfig +++ b/sound/soc/generic/Kconfig @@ -2,3 +2,5 @@ config SND_SIMPLE_CARD tristate "ASoC Simple sound card support" help This option enables generic simple sound card support +config SND_DT_CARD + tristate diff --git a/sound/soc/generic/Makefile b/sound/soc/generic/Makefile index 9c3b246..56834a9 100644 --- a/sound/soc/generic/Makefile +++ b/sound/soc/generic/Makefile @@ -1,3 +1,5 @@ snd-soc-simple-card-objs := simple-card.o +snd-soc-dt-card-objs := dt-card.o obj-$(CONFIG_SND_SIMPLE_CARD) += snd-soc-simple-card.o +obj-$(CONFIG_SND_DT_CARD) += snd-soc-dt-card.o diff --git a/sound/soc/kirkwood/Kconfig b/sound/soc/kirkwood/Kconfig index 132bb83..42af3f4 100644 --- a/sound/soc/kirkwood/Kconfig +++ b/sound/soc/kirkwood/Kconfig @@ -1,6 +1,7 @@ config SND_KIRKWOOD_SOC tristate "SoC Audio for the Marvell Kirkwood and Dove chips" depends on ARCH_DOVE || ARCH_MVEBU || COMPILE_TEST + select SND_DT_CARD help Say Y or M if you want to add support for codecs attached to the Kirkwood I2S interface. You will also need to select the diff --git a/sound/soc/kirkwood/kirkwood-i2s.c b/sound/soc/kirkwood/kirkwood-i2s.c index 3a36d60..942b55d 100644 --- a/sound/soc/kirkwood/kirkwood-i2s.c +++ b/sound/soc/kirkwood/kirkwood-i2s.c @@ -145,6 +145,11 @@ static int kirkwood_i2s_hw_params(struct snd_pcm_substream *substream, unsigned int i2s_reg; unsigned long i2s_value; +//test:trace +pr_info("kirkwood audio hw %s sub %p %p fmt %d rate %d\n", + dai->name, priv->substream_play, substream, + params_format(params), params_rate(params)); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { i2s_reg = KIRKWOOD_I2S_PLAYCTL; } else { @@ -263,7 +268,7 @@ static int kirkwood_i2s_play_trigger(struct snd_pcm_substream *substream, case SNDRV_PCM_TRIGGER_START: /* configure */ ctl = priv->ctl_play; - if (dai->id == 0) + if (dai->name[0] == 'i') ctl &= ~KIRKWOOD_PLAYCTL_SPDIF_EN; /* i2s */ else ctl &= ~KIRKWOOD_PLAYCTL_I2S_EN; /* spdif */ @@ -331,7 +336,7 @@ static int kirkwood_i2s_rec_trigger(struct snd_pcm_substream *substream, case SNDRV_PCM_TRIGGER_START: /* configure */ ctl = priv->ctl_rec; - if (dai->id == 0) + if (dai->name[0] == 'i') ctl &= ~KIRKWOOD_RECCTL_SPDIF_EN; /* i2s */ else ctl &= ~KIRKWOOD_RECCTL_I2S_EN; /* spdif */ @@ -392,8 +397,6 @@ static int kirkwood_i2s_trigger(struct snd_pcm_substream *substream, int cmd, return kirkwood_i2s_play_trigger(substream, cmd, dai); else return kirkwood_i2s_rec_trigger(substream, cmd, dai); - - return 0; } static int kirkwood_i2s_init(struct kirkwood_dma_data *priv) @@ -438,49 +441,8 @@ static const struct snd_soc_dai_ops kirkwood_i2s_dai_ops = { .set_fmt = kirkwood_i2s_set_fmt, }; -static struct snd_soc_dai_driver kirkwood_i2s_dai[2] = { - { - .name = "i2s", - .id = 0, - .playback = { - .channels_min = 1, - .channels_max = 2, - .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | - SNDRV_PCM_RATE_96000, - .formats = KIRKWOOD_I2S_FORMATS, - }, - .capture = { - .channels_min = 1, - .channels_max = 2, - .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | - SNDRV_PCM_RATE_96000, - .formats = KIRKWOOD_I2S_FORMATS, - }, - .ops = &kirkwood_i2s_dai_ops, - }, - { - .name = "spdif", - .id = 1, - .playback = { - .channels_min = 1, - .channels_max = 2, - .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | - SNDRV_PCM_RATE_96000, - .formats = KIRKWOOD_SPDIF_FORMATS, - }, - .capture = { - .channels_min = 1, - .channels_max = 2, - .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | - SNDRV_PCM_RATE_96000, - .formats = KIRKWOOD_SPDIF_FORMATS, - }, - .ops = &kirkwood_i2s_dai_ops, - }, -}; - -static struct snd_soc_dai_driver kirkwood_i2s_dai_extclk[2] = { - { +/* DAI sample for I2S and external clock */ +static struct snd_soc_dai_driver kirkwood_i2s_dai_i2s_ext = { .name = "i2s", .id = 0, .playback = { @@ -500,42 +462,81 @@ static struct snd_soc_dai_driver kirkwood_i2s_dai_extclk[2] = { .formats = KIRKWOOD_I2S_FORMATS, }, .ops = &kirkwood_i2s_dai_ops, - }, - { - .name = "spdif", - .id = 1, - .playback = { - .channels_min = 1, - .channels_max = 2, - .rates = SNDRV_PCM_RATE_CONTINUOUS, - .rate_min = 5512, - .rate_max = 192000, - .formats = KIRKWOOD_SPDIF_FORMATS, - }, - .capture = { - .channels_min = 1, - .channels_max = 2, - .rates = SNDRV_PCM_RATE_CONTINUOUS, - .rate_min = 5512, - .rate_max = 192000, - .formats = KIRKWOOD_SPDIF_FORMATS, - }, - .ops = &kirkwood_i2s_dai_ops, - }, }; static const struct snd_soc_component_driver kirkwood_i2s_component = { .name = DRV_NAME, }; +/* create the DAIs */ +static int kirkwood_i2s_create_dais(struct device_node *np, + struct kirkwood_dma_data *priv) +{ + struct device_node *of_port; + const char *name; + int i, ret, ndai, dai[2]; + + ndai = 0; + if (np) { + for_each_child_of_node(np, of_port) { + if (!of_port->name || + of_node_cmp(of_port->name, "port") != 0) + continue; + ret = of_property_read_string(of_port, + "port-type", + &name); + if (ret) + continue; + if (strcmp(name, "i2s") == 0) { + dai[ndai] = 0; + } else if (strcmp(name, "spdif") == 0) { + dai[ndai] = 1; + } else { + continue; + } + if (++ndai >= 2) + break; + } + } + if (ndai == 0) { /* no DT or no port */ + ndai = 2; + dai[0] = 0; /* i2s(0) - spdif(1) */ + dai[1] = 1; + } else { + priv->is_graph = 1; /* graph of the ports */ + } + for (i = 0; i < ndai; i++) { + memcpy(&priv->dais[i], &kirkwood_i2s_dai_i2s_ext, + sizeof(priv->dais[0])); + priv->dais[i].id = i; + if (dai[i] == 1) { + priv->dais[i].name = "spdif"; + priv->dais[i].playback.formats = + KIRKWOOD_SPDIF_FORMATS; + priv->dais[i].capture.formats = + KIRKWOOD_SPDIF_FORMATS; + } + if (IS_ERR(priv->extclk)) { + priv->dais[i].playback.rates = + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000; + priv->dais[i].capture.rates = + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000; + } + } + return ndai; +} + static int kirkwood_i2s_dev_probe(struct platform_device *pdev) { struct kirkwood_asoc_platform_data *data = pdev->dev.platform_data; - struct snd_soc_dai_driver *soc_dai = kirkwood_i2s_dai; struct kirkwood_dma_data *priv; struct resource *mem; struct device_node *np = pdev->dev.of_node; - int err; + int err, ndais; priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) { @@ -585,7 +586,6 @@ static int kirkwood_i2s_dev_probe(struct platform_device *pdev) } else { dev_info(&pdev->dev, "found external clock\n"); clk_prepare_enable(priv->extclk); - soc_dai = kirkwood_i2s_dai_extclk; } } @@ -602,8 +602,10 @@ static int kirkwood_i2s_dev_probe(struct platform_device *pdev) priv->ctl_rec |= KIRKWOOD_RECCTL_BURST_128; } + ndais = kirkwood_i2s_create_dais(np, priv); + err = snd_soc_register_component(&pdev->dev, &kirkwood_i2s_component, - soc_dai, 2); + priv->dais, ndais); if (err) { dev_err(&pdev->dev, "snd_soc_register_component failed\n"); goto err_component; @@ -617,7 +619,29 @@ static int kirkwood_i2s_dev_probe(struct platform_device *pdev) kirkwood_i2s_init(priv); + /* + * If the DT contains the graph of the audio ports, + * let asoc-dt-card create the sound card. + */ + if (priv->is_graph) { + priv->card_pdev = platform_device_register_resndata(&pdev->dev, + "asoc-dt-card", + PLATFORM_DEVID_NONE, + NULL, 0, + &np, sizeof(np)); + if (IS_ERR(priv->card_pdev)) { + err = PTR_ERR(priv->card_pdev); + dev_err(&pdev->dev, "cannot create audio card %d\n", + err); + priv->card_pdev = NULL; + goto err_card; + } + priv->card_pdev = pdev; + } + return 0; + err_card: + snd_soc_unregister_platform(&pdev->dev); err_platform: snd_soc_unregister_component(&pdev->dev); err_component: @@ -632,6 +656,9 @@ static int kirkwood_i2s_dev_remove(struct platform_device *pdev) { struct kirkwood_dma_data *priv = dev_get_drvdata(&pdev->dev); + if (priv->card_pdev) + platform_device_unregister(priv->card_pdev); + snd_soc_unregister_platform(&pdev->dev); snd_soc_unregister_component(&pdev->dev); diff --git a/sound/soc/kirkwood/kirkwood.h b/sound/soc/kirkwood/kirkwood.h index 90e32a7..110db82 100644 --- a/sound/soc/kirkwood/kirkwood.h +++ b/sound/soc/kirkwood/kirkwood.h @@ -135,12 +135,15 @@ struct kirkwood_dma_data { void __iomem *io; struct clk *clk; struct clk *extclk; + struct platform_device *card_pdev; + struct snd_soc_dai_driver dais[2]; uint32_t ctl_play; uint32_t ctl_rec; struct snd_pcm_substream *substream_play; struct snd_pcm_substream *substream_rec; int irq; - int burst; + short burst; + short is_graph; }; extern struct snd_soc_platform_driver kirkwood_soc_platform; diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 6173d15..caeeb06 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -3509,8 +3509,8 @@ unsigned int snd_soc_of_parse_daifmt(struct device_node *np, } EXPORT_SYMBOL_GPL(snd_soc_of_parse_daifmt); -static int snd_soc_get_dai_name(struct of_phandle_args *args, - const char **dai_name) +int snd_soc_get_dai_name(struct of_phandle_args *args, + const char **dai_name) { struct snd_soc_component *pos; struct device_node *component_of_node; @@ -3561,6 +3561,7 @@ static int snd_soc_get_dai_name(struct of_phandle_args *args, mutex_unlock(&client_mutex); return ret; } +EXPORT_SYMBOL_GPL(snd_soc_get_dai_name); int snd_soc_of_get_dai_name(struct device_node *of_node, const char **dai_name)