/* * Glue audio driver for the SA1111 compagnon chip & Philips UDA1341 codec. * * Copyright (C) 2007, Ryu Euiyoul * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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);