1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved. |
4 | * |
5 | * storm.c -- ALSA SoC machine driver for QTi ipq806x-based Storm board |
6 | */ |
7 | |
8 | #include <linux/device.h> |
9 | #include <linux/module.h> |
10 | #include <linux/of.h> |
11 | #include <linux/mod_devicetable.h> |
12 | #include <linux/platform_device.h> |
13 | #include <sound/pcm.h> |
14 | #include <sound/pcm_params.h> |
15 | #include <sound/soc.h> |
16 | |
17 | #define STORM_SYSCLK_MULT 4 |
18 | |
19 | static int storm_ops_hw_params(struct snd_pcm_substream *substream, |
20 | struct snd_pcm_hw_params *params) |
21 | { |
22 | struct snd_soc_pcm_runtime *soc_runtime = snd_soc_substream_to_rtd(substream); |
23 | struct snd_soc_card *card = soc_runtime->card; |
24 | snd_pcm_format_t format = params_format(p: params); |
25 | unsigned int rate = params_rate(p: params); |
26 | unsigned int sysclk_freq; |
27 | int bitwidth, ret; |
28 | |
29 | bitwidth = snd_pcm_format_width(format); |
30 | if (bitwidth < 0) { |
31 | dev_err(card->dev, "invalid bit width given: %d\n" , bitwidth); |
32 | return bitwidth; |
33 | } |
34 | |
35 | /* |
36 | * as the CPU DAI is the I2S bus master and no system clock is needed by |
37 | * the MAX98357a DAC, simply set the system clock to be a constant |
38 | * multiple of the bit clock for the clock divider |
39 | */ |
40 | sysclk_freq = rate * bitwidth * 2 * STORM_SYSCLK_MULT; |
41 | |
42 | ret = snd_soc_dai_set_sysclk(snd_soc_rtd_to_cpu(soc_runtime, 0), clk_id: 0, freq: sysclk_freq, dir: 0); |
43 | if (ret) { |
44 | dev_err(card->dev, "error setting sysclk to %u: %d\n" , |
45 | sysclk_freq, ret); |
46 | return ret; |
47 | } |
48 | |
49 | return 0; |
50 | } |
51 | |
52 | static const struct snd_soc_ops storm_soc_ops = { |
53 | .hw_params = storm_ops_hw_params, |
54 | }; |
55 | |
56 | SND_SOC_DAILINK_DEFS(hifi, |
57 | DAILINK_COMP_ARRAY(COMP_EMPTY()), |
58 | DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "HiFi" )), |
59 | DAILINK_COMP_ARRAY(COMP_EMPTY())); |
60 | |
61 | static struct snd_soc_dai_link storm_dai_link = { |
62 | .name = "Primary" , |
63 | .stream_name = "Primary" , |
64 | .ops = &storm_soc_ops, |
65 | SND_SOC_DAILINK_REG(hifi), |
66 | }; |
67 | |
68 | static int storm_parse_of(struct snd_soc_card *card) |
69 | { |
70 | struct snd_soc_dai_link *dai_link = card->dai_link; |
71 | struct device_node *np = card->dev->of_node; |
72 | |
73 | dai_link->cpus->of_node = of_parse_phandle(np, phandle_name: "cpu" , index: 0); |
74 | if (!dai_link->cpus->of_node) { |
75 | dev_err(card->dev, "error getting cpu phandle\n" ); |
76 | return -EINVAL; |
77 | } |
78 | dai_link->platforms->of_node = dai_link->cpus->of_node; |
79 | |
80 | dai_link->codecs->of_node = of_parse_phandle(np, phandle_name: "codec" , index: 0); |
81 | if (!dai_link->codecs->of_node) { |
82 | dev_err(card->dev, "error getting codec phandle\n" ); |
83 | return -EINVAL; |
84 | } |
85 | |
86 | return 0; |
87 | } |
88 | |
89 | static int storm_platform_probe(struct platform_device *pdev) |
90 | { |
91 | struct snd_soc_card *card; |
92 | int ret; |
93 | |
94 | card = devm_kzalloc(dev: &pdev->dev, size: sizeof(*card), GFP_KERNEL); |
95 | if (!card) |
96 | return -ENOMEM; |
97 | |
98 | card->dev = &pdev->dev; |
99 | card->owner = THIS_MODULE; |
100 | |
101 | ret = snd_soc_of_parse_card_name(card, propname: "qcom,model" ); |
102 | if (ret) { |
103 | dev_err(&pdev->dev, "error parsing card name: %d\n" , ret); |
104 | return ret; |
105 | } |
106 | |
107 | card->dai_link = &storm_dai_link; |
108 | card->num_links = 1; |
109 | |
110 | ret = storm_parse_of(card); |
111 | if (ret) { |
112 | dev_err(&pdev->dev, "error resolving dai links: %d\n" , ret); |
113 | return ret; |
114 | } |
115 | |
116 | ret = devm_snd_soc_register_card(dev: &pdev->dev, card); |
117 | if (ret) |
118 | dev_err(&pdev->dev, "error registering soundcard: %d\n" , ret); |
119 | |
120 | return ret; |
121 | |
122 | } |
123 | |
124 | #ifdef CONFIG_OF |
125 | static const struct of_device_id storm_device_id[] = { |
126 | { .compatible = "google,storm-audio" }, |
127 | {}, |
128 | }; |
129 | MODULE_DEVICE_TABLE(of, storm_device_id); |
130 | #endif |
131 | |
132 | static struct platform_driver storm_platform_driver = { |
133 | .driver = { |
134 | .name = "storm-audio" , |
135 | .of_match_table = |
136 | of_match_ptr(storm_device_id), |
137 | }, |
138 | .probe = storm_platform_probe, |
139 | }; |
140 | module_platform_driver(storm_platform_driver); |
141 | |
142 | MODULE_DESCRIPTION("QTi IPQ806x-based Storm Machine Driver" ); |
143 | MODULE_LICENSE("GPL" ); |
144 | |