blob: f9b4dfa567c1c90bbd6bb896291e39fbe5510195 [file] [log] [blame]
/*************************************************************************
* Copyright 2010 Broadcom Corporation
*
* Unless you and Broadcom execute a separate written software license
* agreement governing use of this software, this software is licensed to you
* under the terms of the GNU General Public License version 2 (the GPL),
* available at http://www.broadcom.com/licenses/GPLv2.php, with the following
* added to such license:
* As a special exception, the copyright holders of this software give you
* permission to link this software with independent modules, and to copy and
* distribute the resulting executable under terms of your choice, provided that
* you also meet, for each linked independent module, the terms and conditions
* of the license of that module. An independent module is a module which is not
* derived from this software. The special exception does not apply to any
* modifications of the software.
* Notwithstanding the above, under no circumstances may you combine this
* software in any way with any other Broadcom software provided under a license
* other than the GPL, without Broadcom's express prior written consent.
*****************************************************************************/
/* ---- Include Files ---------------------------------------------------- */
#include <linux/version.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <asm/io.h>
#include <linux/i2c.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <asm/gpio.h>
#include <linux/wait.h>
#include <linux/signal.h>
#include <linux/kthread.h>
#include <linux/syscalls.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/hrtimer.h>
#include <asm/io.h>
#include <linux/i2c/bcm2850_mic_detect.h>
#include <mach/rdb/brcm_rdb_aci.h>
#include <mach/rdb/brcm_rdb_auxmic.h>
/* ---- Public Variables ------------------------------------------------- */
/* ---- Private Constants and Types -------------------------------------- */
struct i2c_priv_data
{
struct i2c_client *p_i2c_client;
};
struct mic_det_dev {
struct workqueue_struct *mic_det_wq;
struct work_struct work;
struct mutex mutex_wq;
struct i2c_client *client;
};
#define MIC_DET_ERR(fmt, args...) \
printk(KERN_ERR "[mic-detect]: " fmt, ## args)
#define MIC_DET_DEBUG(fmt, args...) \
printk(KERN_DEBUG "[mic-detect]: " fmt, ## args)
const struct MIC_DET_t *gp_i2c_para = NULL;
static struct mic_det_dev *p_mic_det_dev;
static DECLARE_WAIT_QUEUE_HEAD(g_event_waitqueue);
/* ---- Private Function Prototypes -------------------------------------- */
static int i2c_driver_reg_read(unsigned short regoff, unsigned short *regval);
static int i2c_driver_reg_write(unsigned short regoff, unsigned short regval);
/* ---- Functions -------------------------------------------------------- */
#ifdef CONFIG_PM
/* In preparation for implementing PM_SUSPEND_STANDBY. */
static int mic_det_suspend_driver(struct i2c_client *p_client,
pm_message_t mesg)
{ /* Can put it into deep sleep only if the slave can be reset to bring it out. */
disable_irq(gp_i2c_para->comp1_irq);
return 0;
}
static int mic_det_resume_driver(struct i2c_client *p_client)
{
enable_irq(gp_i2c_para->comp1_irq);
return 0;
}
#endif
static void i2c_set_codec_headset(void)
{
i2c_driver_reg_write(0, 0);
// Disable REG_SYNC, i.e. Register Writes are not Sync'd with SYSCLK
i2c_driver_reg_write(0x101, 0x4);
// Turn on VMID using anti-pop features
i2c_driver_reg_write(0x39, 0x6c);
// Turn on VMID using anti-pop features
i2c_driver_reg_write(0x1, 0x3);
mdelay(15);
//set AIF1
//set master mode
i2c_driver_reg_write(0x302, 0x4000);
// Left (mono microphone) ADC output on both channels, no tdm, 16-bit, dsp mode b
i2c_driver_reg_write(0x300, 0x98);
// bclk1 = aif1clk / 4 (for 44.1k / 48k)
i2c_driver_reg_write(0x303, 0x40);
// aif1clk enabled, fll1 source
i2c_driver_reg_write(0x200, 0x11);
// Enable the DSP Mixer Core Clock and DSP Processing Clock for AIF1
i2c_driver_reg_write(0x208, 0xa);
//set Playback
//Configure the AIF1DAC1L/R path to DAC1L/R
i2c_driver_reg_write(0x601, 0x1);
i2c_driver_reg_write(0x602, 0x1);
//# Disable the DAC Mute in the DAC Digital Volume - DAC1
i2c_driver_reg_write(0x610, 0x1c0);
i2c_driver_reg_write(0x611, 0x1c0);
//# Enable the DAC128OSR for High DAC SNR performance
i2c_driver_reg_write(0x620, 0x1);
//# Configure the Headphone PGA gain
i2c_driver_reg_write(0x1d, 0x15f);
// Enable DAC1L/R
i2c_driver_reg_write(0x5, 0x303);
// # Enable the DAC1L/R 'direct' path to the Headphone PGA
i2c_driver_reg_write(0x2d, 0x100);
i2c_driver_reg_write(0x2e, 0x100);
// Enable the Charge Pump (This needs to be done before
// volume updates on Headphone PGA)
i2c_driver_reg_write(0x4c, 0x8000);
mdelay(4);
//# Configure the Headphone PGA gain
i2c_driver_reg_write(0x1c, 0x5f);
//# Enable the HPOUT1L/R Output Stage
i2c_driver_reg_write(0x1, 0x303);
//# Enable the Headphone Outputs and remove the Headphone Gnd clamp
i2c_driver_reg_write(0x60, 0xee);
//# Disable the AIF1DAC1 Interface Mute
i2c_driver_reg_write(0x420, 0);
// GPIO1 defaults to ADCLRCLK1, which causes LRCLK1 to only output
// on DAC input, and not on ADC output. Setting GPIO1 to any other mode
// causes LRCLK1 to output in both modes, as required, so we set GPIO1 to
// be a GPIO, with other settings left as the default (input with de-bounce).
i2c_driver_reg_write(0x700, 0x8101);
//# Setup bit per sample (reg 0x300 = 0x98)
//# FLL (reg 0x220) is enabled when setup ate
//# Disable clock from FLL1 to AIF1
i2c_driver_reg_write(0x200, 0x0);
//# fracn_ena, FLL1_disa
i2c_driver_reg_write(0x220, 0x4);
//# pre-divider = 1, FLL1 source: MCLK1
i2c_driver_reg_write(0x224, 0x0);
// # N=187(0bbh)
i2c_driver_reg_write(0x223, 0x1760);
//# K=0.5(8000h)
i2c_driver_reg_write(0x222, 0x8000);
//# FLL1_OUTDIV: 8, FLL1_FRATIO: 8
i2c_driver_reg_write(0x221, 0x704);
//# fracn_ena, FLL1_ENA
i2c_driver_reg_write(0x220, 0x5);
mdelay(3);
//#BCLK = AIF1CLK / 8 = 1536K
i2c_driver_reg_write(0x303, 0x40);
//#48K sample rate, AIF1CLK / fs ratio 256
i2c_driver_reg_write(0x210, 0x83);
//# Enable clock from FLL1 to AIF1
i2c_driver_reg_write(0x200, 0x11);
MIC_DET_DEBUG("mic_det_wq -inserted\n");
}
static void i2c_set_codec_speaker(void)
{
i2c_driver_reg_write(0, 0);
// Disable REG_SYNC, i.e. Register Writes are not Sync'd with SYSCLK
i2c_driver_reg_write(0x101, 0x4);
// Turn on VMID using anti-pop features
i2c_driver_reg_write(0x39, 0x6c);
// Turn on VMID using anti-pop features
i2c_driver_reg_write(0x1, 0x3);
mdelay(15);
//set AIF1
//set master mode
i2c_driver_reg_write(0x302, 0x4000);
// Left (mono microphone) ADC output on both channels, no tdm,
// 16-bit, dsp mode b
i2c_driver_reg_write(0x300, 0x98);
// bclk1 = aif1clk / 4 (for 44.1k / 48k)
i2c_driver_reg_write(0x303, 0x40);
// aif1clk enabled, fll1 source
i2c_driver_reg_write(0x200, 0x11);
// Enable the DSP Mixer Core Clock and DSP Processing Clock for AIF1
i2c_driver_reg_write(0x208, 0xa);
//set Playback
//Configure the AIF1DAC1L/R path to DAC1L/R
i2c_driver_reg_write(0x601, 0x1);
i2c_driver_reg_write(0x602, 0x1);
//# Disable the DAC Mute in the DAC Digital Volume - DAC1
i2c_driver_reg_write(0x610, 0x1c0);
i2c_driver_reg_write(0x611, 0x1c0);
//# Enable the DAC128OSR for High DAC SNR performance
i2c_driver_reg_write(0x620, 0x1);
// Enable DAC1L/R
i2c_driver_reg_write(0x5, 0x303);
mdelay(4);
//# Enable Speakers
i2c_driver_reg_write(0x1, 0x3003);
// Disable the AIF1DAC1 Interface Mute
i2c_driver_reg_write(0x420, 0);
// GPIO1 defaults to ADCLRCLK1, which causes LRCLK1 to only output on
// DAC input, and not on ADC output. Setting GPIO1 to any other mode
// causes LRCLK1 to output in both modes, as required, so we set GPIO1
// to be a GPIO, with other settings left as the default
// (input with de-bounce).
i2c_driver_reg_write(0x700, 0x8101);
//# Unmute left speaker mixer vol
i2c_driver_reg_write(0x22, 0);
//# Unmute right speaker mixer vol
i2c_driver_reg_write(0x23, 0);
//# Unmute DAC1L/R to SPKMIXL/R
i2c_driver_reg_write(0x36, 0x3);
// Unmute SPKMIXL_TO_SPKOUTL and SPKMIXR_TO_SPKOUTL
i2c_driver_reg_write(0x24, 0x18);
//# Set SPKOUTL_BOOST to max value
//#i2c_driver_reg_write( 0x25, 0x3f);
//# Setup bit per sample (reg 0x300 = 0x98)
//# FLL (reg 0x220) is enabled when setup ate
//# Disable clock from FLL1 to AIF1
i2c_driver_reg_write(0x200, 0x0);
//# fracn_ena, FLL1_disa
i2c_driver_reg_write(0x220, 0x4);
//# pre-divider = 1, FLL1 source: MCLK1
i2c_driver_reg_write(0x224, 0x0);
// # N=187(0bbh)
i2c_driver_reg_write(0x223, 0x1760);
//# K=0.5(8000h)
i2c_driver_reg_write(0x222, 0x8000);
//# FLL1_OUTDIV: 8, FLL1_FRATIO: 8
i2c_driver_reg_write(0x221, 0x704);
//# fracn_ena, FLL1_ENA
i2c_driver_reg_write(0x220, 0x5);
mdelay(3);
//#BCLK = AIF1CLK / 8 = 1536K
i2c_driver_reg_write(0x303, 0x40);
//#48K sample rate, AIF1CLK / fs ratio 256
i2c_driver_reg_write(0x210, 0x83);
//# Enable clock from FLL1 to AIF1
i2c_driver_reg_write(0x200, 0x11);
MIC_DET_DEBUG("mic_det_wq -removed\n");
}
static void mic_det_wq(struct work_struct *work)
{
struct mic_det_dev *codec_dev = container_of(work, struct mic_det_dev, work);
static int init = 0;
int tout = 200;
mutex_lock(&codec_dev->mutex_wq);
MIC_DET_DEBUG("mic_det_wq run\n");
if(!init) {
int tout = 100;
/* Wait maximum 100ms for ACI_COMP_DOUT_COMP1_DOUT_CMD_ONE bit go low
* to decide if the headset is connected in the very 1st time.
* If it does not go low in 100ms, we think it is not going to go low since the headset
* is connected */
while(tout) {
if(readl(gp_i2c_para->reg_base + ACI_COMP_DOUT_OFFSET) &
ACI_COMP_DOUT_COMP1_DOUT_CMD_ONE) {
mdelay(1);
tout--;
}
else {
init = 1;
break;
}
}
}
if(readl(gp_i2c_para->reg_base + ACI_COMP_DOUT_OFFSET) &
ACI_COMP_DOUT_COMP1_DOUT_CMD_ONE) {
i2c_set_codec_headset();
}
else {
i2c_set_codec_speaker();
}
schedule();
MIC_DET_DEBUG("mic_det_wq leave\n");
mutex_unlock(&codec_dev->mutex_wq);
}
static irqreturn_t mic_det_driver_isr(int irq, void *dev_id)
{
struct mic_det_dev *det_dev = (struct mic_det_dev *)dev_id;
MIC_DET_DEBUG("mic_det_driver_isr with irq:%d\n", irq);
writel(ACI_INT_COMP1INT_STS_MASK | ACI_INT_COMP2INT_STS_MASK |
ACI_INT_COMP1INT_EN_MASK | ACI_INT_INV_COMP2INT_EN_MASK,
gp_i2c_para->reg_base + ACI_INT_OFFSET);
/* postpone I2C transactions to the workqueue as it may block */
queue_work(det_dev->mic_det_wq, &det_dev->work);
return IRQ_HANDLED;
}
int i2c_driver_reg_read(unsigned short regoff, unsigned short *regval)
{
int rc = 0;
unsigned char buf[2];
if (p_mic_det_dev == NULL ||
p_mic_det_dev->client == NULL)
{
MIC_DET_ERR("i2c_driver_reg_read() p_mic_det_dev->client == NULL\n");
return -1;
}
buf[0] = regoff >> 8;
buf[1] = regoff & 0xFF;
if((rc = i2c_master_send(p_mic_det_dev->client, buf, 2)) != 2) {
MIC_DET_ERR("i2c_driver_reg_read failed -: reg %02x\n", regoff);
return -1;
}
if((rc = i2c_master_recv(p_mic_det_dev->client, buf, 2)) != 2) {
MIC_DET_ERR("i2c_driver_reg_read failed: reg %02x\n", regoff);
return -1;
};
return rc;
}
int i2c_driver_reg_write(unsigned short regoff, unsigned short regval)
{
int rc;
unsigned char buf[4];
buf[0] = regoff >> 8;
buf[1] = regoff & 0xFF;
buf[2] = regval >> 8;
buf[3] = regval & 0xFF;
if((rc = i2c_master_send(p_mic_det_dev->client, buf, 4)) !=4){
MIC_DET_ERR("i2c_driver_reg_write failed : reg %02x\n", regoff);
return -1;
}
return rc;
}
static int mic_det_driver_probe(struct i2c_client *p_i2c_client,
const struct i2c_device_id *id)
{
int rc = 0;
if (p_i2c_client == NULL)
{
MIC_DET_ERR("%s mic_det_driver_probe() p_i2c_client == NULL\n",
MIC_DET_DRIVER_NAME);
return -1;
}
if (p_i2c_client->dev.platform_data == NULL)
{
MIC_DET_ERR("%s mic_det_driver_probe() "
"p_i2c_client->dev.platform_data == NULL\n",
MIC_DET_DRIVER_NAME);
return -1;
}
if (!i2c_check_functionality(p_i2c_client->adapter, I2C_FUNC_I2C))
{
MIC_DET_ERR("%s: mic_det_driver_probe() "
"i2c_check_functionality() failed %d\n",
MIC_DET_DRIVER_NAME, -ENODEV);
return -ENODEV;
}
/* Get the I2C information compiled in for this platform. */
gp_i2c_para = (struct MIC_DET_t *)p_i2c_client->dev.platform_data;
if (gp_i2c_para == NULL)
{ /* Cannot access platform data. */
printk("%s:%s Cannot access platform data for I2C slave address %d\n",
MIC_DET_DRIVER_NAME, __FUNCTION__, p_i2c_client->addr);
return -1;
}
printk("%s: slave address 0x%x\n", MIC_DET_DRIVER_NAME, p_i2c_client->addr);
writel(gp_i2c_para->comp1_threshold,
gp_i2c_para->reg_base + ACI_COMP1TH_OFFSET);
writel(ACI_COMPTH_SET_COMPTH1_SET_CMD_LOADING,
gp_i2c_para->reg_base + ACI_COMPTH_SET_OFFSET);
writel(AUXMIC_AUXEN_MICAUX_EN_MASK,
gp_i2c_para->reg_base + AUXMIC_AUXEN_OFFSET);
writel(ACI_ACI_CTRL_SW_MIC_DATAB_MASK,
gp_i2c_para->reg_base + ACI_ACI_CTRL_OFFSET);
writel(ACI_INT_COMP1INT_STS_MASK | ACI_INT_COMP2INT_STS_MASK |
ACI_INT_COMP1INT_EN_MASK | ACI_INT_INV_COMP2INT_EN_MASK,
gp_i2c_para->reg_base + ACI_INT_OFFSET);
p_mic_det_dev->client = p_i2c_client;
mutex_init(&p_mic_det_dev->mutex_wq);
p_mic_det_dev->mic_det_wq = create_workqueue("mic_det_wq");
INIT_WORK(&p_mic_det_dev->work, mic_det_wq);
i2c_set_clientdata(p_i2c_client, p_mic_det_dev);
if (rc < 0)
{ /* This also ensures that the slave is actually there! */
MIC_DET_ERR("%s mic_det_driver_probe() "
"failed to write to slave, rc = %d\n",
MIC_DET_DRIVER_NAME, rc);
return rc;
}
else
{
rc = 0;
}
queue_work(p_mic_det_dev->mic_det_wq, &p_mic_det_dev->work);
if ((rc = request_irq(gp_i2c_para->comp1_irq,
mic_det_driver_isr, (IRQF_SHARED),
"MIC dection irq", p_mic_det_dev)) < 0)
{
MIC_DET_ERR("request_irq(%d) failed, rc = %d\n",
gp_i2c_para->comp1_irq, rc);
}
return rc;
}
static int __devexit mic_det_driver_remove(struct i2c_client *client)
{
free_irq(gp_i2c_para->comp1_irq, p_mic_det_dev);
/* Free all the memory that was allocated. */
if (p_mic_det_dev->client != NULL)
{
kfree(p_mic_det_dev->client);
}
if (p_mic_det_dev != NULL)
{
kfree(p_mic_det_dev);
}
return 0;
}
/* End of if using .probe in i2c_driver. */
static struct i2c_device_id mic_det_idtable[] = {
{ MIC_DET_DRIVER_NAME, 0 },
{ }
};
static struct i2c_driver mic_det_driver = {
.driver = {
.name = MIC_DET_DRIVER_NAME,
},
.id_table = mic_det_idtable,
.class = I2C_CLASS_HWMON,
.probe = mic_det_driver_probe,
.remove = __devexit_p(mic_det_driver_remove),
#ifdef CONFIG_PM
.suspend = mic_det_suspend_driver,
.resume = mic_det_resume_driver,
#endif
};
int __init mic_det_driver_init(void)
{
int rc;
p_mic_det_dev = kzalloc(sizeof(struct mic_det_dev), GFP_KERNEL);
printk("%s: mic_det_driver_init() entering ...\n", MIC_DET_DRIVER_NAME);
if (p_mic_det_dev == NULL)
{
printk("mic_det_driver_init(): memory allocation failed for p_codec_i2c_dev!\n");
return -ENOMEM;
}
rc = i2c_add_driver(&mic_det_driver);
if (rc != 0)
{
MIC_DET_ERR("%s mic_det_driver_init(): i2c_add_driver()"
"failed, errno is %d\n", MIC_DET_DRIVER_NAME, rc);
kfree(p_mic_det_dev);
return rc;
}
return rc;
}
static void __exit mic_det_driver_exit(void)
{
i2c_del_driver(&mic_det_driver);
}
MODULE_DESCRIPTION("Mic-dection driver");
MODULE_AUTHOR("Broadcom");
MODULE_LICENSE("GPL");
module_init(mic_det_driver_init);
module_exit(mic_det_driver_exit);