Драйвер для неуправляемого аудиокодека wm8725
 
	
	sasa  
	
		
	  
	
		
		 
		 
		 
		 
		 
		 
		 
		Регистрация: 20.05.2009  
		 
		 
		
	 
		
		Перепаял недавно на переходник с lcd lph88, решил избавиться от spi и брать тактовую для цап c tc0 - выход A у него находится на том-же разъеме что и ssc, что намного удобней чем PCK0 находящийся на другом разъеме. Только цап работает всего секнду и потом сплошной шум - не понятно  - или возбуждается или я его окончательно подпалил при перепайке... Собственно моего кода там 10 строчек :) в основном убрал все лишнее. Будет работать скорей всего с любым цап не требующим инициализации. У меня частота MCK = 98304000 Гц - именно на нее расчитан драйвер, с другой не заработает, нужно менять настройки plla в bootstrap - как сделать это описывал в старом форуме.
Код  
/* 
 * Driver for WM8725 16-bit stereo DAC connected to Atmel SSC 
 * 
 * Copyright (C) 2006-2007 Atmel Norway 
 * 
 * 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. 
 */ 
 
 
#include <linux/clk.h> 
#include <linux/err.h> 
#include <linux/delay.h> 
#include <linux/device.h> 
#include <linux/dma-mapping.h> 
#include <linux/init.h> 
#include <linux/interrupt.h> 
#include <linux/module.h> 
#include <linux/mutex.h> 
#include <linux/platform_device.h> 
#include <linux/io.h> 
 
#include <sound/initval.h> 
#include <sound/control.h> 
#include <sound/core.h> 
#include <sound/pcm.h> 
 
#include <linux/atmel-ssc.h> 
#include <mach/at91_tc.h> 
#include <mach/gpio.h> 
 
#define BITRATE_TARGET	48000 
 
struct snd_wm8725 { 
	struct snd_card			*card; 
	struct snd_pcm			*pcm; 
	struct snd_pcm_substream	*substream; 
	int				irq; 
	int				period; 
	unsigned long			bitrate; 
	struct clk			*bitclk; 
	struct ssc_device		*ssc; 
	struct platform_device *pdev; 
	/* Protect SSC registers against concurrent access. */ 
	spinlock_t			lock; 
}; 
 
#define get_chip(card) ((struct snd_wm8725 *)card->private_data) 
 
static struct snd_pcm_hardware snd_wm8725_playback_hw = { 
	.info		= SNDRV_PCM_INFO_INTERLEAVED | 
			  SNDRV_PCM_INFO_BLOCK_TRANSFER, 
	.formats	= SNDRV_PCM_FMTBIT_S16_BE, 
	.rates		= SNDRV_PCM_RATE_CONTINUOUS, 
	.rate_min	= 8000,  /* Replaced by chip->bitrate later. */ 
	.rate_max	= 50000, /* Replaced by chip->bitrate later. */ 
	.channels_min	= 1, 
	.channels_max	= 2, 
	.buffer_bytes_max = 64 * 1024 - 1, 
	.period_bytes_min = 512, 
	.period_bytes_max = 64 * 1024 - 1, 
	.periods_min	= 4, 
	.periods_max	= 1024, 
}; 
 
/* 
 * Calculate and set bitrate and divisions. 
 */ 
static int snd_wm8725_set_bitrate(struct snd_wm8725 *chip) 
{ 
	unsigned long ssc_rate = clk_get_rate(chip->ssc->clk); 
	unsigned long ssc_div; 
 
	/* 
	 * The DAC master clock (MCLK) is programmable, and is either 256 
	 * or (not here) 384 times the I2S output clock (BCLK). 
	 */ 
 
	/* SSC clock / (bitrate * stereo * 16-bit). */ 
	ssc_div = ssc_rate / (BITRATE_TARGET * 2 * 16); 
 
	/* Set divider in SSC device. */ 
	ssc_writel(chip->ssc->regs, CMR, ssc_div/2); 
 
	/* SSC clock / (ssc divider * 16-bit * stereo). */ 
	chip->bitrate = ssc_rate / (ssc_div * 16 * 2); 
 
	printk(KERN_INFO "wm8725: supported bitrate is %lu (%lu divider)\n", 
			chip->bitrate, ssc_div); 
 
	return 0; 
} 
 
