summaryrefslogtreecommitdiff
path: root/sound/soc/sa11x0/assabet.c
blob: d6c9cbefc1f607639cbbb56c1c61916e7f44e16f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
/*
 * SA11x0 Assabet ASoC driver
 *
 * Copyright (C) 2012 Russell King
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * The Assabet board uses an unused SDRAM clock output as the source
 * clock for the UDA1341 audio subsystem.  This clock is supplied to
 * via a CPLD divider to the SA11x0 GPIO19 (alternately the SSP block
 * external clock input) and to the UDA1341 as the bit clock.
 *
 * As the SSPs TXD,RXD,SFRM,SCLK outputs are not directly compatible
 * with the UDA1341 input, the CPLD implements logic to provide the
 * WS (LRCK) signal to the UDA1341, and buffer the RXD and clock signals.
 *
 * The UDA1341 is powered by the AUDIO3P3V supply, which can be turned
 * off.  This tristates the CPLD outputs, preventing power draining
 * into the UDA1341.  However, revision 4 Assabets do not tristate the
 * WS signal, and so need to be worked-around to place this at logic 0.
 *
 * A side effect of using the SDRAM clock is that this scales with the
 * core CPU frequency.  Hence, the available sample rate depends on the
 * CPU clock rate.  At present, we have no logic here to restrict the
 * requested sample rate; the UDA1341 driver will just fail the preparation.
 * We rely on userspace (at present) to ask for the correct sample rate.
 */
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/io.h>
#include <linux/module.h>

#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/uda134x.h>

#include <asm/mach-types.h>
#include <mach/assabet.h>
#include <mach/hardware.h>
#include "ssp.h"

struct assabet_card {
	struct snd_soc_card card;
	struct clk *clk_cpu;
	struct gpio_desc *gpio_spkr_shtdn;
};

#define GPIO_TXD	10
#define GPIO_RXD	11
#define GPIO_SCLK	12
#define GPIO_SFRM	13
#define GPIO_L3_DAT	15
#define GPIO_L3_MODE	17
#define GPIO_L3_SCK	18
#define GPIO_CLK	19

/*
 * The UDA1341 wants a WS (or LRCK), and the Assabet derives this from
 * the SFRM pulses from the SSP.  Unfortunately, some Assabets have a bug
 * in the hardware where the WS output from the CPLD remains high even
 * after the codec is powered down, causing power to drain through the
 * CPLD to the UDA1341.
 *
 * Although the Assabet provides a reset signal to initialize WS to a
 * known state, this presents two problems: (a) this forces the WS output
 * high, (b) this signal also resets the UCB1300 device.
 */
static void assabet_asoc_uda1341_power(int on)
{
	static bool state;

	if (state == !!on)
		return;
	state = !!on;

	if (on) {
		/*
		 * Enable the power for the UDA1341 before fixing WS.
		 * Also assert the mute signal.
		 */
		ASSABET_BCR_set(ASSABET_BCR_AUDIO_ON);

		/*
		 * Toggle SFRM with the codec reset signal held active.
		 * This will set LRCK high, which results in the left
		 * sample being transmitted first.
		 */
		gpio_set_value(GPIO_SFRM, 1);
		gpio_set_value(GPIO_SFRM, 0);

		/*
		 * If the reset was being held, release it now.  This
		 * ensures that the above SFRM fiddling has no effect
		 * should the reset be raised elsewhere.
		 */
		assabet_uda1341_reset(0);
	} else {
		/*
		 * The SSP will have transmitted a whole number of 32-bit
		 * words, so we know that LRCK will be left high at this
		 * point.  Toggle SFRM once to kick the LRCK low before
		 * powering down the codec.
		 */
		gpio_set_value(GPIO_SFRM, 1);
		gpio_set_value(GPIO_SFRM, 0);

		/* Finally, disable the audio power */
		ASSABET_BCR_clear(ASSABET_BCR_AUDIO_ON);
	}
}

static void assabet_asoc_init_clk(void)
{
	u32 mdrefr, mdrefr_old;

	/*
	 * The assabet board uses the SDRAM clock as the source clock for
	 * audio. This is supplied to the SA11x0 from the CPLD on pin 19.
	 * At 206MHz we need to run the audio clock (SDRAM bank 2) at half
	 * speed.  This clock will scale with core frequency so the audio
	 * sample rate will also scale. The CPLD on Assabet will need to
	 * be programmed to match the core frequency.
	 */
	mdrefr_old = MDREFR;
	mdrefr = mdrefr_old & ~(MDREFR_EAPD | MDREFR_KAPD);
	mdrefr = mdrefr | MDREFR_K2DB2 | MDREFR_K2RUN;
	if (mdrefr != mdrefr_old) {
		MDREFR = mdrefr;
		(void) MDREFR;
	}
}

