blob: c933b486d9dd3cdc2618b53f403a998a92e7728d [file] [log] [blame]
/*
* Marimba TSADC driver.
*
* Copyright (c) 2009-2010, 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.
*
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/clk.h>
#include <linux/mfd/marimba.h>
#include <linux/mfd/marimba-tsadc.h>
#include <linux/pm.h>
#include <linux/slab.h>
#if defined(CONFIG_HAS_EARLYSUSPEND)
#include <linux/earlysuspend.h>
#endif
/* marimba configuration block: TS_CTL0 */
#define TS_CTL0 0xFF
#define TS_CTL0_RESET BIT(0)
#define TS_CTL0_CLK_EN BIT(1)
#define TS_CTL0_XO_EN BIT(2)
#define TS_CTL0_EOC_EN BIT(3)
#define TS_CTL0_PENIRQ_EN BIT(4)
/* TSADC registers */
#define SSBI_PRESET 0x00
#define TSHK_DIG_CONFIG 0x4F
#define TSHK_INTF_CONFIG 0x50
#define TSHK_SETUP 0x51
#define TSHK_SETUP_EN_ADC BIT(0)
#define TSHK_SETUP_EN_PIRQ BIT(7)
#define TSHK_PARAM 0x52
#define TSHK_DATA_RD 0x53
#define TSHK_STATUS 0x54
#define TSHK_SETUP2 0x55
#define TSHK_RSV1 0x56
#define TSHK_RSV1_PRECHARGE_EN BIT(0)
#define TSHK_COMMAND 0x57
#define TSHK_PARAM2 0x58
#define TSHK_INPUT_CLK_MASK 0x3F
#define TSHK_SAMPLE_PRD_MASK 0xC7
#define TSHK_INPUT_CLK_SHIFT 0x6
#define TSHK_SAMPLE_PRD_SHIFT 0x3
#define TSHK_PARAM3 0x59
#define TSHK_PARAM3_MODE_MASK 0xFC
#define TSHK_PARAM3_PRE_CHG_SHIFT (5)
#define TSHK_PARAM3_STABIZ_SHIFT (2)
#define TSHK_STABLE_TIME_MASK 0xE3
#define TSHK_PRECHG_TIME_MASK 0x1F
#define TSHK_PARAM4 0x5A
#define TSHK_RSV2 0x5B
#define TSHK_RSV3 0x5C
#define TSHK_RSV4 0x5D
#define TSHK_RSV5 0x5E
struct marimba_tsadc_client {
unsigned int is_ts;
struct platform_device *pdev;
};
struct marimba_tsadc {
struct marimba *marimba;
struct device *dev;
struct marimba_tsadc_platform_data *pdata;
struct clk *codec_ssbi;
struct device *child_tssc;
bool clk_enabled;
#if defined(CONFIG_HAS_EARLYSUSPEND)
struct early_suspend early_suspend;
#endif
};
static struct marimba_tsadc *tsadc_dev;
static int marimba_write_u8(struct marimba_tsadc *tsadc, u8 reg, u8 data)
{
int rc;
tsadc->marimba->mod_id = MARIMBA_SLAVE_ID_MARIMBA;
rc = marimba_write(tsadc->marimba, reg, &data, 1);
if (!rc)
dev_warn(tsadc->dev, "Error writing marimba reg %X - ret %X\n",
reg, data);
return 0;
}
static int marimba_tsadc_write(struct marimba_tsadc *tsadc, u8 reg, u8 data)
{
int rc;
tsadc->marimba->mod_id = MARIMBA_ID_TSADC;
rc = marimba_ssbi_write(tsadc->marimba, reg, &data, 1);
if (!rc)
dev_warn(tsadc->dev, "Error writing marimba reg %X - ret %X\n",
reg, data);
return rc;
}
static int marimba_tsadc_shutdown(struct marimba_tsadc *tsadc)
{
u8 val;
int rc;
/* force reset */
val = TS_CTL0_XO_EN | TS_CTL0_EOC_EN | TS_CTL0_PENIRQ_EN |
TS_CTL0_CLK_EN;
rc = marimba_write_u8(tsadc, TS_CTL0, val);
if (rc < 0)
return rc;
/* disable xo, clock */
val = TS_CTL0_PENIRQ_EN | TS_CTL0_EOC_EN;
rc = marimba_write_u8(tsadc, TS_CTL0, val);
if (rc < 0)
return rc;
/* de-vote S2 1.3v */
if (tsadc->pdata->level_vote)
/* REVISIT: Ignore error for level_vote(0) for now*/
tsadc->pdata->level_vote(0);
return 0;
}
static int marimba_tsadc_startup(struct marimba_tsadc *tsadc)
{
u8 val;
int rc = 0;
/* vote for S2 1.3v */
if (tsadc->pdata->level_vote) {
rc = tsadc->pdata->level_vote(1);
if (rc < 0)
return rc;
}
/* disable XO, clock and output enables */
rc = marimba_write_u8(tsadc, TS_CTL0, 0x00);
if (rc < 0)
goto fail_marimba_write;
/* Enable output enables */
val = TS_CTL0_XO_EN | TS_CTL0_EOC_EN | TS_CTL0_PENIRQ_EN;
rc = marimba_write_u8(tsadc, TS_CTL0, val);
if (rc < 0)
goto fail_marimba_write;
/* Enable clock */
val = val | TS_CTL0_CLK_EN;
rc = marimba_write_u8(tsadc, TS_CTL0, val);
if (rc < 0)
goto fail_marimba_write;
/* remove reset */
val = val | TS_CTL0_RESET;
rc = marimba_write_u8(tsadc, TS_CTL0, val);
if (rc < 0)
goto fail_marimba_write;
return 0;
fail_marimba_write:
if (tsadc->pdata->level_vote)
/* REVISIT: Ignore error for level_vote(0) for now*/
tsadc->pdata->level_vote(0);
return rc;
}
static int marimba_tsadc_configure(struct marimba_tsadc *tsadc)
{
u8 rsv1 = 0, setup = 0, i, count = 0;
u8 param2 = 0, param3 = 0;
unsigned long val;
int rc;
rc = marimba_tsadc_write(tsadc, SSBI_PRESET, 0x00);
if (rc < 0)
return rc;
if (!tsadc->pdata)
return -EINVAL;
/* Configure RSV1 register*/
if (tsadc->pdata->tsadc_prechg_en == true)
rsv1 |= TSHK_RSV1_PRECHARGE_EN;
else
rsv1 &= ~TSHK_RSV1_PRECHARGE_EN;
/* Set RSV1 register*/
rc = marimba_tsadc_write(tsadc, TSHK_RSV1, rsv1);
if (rc < 0)
return rc;
/* Configure PARAM2 register */
/* Input clk */
val = tsadc->pdata->params2.input_clk_khz;
param2 &= TSHK_INPUT_CLK_MASK;
val /= 600;
if (val >= 1 && val <= 8 && !(val & (val - 1))) {
/* Input clk can be .6, 1.2, 2.4, 4.8Mhz */
if (val % 4 != 0)
param2 = (4 - (val % 4)) << TSHK_INPUT_CLK_SHIFT;
else
param2 = ((val / 4) - 1) << TSHK_INPUT_CLK_SHIFT;
} else /* Configure the default clk 2.4Mhz */
param2 = 0x00 << TSHK_INPUT_CLK_SHIFT;
/* Sample period */
param2 &= TSHK_SAMPLE_PRD_MASK;
param2 |= tsadc->pdata->params2.sample_prd << TSHK_SAMPLE_PRD_SHIFT;
/* Write PARAM2 register */
rc = marimba_tsadc_write(tsadc, TSHK_PARAM2, param2);
if (rc < 0)
return rc;
/* REVISIT: If Precharge time, stabilization time > 409.6us */
/* Configure PARAM3 register */
val = tsadc->pdata->params3.prechg_time_nsecs;
param3 &= TSHK_PRECHG_TIME_MASK;
val /= 6400;
if (val >= 1 && val <= 64 && !(val & (val - 1))) {
count = 0;
while ((val = val >> 1) != 0)
count++;
param3 |= count << TSHK_PARAM3_PRE_CHG_SHIFT;
} else /* Set default value if the input is wrong */
param3 |= 0x00 << TSHK_PARAM3_PRE_CHG_SHIFT;
val = tsadc->pdata->params3.stable_time_nsecs;
param3 &= TSHK_STABLE_TIME_MASK;
val /= 6400;
if (val >= 1 && val <= 64 && !(val & (val - 1))) {
count = 0;
while ((val = val >> 1) != 0)
count++;
param3 |= count << TSHK_PARAM3_STABIZ_SHIFT;
} else /* Set default value if the input is wrong */
param3 |= 0x00 << TSHK_PARAM3_STABIZ_SHIFT;
/* Get TSADC mode */
val = tsadc->pdata->params3.tsadc_test_mode;
param3 &= TSHK_PARAM3_MODE_MASK;
if (val == 0)
param3 |= 0x00;
else
for (i = 0; i < 3 ; i++) {
if (((val + i) % 39322) == 0) {
param3 |= (i + 1);
break;
}
}
if (i == 3) /* Set to normal mode if input is wrong */
param3 |= 0x00;
rc = marimba_tsadc_write(tsadc, TSHK_PARAM3, param3);
if (rc < 0)
return rc;
/* Configure TSHK SETUP Register */
if (tsadc->pdata->setup.pen_irq_en == true)
setup |= TSHK_SETUP_EN_PIRQ;
else
setup &= ~TSHK_SETUP_EN_PIRQ;
if (tsadc->pdata->setup.tsadc_en == true)
setup |= TSHK_SETUP_EN_ADC;
else
setup &= ~TSHK_SETUP_EN_ADC;
/* Enable signals to ADC, pen irq assertion */
rc = marimba_tsadc_write(tsadc, TSHK_SETUP, setup);
if (rc < 0)
return rc;
return 0;
}
int marimba_tsadc_start(struct marimba_tsadc_client *client)
{
int rc = 0;
if (!client) {
pr_err("%s: Not a valid client\n", __func__);
return -ENODEV;
}
if (!tsadc_dev) {
dev_err(&client->pdev->dev,
"%s: No tsadc device available\n", __func__);
return -ENODEV;
}
/* REVISIT - add locks */
if (client->is_ts) {
rc = marimba_tsadc_startup(tsadc_dev);
if (rc < 0)
goto fail_tsadc_startup;
rc = marimba_tsadc_configure(tsadc_dev);
if (rc < 0)
goto fail_tsadc_conf;
}
return 0;
fail_tsadc_conf:
marimba_tsadc_shutdown(tsadc_dev);
fail_tsadc_startup:
return rc;
}
EXPORT_SYMBOL(marimba_tsadc_start);
struct marimba_tsadc_client *
marimba_tsadc_register(struct platform_device *pdev, unsigned int is_ts)
{
struct marimba_tsadc_client *client;
if (!pdev) {
pr_err("%s: valid platform device pointer please\n", __func__);
return ERR_PTR(-EINVAL);
}
if (!is_ts) {
dev_err(&pdev->dev, "%s: only TS right now\n", __func__);
return ERR_PTR(-EINVAL);
}
if (!tsadc_dev) {
dev_err(&pdev->dev,
"%s: No tsadc device available\n", __func__);
return ERR_PTR(-ENODEV);
}
client = kzalloc(sizeof *client, GFP_KERNEL);
if (!client)
return ERR_PTR(-ENOMEM);
client->pdev = pdev;
client->is_ts = is_ts;
return client;
}
EXPORT_SYMBOL(marimba_tsadc_register);
void marimba_tsadc_unregister(struct marimba_tsadc_client *client)
{
if (client->is_ts)
marimba_tsadc_shutdown(tsadc_dev);
kfree(client);
}
EXPORT_SYMBOL(marimba_tsadc_unregister);
static struct resource resources_tssc[] = {
{
.start = 0xAD300000,
.end = 0xAD300000 + SZ_4K - 1,
.name = "tssc",
.flags = IORESOURCE_MEM,
},
{
.start = 55,
.end = 55,
.name = "tssc1",
.flags = IORESOURCE_IRQ | IRQF_TRIGGER_RISING,
},
{
.start = 56,
.end = 56,
.name = "tssc2",
.flags = IORESOURCE_IRQ | IRQF_TRIGGER_RISING,
},
};
static struct device *
marimba_add_tssc_subdev(struct device *parent, const char *name, int num,
struct resource *resources, int num_resources,
void *pdata, int pdata_len)
{
struct platform_device *pdev;
int status;
pdev = platform_device_alloc(name, num);
if (!pdev) {
dev_dbg(parent, "can't alloc dev\n");
status = -ENOMEM;
goto err;
}
pdev->dev.parent = parent;
if (pdata) {
status = platform_device_add_data(pdev, pdata, pdata_len);
if (status < 0) {
dev_dbg(&pdev->dev, "can't add platform_data\n");
goto err;
}
}
status = platform_device_add_resources(pdev, resources, num_resources);
if (status < 0) {
dev_dbg(&pdev->dev, "can't add resources\n");
goto err;
}
status = platform_device_add(pdev);
err:
if (status < 0) {
platform_device_put(pdev);
dev_err(parent, "can't add %s dev\n", name);
return ERR_PTR(status);
}
return &pdev->dev;
}
#ifdef CONFIG_PM
static int
marimba_tsadc_suspend(struct device *dev)
{
int rc = 0, ret = 0;
struct marimba_tsadc *tsadc = dev_get_drvdata(dev);
if (tsadc->clk_enabled == true) {
clk_disable_unprepare(tsadc->codec_ssbi);
tsadc->clk_enabled = false;
}
if (!(device_may_wakeup(dev) &&
device_may_wakeup(tsadc->child_tssc))) {
rc = marimba_tsadc_shutdown(tsadc);
if (rc < 0) {
pr_err("%s: Unable to shutdown TSADC\n", __func__);
goto fail_shutdown;
}
if (tsadc->pdata->marimba_tsadc_power) {
rc = tsadc->pdata->marimba_tsadc_power(0);
if (rc < 0)
goto fail_tsadc_power;
}
}
return rc;
fail_tsadc_power:
marimba_tsadc_startup(tsadc_dev);
marimba_tsadc_configure(tsadc_dev);
fail_shutdown:
if (tsadc->clk_enabled == false) {
ret = clk_prepare_enable(tsadc->codec_ssbi);
if (ret == 0)
tsadc->clk_enabled = true;
}
return rc;
}
static int marimba_tsadc_resume(struct device *dev)
{
int rc = 0;
struct marimba_tsadc *tsadc = dev_get_drvdata(dev);
if (tsadc->clk_enabled == false) {
rc = clk_prepare_enable(tsadc->codec_ssbi);
if (rc != 0) {
pr_err("%s: Clk enable failed\n", __func__);
return rc;
}
tsadc->clk_enabled = true;
}
if (!(device_may_wakeup(dev) &&
device_may_wakeup(tsadc->child_tssc))) {
if (tsadc->pdata->marimba_tsadc_power) {
rc = tsadc->pdata->marimba_tsadc_power(1);
if (rc) {
pr_err("%s: Unable to power on TSADC \n",
__func__);
goto fail_tsadc_power;
}
}
rc = marimba_tsadc_startup(tsadc_dev);
if (rc < 0) {
pr_err("%s: Unable to startup TSADC\n", __func__);
goto fail_tsadc_startup;
}
rc = marimba_tsadc_configure(tsadc_dev);
if (rc < 0) {
pr_err("%s: Unable to configure TSADC\n", __func__);
goto fail_tsadc_configure;
}
}
return rc;
fail_tsadc_configure:
marimba_tsadc_shutdown(tsadc_dev);
fail_tsadc_startup:
if (tsadc->pdata->marimba_tsadc_power)
tsadc->pdata->marimba_tsadc_power(0);
fail_tsadc_power:
if (tsadc->clk_enabled == true) {
clk_disable_unprepare(tsadc->codec_ssbi);
tsadc->clk_enabled = false;
}
return rc;
}
static struct dev_pm_ops tsadc_pm_ops = {
#ifndef CONFIG_HAS_EARLYSUSPEND
.suspend = marimba_tsadc_suspend,
.resume = marimba_tsadc_resume,
#endif
};
#endif
#ifdef CONFIG_HAS_EARLYSUSPEND
static void marimba_tsadc_early_suspend(struct early_suspend *h)
{
struct marimba_tsadc *tsadc = container_of(h, struct marimba_tsadc,
early_suspend);
marimba_tsadc_suspend(tsadc->dev);
}
static void marimba_tsadc_late_resume(struct early_suspend *h)
{
struct marimba_tsadc *tsadc = container_of(h, struct marimba_tsadc,
early_suspend);
marimba_tsadc_resume(tsadc->dev);
}
#endif
static int __devinit marimba_tsadc_probe(struct platform_device *pdev)
{
struct marimba *marimba = platform_get_drvdata(pdev);
struct marimba_tsadc *tsadc;
struct marimba_tsadc_platform_data *pdata = pdev->dev.platform_data;
int rc = 0;
struct device *child;
printk("%s\n", __func__);
if (!pdata) {
dev_dbg(&pdev->dev, "no tsadc platform data?\n");
return -EINVAL;
}
tsadc = kzalloc(sizeof *tsadc, GFP_KERNEL);
if (!tsadc)
return -ENOMEM;
tsadc->marimba = marimba;
tsadc->dev = &pdev->dev;
tsadc->pdata = pdata;
platform_set_drvdata(pdev, tsadc);
if (tsadc->pdata->init) {
rc = tsadc->pdata->init();
if (rc < 0)
goto fail_tsadc_init;
}
if (tsadc->pdata->marimba_tsadc_power) {
rc = tsadc->pdata->marimba_tsadc_power(1);
if (rc) {
pr_err("%s: Unable to power up TSADC \n", __func__);
goto fail_tsadc_power;
}
}
tsadc->codec_ssbi = clk_get(NULL, "codec_ssbi_clk");
if (IS_ERR(tsadc->codec_ssbi)) {
rc = PTR_ERR(tsadc->codec_ssbi);
goto fail_clk_get;
}
rc = clk_prepare_enable(tsadc->codec_ssbi);
if (rc != 0)
goto fail_clk_enable;
tsadc->clk_enabled = true;
child = marimba_add_tssc_subdev(&pdev->dev, "msm_touchscreen", -1,
resources_tssc, ARRAY_SIZE(resources_tssc),
pdata->tssc_data, sizeof(*pdata->tssc_data));
if (IS_ERR(child)) {
rc = PTR_ERR(child);
goto fail_add_subdev;
}
tsadc->child_tssc = child;
platform_set_drvdata(pdev, tsadc);
#ifdef CONFIG_HAS_EARLYSUSPEND
tsadc->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN +
TSADC_SUSPEND_LEVEL;
tsadc->early_suspend.suspend = marimba_tsadc_early_suspend;
tsadc->early_suspend.resume = marimba_tsadc_late_resume;
register_early_suspend(&tsadc->early_suspend);
#endif
tsadc_dev = tsadc;
device_init_wakeup(&pdev->dev, pdata->can_wakeup);
return rc;
fail_add_subdev:
clk_disable_unprepare(tsadc->codec_ssbi);
fail_clk_enable:
clk_put(tsadc->codec_ssbi);
fail_clk_get:
if (tsadc->pdata->marimba_tsadc_power)
rc = tsadc->pdata->marimba_tsadc_power(0);
fail_tsadc_power:
if (tsadc->pdata->exit)
rc = tsadc->pdata->exit();
fail_tsadc_init:
kfree(tsadc);
return rc;
}
static int __devexit marimba_tsadc_remove(struct platform_device *pdev)
{
int rc = 0;
struct marimba_tsadc *tsadc = platform_get_drvdata(pdev);
device_init_wakeup(&pdev->dev, 0);
if (tsadc->clk_enabled == true)
clk_disable_unprepare(tsadc->codec_ssbi);
clk_put(tsadc->codec_ssbi);
if (tsadc->pdata->exit)
rc = tsadc->pdata->exit();
if (tsadc->pdata->marimba_tsadc_power)
rc = tsadc->pdata->marimba_tsadc_power(0);
#ifdef CONFIG_HAS_EARLYSUSPEND
unregister_early_suspend(&tsadc->early_suspend);
#endif
platform_set_drvdata(pdev, NULL);
kfree(tsadc);
return rc;
}
static struct platform_driver tsadc_driver = {
.probe = marimba_tsadc_probe,
.remove = __devexit_p(marimba_tsadc_remove),
.driver = {
.name = "marimba_tsadc",
.owner = THIS_MODULE,
#ifdef CONFIG_PM
.pm = &tsadc_pm_ops,
#endif
},
};
static int __init marimba_tsadc_init(void)
{
return platform_driver_register(&tsadc_driver);
}
device_initcall(marimba_tsadc_init);
static void __exit marimba_tsadc_exit(void)
{
return platform_driver_unregister(&tsadc_driver);
}
module_exit(marimba_tsadc_exit);
MODULE_DESCRIPTION("Marimba TSADC driver");
MODULE_VERSION("0.1");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:marimba_tsadc");