static int snd_wm8725_pcm_open(struct snd_pcm_substream *substream) 
{ 
	struct snd_wm8725 *chip = snd_pcm_substream_chip(substream); 
	struct snd_pcm_runtime *runtime = substream->runtime; 
	int err; 
 
	/* ensure buffer_size is a multiple of period_size */ 
	err = snd_pcm_hw_constraint_integer(runtime, 
					SNDRV_PCM_HW_PARAM_PERIODS); 
	if (err < 0) 
		return err; 
	snd_wm8725_playback_hw.rate_min = chip->bitrate; 
	snd_wm8725_playback_hw.rate_max = chip->bitrate; 
	runtime->hw = snd_wm8725_playback_hw; 
	chip->substream = substream; 
 
	return 0; 
} 
 
static int snd_wm8725_pcm_close(struct snd_pcm_substream *substream) 
{ 
	struct snd_wm8725 *chip = snd_pcm_substream_chip(substream); 
	chip->substream = NULL; 
	return 0; 
} 
 
static int snd_wm8725_pcm_hw_params(struct snd_pcm_substream *substream, 
				 struct snd_pcm_hw_params *hw_params) 
{ 
	struct snd_wm8725 *chip = snd_pcm_substream_chip(substream); 
	int channels = params_channels(hw_params); 
	int val; 
 
	val = ssc_readl(chip->ssc->regs, TFMR); 
	val = SSC_BFINS(TFMR_DATNB, channels - 1, val); 
	ssc_writel(chip->ssc->regs, TFMR, val); 
 
	return snd_pcm_lib_malloc_pages(substream, 
					params_buffer_bytes(hw_params)); 
} 
 
static int snd_wm8725_pcm_hw_free(struct snd_pcm_substream *substream) 
{ 
	return snd_pcm_lib_free_pages(substream); 
} 
 
static int snd_wm8725_pcm_prepare(struct snd_pcm_substream *substream) 
{ 
	struct snd_wm8725 *chip = snd_pcm_substream_chip(substream); 
	struct snd_pcm_runtime *runtime = substream->runtime; 
	int block_size; 
 
	block_size = frames_to_bytes(runtime, runtime->period_size); 
 
	chip->period = 0; 
 
	ssc_writel(chip->ssc->regs, PDC_TPR, 
			(long)runtime->dma_addr); 
	ssc_writel(chip->ssc->regs, PDC_TCR, 
			runtime->period_size * runtime->channels); 
	ssc_writel(chip->ssc->regs, PDC_TNPR, 
			(long)runtime->dma_addr + block_size); 
	ssc_writel(chip->ssc->regs, PDC_TNCR, 
			runtime->period_size * runtime->channels); 
 
	return 0; 
} 
 
static int snd_wm8725_pcm_trigger(struct snd_pcm_substream *substream, 
				   int cmd) 
{ 
	struct snd_wm8725 *chip = snd_pcm_substream_chip(substream); 
	int retval = 0; 
 
	spin_lock(&chip->lock); 
 
	switch (cmd) { 
	case SNDRV_PCM_TRIGGER_START: 
		ssc_writel(chip->ssc->regs, IER, SSC_BIT(IER_ENDTX)); 
		ssc_writel(chip->ssc->regs, PDC_PTCR, SSC_BIT(PDC_PTCR_TXTEN)); 
		break; 
	case SNDRV_PCM_TRIGGER_STOP: 
		ssc_writel(chip->ssc->regs, PDC_PTCR, SSC_BIT(PDC_PTCR_TXTDIS)); 
		ssc_writel(chip->ssc->regs, IDR, SSC_BIT(IDR_ENDTX)); 
		break; 
	default: 
		dev_dbg(&chip->pdev->dev, "spurious command %x\n", cmd); 
		retval = -EINVAL; 
		break; 
	} 
 
	spin_unlock(&chip->lock); 
 
	return retval; 
} 
 