static int assabet_asoc_resume_pre(struct snd_soc_card *card)
{
	struct snd_soc_pcm_runtime *rtd;
	bool active = false;

	list_for_each_entry(rtd, &card->rtd_list, list)
		if (rtd->cpu_dai->active)
			active = true;

	if (active) {
		unsigned long flags;

		local_irq_save(flags);
		assabet_asoc_init_clk();
		local_irq_restore(flags);
	}
	return 0;
}

/*
 * Calculate the sysclk, which is the codec input clock. This is supplied
 * from the SDRAM bank 2 clock, divided by 18 by the CPLD.  This is then
 * further divided by four by the CPLD, and supplied to the SA1110 GPIO19
 * to synchronously drive the SSP block.
 *
 * Ideally, we would lock cpufreq against any transitions which would upset
 * the sample rate, or have userspace renegotiate the sample rate, but let's
 * keep this simple.
 *
 * We intentionally lose some precision here to avoid the codec bombing out
 * as it claims to only do standard rates.
 */
static unsigned int assabet_asoc_get_sysclk(struct assabet_card *ac)
{
	return (clk_get_rate(ac->clk_cpu) / 18000) * 1000;
}

static int
assabet_asoc_rate_constraint(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule)
{
	struct assabet_card *ac = rule->private;
	struct snd_interval range, *p = hw_param_interval(params, rule->var);
	unsigned int sysclk = assabet_asoc_get_sysclk(ac);

	snd_interval_any(&range);
	range.min = UINT_MAX;
	range.max = 0;

	/* We do 512Fs and 256Fs only */
	if (snd_interval_test(p, sysclk / 256)) {
		range.min = min(range.min, sysclk / 256);
		range.max = max(range.max, sysclk / 256);
	}
	if (snd_interval_test(p, sysclk / 512)) {
		range.min = min(range.min, sysclk / 512);
		range.max = max(range.max, sysclk / 512);
	}
	return snd_interval_refine(p, &range);
}

static int assabet_asoc_startup(struct snd_pcm_substream *substream)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct assabet_card *ac = snd_soc_card_get_drvdata(rtd->card);
	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
	int ret;

	if (!cpu_dai->active) {
		unsigned long flags;
		unsigned int sysclk;

		ret = clk_prepare_enable(ac->clk_cpu);
		if (ret)
			return ret;

		/*
		 * Hand the SFRM signal over to the SSP block as it is
		 * now enabled.  This keeps SFRM low until the interface
		 * starts outputting data.
		 */
		local_irq_save(flags);
		GAFR |= GPIO_SSP_SFRM;

		assabet_asoc_init_clk();
		local_irq_restore(flags);

		sysclk = assabet_asoc_get_sysclk(ac);

		ret = snd_soc_dai_set_sysclk(rtd->codec_dai, 0, sysclk,
					     SND_SOC_CLOCK_IN);
		if (ret < 0)
			goto err;

		ret = snd_soc_dai_set_sysclk(cpu_dai, SA11X0_SSP_CLK_EXT,
					     sysclk / 4, SND_SOC_CLOCK_IN);
		if (ret < 0)
			goto err;
	}

	snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
			    assabet_asoc_rate_constraint, ac,
			    SNDRV_PCM_HW_PARAM_RATE, -1);

	return 0;

err:
	clk_disable_unprepare(ac->clk_cpu);
	return ret;
}

static void assabet_asoc_shutdown(struct snd_pcm_substream *substream)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct assabet_card *ac = snd_soc_card_get_drvdata(rtd->card);
	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;

	if (!cpu_dai->active) {
		unsigned long flags;

		/*
		 * Take the SFRM pin away from the SSP block before it
		 * shuts down.  We must do this to keep SFRM low.
		 */
		local_irq_save(flags);
		GAFR &= ~GPIO_SSP_SFRM;
		local_irq_restore(flags);

		clk_disable_unprepare(ac->clk_cpu);
	}
}

static struct snd_soc_ops assabet_asoc_ops = {
	.startup = assabet_asoc_startup,
	.shutdown = assabet_asoc_shutdown,
};

