blob: 8eca7aa219f5ac6f78495efa149856172d054fcd [file] [log] [blame]
/* Copyright (c) 2011, Code Aurora Forum. 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.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/mfd/core.h>
#include <linux/mfd/pm8xxx/pm8921.h>
#include <linux/mfd/wcd9310/core.h>
#include <linux/mfd/wcd9310/pdata.h>
#include <linux/mfd/wcd9310/registers.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/debugfs.h>
#include <linux/regulator/consumer.h>
#include <sound/soc.h>
#define TABLA_SLIM_GLA_MAX_RETRIES 5
#define TABLA_REGISTER_START_OFFSET 0x800
static int tabla_read(struct tabla *tabla, unsigned short reg,
int bytes, void *dest, bool interface_reg)
{
int ret;
u8 *buf = dest;
if (bytes <= 0) {
dev_err(tabla->dev, "Invalid byte read length %d\n", bytes);
return -EINVAL;
}
ret = tabla->read_dev(tabla, reg, bytes, dest, interface_reg);
if (ret < 0) {
dev_err(tabla->dev, "Tabla read failed\n");
return ret;
} else
dev_dbg(tabla->dev, "Read 0x%02x from R%d(0x%x)\n",
*buf, reg, reg);
return 0;
}
int tabla_reg_read(struct tabla *tabla, unsigned short reg)
{
u8 val;
int ret;
mutex_lock(&tabla->io_lock);
ret = tabla_read(tabla, reg, 1, &val, false);
mutex_unlock(&tabla->io_lock);
if (ret < 0)
return ret;
else
return val;
}
EXPORT_SYMBOL_GPL(tabla_reg_read);
static int tabla_write(struct tabla *tabla, unsigned short reg,
int bytes, void *src, bool interface_reg)
{
u8 *buf = src;
if (bytes <= 0) {
pr_err("%s: Error, invalid write length\n", __func__);
return -EINVAL;
}
dev_dbg(tabla->dev, "Write %02x to R%d(0x%x)\n",
*buf, reg, reg);
return tabla->write_dev(tabla, reg, bytes, src, interface_reg);
}
int tabla_reg_write(struct tabla *tabla, unsigned short reg,
u8 val)
{
int ret;
mutex_lock(&tabla->io_lock);
ret = tabla_write(tabla, reg, 1, &val, false);
mutex_unlock(&tabla->io_lock);
return ret;
}
EXPORT_SYMBOL_GPL(tabla_reg_write);
static u8 tabla_pgd_la;
static u8 tabla_inf_la;
int tabla_get_logical_addresses(u8 *pgd_la, u8 *inf_la)
{
*pgd_la = tabla_pgd_la;
*inf_la = tabla_inf_la;
return 0;
}
EXPORT_SYMBOL_GPL(tabla_get_logical_addresses);
int tabla_interface_reg_read(struct tabla *tabla, unsigned short reg)
{
u8 val;
int ret;
mutex_lock(&tabla->io_lock);
ret = tabla_read(tabla, reg, 1, &val, true);
mutex_unlock(&tabla->io_lock);
if (ret < 0)
return ret;
else
return val;
}
EXPORT_SYMBOL_GPL(tabla_interface_reg_read);
int tabla_interface_reg_write(struct tabla *tabla, unsigned short reg,
u8 val)
{
int ret;
mutex_lock(&tabla->io_lock);
ret = tabla_write(tabla, reg, 1, &val, true);
mutex_unlock(&tabla->io_lock);
return ret;
}
EXPORT_SYMBOL_GPL(tabla_interface_reg_write);
int tabla_bulk_read(struct tabla *tabla, unsigned short reg,
int count, u8 *buf)
{
int ret;
mutex_lock(&tabla->io_lock);
ret = tabla_read(tabla, reg, count, buf, false);
mutex_unlock(&tabla->io_lock);
return ret;
}
EXPORT_SYMBOL_GPL(tabla_bulk_read);
int tabla_bulk_write(struct tabla *tabla, unsigned short reg,
int count, u8 *buf)
{
int ret;
mutex_lock(&tabla->io_lock);
ret = tabla_write(tabla, reg, count, buf, false);
mutex_unlock(&tabla->io_lock);
return ret;
}
EXPORT_SYMBOL_GPL(tabla_bulk_write);
static int tabla_slim_read_device(struct tabla *tabla, unsigned short reg,
int bytes, void *dest, bool interface)
{
int ret;
struct slim_ele_access msg;
msg.start_offset = TABLA_REGISTER_START_OFFSET + reg;
msg.num_bytes = bytes;
msg.comp = NULL;
mutex_lock(&tabla->xfer_lock);
if (interface)
ret = slim_request_val_element(tabla->slim_slave, &msg, dest,
bytes);
else
ret = slim_request_val_element(tabla->slim, &msg, dest, bytes);
if (ret)
pr_err("%s: Error, Tabla read failed\n", __func__);
mutex_unlock(&tabla->xfer_lock);
return ret;
}
/* Interface specifies whether the write is to the interface or general
* registers.
*/
static int tabla_slim_write_device(struct tabla *tabla, unsigned short reg,
int bytes, void *src, bool interface)
{
int ret;
struct slim_ele_access msg;
msg.start_offset = TABLA_REGISTER_START_OFFSET + reg;
msg.num_bytes = bytes;
msg.comp = NULL;
mutex_lock(&tabla->xfer_lock);
if (interface)
ret = slim_change_val_element(tabla->slim_slave, &msg, src,
bytes);
else
ret = slim_change_val_element(tabla->slim, &msg, src, bytes);
if (ret)
pr_err("%s: Error, Tabla write failed\n", __func__);
mutex_unlock(&tabla->xfer_lock);
return ret;
}
static struct mfd_cell tabla_devs[] = {
{
.name = "tabla_codec",
},
};
static void tabla_bring_up(struct tabla *tabla)
{
tabla_reg_write(tabla, TABLA_A_LEAKAGE_CTL, 0x4);
tabla_reg_write(tabla, TABLA_A_CDC_CTL, 0);
usleep_range(5000, 5000);
tabla_reg_write(tabla, TABLA_A_CDC_CTL, 3);
tabla_reg_write(tabla, TABLA_A_LEAKAGE_CTL, 3);
}
static void tabla_bring_down(struct tabla *tabla)
{
tabla_reg_write(tabla, TABLA_A_LEAKAGE_CTL, 0x7);
tabla_reg_write(tabla, TABLA_A_LEAKAGE_CTL, 0x6);
tabla_reg_write(tabla, TABLA_A_LEAKAGE_CTL, 0xe);
tabla_reg_write(tabla, TABLA_A_LEAKAGE_CTL, 0x8);
}
static int tabla_reset(struct tabla *tabla)
{
int ret;
struct pm_gpio param = {
.direction = PM_GPIO_DIR_OUT,
.output_buffer = PM_GPIO_OUT_BUF_CMOS,
.output_value = 1,
.pull = PM_GPIO_PULL_NO,
.vin_sel = PM_GPIO_VIN_S4,
.out_strength = PM_GPIO_STRENGTH_MED,
.function = PM_GPIO_FUNC_NORMAL,
};
if (tabla->reset_gpio) {
ret = gpio_request(tabla->reset_gpio, "CDC_RESET");
if (ret) {
pr_err("%s: Failed to request gpio %d\n", __func__,
tabla->reset_gpio);
tabla->reset_gpio = 0;
return ret;
}
ret = pm8xxx_gpio_config(tabla->reset_gpio, &param);
if (ret)
pr_err("%s: Failed to configure gpio\n", __func__);
gpio_direction_output(tabla->reset_gpio, 1);
msleep(20);
gpio_direction_output(tabla->reset_gpio, 0);
msleep(20);
gpio_direction_output(tabla->reset_gpio, 1);
msleep(20);
}
return 0;
}
static void tabla_free_reset(struct tabla *tabla)
{
if (tabla->reset_gpio) {
gpio_free(tabla->reset_gpio);
tabla->reset_gpio = 0;
}
}
struct tabla_regulator {
const char *name;
int min_uV;
int max_uV;
int optimum_uA;
struct regulator *regulator;
};
/*
* format : TABLA_<POWER_SUPPLY_PIN_NAME>_CUR_MAX
*
* <POWER_SUPPLY_PIN_NAME> from Tabla objective spec
*/
#define TABLA_CDC_VDDA_CP_CUR_MAX 500000
#define TABLA_CDC_VDDA_RX_CUR_MAX 20000
#define TABLA_CDC_VDDA_TX_CUR_MAX 20000
#define TABLA_VDDIO_CDC_CUR_MAX 5000
#define TABLA_VDDD_CDC_D_CUR_MAX 5000
#define TABLA_VDDD_CDC_A_CUR_MAX 5000
static struct tabla_regulator tabla_regulators[] = {
{
.name = "CDC_VDD_CP",
.min_uV = 1800000,
.max_uV = 1800000,
.optimum_uA = TABLA_CDC_VDDA_CP_CUR_MAX,
},
{
.name = "CDC_VDDA_RX",
.min_uV = 1800000,
.max_uV = 1800000,
.optimum_uA = TABLA_CDC_VDDA_RX_CUR_MAX,
},
{
.name = "CDC_VDDA_TX",
.min_uV = 1800000,
.max_uV = 1800000,
.optimum_uA = TABLA_CDC_VDDA_TX_CUR_MAX,
},
{
.name = "VDDIO_CDC",
.min_uV = 1800000,
.max_uV = 1800000,
.optimum_uA = TABLA_VDDIO_CDC_CUR_MAX,
},
{
.name = "VDDD_CDC_D",
.min_uV = 1225000,
.max_uV = 1225000,
.optimum_uA = TABLA_VDDD_CDC_D_CUR_MAX,
},
{
.name = "CDC_VDDA_A_1P2V",
.min_uV = 1225000,
.max_uV = 1225000,
.optimum_uA = TABLA_VDDD_CDC_A_CUR_MAX,
},
};
static int tabla_device_init(struct tabla *tabla, int irq)
{
int ret;
mutex_init(&tabla->io_lock);
mutex_init(&tabla->xfer_lock);
dev_set_drvdata(tabla->dev, tabla);
tabla_bring_up(tabla);
ret = tabla_irq_init(tabla);
if (ret) {
pr_err("IRQ initialization failed\n");
goto err;
}
ret = mfd_add_devices(tabla->dev, -1,
tabla_devs, ARRAY_SIZE(tabla_devs),
NULL, 0);
if (ret != 0) {
dev_err(tabla->dev, "Failed to add children: %d\n", ret);
goto err_irq;
}
return ret;
err_irq:
tabla_irq_exit(tabla);
err:
tabla_bring_down(tabla);
mutex_destroy(&tabla->io_lock);
mutex_destroy(&tabla->xfer_lock);
return ret;
}
static void tabla_device_exit(struct tabla *tabla)
{
tabla_irq_exit(tabla);
tabla_bring_down(tabla);
tabla_free_reset(tabla);
mutex_destroy(&tabla->io_lock);
mutex_destroy(&tabla->xfer_lock);
slim_remove_device(tabla->slim_slave);
kfree(tabla);
}
#ifdef CONFIG_DEBUG_FS
struct tabla *debugTabla;
static struct dentry *debugfs_tabla_dent;
static struct dentry *debugfs_peek;
static struct dentry *debugfs_poke;
static unsigned char read_data;
static int codec_debug_open(struct inode *inode, struct file *file)
{
file->private_data = inode->i_private;
return 0;
}
static int get_parameters(char *buf, long int *param1, int num_of_par)
{
char *token;
int base, cnt;
token = strsep(&buf, " ");
for (cnt = 0; cnt < num_of_par; cnt++) {
if (token != NULL) {
if ((token[1] == 'x') || (token[1] == 'X'))
base = 16;
else
base = 10;
if (strict_strtoul(token, base, &param1[cnt]) != 0)
return -EINVAL;
token = strsep(&buf, " ");
} else
return -EINVAL;
}
return 0;
}
static ssize_t codec_debug_read(struct file *file, char __user *ubuf,
size_t count, loff_t *ppos)
{
char lbuf[8];
snprintf(lbuf, sizeof(lbuf), "0x%x\n", read_data);
return simple_read_from_buffer(ubuf, count, ppos, lbuf,
strnlen(lbuf, 7));
}
static ssize_t codec_debug_write(struct file *filp,
const char __user *ubuf, size_t cnt, loff_t *ppos)
{
char *access_str = filp->private_data;
char lbuf[32];
int rc;
long int param[5];
if (cnt > sizeof(lbuf) - 1)
return -EINVAL;
rc = copy_from_user(lbuf, ubuf, cnt);
if (rc)
return -EFAULT;
lbuf[cnt] = '\0';
if (!strncmp(access_str, "poke", 6)) {
/* write */
rc = get_parameters(lbuf, param, 2);
if ((param[0] <= 0x3FF) && (param[1] <= 0xFF) &&
(rc == 0))
tabla_interface_reg_write(debugTabla, param[0],
param[1]);
else
rc = -EINVAL;
} else if (!strncmp(access_str, "peek", 6)) {
/* read */
rc = get_parameters(lbuf, param, 1);
if ((param[0] <= 0x3FF) && (rc == 0))
read_data = tabla_interface_reg_read(debugTabla,
param[0]);
else
rc = -EINVAL;
}
if (rc == 0)
rc = cnt;
else
pr_err("%s: rc = %d\n", __func__, rc);
return rc;
}
static const struct file_operations codec_debug_ops = {
.open = codec_debug_open,
.write = codec_debug_write,
.read = codec_debug_read
};
#endif
static int tabla_enable_supplies(struct tabla *tabla)
{
int ret;
int i;
tabla->supplies = kzalloc(sizeof(struct regulator_bulk_data) *
ARRAY_SIZE(tabla_regulators),
GFP_KERNEL);
if (!tabla->supplies) {
ret = -ENOMEM;
goto err;
}
for (i = 0; i < ARRAY_SIZE(tabla_regulators); i++)
tabla->supplies[i].supply = tabla_regulators[i].name;
ret = regulator_bulk_get(tabla->dev, ARRAY_SIZE(tabla_regulators),
tabla->supplies);
if (ret != 0) {
dev_err(tabla->dev, "Failed to get supplies: err = %d\n", ret);
goto err_supplies;
}
for (i = 0; i < ARRAY_SIZE(tabla_regulators); i++) {
ret = regulator_set_voltage(tabla->supplies[i].consumer,
tabla_regulators[i].min_uV, tabla_regulators[i].max_uV);
if (ret) {
pr_err("%s: Setting regulator voltage failed for "
"regulator %s err = %d\n", __func__,
tabla->supplies[i].supply, ret);
goto err_get;
}
ret = regulator_set_optimum_mode(tabla->supplies[i].consumer,
tabla_regulators[i].optimum_uA);
if (ret < 0) {
pr_err("%s: Setting regulator optimum mode failed for "
"regulator %s err = %d\n", __func__,
tabla->supplies[i].supply, ret);
goto err_get;
}
}
ret = regulator_bulk_enable(ARRAY_SIZE(tabla_regulators),
tabla->supplies);
if (ret != 0) {
dev_err(tabla->dev, "Failed to enable supplies: err = %d\n",
ret);
goto err_configure;
}
return ret;
err_configure:
for (i = 0; i < ARRAY_SIZE(tabla_regulators); i++) {
regulator_set_voltage(tabla->supplies[i].consumer, 0,
tabla_regulators[i].max_uV);
regulator_set_optimum_mode(tabla->supplies[i].consumer, 0);
}
err_get:
regulator_bulk_free(ARRAY_SIZE(tabla_regulators), tabla->supplies);
err_supplies:
kfree(tabla->supplies);
err:
return ret;
}
static void tabla_disable_supplies(struct tabla *tabla)
{
int i;
regulator_bulk_disable(ARRAY_SIZE(tabla_regulators),
tabla->supplies);
for (i = 0; i < ARRAY_SIZE(tabla_regulators); i++) {
regulator_set_voltage(tabla->supplies[i].consumer, 0,
tabla_regulators[i].max_uV);
regulator_set_optimum_mode(tabla->supplies[i].consumer, 0);
}
regulator_bulk_free(ARRAY_SIZE(tabla_regulators), tabla->supplies);
kfree(tabla->supplies);
}
static int tabla_slim_probe(struct slim_device *slim)
{
struct tabla *tabla;
struct tabla_pdata *pdata;
int ret = 0;
int sgla_retry_cnt;
dev_info(&slim->dev, "Initialized slim device %s\n", slim->name);
pdata = slim->dev.platform_data;
if (!pdata) {
dev_err(&slim->dev, "Error, no platform data\n");
ret = -EINVAL;
goto err;
}
tabla = kzalloc(sizeof(struct tabla), GFP_KERNEL);
if (tabla == NULL) {
pr_err("%s: error, allocation failed\n", __func__);
ret = -ENOMEM;
goto err;
}
if (!slim->ctrl) {
pr_err("Error, no SLIMBUS control data\n");
ret = -EINVAL;
goto err_tabla;
}
tabla->slim = slim;
slim_set_clientdata(slim, tabla);
tabla->reset_gpio = pdata->reset_gpio;
tabla->dev = &slim->dev;
ret = tabla_enable_supplies(tabla);
if (ret) {
pr_info("%s: Fail to enable Tabla supplies\n", __func__);
goto err_tabla;
}
usleep_range(5, 5);
ret = tabla_reset(tabla);
if (ret) {
pr_err("%s: Resetting Tabla failed\n", __func__);
goto err_supplies;
}
ret = slim_get_logical_addr(tabla->slim, tabla->slim->e_addr,
ARRAY_SIZE(tabla->slim->e_addr), &tabla->slim->laddr);
if (ret) {
pr_err("fail to get slimbus logical address %d\n", ret);
goto err_reset;
}
tabla->read_dev = tabla_slim_read_device;
tabla->write_dev = tabla_slim_write_device;
tabla->irq = pdata->irq;
tabla->irq_base = pdata->irq_base;
tabla_pgd_la = tabla->slim->laddr;
if (pdata->num_irqs < TABLA_NUM_IRQS) {
pr_err("%s: Error, not enough interrupt lines allocated\n",
__func__);
goto err_reset;
}
tabla->slim_slave = &pdata->slimbus_slave_device;
ret = slim_add_device(slim->ctrl, tabla->slim_slave);
if (ret) {
pr_err("%s: error, adding SLIMBUS device failed\n", __func__);
goto err_reset;
}
sgla_retry_cnt = 0;
while (1) {
ret = slim_get_logical_addr(tabla->slim_slave,
tabla->slim_slave->e_addr,
ARRAY_SIZE(tabla->slim_slave->e_addr),
&tabla->slim_slave->laddr);
if (ret) {
if (sgla_retry_cnt++ < TABLA_SLIM_GLA_MAX_RETRIES) {
/* Give SLIMBUS slave time to report present
and be ready.
*/
usleep_range(1000, 1000);
pr_debug("%s: retry slim_get_logical_addr()\n",
__func__);
continue;
}
pr_err("fail to get slimbus slave logical address"
" %d\n", ret);
goto err_slim_add;
}
break;
}
tabla_inf_la = tabla->slim_slave->laddr;
ret = tabla_device_init(tabla, tabla->irq);
if (ret) {
pr_err("%s: error, initializing device failed\n", __func__);
goto err_slim_add;
}
#ifdef CONFIG_DEBUG_FS
debugTabla = tabla;
debugfs_tabla_dent = debugfs_create_dir
("wcd9310_slimbus_interface_device", 0);
if (!IS_ERR(debugfs_tabla_dent)) {
debugfs_peek = debugfs_create_file("peek",
S_IFREG | S_IRUGO, debugfs_tabla_dent,
(void *) "peek", &codec_debug_ops);
debugfs_poke = debugfs_create_file("poke",
S_IFREG | S_IRUGO, debugfs_tabla_dent,
(void *) "poke", &codec_debug_ops);
}
#endif
return ret;
err_slim_add:
slim_remove_device(tabla->slim_slave);
err_reset:
tabla_free_reset(tabla);
err_supplies:
tabla_disable_supplies(tabla);
err_tabla:
kfree(tabla);
err:
return ret;
}
static int tabla_slim_remove(struct slim_device *pdev)
{
struct tabla *tabla;
#ifdef CONFIG_DEBUG_FS
debugfs_remove(debugfs_peek);
debugfs_remove(debugfs_poke);
debugfs_remove(debugfs_tabla_dent);
#endif
tabla = slim_get_devicedata(pdev);
tabla_device_exit(tabla);
tabla_disable_supplies(tabla);
kfree(tabla);
return 0;
}
static const struct slim_device_id slimtest_id[] = {
{"tabla-slim", 0},
{}
};
static struct slim_driver tabla_slim_driver = {
.driver = {
.name = "tabla-slim",
.owner = THIS_MODULE,
},
.probe = tabla_slim_probe,
.remove = tabla_slim_remove,
.id_table = slimtest_id,
};
static const struct slim_device_id slimtest2x_id[] = {
{"tabla2x-slim", 0},
{}
};
static struct slim_driver tabla2x_slim_driver = {
.driver = {
.name = "tabla2x-slim",
.owner = THIS_MODULE,
},
.probe = tabla_slim_probe,
.remove = tabla_slim_remove,
.id_table = slimtest2x_id,
};
static int __init tabla_init(void)
{
int ret1, ret2;
ret1 = slim_driver_register(&tabla_slim_driver);
if (ret1 != 0)
pr_err("Failed to register tabla SB driver: %d\n", ret1);
ret2 = slim_driver_register(&tabla2x_slim_driver);
if (ret2 != 0)
pr_err("Failed to register tabla2x SB driver: %d\n", ret2);
return (ret1 && ret2) ? -1 : 0;
}
module_init(tabla_init);
static void __exit tabla_exit(void)
{
}
module_exit(tabla_exit);
MODULE_DESCRIPTION("Tabla core driver");
MODULE_VERSION("1.0");
MODULE_LICENSE("GPL v2");