static snd_pcm_uframes_t 
snd_wm8725_pcm_pointer(struct snd_pcm_substream *substream) 
{ 
	struct snd_wm8725 *chip = snd_pcm_substream_chip(substream); 
	struct snd_pcm_runtime *runtime = substream->runtime; 
	snd_pcm_uframes_t pos; 
	unsigned long bytes; 
 
	bytes = ssc_readl(chip->ssc->regs, PDC_TPR) 
		- (unsigned long)runtime->dma_addr; 
 
	pos = bytes_to_frames(runtime, bytes); 
	if (pos >= runtime->buffer_size) 
		pos -= runtime->buffer_size; 
 
	return pos; 
} 
 
static struct snd_pcm_ops wm8725_playback_ops = { 
	.open		= snd_wm8725_pcm_open, 
	.close		= snd_wm8725_pcm_close, 
	.ioctl		= snd_pcm_lib_ioctl, 
	.hw_params	= snd_wm8725_pcm_hw_params, 
	.hw_free	= snd_wm8725_pcm_hw_free, 
	.prepare	= snd_wm8725_pcm_prepare, 
	.trigger	= snd_wm8725_pcm_trigger, 
	.pointer	= snd_wm8725_pcm_pointer, 
}; 
 
static int __init snd_wm8725_pcm_new(struct snd_wm8725 *chip, int device) 
{ 
	struct snd_pcm *pcm; 
	int retval; 
 
	retval = snd_pcm_new(chip->card, chip->card->shortname, 
			device, 1, 0, &pcm); 
	if (retval < 0) 
		goto out; 
 
	pcm->private_data = chip; 
	pcm->info_flags = SNDRV_PCM_INFO_BLOCK_TRANSFER; 
	strcpy(pcm->name, "wm8725"); 
	chip->pcm = pcm; 
 
	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &wm8725_playback_ops); 
 
	retval = snd_pcm_lib_preallocate_pages_for_all(chip->pcm, 
			SNDRV_DMA_TYPE_DEV, &chip->ssc->pdev->dev, 
			64 * 1024, 64 * 1024); 
out: 
	return retval; 
} 
 
static irqreturn_t snd_wm8725_interrupt(int irq, void *dev_id) 
{ 
	struct snd_wm8725 *chip = dev_id; 
	struct snd_pcm_runtime *runtime = chip->substream->runtime; 
	u32 status; 
	int offset; 
	int block_size; 
	int next_period; 
	int retval = IRQ_NONE; 
 
	spin_lock(&chip->lock); 
 
	block_size = frames_to_bytes(runtime, runtime->period_size); 
	status = ssc_readl(chip->ssc->regs, IMR); 
 
	if (status & SSC_BIT(IMR_ENDTX)) { 
		chip->period++; 
		if (chip->period == runtime->periods) 
			chip->period = 0; 
		next_period = chip->period + 1; 
		if (next_period == runtime->periods) 
			next_period = 0; 
 
		offset = block_size * next_period; 
 
		ssc_writel(chip->ssc->regs, PDC_TNPR, 
				(long)runtime->dma_addr + offset); 
		ssc_writel(chip->ssc->regs, PDC_TNCR, 
				runtime->period_size * runtime->channels); 
		retval = IRQ_HANDLED; 
	} 
 
	ssc_readl(chip->ssc->regs, IMR); 
	spin_unlock(&chip->lock); 
 
	if (status & SSC_BIT(IMR_ENDTX)) 
		snd_pcm_period_elapsed(chip->substream); 
 
	return retval; 
} 
 
/* 
 * Device functions 
 */ 