static struct snd_soc_dai_link assabet_asoc_dai = {
	.name = "Assabet",
	.stream_name = "assabet-ssp",
	.codec_name = "uda134x-codec",
	.platform_name = "sa11x0-ssp",
	.cpu_dai_name = "sa11x0-ssp",
	.codec_dai_name = "uda134x-hifi",
	.ops = &assabet_asoc_ops,
	.dai_fmt = SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_NB_NF |
		   SND_SOC_DAIFMT_CBS_CFS,
};

static int
assabet_pwramp_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k,
	int event)
{
	struct assabet_card *ac = snd_soc_card_get_drvdata(w->dapm->card);

	gpiod_set_value(ac->gpio_spkr_shtdn, !SND_SOC_DAPM_EVENT_ON(event));

	return 0;
}

static const struct snd_soc_dapm_widget assabet_dapm_widgets[] = {
	SND_SOC_DAPM_SPK("Power Amp", assabet_pwramp_event),
};

static const struct snd_soc_dapm_route assabet_dapm_routes[] = {
	{ "Power Amp", NULL, "VOUTL" },
	{ "Power Amp", NULL, "VOUTR" },
};

static struct snd_soc_card snd_soc_assabet = {
	.name = "Assabet",
	.resume_pre = assabet_asoc_resume_pre,
	.dai_link = &assabet_asoc_dai,
	.num_links = 1,
	.dapm_widgets = assabet_dapm_widgets,
	.num_dapm_widgets = ARRAY_SIZE(assabet_dapm_widgets),
	.dapm_routes = assabet_dapm_routes,
	.num_dapm_routes = ARRAY_SIZE(assabet_dapm_routes),
};

/*
 * This is not-quite-right stuff: we have to _hope_ by a miracle that
 * we don't stamp on the toes of I2C, which shares these pins.
 */
static void assabet_setdat(struct l3_pins *adap, int v)
{
	gpio_set_value(GPIO_L3_DAT, !!v);
}

static void assabet_setclk(struct l3_pins *adap, int v)
{
	gpio_set_value(GPIO_L3_SCK, !!v);
}

static void assabet_setmode(struct l3_pins *adap, int v)
{
	gpio_set_value(GPIO_L3_MODE, !!v);
}

static struct uda134x_platform_data uda1341_data = {
	.l3 = {
		.setdat = assabet_setdat,
		.setclk = assabet_setclk,
		.setmode = assabet_setmode,
		.data_hold = 1,
		.data_setup = 1,
		.clock_high = 1,
		.mode_hold = 1,
		.mode = 1,
		.mode_setup = 1,
	},
	.model = UDA134X_UDA1341,
	.power = assabet_asoc_uda1341_power,
};

static struct gpio ssp_gpio[] = {
	{
		.gpio = GPIO_TXD,
		.flags = GPIOF_OUT_INIT_LOW,
		.label = "ssp_txd",
	}, {
		.gpio = GPIO_RXD,
		.flags = GPIOF_IN,
		.label = "ssp_rxd",
	}, {
		.gpio = GPIO_SCLK,
		.flags = GPIOF_OUT_INIT_LOW,
		.label = "ssp_sclk",
	}, {
		.gpio = GPIO_SFRM,
		.flags = GPIOF_OUT_INIT_LOW,
		.label = "ssp_sfrm",
	}, {
		.gpio = GPIO_CLK,
		.flags = GPIOF_IN,
		.label = "ssp_clk",
	}, {
		.gpio = GPIO_L3_MODE,
		.flags = GPIOF_OUT_INIT_LOW,
		.label = "l3_mode",
	},
};

static int assabet_asoc_probe(struct platform_device *pdev)
{
	struct assabet_card *ac;

	ac = devm_kzalloc(&pdev->dev, sizeof(*ac), GFP_KERNEL);
	if (!ac)
		return -ENOMEM;

	memcpy(&ac->card, &snd_soc_assabet, sizeof(ac->card));
	ac->card.dev = &pdev->dev;

	ac->clk_cpu = devm_clk_get(&pdev->dev, NULL);
	if (IS_ERR(ac->clk_cpu))
		return PTR_ERR(ac->clk_cpu);

	ac->gpio_spkr_shtdn = devm_gpiod_get(&pdev->dev, "spkr-shtdn",
					     GPIOD_OUT_HIGH);
	if (IS_ERR(ac->gpio_spkr_shtdn))
		return PTR_ERR(ac->gpio_spkr_shtdn);

	snd_soc_card_set_drvdata(&ac->card, ac);

	return snd_soc_register_card(&ac->card);
}

