blob: 27548ac1a2e896187397fa28a7513435852cceeb [file] [log] [blame]
Duy Truong790f06d2013-02-13 16:38:12 -08001/* Copyright (c) 2009-2011, The Linux Foundation. All rights reserved.
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07002 *
3 * Based on the mp3 native driver in arch/arm/mach-msm/qdsp5v2/audio_mp3.c
4 *
5 * Copyright (C) 2008 Google, Inc.
6 * Copyright (C) 2008 HTC Corporation
7 *
8 * All source code in this file is licensed under the following license except
9 * where indicated.
10 *
11 * This program is free software; you can redistribute it and/or modify it
12 * under the terms of the GNU General Public License version 2 as published
13 * by the Free Software Foundation.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18 *
19 * See the GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, you can find it at http://www.fsf.org
22 */
23
24#include <linux/module.h>
25#include <linux/fs.h>
26#include <linux/miscdevice.h>
27#include <linux/uaccess.h>
28#include <linux/kthread.h>
29#include <linux/wait.h>
30#include <linux/dma-mapping.h>
31#include <linux/delay.h>
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -070032#include <linux/msm_audio.h>
33#include <asm/atomic.h>
34#include <asm/ioctls.h>
35#include <mach/msm_adsp.h>
36#include <mach/debug_mm.h>
37#include <mach/qdsp5v2/audio_dev_ctl.h>
38#include <mach/qdsp5v2/afe.h>
39#include <mach/qdsp5v2/acdb_commands.h>
40#include <mach/qdsp5v2/audio_acdbi.h>
41#include <mach/qdsp5v2/audio_acdb_def.h>
42
43#define SESSION_ID_FM 6
44#define FM_ENABLE 0xFFFF
45#define FM_DISABLE 0x0
46#define FM_COPP 0x2
47/* Macro specifies maximum FM routing
48 possible */
49#define FM_MAX_RX_ROUTE 0x2
50
51struct fm_rx_calib_gain {
52 uint16_t device_id;
53 struct auddev_evt_devinfo dev_details;
54 struct acdb_calib_gain_rx calib_rx;
55};
56
57struct audio {
58 struct mutex lock;
59
60 int opened;
61 int enabled;
62 int running;
63
64 uint16_t dec_id;
65 uint16_t source;
66 uint16_t fm_source;
67 uint16_t fm_mask;
68 uint32_t device_events;
69 uint16_t volume;
70 struct fm_rx_calib_gain fm_calibration_rx[FM_MAX_RX_ROUTE];
71};
72
73static struct audio fm_audio;
74
75/* must be called with audio->lock held */
76static int audio_enable(struct audio *audio)
77{
78 int rc = 0;
79 if (audio->enabled)
80 return 0;
81
82 MM_DBG("fm mask= %08x fm_source = %08x\n",
83 audio->fm_mask, audio->fm_source);
84 if (audio->fm_mask && audio->fm_source) {
85 rc = afe_config_fm_codec(FM_ENABLE, audio->fm_mask);
86 if (!rc)
87 audio->running = 1;
88 /* Routed to icodec rx path */
89 if ((audio->fm_mask & AFE_HW_PATH_CODEC_RX) ==
90 AFE_HW_PATH_CODEC_RX) {
91 afe_config_fm_calibration_gain(
92 audio->fm_calibration_rx[0].device_id,
93 audio->fm_calibration_rx[0].calib_rx.audppcalgain);
94 }
95 /* Routed to aux codec rx path */
96 if ((audio->fm_mask & AFE_HW_PATH_AUXPCM_RX) ==
97 AFE_HW_PATH_AUXPCM_RX){
98 afe_config_fm_calibration_gain(
99 audio->fm_calibration_rx[1].device_id,
100 audio->fm_calibration_rx[1].calib_rx.audppcalgain);
101 }
102 }
103
104 audio->enabled = 1;
105 return rc;
106}
107
108static void fm_listner(u32 evt_id, union auddev_evt_data *evt_payload,
109 void *private_data)
110{
111 struct audio *audio = (struct audio *) private_data;
112 struct auddev_evt_devinfo *devinfo =
113 (struct auddev_evt_devinfo *)evt_payload;
114 switch (evt_id) {
115 case AUDDEV_EVT_DEV_RDY:
116 MM_DBG(":AUDDEV_EVT_DEV_RDY\n");
117 if (evt_payload->routing_id == FM_COPP)
118 audio->fm_source = 1;
119 else
120 audio->source = (0x1 << evt_payload->routing_id);
121
122 if (audio->source & 0x1)
123 audio->fm_mask = 0x1;
124 else if (audio->source & 0x2)
125 audio->fm_mask = 0x3;
126 else
127 audio->fm_mask = 0x0;
128
129 if (!audio->enabled
130 || !audio->fm_mask
131 || !audio->fm_source)
132 break;
133 else {
134 afe_config_fm_codec(FM_ENABLE, audio->fm_mask);
135 audio->running = 1;
136 }
137 break;
138 case AUDDEV_EVT_DEV_RLS:
139 MM_DBG(":AUDDEV_EVT_DEV_RLS\n");
140 if (evt_payload->routing_id == FM_COPP)
141 audio->fm_source = 0;
142 else
143 audio->source &= ~(0x1 << evt_payload->routing_id);
144
145 if (audio->source & 0x1)
146 audio->fm_mask = 0x1;
147 else if (audio->source & 0x2)
148 audio->fm_mask = 0x3;
149 else
150 audio->fm_mask = 0x0;
151
152 if (audio->running
153 && (!audio->fm_mask || !audio->fm_source)) {
154 afe_config_fm_codec(FM_DISABLE, audio->fm_mask);
155 audio->running = 0;
156 }
157 break;
158 case AUDDEV_EVT_STREAM_VOL_CHG:
159 MM_DBG(":AUDDEV_EVT_STREAM_VOL_CHG, stream vol \n");
160 audio->volume = evt_payload->session_vol;
161 afe_config_fm_volume(audio->volume);
162 break;
163 case AUDDEV_EVT_DEVICE_INFO:{
164 struct acdb_get_block get_block;
165 int rc = 0;
166 MM_DBG(":AUDDEV_EVT_DEVICE_INFO\n");
167 MM_DBG("sample_rate = %d\n", devinfo->sample_rate);
168 MM_DBG("acdb_id = %d\n", devinfo->acdb_id);
169 /* Applucable only for icodec rx and aux codec rx path
170 and fm stream routed to it */
171 if (((devinfo->dev_id == 0x00) || (devinfo->dev_id == 0x01)) &&
172 (devinfo->sessions && (1 << audio->dec_id))) {
173 /* Query ACDB driver for calib gain, only if difference
174 in device */
175 if ((audio->fm_calibration_rx[devinfo->dev_id].
176 dev_details.acdb_id != devinfo->acdb_id) ||
177 (audio->fm_calibration_rx[devinfo->dev_id].
178 dev_details.sample_rate !=
179 devinfo->sample_rate)) {
180 audio->fm_calibration_rx[devinfo->dev_id].
181 dev_details.dev_id = devinfo->dev_id;
182 audio->fm_calibration_rx[devinfo->dev_id].
183 dev_details.sample_rate =
184 devinfo->sample_rate;
185 audio->fm_calibration_rx[devinfo->dev_id].
186 dev_details.dev_type =
187 devinfo->dev_type;
188 audio->fm_calibration_rx[devinfo->dev_id].
189 dev_details.sessions =
190 devinfo->sessions;
191 /* Query ACDB driver for calibration gain */
192 get_block.acdb_id = devinfo->acdb_id;
193 get_block.sample_rate_id = devinfo->sample_rate;
194 get_block.interface_id =
195 IID_AUDIO_CALIBRATION_GAIN_RX;
196 get_block.algorithm_block_id =
197 ABID_AUDIO_CALIBRATION_GAIN_RX;
198 get_block.total_bytes =
199 sizeof(struct acdb_calib_gain_rx);
200 get_block.buf_ptr = (u32 *)
201 &audio->fm_calibration_rx[devinfo->dev_id].
202 calib_rx;
203
204 rc = acdb_get_calibration_data(&get_block);
205 if (rc < 0) {
206 MM_ERR("Unable to get calibration"\
207 "gain\n");
208 /* Set to unity incase of error */
209 audio->\
210 fm_calibration_rx[devinfo->dev_id].
211 calib_rx.audppcalgain = 0x2000;
212 } else
213 MM_DBG("calibration gain = 0x%8x\n",
214 *(get_block.buf_ptr));
215 }
216 if (audio->running) {
217 afe_config_fm_calibration_gain(
218 audio->fm_calibration_rx[devinfo->dev_id].
219 device_id,
220 audio->fm_calibration_rx[devinfo->dev_id].
221 calib_rx.audppcalgain);
222 }
223 }
224 break;
225 }
226 default:
227 MM_DBG(":ERROR:wrong event\n");
228 break;
229 }
230}
231/* must be called with audio->lock held */
232static int audio_disable(struct audio *audio)
233{
234 MM_DBG("\n"); /* Macro prints the file name and function */
235 return afe_config_fm_codec(FM_DISABLE, audio->source);
236}
237
238static long audio_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
239{
240 struct audio *audio = file->private_data;
241 int rc = -EINVAL;
242
243 MM_DBG("cmd = %d\n", cmd);
244
245 mutex_lock(&audio->lock);
246 switch (cmd) {
247 case AUDIO_START:
248 MM_DBG("AUDIO_START\n");
249 rc = audio_enable(audio);
250 break;
251 case AUDIO_STOP:
252 MM_DBG("AUDIO_STOP\n");
253 rc = audio_disable(audio);
254 audio->running = 0;
255 audio->enabled = 0;
256 break;
257 case AUDIO_GET_SESSION_ID:
258 if (copy_to_user((void *) arg, &audio->dec_id,
259 sizeof(unsigned short)))
260 rc = -EFAULT;
261 else
262 rc = 0;
263 break;
264 default:
265 rc = -EINVAL;
266 }
267 mutex_unlock(&audio->lock);
268 return rc;
269}
270
271static int audio_release(struct inode *inode, struct file *file)
272{
273 struct audio *audio = file->private_data;
274
275 MM_DBG("audio instance 0x%08x freeing\n", (int)audio);
276 mutex_lock(&audio->lock);
277 auddev_unregister_evt_listner(AUDDEV_CLNT_DEC, audio->dec_id);
278 audio_disable(audio);
279 audio->running = 0;
280 audio->enabled = 0;
281 audio->opened = 0;
282 mutex_unlock(&audio->lock);
283 return 0;
284}
285
286static int audio_open(struct inode *inode, struct file *file)
287{
288 struct audio *audio = &fm_audio;
289 int rc = 0;
290
291
292 if (audio->opened)
293 return -EPERM;
294
295 /* Allocate the decoder */
296 audio->dec_id = SESSION_ID_FM;
297
298 audio->running = 0;
299 audio->fm_source = 0;
300 audio->fm_mask = 0;
301
302 /* Initialize the calibration gain structure */
303 audio->fm_calibration_rx[0].device_id = AFE_HW_PATH_CODEC_RX;
304 audio->fm_calibration_rx[1].device_id = AFE_HW_PATH_AUXPCM_RX;
305 audio->fm_calibration_rx[0].calib_rx.audppcalgain = 0x2000;
306 audio->fm_calibration_rx[1].calib_rx.audppcalgain = 0x2000;
307 audio->fm_calibration_rx[0].dev_details.acdb_id = PSEUDO_ACDB_ID;
308 audio->fm_calibration_rx[1].dev_details.acdb_id = PSEUDO_ACDB_ID;
309
310 audio->device_events = AUDDEV_EVT_DEV_RDY
311 |AUDDEV_EVT_DEV_RLS|
312 AUDDEV_EVT_STREAM_VOL_CHG|
313 AUDDEV_EVT_DEVICE_INFO;
314
315 rc = auddev_register_evt_listner(audio->device_events,
316 AUDDEV_CLNT_DEC,
317 audio->dec_id,
318 fm_listner,
319 (void *)audio);
320
321 if (rc) {
322 MM_ERR("%s: failed to register listnet\n", __func__);
323 goto event_err;
324 }
325
326 audio->opened = 1;
327 file->private_data = audio;
328
329event_err:
330 return rc;
331}
332
333static const struct file_operations audio_fm_fops = {
334 .owner = THIS_MODULE,
335 .open = audio_open,
336 .release = audio_release,
337 .unlocked_ioctl = audio_ioctl,
338};
339
340struct miscdevice audio_fm_misc = {
341 .minor = MISC_DYNAMIC_MINOR,
342 .name = "msm_fm",
343 .fops = &audio_fm_fops,
344};
345
346static int __init audio_init(void)
347{
348 struct audio *audio = &fm_audio;
349
350 mutex_init(&audio->lock);
351 return misc_register(&audio_fm_misc);
352}
353
354device_initcall(audio_init);
355
356MODULE_DESCRIPTION("MSM FM driver");
357MODULE_LICENSE("GPL v2");