static int __init snd_wm8725_ssc_init(struct snd_wm8725 *chip) 
{ 
	/* 
	 * Continuous clock output. 
	 * Starts on falling TF. 
	 * Delay 1 cycle (1 bit). 
	 * Periode is 16 bit (16 - 1). 
	 */ 
	ssc_writel(chip->ssc->regs, TCMR, 
			SSC_BF(TCMR_CKO, 1) 
			| SSC_BF(TCMR_START, 4) 
//			| SSC_BF(TCMR_STTDLY, 1) 
			| SSC_BF(TCMR_PERIOD, 16 - 1)); 
	/* 
	 * Data length is 16 bit (16 - 1). 
	 * Transmit MSB first. 
	 * Transmit 2 words each transfer. 
	 * Frame sync length is 16 bit (16 - 1). 
	 * Frame starts on negative pulse. 
	 */ 
	ssc_writel(chip->ssc->regs, TFMR, 
			SSC_BF(TFMR_DATLEN, 16 - 1) 
			| SSC_BIT(TFMR_MSBF) 
			| SSC_BF(TFMR_DATNB, 1) 
			| SSC_BF(TFMR_FSLEN, 16 - 1) 
			| SSC_BF(TFMR_FSOS, 1)); 
 
	return 0; 
} 
 
#define at91_tc_read(reg)		__raw_readl(tc0_base + (reg)) 
#define at91_tc_write(reg, val)	__raw_writel((val), tc0_base + (reg)) 
 
static int __init snd_wm8725_chip_init(struct snd_wm8725 *chip) 
{ 
	int retval; 
	void __iomem *tc0_base; 
	struct clk *tc0_clk; 
 
	retval = snd_wm8725_set_bitrate(chip); 
 
	/* Enable DAC master clock. */ 
	tc0_clk = clk_get(NULL, "tc0_clk"); 
	clk_enable(tc0_clk); 
 
	tc0_base = ioremap_nocache(AT91SAM9260_BASE_TC0, 256); 
	if (!tc0_base) { 
		clk_disable(tc0_clk); 
		printk(KERN_ERR "at91adc: Can't remap TC0 register area\n"); 
		return -EACCES; 
	} 
 
	at91_set_A_periph(AT91_PIN_PA26, 0); 
 
	at91_tc_write(AT91_TC_CMR, AT91_TC_WAVE | AT91_TC_WAVESEL_UP_AUTO 
	            | AT91_TC_TIMER_CLOCK1 | AT91_TC_ACPA_SET | AT91_TC_ACPC_CLEAR); 
 
	/* set rate */ 
	at91_tc_write(AT91_TC_RC, 4); 
	/* duty 50 % */ 
	at91_tc_write(AT91_TC_RA, 2); 
 
	at91_tc_write(AT91_TC_IDR, -1); 
 
	at91_tc_write(AT91_TC_CCR, AT91_TC_SWTRG | AT91_TC_CLKEN); 
 
	/* Enable I2S device, i.e. clock output. */ 
	ssc_writel(chip->ssc->regs, CR, SSC_BIT(CR_TXEN)); 
 
	return retval; 
} 
 
static int snd_wm8725_dev_free(struct snd_device *device) 
{ 
	struct snd_wm8725 *chip = device->device_data; 
 
	ssc_writel(chip->ssc->regs, CR, SSC_BIT(CR_TXDIS)); 
	if (chip->irq >= 0) { 
		free_irq(chip->irq, chip); 
		chip->irq = -1; 
	} 
 
	return 0; 
} 
 
static int __init snd_wm8725_dev_init(struct snd_card *card, 
					 struct platform_device *pdev) 
{ 
	static struct snd_device_ops ops = { 
		.dev_free	= snd_wm8725_dev_free, 
	}; 
	struct snd_wm8725 *chip = get_chip(card); 
	int irq, retval; 
 
	irq = chip->ssc->irq; 
	if (irq < 0) 
		return irq; 
 
	spin_lock_init(&chip->lock); 
	chip->card = card; 
	chip->irq = -1; 
 
	retval = request_irq(irq, snd_wm8725_interrupt, 0, "wm8725", chip); 
	if (retval) { 
		dev_dbg(&pdev->dev, "unable to request irq %d\n", irq); 
		goto out; 
	} 
	chip->irq = irq; 
 
	retval = snd_wm8725_ssc_init(chip); 
	if (retval) 
		goto out_irq; 
 
