Linux Kernel  3.7.1
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
mpc8610_hpcd.c
Go to the documentation of this file.
1 
13 #include <linux/module.h>
14 #include <linux/interrupt.h>
15 #include <linux/of_device.h>
16 #include <linux/slab.h>
17 #include <sound/soc.h>
18 #include <asm/fsl_guts.h>
19 
20 #include "fsl_dma.h"
21 #include "fsl_ssi.h"
22 #include "fsl_utils.h"
23 
24 /* There's only one global utilities register */
25 static phys_addr_t guts_phys;
26 
34  struct snd_soc_dai_link dai[2];
36  unsigned int dai_format;
37  unsigned int codec_clk_direction;
38  unsigned int cpu_clk_direction;
39  unsigned int clk_frequency;
40  unsigned int ssi_id; /* 0 = SSI1, 1 = SSI2, etc */
41  unsigned int dma_id[2]; /* 0 = DMA1, 1 = DMA2, etc */
42  unsigned int dma_channel_id[2]; /* 0 = ch 0, 1 = ch 1, etc*/
44  char platform_name[2][DAI_NAME_SIZE]; /* One for each DMA channel */
45 };
46 
54 static int mpc8610_hpcd_machine_probe(struct snd_soc_card *card)
55 {
57  container_of(card, struct mpc8610_hpcd_data, card);
58  struct ccsr_guts __iomem *guts;
59 
60  guts = ioremap(guts_phys, sizeof(struct ccsr_guts));
61  if (!guts) {
62  dev_err(card->dev, "could not map global utilities\n");
63  return -ENOMEM;
64  }
65 
66  /* Program the signal routing between the SSI and the DMA */
67  guts_set_dmacr(guts, machine_data->dma_id[0],
68  machine_data->dma_channel_id[0],
69  CCSR_GUTS_DMACR_DEV_SSI);
70  guts_set_dmacr(guts, machine_data->dma_id[1],
71  machine_data->dma_channel_id[1],
72  CCSR_GUTS_DMACR_DEV_SSI);
73 
74  guts_set_pmuxcr_dma(guts, machine_data->dma_id[0],
75  machine_data->dma_channel_id[0], 0);
76  guts_set_pmuxcr_dma(guts, machine_data->dma_id[1],
77  machine_data->dma_channel_id[1], 0);
78 
79  switch (machine_data->ssi_id) {
80  case 0:
81  clrsetbits_be32(&guts->pmuxcr,
82  CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_SSI);
83  break;
84  case 1:
85  clrsetbits_be32(&guts->pmuxcr,
86  CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_SSI);
87  break;
88  }
89 
90  iounmap(guts);
91 
92  return 0;
93 }
94 
102 static int mpc8610_hpcd_startup(struct snd_pcm_substream *substream)
103 {
104  struct snd_soc_pcm_runtime *rtd = substream->private_data;
105  struct mpc8610_hpcd_data *machine_data =
106  container_of(rtd->card, struct mpc8610_hpcd_data, card);
107  struct device *dev = rtd->card->dev;
108  int ret = 0;
109 
110  /* Tell the codec driver what the serial protocol is. */
111  ret = snd_soc_dai_set_fmt(rtd->codec_dai, machine_data->dai_format);
112  if (ret < 0) {
113  dev_err(dev, "could not set codec driver audio format\n");
114  return ret;
115  }
116 
117  /*
118  * Tell the codec driver what the MCLK frequency is, and whether it's
119  * a slave or master.
120  */
121  ret = snd_soc_dai_set_sysclk(rtd->codec_dai, 0,
122  machine_data->clk_frequency,
123  machine_data->codec_clk_direction);
124  if (ret < 0) {
125  dev_err(dev, "could not set codec driver clock params\n");
126  return ret;
127  }
128 
129  return 0;
130 }
131 
138 static int mpc8610_hpcd_machine_remove(struct snd_soc_card *card)
139 {
140  struct mpc8610_hpcd_data *machine_data =
141  container_of(card, struct mpc8610_hpcd_data, card);
142  struct ccsr_guts __iomem *guts;
143 
144  guts = ioremap(guts_phys, sizeof(struct ccsr_guts));
145  if (!guts) {
146  dev_err(card->dev, "could not map global utilities\n");
147  return -ENOMEM;
148  }
149 
150  /* Restore the signal routing */
151 
152  guts_set_dmacr(guts, machine_data->dma_id[0],
153  machine_data->dma_channel_id[0], 0);
154  guts_set_dmacr(guts, machine_data->dma_id[1],
155  machine_data->dma_channel_id[1], 0);
156 
157  switch (machine_data->ssi_id) {
158  case 0:
159  clrsetbits_be32(&guts->pmuxcr,
160  CCSR_GUTS_PMUXCR_SSI1_MASK, CCSR_GUTS_PMUXCR_SSI1_LA);
161  break;
162  case 1:
163  clrsetbits_be32(&guts->pmuxcr,
164  CCSR_GUTS_PMUXCR_SSI2_MASK, CCSR_GUTS_PMUXCR_SSI2_LA);
165  break;
166  }
167 
168  iounmap(guts);
169 
170  return 0;
171 }
172 
176 static struct snd_soc_ops mpc8610_hpcd_ops = {
177  .startup = mpc8610_hpcd_startup,
178 };
179 
187 static int mpc8610_hpcd_probe(struct platform_device *pdev)
188 {
189  struct device *dev = pdev->dev.parent;
190  /* ssi_pdev is the platform device for the SSI node that probed us */
191  struct platform_device *ssi_pdev =
192  container_of(dev, struct platform_device, dev);
193  struct device_node *np = ssi_pdev->dev.of_node;
194  struct device_node *codec_np = NULL;
195  struct mpc8610_hpcd_data *machine_data;
196  int ret = -ENODEV;
197  const char *sprop;
198  const u32 *iprop;
199 
200  /* Find the codec node for this SSI. */
201  codec_np = of_parse_phandle(np, "codec-handle", 0);
202  if (!codec_np) {
203  dev_err(dev, "invalid codec node\n");
204  return -EINVAL;
205  }
206 
207  machine_data = kzalloc(sizeof(struct mpc8610_hpcd_data), GFP_KERNEL);
208  if (!machine_data) {
209  ret = -ENOMEM;
210  goto error_alloc;
211  }
212 
213  machine_data->dai[0].cpu_dai_name = dev_name(&ssi_pdev->dev);
214  machine_data->dai[0].ops = &mpc8610_hpcd_ops;
215 
216  /* ASoC core can match codec with device node */
217  machine_data->dai[0].codec_of_node = codec_np;
218 
219  /* The DAI name from the codec (snd_soc_dai_driver.name) */
220  machine_data->dai[0].codec_dai_name = "cs4270-hifi";
221 
222  /* We register two DAIs per SSI, one for playback and the other for
223  * capture. Currently, we only support codecs that have one DAI for
224  * both playback and capture.
225  */
226  memcpy(&machine_data->dai[1], &machine_data->dai[0],
227  sizeof(struct snd_soc_dai_link));
228 
229  /* Get the device ID */
230  iprop = of_get_property(np, "cell-index", NULL);
231  if (!iprop) {
232  dev_err(&pdev->dev, "cell-index property not found\n");
233  ret = -EINVAL;
234  goto error;
235  }
236  machine_data->ssi_id = be32_to_cpup(iprop);
237 
238  /* Get the serial format and clock direction. */
239  sprop = of_get_property(np, "fsl,mode", NULL);
240  if (!sprop) {
241  dev_err(&pdev->dev, "fsl,mode property not found\n");
242  ret = -EINVAL;
243  goto error;
244  }
245 
246  if (strcasecmp(sprop, "i2s-slave") == 0) {
247  machine_data->dai_format =
249  machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
250  machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
251 
252  /* In i2s-slave mode, the codec has its own clock source, so we
253  * need to get the frequency from the device tree and pass it to
254  * the codec driver.
255  */
256  iprop = of_get_property(codec_np, "clock-frequency", NULL);
257  if (!iprop || !*iprop) {
258  dev_err(&pdev->dev, "codec bus-frequency "
259  "property is missing or invalid\n");
260  ret = -EINVAL;
261  goto error;
262  }
263  machine_data->clk_frequency = be32_to_cpup(iprop);
264  } else if (strcasecmp(sprop, "i2s-master") == 0) {
265  machine_data->dai_format =
267  machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
268  machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
269  } else if (strcasecmp(sprop, "lj-slave") == 0) {
270  machine_data->dai_format =
272  machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
273  machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
274  } else if (strcasecmp(sprop, "lj-master") == 0) {
275  machine_data->dai_format =
277  machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
278  machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
279  } else if (strcasecmp(sprop, "rj-slave") == 0) {
280  machine_data->dai_format =
282  machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
283  machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
284  } else if (strcasecmp(sprop, "rj-master") == 0) {
285  machine_data->dai_format =
287  machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
288  machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
289  } else if (strcasecmp(sprop, "ac97-slave") == 0) {
290  machine_data->dai_format =
292  machine_data->codec_clk_direction = SND_SOC_CLOCK_OUT;
293  machine_data->cpu_clk_direction = SND_SOC_CLOCK_IN;
294  } else if (strcasecmp(sprop, "ac97-master") == 0) {
295  machine_data->dai_format =
297  machine_data->codec_clk_direction = SND_SOC_CLOCK_IN;
298  machine_data->cpu_clk_direction = SND_SOC_CLOCK_OUT;
299  } else {
300  dev_err(&pdev->dev,
301  "unrecognized fsl,mode property '%s'\n", sprop);
302  ret = -EINVAL;
303  goto error;
304  }
305 
306  if (!machine_data->clk_frequency) {
307  dev_err(&pdev->dev, "unknown clock frequency\n");
308  ret = -EINVAL;
309  goto error;
310  }
311 
312  /* Find the playback DMA channel to use. */
313  machine_data->dai[0].platform_name = machine_data->platform_name[0];
314  ret = fsl_asoc_get_dma_channel(np, "fsl,playback-dma",
315  &machine_data->dai[0],
316  &machine_data->dma_channel_id[0],
317  &machine_data->dma_id[0]);
318  if (ret) {
319  dev_err(&pdev->dev, "missing/invalid playback DMA phandle\n");
320  goto error;
321  }
322 
323  /* Find the capture DMA channel to use. */
324  machine_data->dai[1].platform_name = machine_data->platform_name[1];
325  ret = fsl_asoc_get_dma_channel(np, "fsl,capture-dma",
326  &machine_data->dai[1],
327  &machine_data->dma_channel_id[1],
328  &machine_data->dma_id[1]);
329  if (ret) {
330  dev_err(&pdev->dev, "missing/invalid capture DMA phandle\n");
331  goto error;
332  }
333 
334  /* Initialize our DAI data structure. */
335  machine_data->dai[0].stream_name = "playback";
336  machine_data->dai[1].stream_name = "capture";
337  machine_data->dai[0].name = machine_data->dai[0].stream_name;
338  machine_data->dai[1].name = machine_data->dai[1].stream_name;
339 
340  machine_data->card.probe = mpc8610_hpcd_machine_probe;
341  machine_data->card.remove = mpc8610_hpcd_machine_remove;
342  machine_data->card.name = pdev->name; /* The platform driver name */
343  machine_data->card.owner = THIS_MODULE;
344  machine_data->card.dev = &pdev->dev;
345  machine_data->card.num_links = 2;
346  machine_data->card.dai_link = machine_data->dai;
347 
348  /* Register with ASoC */
349  ret = snd_soc_register_card(&machine_data->card);
350  if (ret) {
351  dev_err(&pdev->dev, "could not register card\n");
352  goto error;
353  }
354 
355  of_node_put(codec_np);
356 
357  return 0;
358 
359 error:
360  kfree(machine_data);
361 error_alloc:
362  of_node_put(codec_np);
363  return ret;
364 }
365 
371 static int __devexit mpc8610_hpcd_remove(struct platform_device *pdev)
372 {
373  struct snd_soc_card *card = platform_get_drvdata(pdev);
374  struct mpc8610_hpcd_data *machine_data =
375  container_of(card, struct mpc8610_hpcd_data, card);
376 
378  kfree(machine_data);
379 
380  return 0;
381 }
382 
383 static struct platform_driver mpc8610_hpcd_driver = {
384  .probe = mpc8610_hpcd_probe,
385  .remove = __devexit_p(mpc8610_hpcd_remove),
386  .driver = {
387  /* The name must match 'compatible' property in the device tree,
388  * in lowercase letters.
389  */
390  .name = "snd-soc-mpc8610hpcd",
391  .owner = THIS_MODULE,
392  },
393 };
394 
400 static int __init mpc8610_hpcd_init(void)
401 {
402  struct device_node *guts_np;
403  struct resource res;
404 
405  pr_info("Freescale MPC8610 HPCD ALSA SoC machine driver\n");
406 
407  /* Get the physical address of the global utilities registers */
408  guts_np = of_find_compatible_node(NULL, NULL, "fsl,mpc8610-guts");
409  if (of_address_to_resource(guts_np, 0, &res)) {
410  pr_err("mpc8610-hpcd: missing/invalid global utilities node\n");
411  return -EINVAL;
412  }
413  guts_phys = res.start;
414 
415  return platform_driver_register(&mpc8610_hpcd_driver);
416 }
417 
423 static void __exit mpc8610_hpcd_exit(void)
424 {
425  platform_driver_unregister(&mpc8610_hpcd_driver);
426 }
427 
428 module_init(mpc8610_hpcd_init);
429 module_exit(mpc8610_hpcd_exit);
430 
431 MODULE_AUTHOR("Timur Tabi <[email protected]>");
432 MODULE_DESCRIPTION("Freescale MPC8610 HPCD ALSA SoC machine driver");
433 MODULE_LICENSE("GPL v2");