/* * 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 #include #include #include #include #include #include #include #include #if defined(CONFIG_HAS_EARLYSUSPEND) #include #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");