	retval = snd_wm8725_chip_init(chip); 
	if (retval) 
		goto out_irq; 
 
	retval = snd_wm8725_pcm_new(chip, 0); 
	if (retval) 
		goto out_irq; 
 
	retval = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); 
	if (retval) 
		goto out_irq; 
 
	snd_card_set_dev(card, &pdev->dev); 
 
	goto out; 
 
out_irq: 
	free_irq(chip->irq, chip); 
	chip->irq = -1; 
out: 
	return retval; 
} 
 
static int __init snd_wm8725_probe(struct platform_device *pdev) 
{ 
	struct snd_card			*card; 
	struct snd_wm8725		*chip; 
	int				retval; 
	char				id[16]; 
 
	retval = -ENOMEM; 
 
	/* Allocate "card" using some unused identifiers. */ 
	snprintf(id, sizeof id, "wm8725_%d", 0); 
	card = snd_card_new(-1, id, THIS_MODULE, sizeof(struct snd_wm8725)); 
	if (!card) 
		goto out; 
 
	chip = card->private_data; 
	chip->pdev = pdev; 
 
	chip->ssc = ssc_request(0); 
	if (IS_ERR(chip->ssc)) { 
		dev_dbg(&pdev->dev, "could not get ssc%d device\n", 0); 
		retval = PTR_ERR(chip->ssc); 
		goto out_card; 
	} 
 
	retval = snd_wm8725_dev_init(card, pdev); 
	if (retval) 
		goto out_ssc; 
 
	strcpy(card->driver, "wm8725"); 
	strcpy(card->shortname, "AT91SAM9260-EK external DAC"); 
	sprintf(card->longname, "%s on irq %d", card->shortname, chip->irq); 
 
	retval = snd_card_register(card); 
	if (retval) 
		goto out_ssc; 
 
	platform_set_drvdata(pdev, card); 
 
	goto out; 
 
out_ssc: 
	ssc_free(chip->ssc); 
out_card: 
	snd_card_free(card); 
out: 
	return retval; 
} 
 
static int snd_wm8725_remove(struct platform_device *pdev) 
{ 
	struct snd_card *card = platform_get_drvdata(pdev); 
	struct snd_wm8725 *chip = card->private_data; 
 
	/* Stop playback. */ 
	ssc_writel(chip->ssc->regs, CR, SSC_BIT(CR_TXDIS)); 
 
	ssc_free(chip->ssc); 
	snd_card_free(card); 
 
	return 0; 
} 
 
static struct platform_driver wm8725_driver = { 
	.driver		= { 
		.name	= "wm8725", 
	}, 
	.probe		= snd_wm8725_probe, 
	.remove		= snd_wm8725_remove, 
}; 
 
static struct platform_device *wm8725_device; 
 
static int __init wm8725_init(void) 
{ 
	int ret = 0; 
 
	ret = platform_driver_register(&wm8725_driver); 
 
	if (!ret) { 
		wm8725_device = platform_device_alloc("wm8725", 0); 
 
		if (wm8725_device) 
			ret = platform_device_add(wm8725_device); 
		else 
			ret = -ENOMEM; 
 
		if (ret) { 
			platform_device_put(wm8725_device); 
			platform_driver_unregister(&wm8725_driver); 
		} 
	} 
 
	return ret; 
} 
 
static void __exit wm8725_exit(void) 
{ 
	platform_device_unregister(wm8725_device); 
	platform_driver_unregister(&wm8725_driver); 
} 
 
module_init(wm8725_init); 
module_exit(wm8725_exit); 
 
 
MODULE_AUTHOR("Alexander Kudjashev"); 
MODULE_DESCRIPTION("Sound driver for WM8725 with Atmel SSC"); 
MODULE_LICENSE("GPL"); 
 
		 
		
	  
	
		
	 
	
		
	 
 
 
	
	Jury093  
	
		
	  
	
		
		 
		 
		 
		 
		 
		 
		Пункты: 54288  
		Регистрация: 25.05.2009  
		Пол: Мужчина  
		Из: Санкт-Петербург  
		
	 
		
		Эхе-хех.. наверно я все же "тупой" и "мальчика звали Хуан", или тут все повально профи в линухе и сях :)
