1 // SPDX-License-Identifier: GPL-2.0+ 2 // 3 // Copyright 2012 Freescale Semiconductor, Inc. 4 // Copyright 2012 Linaro Ltd. 5 6 #include <linux/gpio.h> 7 #include <linux/module.h> 8 #include <linux/of.h> 9 #include <linux/of_platform.h> 10 #include <linux/i2c.h> 11 #include <linux/of_gpio.h> 12 #include <sound/soc.h> 13 #include <sound/jack.h> 14 15 #include "imx-audmux.h" 16 17 #define DAI_NAME_SIZE 32 18 #define MUX_PORT_MAX 7 19 20 struct imx_es8328_data { 21 struct device *dev; 22 struct snd_soc_dai_link dai; 23 struct snd_soc_card card; 24 char codec_dai_name[DAI_NAME_SIZE]; 25 char platform_name[DAI_NAME_SIZE]; 26 int jack_gpio; 27 }; 28 29 static struct snd_soc_jack_gpio headset_jack_gpios[] = { 30 { 31 .gpio = -1, 32 .name = "headset-gpio", 33 .report = SND_JACK_HEADSET, 34 .invert = 0, 35 .debounce_time = 200, 36 }, 37 }; 38 39 static struct snd_soc_jack headset_jack; 40 41 static int imx_es8328_dai_init(struct snd_soc_pcm_runtime *rtd) 42 { 43 struct imx_es8328_data *data = container_of(rtd->card, 44 struct imx_es8328_data, card); 45 int ret = 0; 46 47 /* Headphone jack detection */ 48 if (gpio_is_valid(data->jack_gpio)) { 49 ret = snd_soc_card_jack_new(rtd->card, "Headphone", 50 SND_JACK_HEADPHONE | SND_JACK_BTN_0, 51 &headset_jack, NULL, 0); 52 if (ret) 53 return ret; 54 55 headset_jack_gpios[0].gpio = data->jack_gpio; 56 ret = snd_soc_jack_add_gpios(&headset_jack, 57 ARRAY_SIZE(headset_jack_gpios), 58 headset_jack_gpios); 59 } 60 61 return ret; 62 } 63 64 static const struct snd_soc_dapm_widget imx_es8328_dapm_widgets[] = { 65 SND_SOC_DAPM_MIC("Mic Jack", NULL), 66 SND_SOC_DAPM_HP("Headphone", NULL), 67 SND_SOC_DAPM_SPK("Speaker", NULL), 68 SND_SOC_DAPM_REGULATOR_SUPPLY("audio-amp", 1, 0), 69 }; 70 71 static int imx_es8328_probe(struct platform_device *pdev) 72 { 73 struct device_node *np = pdev->dev.of_node; 74 struct device_node *ssi_np = NULL, *codec_np = NULL; 75 struct platform_device *ssi_pdev; 76 struct imx_es8328_data *data; 77 struct snd_soc_dai_link_component *comp; 78 u32 int_port, ext_port; 79 int ret; 80 struct device *dev = &pdev->dev; 81 82 ret = of_property_read_u32(np, "mux-int-port", &int_port); 83 if (ret) { 84 dev_err(dev, "mux-int-port missing or invalid\n"); 85 goto fail; 86 } 87 if (int_port > MUX_PORT_MAX || int_port == 0) { 88 dev_err(dev, "mux-int-port: hardware only has %d mux ports\n", 89 MUX_PORT_MAX); 90 goto fail; 91 } 92 93 ret = of_property_read_u32(np, "mux-ext-port", &ext_port); 94 if (ret) { 95 dev_err(dev, "mux-ext-port missing or invalid\n"); 96 goto fail; 97 } 98 if (ext_port > MUX_PORT_MAX || ext_port == 0) { 99 dev_err(dev, "mux-ext-port: hardware only has %d mux ports\n", 100 MUX_PORT_MAX); 101 ret = -EINVAL; 102 goto fail; 103 } 104 105 /* 106 * The port numbering in the hardware manual starts at 1, while 107 * the audmux API expects it starts at 0. 108 */ 109 int_port--; 110 ext_port--; 111 ret = imx_audmux_v2_configure_port(int_port, 112 IMX_AUDMUX_V2_PTCR_SYN | 113 IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) | 114 IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) | 115 IMX_AUDMUX_V2_PTCR_TFSDIR | 116 IMX_AUDMUX_V2_PTCR_TCLKDIR, 117 IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port)); 118 if (ret) { 119 dev_err(dev, "audmux internal port setup failed\n"); 120 return ret; 121 } 122 ret = imx_audmux_v2_configure_port(ext_port, 123 IMX_AUDMUX_V2_PTCR_SYN, 124 IMX_AUDMUX_V2_PDCR_RXDSEL(int_port)); 125 if (ret) { 126 dev_err(dev, "audmux external port setup failed\n"); 127 return ret; 128 } 129 130 ssi_np = of_parse_phandle(pdev->dev.of_node, "ssi-controller", 0); 131 codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0); 132 if (!ssi_np || !codec_np) { 133 dev_err(dev, "phandle missing or invalid\n"); 134 ret = -EINVAL; 135 goto fail; 136 } 137 138 ssi_pdev = of_find_device_by_node(ssi_np); 139 if (!ssi_pdev) { 140 dev_err(dev, "failed to find SSI platform device\n"); 141 ret = -EINVAL; 142 goto fail; 143 } 144 145 data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); 146 if (!data) { 147 ret = -ENOMEM; 148 goto fail; 149 } 150 151 comp = devm_kzalloc(dev, 3 * sizeof(*comp), GFP_KERNEL); 152 if (!comp) { 153 ret = -ENOMEM; 154 goto fail; 155 } 156 157 data->dev = dev; 158 159 data->jack_gpio = of_get_named_gpio(pdev->dev.of_node, "jack-gpio", 0); 160 161 data->dai.cpus = &comp[0]; 162 data->dai.codecs = &comp[1]; 163 data->dai.platforms = &comp[2]; 164 165 data->dai.num_cpus = 1; 166 data->dai.num_codecs = 1; 167 data->dai.num_platforms = 1; 168 169 data->dai.name = "hifi"; 170 data->dai.stream_name = "hifi"; 171 data->dai.codecs->dai_name = "es8328-hifi-analog"; 172 data->dai.codecs->of_node = codec_np; 173 data->dai.cpus->of_node = ssi_np; 174 data->dai.platforms->of_node = ssi_np; 175 data->dai.init = &imx_es8328_dai_init; 176 data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | 177 SND_SOC_DAIFMT_CBM_CFM; 178 179 data->card.dev = dev; 180 data->card.dapm_widgets = imx_es8328_dapm_widgets; 181 data->card.num_dapm_widgets = ARRAY_SIZE(imx_es8328_dapm_widgets); 182 ret = snd_soc_of_parse_card_name(&data->card, "model"); 183 if (ret) { 184 dev_err(dev, "Unable to parse card name\n"); 185 goto fail; 186 } 187 ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing"); 188 if (ret) { 189 dev_err(dev, "Unable to parse routing: %d\n", ret); 190 goto fail; 191 } 192 data->card.num_links = 1; 193 data->card.owner = THIS_MODULE; 194 data->card.dai_link = &data->dai; 195 196 ret = snd_soc_register_card(&data->card); 197 if (ret) { 198 dev_err(dev, "Unable to register: %d\n", ret); 199 goto fail; 200 } 201 202 platform_set_drvdata(pdev, data); 203 fail: 204 of_node_put(ssi_np); 205 of_node_put(codec_np); 206 207 return ret; 208 } 209 210 static int imx_es8328_remove(struct platform_device *pdev) 211 { 212 struct imx_es8328_data *data = platform_get_drvdata(pdev); 213 214 snd_soc_unregister_card(&data->card); 215 216 return 0; 217 } 218 219 static const struct of_device_id imx_es8328_dt_ids[] = { 220 { .compatible = "fsl,imx-audio-es8328", }, 221 { /* sentinel */ } 222 }; 223 MODULE_DEVICE_TABLE(of, imx_es8328_dt_ids); 224 225 static struct platform_driver imx_es8328_driver = { 226 .driver = { 227 .name = "imx-es8328", 228 .of_match_table = imx_es8328_dt_ids, 229 }, 230 .probe = imx_es8328_probe, 231 .remove = imx_es8328_remove, 232 }; 233 module_platform_driver(imx_es8328_driver); 234 235 MODULE_AUTHOR("Sean Cross <xobs@kosagi.com>"); 236 MODULE_DESCRIPTION("Kosagi i.MX6 ES8328 ASoC machine driver"); 237 MODULE_LICENSE("GPL v2"); 238 MODULE_ALIAS("platform:imx-audio-es8328"); 239