cyb4_linux/sound/oss/s3c24xx_i2s.c

397 lines
9.5 KiB
C

/*
* Glue audio driver for the SA1111 compagnon chip & Philips UDA1341 codec.
*
* Copyright (C) 2007, Ryu Euiyoul <ryu.real@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License.
*
* History:
*
* 2000-09-04 John Dorsey SA-1111 Serial Audio Controller support
* was initially added to the sa1100-uda1341.c
* driver.
*
* 2001-06-03 Nicolas Pitre Made this file a separate module, based on
* the former sa1100-uda1341.c driver.
*
* 2001-09-23 Russell King Remove old L3 bus driver.
*
* 2001-12-19 Nicolas Pitre Moved SA1111 SAC support to this file where
* it actually belongs (formerly in dma-sa1111.c
* from John Dorsey).
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/sound.h>
#include <linux/soundcard.h>
#include <linux/ioport.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/l3/l3.h>
#include <linux/l3/uda1341.h>
#include <asm/semaphore.h>
#include <asm/mach-types.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/hardware.h>
#include <asm/dma.h>
#include <asm/io.h>
#include <asm/arch/dma.h>
#include <asm/arch/regs-iis.h>
#include <asm/arch/regs-gpio.h>
#include <asm/arch/regs-clock.h>
#include <asm/arch/regs-s3c2443-clock.h>
#include "s3c24xx_pcm.h"
//#define DPRINTK printk
#define DPRINTK( x... )
#define S_CLOCK_FREQ 384
static long audio_rate=44100;
static struct uda1341 *uda1341;
static struct clk *iis_clock;
static DECLARE_MUTEX(s3c_lock);
static struct l3_adapter l3_s3c_adapter = {
.owner = THIS_MODULE,
.name = "l3-s3c",
.lock = &s3c_lock,
};
struct s3c24xx_i2s_info {
void __iomem *regs;
};
static struct s3c24xx_i2s_info s3c24xx_i2s;
static int
mixer_ioctl(struct inode *inode, struct file *file, uint cmd, ulong arg)
{
return uda1341_mixer_ctl(uda1341, cmd, (void *)arg);
}
static struct file_operations uda1341_mixer_fops = {
.owner = THIS_MODULE,
.ioctl = mixer_ioctl,
};
static int iispsr_value(int sample_rate)
{
int i, prescaler = 0;
unsigned long fact0 = clk_get_rate(iis_clock)/(S_CLOCK_FREQ);
unsigned long r0_sample_rate, r1_sample_rate = 0, r2_sample_rate;
DPRINTK("requested sample_rate = %d\n", sample_rate);
for(i = 1; i < 32; i++) {
r1_sample_rate = fact0 / i;
if(r1_sample_rate < sample_rate)
break;
}
r0_sample_rate = fact0 / (i + 1);
r2_sample_rate = fact0 / (i - 1);
DPRINTK("calculated (%d-1) freq = %ld, error = %d\n",
i + 1, r0_sample_rate, abs(r0_sample_rate - sample_rate));
DPRINTK("calculated (%d-1) freq = %ld, error = %d\n",
i, r1_sample_rate, abs(r1_sample_rate - sample_rate));
DPRINTK("calculated (%d-1) freq = %ld, error = %d\n",
i - 1, r2_sample_rate, abs(r2_sample_rate - sample_rate));
prescaler = i;
if(abs(r0_sample_rate - sample_rate) <
abs(r1_sample_rate - sample_rate))
prescaler = i + 1;
if(abs(r2_sample_rate - sample_rate) <
abs(r1_sample_rate - sample_rate))
prescaler = i - 1;
prescaler = max_t(int, 0, (prescaler - 1));
DPRINTK("selected prescale value = %d, freq = %ld, error = %d\n",
prescaler, fact0 / (prescaler + 1),
abs((fact0 / (prescaler + 1)) - sample_rate));
return prescaler;
}
static int s3c_init_iis(void)
{
u32 val =0;
val = S3C2443_IISMOD_IMS_INTERNAL_MASTER | S3C2443_IISMOD_TXRXMODE | S3C2443_IISMOD_IIS
| S3C2443_IISMOD_384FS | S3C2443_IISMOD_32FS | S3C2443_IISMOD_16BIT;
writel(val,s3c24xx_i2s.regs + S3C2410_IISMOD);
val =0;
val = S3C2443_IISCON_TXDMAEN | S3C2443_IISCON_RXDMAEN | S3C2410_IISCON_IISEN;
writel(val,s3c24xx_i2s.regs + S3C2410_IISCON);
val = 0 ;
val = (1<<15) ;
writel(val,s3c24xx_i2s.regs + S3C2443_IISFIC);
val = (0<<15) ;
writel(val,s3c24xx_i2s.regs + S3C2443_IISFIC);
DPRINTK("IISMOD : 0x%08x, IISCON : 0x%08x, IISFIC : 0x%08x\n",
readl(s3c24xx_i2s.regs + S3C2410_IISMOD),
readl(s3c24xx_i2s.regs + S3C2410_IISCON),
readl(s3c24xx_i2s.regs + S3C2443_IISFIC));
return 0;
}
static long audio_set_dsp_speed(long val)
{
unsigned long tmp;
int prescaler = 0;
DPRINTK("requested = %ld\n", val);
tmp = clk_get_rate(iis_clock)/S_CLOCK_FREQ;
DPRINTK("requested = %ld, limit = %ld\n", val, tmp);
if(val > (tmp >> 1))
return -1;
prescaler = iispsr_value(val);
tmp = (prescaler) | 1 << 15 ; /*prescale enable*/
writel(tmp,s3c24xx_i2s.regs + S3C2443_IISPSR);
audio_rate = val;
DPRINTK("return audio_rate = %ld\n", (unsigned long)audio_rate);
return audio_rate;
}
static int
s3c_audio_ioctl(struct inode *inode, struct file *file, uint cmd, ulong arg)
{
long val = 0;
int ret = 0;
audio_state_t *state = file->private_data;
s3c_init_iis();
switch (cmd) {
case SNDCTL_DSP_STEREO:
if (get_user(val, (int *) arg))
return -EINVAL;
state->sound_mode = (val) ? 2 : 1;
return 0;
case SNDCTL_DSP_CHANNELS:
if (get_user(val, (int *) arg))
return -EINVAL;
if (val != 1 && val != 2)
return -EINVAL;
state->sound_mode = val;
return put_user(val, (int *) arg);
case SOUND_PCM_READ_CHANNELS:
return put_user(state->sound_mode, (long *) arg);
case SNDCTL_DSP_SPEED:
ret = get_user(val, (long *) arg);
if (ret)
break;
audio_set_dsp_speed(val);
break;
case SOUND_PCM_READ_RATE:
ret = put_user(val, (long *) arg);
break;
case SNDCTL_DSP_SETFMT:
case SNDCTL_DSP_GETFMTS:
/* we can do 16-bit only */
ret = put_user(AFMT_S16_LE, (long *) arg);
break;
default:
/* Maybe this is meant for the mixer (as per OSS Docs) */
ret = mixer_ioctl(inode, file, cmd, arg);
break;
}
return ret;
}
static void s3c_audio_shutdown(void *dummy)
{
uda1341_close(uda1341);
/* Disable the I2S clock and L3 bus clock */
clk_disable(iis_clock);
}
static void s3c_select_clkdiv(void)
{
#if defined CONFIG_CPU_S3C2443
writel((readl(S3C2443_PCLKCON)|(1<<9)), S3C2443_PCLKCON);
writel((readl(S3C2443_SCLKCON)|(1<<9)), S3C2443_SCLKCON);
writel((readl(S3C2443_CLKDIV1) & ~(0xf << 12)),S3C2443_CLKDIV1);
writel((readl(s3c24xx_i2s.regs + S3C2443_IISPSR)|(1<<15) |(5<<0)) ,s3c24xx_i2s.regs + S3C2443_IISPSR);
#elif defined CONFIG_CPU_S3C2412
writel((readl(S3C_CLKCON)| (1 << 26)| (1<<13)),S3C_CLKCON);
writel((readl(S3C_CLKDIVN) & ~(0xf << 12)),S3C_CLKDIVN);
writel((readl(S3C_IISPSR)&0),S3C_IISPSR);
#endif
}
static void s3c_iis_config_pins(void)
{
s3c2410_gpio_cfgpin(S3C2410_GPE0, S3C2410_GPE0_I2SLRCK);
s3c2410_gpio_cfgpin(S3C2410_GPE1, S3C2410_GPE1_I2SSCLK);
s3c2410_gpio_cfgpin(S3C2410_GPE2, S3C2410_GPE2_CDCLK);
s3c2410_gpio_cfgpin(S3C2410_GPE3, S3C2410_GPE3_I2SSDI);
s3c2410_gpio_cfgpin(S3C2410_GPE4, S3C2410_GPE4_I2SSDO);
}
static int s3c_select_clk(void)
{
iis_clock = clk_get(NULL, "iis");
if (!iis_clock) {
printk("failed to get iis clock source\n");
return -ENOENT;
}
clk_enable(iis_clock);
s3c_select_clkdiv();
return 0;
}
static audio_stream_t output_stream = {
id: "s3c audio out ",
dma: DMACH_I2S_OUT,
};
static audio_stream_t input_stream = {
id: "s3c audio in ",
dma: DMACH_I2S_IN,
};
static audio_state_t audio_state = {
.output_stream = &output_stream,
.input_stream = &input_stream,
.hw_shutdown = s3c_audio_shutdown,
.client_ioctl = s3c_audio_ioctl,
};
static int s3c_audio_open(struct inode *inode, struct file *file)
{
s3c_iis_config_pins();
s3c_select_clk();
init_MUTEX(&audio_state.sem);
return s3c_audio_attach(inode, file, &audio_state);
}
/*
* Missing fields of this structure will be patched with the call
* to s3c_audio_attach().
*/
static struct file_operations s3c_audio_fops = {
.owner = THIS_MODULE,
.open = s3c_audio_open,
};
static int audio_dev_id, mixer_dev_id;
static int s3c_audio_probe(struct device *_dev)
{
struct uda1341_cfg cfg;
int ret;
s3c24xx_i2s.regs = ioremap(S3C24XX_PA_IIS, 0x100);
if (s3c24xx_i2s.regs == NULL)
return -ENXIO;
ret = l3_add_adapter(&l3_s3c_adapter);
if (ret)
goto out;
uda1341 = uda1341_attach("l3-bit-s3c-gpio");
if (IS_ERR(uda1341)) {
ret = PTR_ERR(uda1341);
goto remove_l3;
}
cfg.fs = 384;
cfg.format = FMT_I2S;
uda1341_open(uda1341);
audio_dev_id = register_sound_dsp(&s3c_audio_fops, -1);
mixer_dev_id = register_sound_mixer(&uda1341_mixer_fops, -1);
printk(KERN_INFO "Sound uda1341: dsp id %d mixer id %d maped %p\n",
audio_dev_id, mixer_dev_id, s3c24xx_i2s.regs);
flush_scheduled_work();
return 0;
remove_l3:
uda1341_detach(uda1341);
l3_del_adapter(&l3_s3c_adapter);
out:
return ret;
}
static int s3c_audio_remove(struct device *_dev)
{
unregister_sound_dsp(audio_dev_id);
unregister_sound_mixer(mixer_dev_id);
uda1341_detach(uda1341);
l3_del_adapter(&l3_s3c_adapter);
return 0;
}
#ifdef CONFIG_S3C2443_PM
static int smdk_audio_suspend( u32 state)
{
return s3c_audio_suspend(&audio_state, state, SUSPEND_POWER_DOWN);
}
static int smdk_audio_resume()
{
return s3c_audio_resume(&audio_state, RESUME_ENABLE);
}
#endif
static struct device_driver s3c_iis_driver = {
.name = "s3c2410-iis",
.bus = &platform_bus_type,
.probe = s3c_audio_probe,
.remove = s3c_audio_remove,
#ifdef CONFIG_S3C2443_PM
.suspend = smdk_audio_suspend,
.resume = smdk_audio_resume,
#endif
};
static int __init s3c_uda1341_init(void)
{
DPRINTK("Sound: %s installed\n", __FUNCTION__);
return driver_register(&s3c_iis_driver);
}
static void s3c_uda1341_exit(void)
{
driver_unregister(&s3c_iis_driver);
}
module_init(s3c_uda1341_init);
module_exit(s3c_uda1341_exit);