Все классно, и драйвер несомненно полезен. Не хватает (на мой взгляд) мелочи - рассказа как его _правильно_ прикрутить к ядру.
Ну положу я его, например, в /sound/spi/wm8725.c
в Makefile, Kconfig пропишу, далее надо шаманить в board*.c
SSC правильно инитить, драйвер прописывать, иначе как?
"MCK = 98304000 Гц" - это tc0 или на входе тактовой чипа?
[quote]Перепаял недавно на переходник с lcd lph88, решил избавиться от spi и брать тактовую для цап c tc0 - выход A у него находится на том-же разъеме что и ssc, что намного удобней чем PCK0 находящийся на другом разъеме. Только цап работает всего секнду и потом сплошной шум - не понятно  - или возбуждается или я его окончательно подпалил при перепайке... Собственно моего кода там 10 строчек :) в основном убрал все лишнее. Будет работать скорей всего с любым цап не требующим инициализации. У меня частота MCK = 98304000 Гц - именно на нее расчитан драйвер, с другой не заработает, нужно менять настройки plla в bootstrap - как сделать это описывал в старом форуме.
Код  
код кусь-кусь 
[/quote]
 
		На любой  вопрос есть любой  ответ.  
		
	  
	
		
	 
	
		
	 
 
 
	
	sasa  
	
		
	  
	
		
		 
		 
		 
		 
		 
		 
		 
		Регистрация: 20.05.2009  
		 
		 
		
	 
		
		Мне все понятно вот и кажется что всем остальным тоже все ясно как день :) Вот тут 
http://www.starterkit.ru/new/index.php?name=Forums&op=showtopic&id=187&pagenum=1#15  я описывал для старого драйвера что надо сделать там и схемка есть - разница что вместо вывода PCK1 нужно использовать вывод А TC0, на нашей плате это 26 нога разъема Х2 (pin_3 по схеме) ее нужно завести на SCKI wm8725 (14 нога по даташиту этого dac). Только не нужно делать вот это ни в коем случае
Цитата Все :) Можно в конфиге ядра выбирать звук spi atmel at73c213 и наслаждаться :) Не забывать что драйвер гоняет данные по spi в пустоту - так что spi1.0 все равно занят. 
 
даже наоборот - проследить что не включена поддержка для этого драйвера случайно. Нужно выбрать мой новый драйвер если вы его прописали в Kconfig или можно просто собрать его как внешний модуль ничего в ядре не меняя  и подгрузить его потом через insmod или modprobe - это драйвер платформы специальной виртуальной псевдошины, ничего в board-файле добавлять или убирать не требуется как в случае с spi драйвером, spi нужен для управления, этот кодек неуправляемый, spi я вообще выбросил из драйвера.
ЗЫ 
http://www.starterkit.ru/new/index.php?name=Forums&op=showtopic&id=187&pagenum=1#16  остается в силе - я так и не понял почему мой dac не переключается в i2s режим.
 
		 
		
	  
	
		
	 
	
		
	 
 
 
	
	Jury093  
	
		
	  
	
		
		 
		 
		 
		 
		 
		 
		Пункты: 54288  
		Регистрация: 25.05.2009  
		Пол: Мужчина  
		Из: Санкт-Петербург  
		
	 
		
		Цитата Мне все понятно вот и кажется что всем остальным тоже все ясно как день :) Вот тут 
 
Ну тогда все понятно.. значит я тут единственный "новайс от линукса", раз все молчат..
По ссылкам ходил, просветления не наступило, там о неуправляемом wm8725 а я запаял (как упоминал ранее tlv320)
Шаг пинов у нее изрядно мелковат, но паял 0.5 жалом смотрел глазами, контроль пайки - линза и цифрофот на макросе. С тоской смотрю на ножки разъема от экрана нокии - ну почему не повезло со шлейфом, был бы желтый - горя бы не знал :/
 
		На любой  вопрос есть любой  ответ.