300 lines
7.3 KiB
C
300 lines
7.3 KiB
C
|
/* Copyright (c) 2012, The Linux Foundation. All rights reserved.
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License version 2 and
|
||
|
* only version 2 as published by the Free Software Foundation.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
*/
|
||
|
|
||
|
#define pr_fmt(fmt) "%s: " fmt, __func__
|
||
|
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/err.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/stddef.h>
|
||
|
#include <linux/debugfs.h>
|
||
|
#include <linux/mfd/pm8xxx/core.h>
|
||
|
#include <linux/mfd/pm8xxx/spk.h>
|
||
|
|
||
|
#define PM8XXX_SPK_CTL1_REG_OFF 0
|
||
|
#define PM8XXX_SPK_CTL2_REG_OFF 1
|
||
|
#define PM8XXX_SPK_CTL3_REG_OFF 2
|
||
|
#define PM8XXX_SPK_CTL4_REG_OFF 3
|
||
|
#define PM8XXX_SPK_TEST_REG_1_OFF 4
|
||
|
#define PM8XXX_SPK_TEST_REG_2_OFF 5
|
||
|
|
||
|
#define PM8XXX_SPK_BANK_SEL 4
|
||
|
#define PM8XXX_SPK_BANK_WRITE 0x80
|
||
|
#define PM8XXX_SPK_BANK_VAL_MASK 0xF
|
||
|
|
||
|
#define BOOST_6DB_GAIN_EN_MASK 0x8
|
||
|
#define VSEL_LD0_1P1 0x0
|
||
|
#define VSEL_LD0_1P2 0x2
|
||
|
#define VSEL_LD0_1P0 0x4
|
||
|
|
||
|
#define PWM_EN_MASK 0xF
|
||
|
#define PM8XXX_SPK_TEST_REG_1_BANKS 8
|
||
|
#define PM8XXX_SPK_TEST_REG_2_BANKS 2
|
||
|
|
||
|
#define PM8XXX_SPK_GAIN 0x5
|
||
|
#define PM8XXX_ADD_EN 0x1
|
||
|
|
||
|
struct pm8xxx_spk_chip {
|
||
|
struct list_head link;
|
||
|
struct pm8xxx_spk_platform_data pdata;
|
||
|
struct device *dev;
|
||
|
enum pm8xxx_version version;
|
||
|
struct mutex spk_mutex;
|
||
|
u16 base;
|
||
|
u16 end;
|
||
|
};
|
||
|
|
||
|
static struct pm8xxx_spk_chip *the_spk_chip;
|
||
|
|
||
|
static inline bool spk_defined(void)
|
||
|
{
|
||
|
if (the_spk_chip == NULL || IS_ERR(the_spk_chip))
|
||
|
return false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static int pm8xxx_spk_bank_write(u16 reg, u16 bank, u8 val)
|
||
|
{
|
||
|
int rc = 0;
|
||
|
u8 bank_val = PM8XXX_SPK_BANK_WRITE | (bank << PM8XXX_SPK_BANK_SEL);
|
||
|
|
||
|
bank_val |= (val & PM8XXX_SPK_BANK_VAL_MASK);
|
||
|
mutex_lock(&the_spk_chip->spk_mutex);
|
||
|
rc = pm8xxx_writeb(the_spk_chip->dev->parent, reg, bank_val);
|
||
|
if (rc)
|
||
|
pr_err("pm8xxx_writeb(): rc=%d\n", rc);
|
||
|
mutex_unlock(&the_spk_chip->spk_mutex);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
|
||
|
static int pm8xxx_spk_read(u16 addr)
|
||
|
{
|
||
|
int rc = 0;
|
||
|
u8 val = 0;
|
||
|
|
||
|
mutex_lock(&the_spk_chip->spk_mutex);
|
||
|
rc = pm8xxx_readb(the_spk_chip->dev->parent,
|
||
|
the_spk_chip->base + addr, &val);
|
||
|
if (rc) {
|
||
|
pr_err("pm8xxx_spk_readb() failed: rc=%d\n", rc);
|
||
|
val = rc;
|
||
|
}
|
||
|
mutex_unlock(&the_spk_chip->spk_mutex);
|
||
|
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
static int pm8xxx_spk_write(u16 addr, u8 val)
|
||
|
{
|
||
|
int rc = 0;
|
||
|
|
||
|
mutex_lock(&the_spk_chip->spk_mutex);
|
||
|
rc = pm8xxx_writeb(the_spk_chip->dev->parent,
|
||
|
the_spk_chip->base + addr, val);
|
||
|
if (rc)
|
||
|
pr_err("pm8xxx_writeb() failed: rc=%d\n", rc);
|
||
|
mutex_unlock(&the_spk_chip->spk_mutex);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
int pm8xxx_spk_mute(bool mute)
|
||
|
{
|
||
|
u8 val = 0;
|
||
|
int ret = 0;
|
||
|
if (spk_defined() == false) {
|
||
|
pr_err("Invalid spk handle or no spk_chip\n");
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
|
||
|
val = pm8xxx_spk_read(PM8XXX_SPK_CTL1_REG_OFF);
|
||
|
val |= mute << 2;
|
||
|
ret = pm8xxx_spk_write(PM8XXX_SPK_CTL1_REG_OFF, val);
|
||
|
return ret;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(pm8xxx_spk_mute);
|
||
|
|
||
|
int pm8xxx_spk_gain(u8 gain)
|
||
|
{
|
||
|
u8 val;
|
||
|
int ret = 0;
|
||
|
|
||
|
if (spk_defined() == false) {
|
||
|
pr_err("Invalid spk handle or no spk_chip\n");
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
|
||
|
val = pm8xxx_spk_read(PM8XXX_SPK_CTL1_REG_OFF);
|
||
|
val = (gain << 4) | (val & 0xF);
|
||
|
ret = pm8xxx_spk_write(PM8XXX_SPK_CTL1_REG_OFF, val);
|
||
|
if (!ret) {
|
||
|
pm8xxx_spk_bank_write(the_spk_chip->base
|
||
|
+ PM8XXX_SPK_TEST_REG_1_OFF,
|
||
|
0, BOOST_6DB_GAIN_EN_MASK | VSEL_LD0_1P2);
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(pm8xxx_spk_gain);
|
||
|
|
||
|
int pm8xxx_spk_enable(int enable)
|
||
|
{
|
||
|
int val = 0;
|
||
|
u16 addr;
|
||
|
int ret = 0;
|
||
|
|
||
|
if (spk_defined() == false) {
|
||
|
pr_err("Invalid spk handle or no spk_chip\n");
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
|
||
|
addr = the_spk_chip->base + PM8XXX_SPK_TEST_REG_1_OFF;
|
||
|
val = pm8xxx_spk_read(PM8XXX_SPK_CTL1_REG_OFF);
|
||
|
if (val < 0)
|
||
|
return val;
|
||
|
if (enable)
|
||
|
val |= (1 << 3);
|
||
|
else
|
||
|
val &= ~(1 << 3);
|
||
|
ret = pm8xxx_spk_write(PM8XXX_SPK_CTL1_REG_OFF, val);
|
||
|
if (!ret)
|
||
|
ret = pm8xxx_spk_bank_write(addr, 6, PWM_EN_MASK);
|
||
|
return ret;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(pm8xxx_spk_enable);
|
||
|
|
||
|
static int pm8xxx_spk_config(void)
|
||
|
{
|
||
|
u16 addr;
|
||
|
int ret = 0;
|
||
|
|
||
|
if (spk_defined() == false) {
|
||
|
pr_err("Invalid spk handle or no spk_chip\n");
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
|
||
|
addr = the_spk_chip->base + PM8XXX_SPK_TEST_REG_1_OFF;
|
||
|
ret = pm8xxx_spk_bank_write(addr, 6, PWM_EN_MASK & 0);
|
||
|
if (!ret)
|
||
|
ret = pm8xxx_spk_gain(PM8XXX_SPK_GAIN);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int __devinit pm8xxx_spk_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
const struct pm8xxx_spk_platform_data *pdata = pdev->dev.platform_data;
|
||
|
int ret = 0;
|
||
|
u8 value = 0;
|
||
|
|
||
|
if (!pdata) {
|
||
|
pr_err("missing platform data\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
the_spk_chip = kzalloc(sizeof(struct pm8xxx_spk_chip), GFP_KERNEL);
|
||
|
if (the_spk_chip == NULL) {
|
||
|
pr_err("kzalloc() failed.\n");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
mutex_init(&the_spk_chip->spk_mutex);
|
||
|
|
||
|
the_spk_chip->dev = &pdev->dev;
|
||
|
the_spk_chip->version = pm8xxx_get_version(the_spk_chip->dev->parent);
|
||
|
switch (pm8xxx_get_version(the_spk_chip->dev->parent)) {
|
||
|
case PM8XXX_VERSION_8038:
|
||
|
break;
|
||
|
default:
|
||
|
ret = -ENODEV;
|
||
|
goto err_handle;
|
||
|
}
|
||
|
|
||
|
memcpy(&(the_spk_chip->pdata), pdata,
|
||
|
sizeof(struct pm8xxx_spk_platform_data));
|
||
|
|
||
|
the_spk_chip->base = pdev->resource[0].start;
|
||
|
the_spk_chip->end = pdev->resource[0].end;
|
||
|
|
||
|
if (the_spk_chip->pdata.spk_add_enable) {
|
||
|
int val;
|
||
|
val = pm8xxx_spk_read(PM8XXX_SPK_CTL1_REG_OFF);
|
||
|
if (val < 0) {
|
||
|
ret = val;
|
||
|
goto err_handle;
|
||
|
}
|
||
|
val |= (the_spk_chip->pdata.spk_add_enable & PM8XXX_ADD_EN);
|
||
|
ret = pm8xxx_spk_write(PM8XXX_SPK_CTL1_REG_OFF, val);
|
||
|
if (ret < 0)
|
||
|
goto err_handle;
|
||
|
}
|
||
|
value = ((the_spk_chip->pdata.cd_ng_threshold << 5) |
|
||
|
the_spk_chip->pdata.cd_nf_preamp_bias << 3);
|
||
|
pr_debug("Setting SPK_CTL2_REG = %02x\n", value);
|
||
|
pm8xxx_spk_write(PM8XXX_SPK_CTL2_REG_OFF, value);
|
||
|
|
||
|
value = ((the_spk_chip->pdata.cd_ng_hold << 5) |
|
||
|
(the_spk_chip->pdata.cd_ng_max_atten << 1) |
|
||
|
the_spk_chip->pdata.noise_mute);
|
||
|
pr_debug("Setting SPK_CTL3_REG = %02x\n", value);
|
||
|
pm8xxx_spk_write(PM8XXX_SPK_CTL3_REG_OFF, value);
|
||
|
|
||
|
value = ((the_spk_chip->pdata.cd_ng_decay_rate << 5) |
|
||
|
(the_spk_chip->pdata.cd_ng_attack_rate << 3) |
|
||
|
the_spk_chip->pdata.cd_delay << 2);
|
||
|
pr_debug("Setting SPK_CTL4_REG = %02x\n", value);
|
||
|
pm8xxx_spk_write(PM8XXX_SPK_CTL4_REG_OFF, value);
|
||
|
|
||
|
return pm8xxx_spk_config();
|
||
|
err_handle:
|
||
|
pr_err("pm8xxx_spk_probe failed."
|
||
|
"Audio unavailable on speaker.\n");
|
||
|
mutex_destroy(&the_spk_chip->spk_mutex);
|
||
|
kfree(the_spk_chip);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int __devexit pm8xxx_spk_remove(struct platform_device *pdev)
|
||
|
{
|
||
|
if (spk_defined() == false) {
|
||
|
pr_err("Invalid spk handle or no spk_chip\n");
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
mutex_destroy(&the_spk_chip->spk_mutex);
|
||
|
kfree(the_spk_chip);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct platform_driver pm8xxx_spk_driver = {
|
||
|
.probe = pm8xxx_spk_probe,
|
||
|
.remove = __devexit_p(pm8xxx_spk_remove),
|
||
|
.driver = {
|
||
|
.name = PM8XXX_SPK_DEV_NAME,
|
||
|
.owner = THIS_MODULE,
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static int __init pm8xxx_spk_init(void)
|
||
|
{
|
||
|
return platform_driver_register(&pm8xxx_spk_driver);
|
||
|
}
|
||
|
subsys_initcall(pm8xxx_spk_init);
|
||
|
|
||
|
static void __exit pm8xxx_spk_exit(void)
|
||
|
{
|
||
|
platform_driver_unregister(&pm8xxx_spk_driver);
|
||
|
}
|
||
|
module_exit(pm8xxx_spk_exit);
|
||
|
|
||
|
MODULE_LICENSE("GPL v2");
|
||
|
MODULE_DESCRIPTION("PM8XXX SPK driver");
|
||
|
MODULE_ALIAS("platform:" PM8XXX_SPK_DEV_NAME);
|