static int assabet_asoc_remove(struct platform_device *pdev)
{
	struct snd_soc_card *card = platform_get_drvdata(pdev);

	snd_soc_unregister_card(card);

	return 0;
}

static struct platform_driver assabet_asoc_driver = {
	.probe	= assabet_asoc_probe,
	.remove	= assabet_asoc_remove,
	.driver = {
		.name = "assabet-asoc",
		.owner = THIS_MODULE,
	},
};

static struct platform_device *soc, *uda, *dma;

static int assabet_init(void)
{
	unsigned long flags;
	int ret;

	if (!machine_is_assabet())
		return -ENODEV;

	ret = gpio_request_array(ssp_gpio, ARRAY_SIZE(ssp_gpio));
	if (ret)
		return ret;

	/*
	 * Request these irrespective of whether they succeed.  Wish we
	 * could have our shared I2C/L3 driver from v2.4 kernels, but
	 * alas this would require a buggeration of the ASoC code.
	 * Not only this, but we have no way to do any kind of locking
	 * between the UDA134x L3 driver and I2C.
	 */
	gpio_request_one(GPIO_L3_DAT, GPIOF_OUT_INIT_LOW, "l3_dat");
	gpio_request_one(GPIO_L3_SCK, GPIOF_OUT_INIT_LOW, "l3_sck");

	/*
	 * Put the LRCK signal into a known good state: early Assabets
	 * do not mask the LRCK signal when the codec is powered down,
	 * and it defaults to logic 1.  So, first release the reset,
	 * and then toggle the SFRM signal to set LRCK to zero.
	 */
	ASSABET_BCR_clear(ASSABET_BCR_STEREO_LB);
	assabet_uda1341_reset(0);

	gpio_set_value(GPIO_SFRM, 1);
	gpio_set_value(GPIO_SFRM, 0);

	local_irq_save(flags);
	/*
	 * Ensure SSP pin reassignment is enabled, so that the SSP appears
	 * on the GPIO pins rather than the deddicated UART4 pins.
	 */
	PPAR |= PPAR_SPR;

	/* Configure the SSP input clock, and most SSP output signals */
	GAFR |= GPIO_SSP_TXD | GPIO_SSP_RXD | GPIO_SSP_SCLK | GPIO_SSP_CLK;

	local_irq_restore(flags);

	soc = platform_device_alloc("assabet-asoc", -1);
	uda = platform_device_alloc(assabet_asoc_dai.codec_name, -1);
	dma = platform_device_alloc("soc-dmaengine", -1);
	if (!soc || !uda || !dma) {
		ret = -ENOMEM;
		goto err_dev_alloc;
	}

	/* Set some platform data for the UDA1341 codec driver... */
	uda->dev.platform_data = &uda1341_data;

	/* Set some sensible parents */
	uda->dev.parent = &soc->dev;
	dma->dev.parent = &soc->dev;

	ret = platform_device_add(soc);
	if (ret)
		goto err_dev_alloc;
	ret = platform_device_add(uda);
	if (ret)
		goto err_dev_add_soc;
	ret = platform_device_add(dma);
	if (ret)
		goto err_dev_add_uda;
	ret = platform_driver_register(&assabet_asoc_driver);
	if (ret)
		goto err_dev_add_dma;
	return 0;

 err_dev_add_dma:
	platform_device_del(dma);
 err_dev_add_uda:
	platform_device_del(uda);
 err_dev_add_soc:
	platform_device_del(soc);
 err_dev_alloc:
	if (dma)
		platform_device_put(dma);
	if (uda)
		platform_device_put(uda);
	if (soc)
		platform_device_put(soc);

	gpio_free_array(ssp_gpio, ARRAY_SIZE(ssp_gpio));

	return ret;
}
module_init(assabet_init);

static void assabet_exit(void)
{
	if (!machine_is_assabet())
		return;

	platform_driver_unregister(&assabet_asoc_driver);
	platform_device_unregister(dma);
	platform_device_unregister(uda);

	/* Don't try to kfree this data */
	soc->dev.platform_data = NULL;

	platform_device_unregister(soc);
	gpio_free_array(ssp_gpio, ARRAY_SIZE(ssp_gpio));
}
module_exit(assabet_exit);

MODULE_LICENSE("GPL");