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