cyb4_linux/sound/oss/s3c6400_i2s.c

1034 lines
27 KiB
C

/*
* s3c64xx-wm8753.c -- Samsung audio driver for WM8753
* "$Id: s3c6400_i2s.c,v 1.6 2008/01/31 07:22:14 eyryu Exp $"
*
* Copyright 2003 Wolfson Microelectronics PLC.
* Copyright (C) 2007, Ryu Euiyoul <ryu.real@gmail.com>
* Author: Liam Girdwood
* liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* Notes:
* The WM8753 is a low power, high quality stereo codec with integrated PCM
* codec designed for portable digital telephony applications.
*
* Features:
* - supports 16 bit mono voice DAC and left ADC in PCM mode
* - support VDAC sample rates of 8k, 12k, 16k, 24k, 48k.
* - support 16 bit stereo playback on HiFi DAC.
* - support HiFi DAC sample rates of 8k, 12k, 16k, 48k.
* - low power consumption
* - support 8753 stand alone and 8753/9712 combi boards
* - workaround for bug#1190412 on Bulverde B0
*
* TODO:
* - Test PM, IOCTL's, recording
* - TEST, TEST and more TESTING and then fix the bugs !
*
* Bugs:
* - SSP2 RX is held high and not working
*
* Revision history
* 26th Aug 2003 Initial version.
* 1st Dec 2003 Version 0.5. Added new sample rates and cleaned up.
* 21st Jan 2004 Audio distortion workaround added, power settings
* have been refined to minimize consumption.
* 2nd Feb 2004 HiFi DAC added.
* 9th Dec 2004 Ported to 2.6.
* 10th Jan 2005 Modified for s3c2442
* 28th Mar 2007 Support at smdk6400 by ryu
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/sound.h>
#include <linux/soundcard.h>
#include <linux/errno.h>
#include <linux/device.h>
#include <linux/i2c.h>
#include <linux/i2c-id.h>
#include <linux/pm.h>
#include <linux/platform_device.h>
#include <asm/hardware.h>
#include <asm/irq.h>
#include <asm/uaccess.h>
#include <asm/semaphore.h>
#include <asm/io.h>
#include <asm/dma.h>
#include <asm/arch/regs-iis.h>
#include <asm/arch/regs-s3c6400-clock.h>
#include <asm/arch/regs-gpio.h>
#include "wm8753.h"
#include "s3c6400_pcm.h"
#define AUDIO_NAME "s3c64xx-wm8753"
/*
* HiFi DAC.
*
* Set hifi to 1 to enable the HiFi DAC over the I2S link.
*/
static int hifi = 1;
module_param(hifi, int, 0);
MODULE_PARM_DESC(hifi, "Enable the HiFi DAC.");
/*
* Voice ADC/DAC
*
* Set voice to 1 to enable the voice ADC/DAC over the SSP link.
*/
static int voice = 1;
module_param(voice, int, 0);
MODULE_PARM_DESC(voice, "Enable the Voice ADC/DAC.");
/* OSS interface to WM8753 */
#define WM8753_STEREO_MASK (SOUND_MASK_VOLUME | SOUND_MASK_PCM | SOUND_MASK_IGAIN)
#define WM8753_SUPPORTED_MASK (WM8753_STEREO_MASK | \
SOUND_MASK_BASS | SOUND_MASK_TREBLE | SOUND_MASK_MIC)
#define WM8753_RECORD_MASK (SOUND_MASK_MIC | SOUND_MASK_IGAIN)
/* taken from ac97_codec.h */
/* original check is not good enough in case FOO is greater than
* SOUND_MIXER_NRDEVICES because the supported_mixers has exactly
* SOUND_MIXER_NRDEVICES elements.
* before matching the given mixer against the bitmask in supported_mixers we
* check if mixer number exceeds maximum allowed size which is as mentioned
* above SOUND_MIXER_NRDEVICES */
#define supported_mixer(CODEC,FOO) ((FOO >= 0) && \
(FOO < SOUND_MIXER_NRDEVICES) && \
(CODEC)->supported_mixers & (1 << FOO))
/* PLL divisors */
#define PLL_N 0x7
#define PLL_K 0x23F548
//#define DBG printk
#define DBG(...)
//static struct clk *iis_clock;
extern struct clk *clk_get(struct device *dev, const char *id);
extern int clk_enable(struct clk *clk);
extern void clk_disable(struct clk *clk);
extern unsigned long clk_get_rate(struct clk *clk);
extern int s3c_audio_attach(struct inode *inode, struct file *file,
audio_state_t * state);
/* default ADC, HiFi and Voice DAC sample rates */
#define WM8753_DEFAULT_HSRATE WM8753_A8D8
#define WM8753_DEFAULT_VSRATE WM8753_VD48K
/*
* WM8753 2 wire address is determined by GPIO5
* state during powerup.
* low = WM8753_2W_ADDR1
* high = WM8753_2W_ADDR2
*/
#define WM8753_2W_ADDR1 0x1a
#define WM8753_2W_ADDR2 0x1b
static unsigned short normal_i2c[] = {
WM8753_2W_ADDR1, I2C_CLIENT_END
};
/* Magic definition of all other i2c variables and things */
I2C_CLIENT_INSMOD;
struct codec_t {
char *name;
int modcnt;
int supported_mixers;
int stereo_mixers;
int record_sources;
int codec_rate;
unsigned int mixer_state[SOUND_MIXER_NRDEVICES];
#ifdef CONFIG_PM
struct pm_dev *pm;
#endif /* */
struct i2c_client wm_client;
struct completion i2c_init;
int wm8753_valid;
};
static struct codec_t codec = {
.name = AUDIO_NAME,
.modcnt = 0,
.codec_rate = WM8753_DEFAULT_HSRATE,
.supported_mixers = WM8753_SUPPORTED_MASK,
.stereo_mixers = WM8753_STEREO_MASK,
.record_sources = WM8753_RECORD_MASK,
.wm8753_valid = 0,
};
/*
* wm8753 register cache
* We can't read the WM8753 register space when we
* are using 2 wire for device control, so we cache them instead.
*/
static u16 reg_cache[64] = {
0x0008, 0x0000, 0x000a, 0x000a, 0x0003, 0x0000, 0x0007, 0x01ff,
0x01ff, 0x000f, 0x000f, 0x007b, 0x0000, 0x0032,
0x0000, 0x00c3, 0x00c3, 0x00c0, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0040, 0x0010,
0x018f, 0x0000, 0x0000, 0x0000, 0x0000, 0x0055,
0x0005, 0x0050, 0x0055, 0x0050, 0x0055, 0x0050,
0x0055, 0x0179, 0x0179, 0x0179, 0x0179, 0x0079,
0x0064, 0x0000, 0x0000, 0x0000, 0x0097, 0x0097,
0x0000, 0x0004, 0x0000, 0x0083, 0x0024, 0x01ba,
0x0000, 0x0083, 0x0024, 0x01ba
};
static int wm8753_reset(struct i2c_client *client);
static int wm8753_2w_write(struct i2c_client *client, u8 reg, u16 value);
/*
* read wm8753 register cache
*/
static inline u16 wm8753_read_cache(u8 reg)
{
return reg_cache[reg - 1];
}
/*
* write wm8753 register cache
*/
static inline void wm8753_write_cache(u8 reg, u16 value)
{
reg_cache[reg - 1] = value;
}
/*
* write to the WM8753 register space
*/
static int wm8753_2w_write(struct i2c_client *client, u8 reg, u16 value)
{
u8 data[2];
/* data is
* D15..D9 WM8753 register offset
* D8...D0 register data
*/
data[0] = (reg << 1) | ((value >> 8) & 0x0001);
data[1] = value & 0x00ff;
if (i2c_master_send(client, data, 2) == 2) {
wm8753_write_cache(reg, value);
return 0;
} else {
printk(KERN_ERR
"%s error: failed to write 0x%x to register 0x%x\n",
AUDIO_NAME, value, reg);
return -EIO;
}
}
static long audio_set_dsp_speed(long val)
{
u32 iispsr = 0, prescaler = 0;
u32 iiscon = 0, iismod = 0, iisfic = 0;
iiscon = S3C_IIS0CON_TXDMACTIVE | S3C_IIS0CON_RXDMACTIVE;
writel(iiscon, S3C_IIS0CON);
/* Setup the EPLL clock source */
writel(0, S3C_EPLL_CON1);
switch (val) {
case 8000:
case 16000:
case 32000:
writel((1 << 31) | (128 << 16) | (25 << 8) | (0 << 0),
S3C_EPLL_CON0);
break;
case 48000:
writel((1 << 31) | (192 << 16) | (25 << 8) | (0 << 0),
S3C_EPLL_CON0);
break;
case 11025:
case 22050:
case 44100:
default:
writel((1 << 31) | (254 << 16) | (9 << 8) | (2 << 0),
S3C_EPLL_CON0);
break;
}
while (!(readl(S3C_EPLL_CON0) & (1 << 30)));
//MUXepll : FOUTepll
writel(readl(S3C_CLK_SRC) | S3C_CLKSRC_EPLL_CLKSEL, S3C_CLK_SRC);
//AUDIO0 sel : FOUTepll
writel((readl(S3C_CLK_SRC) & ~(0x7 << 7)) | (0 << 7), S3C_CLK_SRC);
//CLK_DIV2 setting
writel(0x0, S3C_CLK_DIV2);
/* FIFO is flushed before operation */
iisfic = S3C_IIS_TX_FLUSH | S3C_IIS_RX_FLUSH;
writel(iisfic, S3C_IIS0FIC);
writel(0, S3C_IIS0FIC);
/* Clear I2S prescaler value [13:8] and disable prescaler */
iispsr = readl(S3C_IIS0PSR);
iispsr &= ~((0x3f << 8) | (1 << 15));
writel(iispsr, S3C_IIS0PSR);
/* Configure I2SMOD */
iismod =
S3C_IIS0MOD_IMS_EXTERNAL_MASTER | S3C_IIS0MOD_TXRXMODE
| S3C_IIS0MOD_IIS | S3C_IIS0MOD_32FS | S3C_IIS0MOD_16BIT;
iismod |= S3C_IIS0MOD_384FS;
writel(iismod, S3C_IIS0MOD);
switch (val) {
case 8000:
case 11025:
prescaler = 19;
break;
case 16000:
case 22050:
prescaler = 9;
break;
case 32000:
case 44100:
case 48000:
default:
prescaler = 4;
break;
}
writel(readl(S3C_IIS0PSR) | (prescaler << 8) | (1 << 15),
S3C_IIS0PSR);
DBG("#### IISCON: 0x%08x IISMOD: 0x%08x IISPSR: 0x%08x\n",
readl(S3C_IIS0CON), readl(S3C_IIS0MOD), readl(S3C_IIS0PSR));
iiscon |= S3C_IIS0CON_I2SACTIVE;
writel(iiscon, S3C_IIS0CON);
return val;
}
static int wm8753_get_mixer(int cmd)
{
int val = 0;
u16 r = 0, l = 0;
switch (cmd) {
case SOUND_MIXER_VOLUME: /* OUT1 Volume */
l = wm8753_read_cache(WM8753_LOUT1V) & 0x7f;
r = wm8753_read_cache(WM8753_ROUT1V) & 0x7f;
break;
case SOUND_MIXER_BASS: /* bass */
l = wm8753_read_cache(WM8753_BASS) & 0x0f;
break;
case SOUND_MIXER_TREBLE: /* treble */
l = wm8753_read_cache(WM8753_TREBLE) & 0x0f;
break;
case SOUND_MIXER_SYNTH:
break;
case SOUND_MIXER_PCM:
break;
case SOUND_MIXER_SPEAKER: /* OUT2 Volume */
l = wm8753_read_cache(WM8753_LOUT2V) & 0x7f;
r = wm8753_read_cache(WM8753_ROUT2V) & 0x7f;
break;
case SOUND_MIXER_LINE:
break;
case SOUND_MIXER_MIC:
l = wm8753_read_cache(WM8753_INCTL1) & 0x060;
r = wm8753_read_cache(WM8753_INCTL1) & 0x180;
break;
case SOUND_MIXER_CD:
break;
case SOUND_MIXER_IMIX: /* Recording monitor */
break;
case SOUND_MIXER_ALTPCM:
break;
case SOUND_MIXER_RECLEV: /* Recording level */
break;
case SOUND_MIXER_IGAIN: /* Input gain */
l = wm8753_read_cache(WM8753_LADC) & 0xff;
r = wm8753_read_cache(WM8753_RADC) & 0xff;
break;
case SOUND_MIXER_OGAIN: /* Output gain */
l = wm8753_read_cache(WM8753_LDAC) & 0xff;
r = wm8753_read_cache(WM8753_RDAC) & 0xff;
break;
default:
DBG(KERN_WARNING "unknown mixer IOCTL\n");
return -EINVAL;
}
/* mix left and right */
val = ((l << 8) & 0xff00) + (r & 0xff);
return val;
}
/* ** NOT TESTED ** */
static int wm8753_set_mixer(int cmd, int val)
{
int ret = 0;
u16 reg;
unsigned int left, right;
/* separate left and right settings */
right = ((val >> 8) & 0xff);
right = right + 0x32;
left = (val & 0xff);
left = left + 0x32;
/* 1111111 = +6dB, 0110000 = -73dB */
if (right > 0x7f)
right = 0x7f;
if (right < 0x30)
right = 0x30;
if (left > 0x7f)
left = 0x7f;
if (left < 0x30)
left = 0x30;
switch (cmd) {
case SOUND_MIXER_VOLUME: /* volume OUT1 */
reg = wm8753_read_cache(WM8753_LOUT1V) & 0x180;
wm8753_2w_write(&codec.wm_client, WM8753_LOUT1V,
reg | left);
reg = wm8753_read_cache(WM8753_ROUT1V) & 0x180;
wm8753_2w_write(&codec.wm_client, WM8753_ROUT1V,
reg | right);
/* volume OUT2 */
reg = wm8753_read_cache(WM8753_LOUT2V) & 0x180;
wm8753_2w_write(&codec.wm_client, WM8753_LOUT2V,
reg | left);
reg = wm8753_read_cache(WM8753_ROUT2V) & 0x180;
wm8753_2w_write(&codec.wm_client, WM8753_ROUT2V,
reg | right);
break;
case SOUND_MIXER_BASS: /* bass */
reg = wm8753_read_cache(WM8753_BASS) & 0x1f0;
wm8753_2w_write(&codec.wm_client, WM8753_BASS,
reg | (left & 0x0f));
break;
case SOUND_MIXER_TREBLE: /* treble */
reg = wm8753_read_cache(WM8753_TREBLE) & 0x1f0;
wm8753_2w_write(&codec.wm_client, WM8753_TREBLE,
reg | (left & 0x0f));
break;
case SOUND_MIXER_SYNTH:
break;
case SOUND_MIXER_PCM:
break;
case SOUND_MIXER_SPEAKER: /* volume OUT2 */
reg = wm8753_read_cache(WM8753_LOUT2V) & 0x180;
wm8753_2w_write(&codec.wm_client, WM8753_LOUT2V,
reg | left);
reg = wm8753_read_cache(WM8753_ROUT2V) & 0x180;
wm8753_2w_write(&codec.wm_client, WM8753_ROUT2V,
reg | right);
break;
case SOUND_MIXER_LINE:
break;
case SOUND_MIXER_MIC:
break;
case SOUND_MIXER_CD:
break;
case SOUND_MIXER_IMIX: /* Recording monitor */
break;
case SOUND_MIXER_ALTPCM:
break;
case SOUND_MIXER_RECLEV: /* Recording level */
break;
case SOUND_MIXER_IGAIN: /* Input gain LR ADC */
reg = wm8753_read_cache(WM8753_LADC) & 0x100;
wm8753_2w_write(&codec.wm_client, WM8753_LADC, reg | left);
reg = wm8753_read_cache(WM8753_RADC) & 0x100;
wm8753_2w_write(&codec.wm_client, WM8753_RADC,
reg | right);
break;
case SOUND_MIXER_OGAIN: /* Output gain LR DAC */
reg = wm8753_read_cache(WM8753_LDAC) & 0x100;
wm8753_2w_write(&codec.wm_client, WM8753_LDAC, reg | left);
reg = wm8753_read_cache(WM8753_RDAC) & 0x100;
wm8753_2w_write(&codec.wm_client, WM8753_RDAC,
reg | right);
break;
default:
DBG(KERN_WARNING "unknown mixer IOCTL");
ret = -EINVAL;
break;
}
return ret;
}
static int wm8753_mixer_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
int i, val = 0;
if (cmd == SOUND_MIXER_INFO) {
mixer_info info;
strncpy(info.id, codec.name, sizeof(info.id));
strncpy(info.name, codec.name, sizeof(info.name));
info.modify_counter = codec.modcnt;
if (copy_to_user((void *) arg, &info, sizeof(info)))
return -EFAULT;
return 0;
}
if (cmd == SOUND_OLD_MIXER_INFO) {
_old_mixer_info info;
strncpy(info.id, codec.name, sizeof(info.id));
strncpy(info.name, codec.name, sizeof(info.name));
if (copy_to_user((void *) arg, &info, sizeof(info)))
return -EFAULT;
return 0;
}
if (_IOC_TYPE(cmd) != 'M' || _SIOC_SIZE(cmd) != sizeof(int))
return -EINVAL;
if (cmd == OSS_GETVERSION)
return put_user(SOUND_VERSION, (int *) arg);
if (_SIOC_DIR(cmd) == _SIOC_READ) {
switch (_IOC_NR(cmd)) {
case SOUND_MIXER_DEVMASK: /* give them the supported mixers */
val = codec.supported_mixers;
break;
case SOUND_MIXER_RECMASK: /* Arg contains a bit for each supported recording source */
val = codec.record_sources;
break;
case SOUND_MIXER_STEREODEVS: /* Mixer channels supporting stereo */
val = codec.stereo_mixers;
break;
case SOUND_MIXER_CAPS:
val = SOUND_CAP_EXCL_INPUT;
break;
default: /* read a specific mixer */
i = _IOC_NR(cmd);
if (!supported_mixer(&codec, i))
return -EINVAL;
val = wm8753_get_mixer(i);
break;
}
return put_user(val, (int *) arg);
}
if (_SIOC_DIR(cmd) == (_SIOC_WRITE | _SIOC_READ)) {
codec.modcnt++;
if (get_user(val, (int *) arg))
return -EFAULT;
switch (_IOC_NR(cmd)) {
default: /* write a specific mixer */
i = _IOC_NR(cmd);
if (!supported_mixer(&codec, i))
return -EINVAL;
return wm8753_set_mixer(i, val);
}
}
return -EINVAL;
}
static struct file_operations wm8753_mixer_fops = {
ioctl : wm8753_mixer_ioctl,
llseek: no_llseek,
owner : THIS_MODULE
};
static int wm8753_power_down(void)
{
if ((wm8753_2w_write(&codec.wm_client, WM8753_PWR1, 0x0100) != 0)
&& (wm8753_2w_write(&codec.wm_client, WM8753_PWR2, 0x0000) !=
0)
&& (wm8753_2w_write(&codec.wm_client, WM8753_PWR3, 0x0000) !=
0)
&& (wm8753_2w_write(&codec.wm_client, WM8753_PWR4, 0x0000) !=
0)) {
DBG(KERN_ERR "could not powerdown WM8753");
return -EIO;
}
return 0;
}
int wm8753_set_mic1_path(void)
{
/* ALCSEL : Left ch only, Set ALC target gain */
if (wm8753_2w_write(&codec.wm_client, WM8753_ALC1, 0x017b) != 0)
return -EIO;
/* MICSEL : Mic1 selected */
if (wm8753_2w_write(&codec.wm_client, WM8753_MICBIAS, 0x0000)
!= 0)
return -EIO;
/* MIC1ALC : ALC mix input select mic1 */
if (wm8753_2w_write(&codec.wm_client, WM8753_INCTL2, 0x0002) != 0)
return -EIO;
/* RADCSEL : PGA, LADCSEL : PGA */
if (wm8753_2w_write(&codec.wm_client, WM8753_ADCIN, 0x0000) != 0)
return -EIO;
/* 50k, VREF : on, MICB,VDAC : off, DACL,DACR : off, DIGENB : MCLK */
if (wm8753_2w_write(&codec.wm_client, WM8753_PWR1, 0x00c0) != 0)
return -EIO;
/* MICAMP1EN,MICAMP2EN,ALCMIX,PGAL,PGAR,ADCL,ADCR : on, RXMIX,LINEMIX : off */
if (wm8753_2w_write(&codec.wm_client, WM8753_PWR2, 0x01fc) != 0)
return -EIO;
return 0;
}
int wm8753_set_linein_path(void)
{
/* RADCSEL : LINE2, LADCSEL : LINE1 */
if (wm8753_2w_write(&codec.wm_client, WM8753_ADCIN, 0x0015) != 0)
return -EIO;
/* ALCSEL : off */
if (wm8753_2w_write(&codec.wm_client, WM8753_ALC1, 0x0000) != 0)
return -EIO;
/* RM : LINE2, LM : LINE1 */
if (wm8753_2w_write(&codec.wm_client, WM8753_INCTL1, 0x0000) != 0)
return -EIO;
/* 50k, VREF : on, MICB,VDAC : off, DACL,DACR : off, DIGENB : MCLK */
if (wm8753_2w_write(&codec.wm_client, WM8753_PWR1, 0x00c0) != 0)
return -EIO;
/* MICAMP1EN,MICAMP2EN,RXMIX,LINEMIX,ALCMIX,PGAL,PGAR : off, ADCL,ADCR : on */
if (wm8753_2w_write(&codec.wm_client, WM8753_PWR2, 0x000c) != 0)
return -EIO;
return 0;
}
int wm8753_set_hpout_path(void)
{
/* LOUT1,ROUT1,LOUT2,ROUT2,OUT3,OUT4 : on, MONO1,2 : off */
if (wm8753_2w_write(&codec.wm_client, WM8753_PWR3, 0x01f8) != 0)
return -EIO;
/* 50k, VREF : on, MICB,VDAC : off, DACL,DACR : on, DIGENB : MCLK */
if (wm8753_2w_write(&codec.wm_client, WM8753_PWR1, 0x00cc) != 0)
return -EIO;
return 0;
}
/*
* initiliase the WM8753
*/
static int wm8753_init(void)
{
int ret = 0;
/* power up the device */
/* Reset all registers to their default value */
ret += wm8753_2w_write(&codec.wm_client, WM8753_RESET, 0x0000);
/* 5k divider en, VREF on, MICB off, VDAC off, DACL off, DACR off, MCLK dis */
ret += wm8753_2w_write(&codec.wm_client, WM8753_PWR1, 0x01c1);
/* Set Left DAC volume */
ret += wm8753_2w_write(&codec.wm_client, WM8753_LDAC, 0x01ff);
/* Set Right DAC volume */
ret += wm8753_2w_write(&codec.wm_client, WM8753_RDAC, 0x01ff);
/* Set Left OUT1 volume */
ret += wm8753_2w_write(&codec.wm_client, WM8753_LOUT1V, 0x0179);
/* Set Right OUT1 volume */
ret += wm8753_2w_write(&codec.wm_client, WM8753_ROUT1V, 0x0179);
/* Set Left OUT2 volume */
ret += wm8753_2w_write(&codec.wm_client, WM8753_LOUT2V, 0x0179);
/* Set Right OUT2 volume */
ret += wm8753_2w_write(&codec.wm_client, WM8753_ROUT2V, 0x0179);
/* Set Left input volume, Mute en */
ret += wm8753_2w_write(&codec.wm_client, WM8753_LINVOL, 0x0197);
/* Set Right input volume, Mute en */
ret += wm8753_2w_write(&codec.wm_client, WM8753_RINVOL, 0x0197);
/* Set Left ADC volume */
ret += wm8753_2w_write(&codec.wm_client, WM8753_LADC, 0x01e8);
/* Set Right ADC volume */
ret += wm8753_2w_write(&codec.wm_client, WM8753_RADC, 0x01e8);
/* LOUT1,ROUT1,LOUT2,ROUT2,MONO1,2 : off, OUT3,OUT4 : on */
ret += wm8753_2w_write(&codec.wm_client, WM8753_PWR3, 0x0018);
/* IFMODE : HiFi over HiFi, VXFS : out, LRCOE : out */
ret += wm8753_2w_write(&codec.wm_client, WM8753_IOCTL, 0x000b);
/* RADC input : LINE2, LADC input : LINE1 */
ret += wm8753_2w_write(&codec.wm_client, WM8753_ADCIN, 0x0015);
/* RDAC -> Right Mixer */
ret += wm8753_2w_write(&codec.wm_client, WM8753_ROUTM1, 0x150);
/* LDAC -> Left Mixer */
ret += wm8753_2w_write(&codec.wm_client, WM8753_LOUTM1, 0x150);
/* MCLKSEL : from MCLK pin, PCMCLKSEL : PCMCLK pin, CLKEQ : HiFi DAC */
ret += wm8753_2w_write(&codec.wm_client, WM8753_CLOCK, 0x0004);
/* HiFi audio interface : I2S format */
ret += wm8753_2w_write(&codec.wm_client, WM8753_HIFI, 0x0002);
/* PCM audio interface : I2S format */
ret += wm8753_2w_write(&codec.wm_client, WM8753_PCM, 0x0002);
/* VXDACOSR : 1, ADCOSR : 1, DACOSR : 1 */
ret += wm8753_2w_write(&codec.wm_client, WM8753_SRATE2, 0x0007);
/* IFMODE : HiFi over HiFi, VXFS : out, LRCOE : input */
ret += wm8753_2w_write(&codec.wm_client, WM8753_IOCTL, 0x000a);
/* 50k, VREF : on, MICB,VDAC : off, DACL,DACR : on, DIGENB : MCLK */
ret += wm8753_2w_write(&codec.wm_client, WM8753_PWR1, 0x00cc);
/* ADCL,ADCR : on, MICAPM1,2, ALCMIX,PGAL,PGAR,RXMIX,LINEMIX : off */
ret += wm8753_2w_write(&codec.wm_client, WM8753_PWR2, 0x000c);
/* LOUT1,ROUT1,LOUT2,ROUT2,OUT3,OUT4 : on, MONO1,2 : off */
ret += wm8753_2w_write(&codec.wm_client, WM8753_PWR3, 0x01f8);
/* RECMIX,MONOMIX : off, RIGHT MIX, LEFT MIX : on */
ret += wm8753_2w_write(&codec.wm_client, WM8753_PWR4, 0x0003);
ret += wm8753_2w_write(&codec.wm_client, WM8753_DAC, 0x0000);
return ret != 0 ? -EIO : 0;
}
static int s3c_select_clk(void)
{
/* PCLK & SCLK gating enable */
writel(readl(S3C_PCLK_GATE) | S3C_CLKCON_PCLK_IIS0, S3C_PCLK_GATE);
writel(readl(S3C_SCLK_GATE) | S3C_CLKCON_SCLK_AUDIO0,
S3C_SCLK_GATE);
return 0;
}
/* open the WM8753 audio driver */
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;
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);
switch (val) {
case 8000:
case 16000:
case 32000:
wm8753_2w_write(&codec.wm_client, WM8753_SRATE1,
0x0018);
break;
case 11025:
case 22050:
case 44100:
wm8753_2w_write(&codec.wm_client, WM8753_SRATE1,
0x0022);
break;
case 48000:
wm8753_2w_write(&codec.wm_client, WM8753_SRATE1,
0x000a);
break;
}
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 = wm8753_mixer_ioctl(inode, file, cmd, arg);
break;
}
return ret;
}
static void s3c_audio_shutdown(void *dummy)
{
unsigned long iismod, iiscon;
iismod = readl(S3C_IIS0MOD);
iismod &= ~S3C_IIS0MOD_TXMODE;
iismod &= ~S3C_IIS0MOD_RXMODE;
writel(iismod, S3C_IIS0MOD);
iiscon = readl(S3C_IIS0CON);
if (iismod & (S3C_IIS0MOD_TXMODE | S3C_IIS0MOD_RXMODE)) {
iiscon &= !S3C_IIS0CON_I2SACTIVE;
writel(iiscon, S3C_IIS0CON);
}
/* Disable the I2S clock */
writel(readl(S3C_PCLK_GATE) & ~(S3C_CLKCON_PCLK_IIS0),
S3C_PCLK_GATE);
writel(readl(S3C_SCLK_GATE) & ~(S3C_CLKCON_SCLK_AUDIO0),
S3C_SCLK_GATE);
writel(readl(S3C_EPLL_CON0) & ~(1 << 31), S3C_EPLL_CON0);
}
static int s3c_init_iis(void)
{
/*Set I2C port to controll WM8753 codec */
s3c_gpio_pullup(S3C_GPB5, 0);
s3c_gpio_pullup(S3C_GPB6, 0);
s3c_gpio_cfgpin(S3C_GPB5, S3C_GPB5_I2C_SCL);
s3c_gpio_cfgpin(S3C_GPB6, S3C_GPB6_I2C_SDA);
s3c_gpio_cfgpin(S3C_GPD0, S3C_GPD0_I2S_CLK0);
s3c_gpio_cfgpin(S3C_GPD1, S3C_GPD1_I2S_CDCLK0);
s3c_gpio_cfgpin(S3C_GPD2, S3C_GPD2_I2S_LRCLK0);
s3c_gpio_cfgpin(S3C_GPD3, S3C_GPD3_I2S_DI0);
s3c_gpio_cfgpin(S3C_GPD4, S3C_GPD4_I2S_DO0);
s3c_gpio_pullup(S3C_GPD0, 2);
s3c_gpio_pullup(S3C_GPD1, 2);
s3c_gpio_pullup(S3C_GPD2, 2);
s3c_gpio_pullup(S3C_GPD3, 2);
s3c_gpio_pullup(S3C_GPD4, 2);
return 0;
}
static audio_stream_t output_stream = {
id: "s3c64xx audio out ",
subchannel: DMACH_I2S_OUT,
};
static audio_stream_t input_stream = {
id: "s3c64xx audio in ",
subchannel: 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 wm8753_hifi_open(struct inode *inode, struct file *file)
{
s3c_init_iis();
s3c_select_clk();
init_MUTEX(&audio_state.sem);
return s3c_audio_attach(inode, file, &audio_state);
}
static struct file_operations wm8753_hifi_fops = {
open : wm8753_hifi_open,
owner: THIS_MODULE
};
/*
* reset the WM8753
*/
static inline int wm8753_reset(struct i2c_client *client)
{
return wm8753_2w_write(client, WM8753_RESET, 0);
}
/* WM8753 2 Wire layer */
static int wm8753_attach_adapter(struct i2c_adapter *adapter);
static int wm8753_detach_client(struct i2c_client *client);
static struct i2c_driver wm_driver = {
.driver = {
.name = "wm8753",
.owner = THIS_MODULE,},
.id = I2C_DRIVERID_WM8753,
.attach_adapter = wm8753_attach_adapter,
.detach_client = wm8753_detach_client,
};
static int wm8753_detect(struct i2c_adapter *adap, int addr, int kind)
{
int ret;
i2c_set_clientdata(&codec.wm_client, &codec);
codec.wm_client.addr = addr;
codec.wm_client.adapter = adap;
codec.wm_client.driver = &wm_driver;
codec.wm_client.flags = 0;
/* since the codec is read only, we have to assume that a successful
* write (via a codec reset) is proof that the codec is responding
*/
if (wm8753_reset(&codec.wm_client) != 0) {
printk(KERN_ERR "cannot reset the WM8753");
complete(&codec.i2c_init);
return -EIO;
}
strlcpy(codec.wm_client.name, "wm8753", I2C_NAME_SIZE);
if ((ret = i2c_attach_client(&codec.wm_client)) == 0)
codec.wm8753_valid = 1;
complete(&codec.i2c_init);
return ret;
}
//static int wm8753_attach_adapter(struct i2c_adapter *adapter)
static int wm8753_attach_adapter(struct i2c_adapter *adap)
{
return i2c_probe(adap, &addr_data, wm8753_detect);
//return i2c_probe(adapter, &addr_data, wm8753_detect);
}
/*
* Detach WM8753 2 wire client
*/
static int wm8753_detach_client(struct i2c_client *client)
{
i2c_detach_client(client);
return 0;
}
static int s3c_i2s_init(void)
{
init_completion(&codec.i2c_init);
if (i2c_add_driver(&wm_driver)) {
printk(KERN_ERR "can't add i2c driver\n");
goto err_dev;
}
/* wait for i2c init to complete */
wait_for_completion(&codec.i2c_init);
if (!codec.wm8753_valid) {
printk(KERN_ERR "can't find codec\n");
goto err_dev;
}
/* initialise WM8753 */
if (wm8753_init() != 0) {
printk(KERN_ERR "can't initialise WM8753\n");
goto err_io;
}
return 0;
err_dev:
return -ENODEV;
err_io:
i2c_del_driver(&wm_driver);
return -EIO;
}
#ifdef CONFIG_PM
/* TODO : Power management */
int s3c64xx_i2s_suspend(void)
{
return 0;
}
int s3c64xx_i2s_resume(void)
{
return 0;
}
static int s3c64xx_pm_callback(struct pm_dev *dev, pm_request_t rqst,
void *data)
{
switch (rqst) {
case PM_SUSPEND:
s3c64xx_i2s_suspend();
break;
case PM_RESUME:
s3c64xx_i2s_resume();
break;
}
return 0;
}
#else /* */
#define s3c64xx_i2s_suspend NULL
#define s3c64xx_i2s_resume NULL
#endif /* */
static int audio_dev_id, mixer_dev_id;
static int s3c64xx_audio_probe(struct device *dev)
{
s3c_i2s_init();
audio_dev_id = register_sound_dsp(&wm8753_hifi_fops, -1);
mixer_dev_id = register_sound_mixer(&wm8753_mixer_fops, -1);
printk(KERN_INFO "Sound: S3C-I2S: dsp id %d mixer id %d\n",
audio_dev_id, mixer_dev_id);
flush_scheduled_work();
#ifdef CONFIG_PM
pm_register(PM_SYS_DEV, PM_SYS_UNKNOWN, s3c64xx_pm_callback);
#endif /* */
return 0;
}
static int s3c64xx_audio_remove(struct device *dev)
{
wm8753_power_down();
unregister_sound_dsp(audio_dev_id);
unregister_sound_mixer(mixer_dev_id);
i2c_del_driver(&wm_driver);
return 0;
}
static struct device_driver s3c64xx_i2s_driver = {
.name = "s3c2410-iis",
.bus = &platform_bus_type,
.probe = s3c64xx_audio_probe,
.remove = s3c64xx_audio_remove,
#ifdef CONFIG_PM
.suspend = s3c64xx_audio_suspend,
.resume = s3c64xx_audio_resume,
#endif
};
/*
* initialise the WM8753 driver
* register the mixer and dsp interfaces with the kernel
*/
static int __init s3c_wm8753_init(void)
{
return driver_register(&s3c64xx_i2s_driver);
}
module_init(s3c_wm8753_init);
/*
* unregister interfaces and clean up
*/
static void __exit s3c_wm8753_exit(void)
{
driver_unregister(&s3c64xx_i2s_driver);
}
module_exit(s3c_wm8753_exit);
MODULE_AUTHOR("Ryu Euiyoul");
MODULE_DESCRIPTION("Samsung S3C WM8753 driver");
MODULE_LICENSE("GPL");