aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-exynos/gsd4t.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mach-exynos/gsd4t.c')
-rw-r--r--arch/arm/mach-exynos/gsd4t.c299
1 files changed, 299 insertions, 0 deletions
diff --git a/arch/arm/mach-exynos/gsd4t.c b/arch/arm/mach-exynos/gsd4t.c
new file mode 100644
index 0000000..c30fa20
--- /dev/null
+++ b/arch/arm/mach-exynos/gsd4t.c
@@ -0,0 +1,299 @@
+/*
+ * gsd4t.c (SiRFstarIV)
+ * GPS driver for SiRFstar based chip.
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * Minho Ban <mhban@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/mutex.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/rfkill.h>
+#include <linux/regulator/machine.h>
+#include <asm/mach-types.h>
+#include <mach/gpio.h>
+#include <plat/gpio-cfg.h>
+
+#include <mach/gsd4t.h>
+
+struct gsd4t_data {
+ struct gsd4t_platform_data *pdata;
+ struct rfkill *rfk;
+ bool in_use;
+ /* No need below if constraints is always_on */
+ struct regulator *vdd_18;
+ struct regulator *rtc_xi;
+};
+
+static int gsd4t_set_block(void *data, bool blocked)
+{
+ struct gsd4t_data *bd = data;
+ struct gsd4t_platform_data *pdata = bd->pdata;
+
+ if (!blocked) {
+ if (bd->in_use)
+ return 0;
+
+ pr_info("gsd4t on\n");
+
+ regulator_enable(bd->vdd_18);
+ regulator_enable(bd->rtc_xi);
+
+ /*
+ * CSR(SiRF) recommends,
+ *
+ * _|^^^|_100ms_|^^^^^^^^^^^^^^^ nrst
+ * _____________,___200ms___|^^^ onoff
+ *
+ */
+
+ gpio_set_value(pdata->nrst, 1);
+ gpio_set_value(pdata->onoff, 0);
+ /*
+ * But real boot sequence should be handled by user layer
+ * (including download firmware) so we don't need control pins
+ * here actually.
+ *
+ msleep(50);
+ gpio_set_value(pdata->nrst, 0);
+ msleep(100);
+ gpio_set_value(pdata->nrst, 1);
+ msleep(200);
+ gpio_set_value(pdata->onoff, 1);
+ */
+
+ bd->in_use = true;
+ } else {
+ if (!bd->in_use)
+ return 0;
+
+ pr_info("gsd4t off\n");
+
+ gpio_set_value(pdata->nrst, 0);
+
+ regulator_disable(bd->vdd_18);
+ regulator_disable(bd->rtc_xi);
+
+ bd->in_use = false;
+ }
+ return 0;
+}
+
+static const struct rfkill_ops gsd4t_rfkill_ops = {
+ .set_block = gsd4t_set_block,
+};
+
+static int __devinit gsd4t_probe(struct platform_device *dev)
+{
+ struct gsd4t_platform_data *pdata;
+ struct gsd4t_data *bd;
+ unsigned int gpio;
+ int ret = 0;
+
+ pdata = dev->dev.platform_data;
+ if (!pdata) {
+ dev_err(&dev->dev, "No platform data.\n");
+ return -EINVAL;
+ }
+
+ bd = kzalloc(sizeof(struct gsd4t_data), GFP_KERNEL);
+ if (!bd)
+ return -ENOMEM;
+
+ bd->pdata = pdata;
+
+ if (gpio_is_valid(pdata->nrst)) {
+ gpio = pdata->nrst;
+ /* GPS_nRST is high */
+ gpio_request(gpio, "GPS_nRST");
+ s3c_gpio_cfgpin(gpio, S3C_GPIO_OUTPUT);
+ gpio_direction_output(gpio, 0);
+
+ gpio_export(gpio, 1);
+ gpio_export_link(&dev->dev, "reset", gpio);
+ } else {
+ dev_err(&dev->dev, "Invalid nRST pin\n");
+ ret = -EINVAL;
+ goto err_invalid_pin1;
+ }
+
+
+ if (gpio_is_valid(pdata->onoff)) {
+ gpio = pdata->onoff;
+ /* GPS_EN is low */
+ gpio_request(gpio, "GPS_EN");
+ s3c_gpio_cfgpin(gpio, S3C_GPIO_OUTPUT);
+ gpio_direction_output(gpio, 0);
+
+ gpio_export(gpio, 1);
+ gpio_export_link(&dev->dev, "onoff", gpio);
+ } else {
+ dev_err(&dev->dev, "Invalid GPS_EN pin\n");
+ ret = -EINVAL;
+ goto err_invalid_pin2;
+ }
+
+ /* Optional aiding pin */
+ if (gpio_is_valid(pdata->tsync)) {
+ gpio = pdata->tsync;
+ /* AP_AGPS_TSYNC is low */
+ gpio_request(gpio, "AP_AGPS_TSYNC");
+ s3c_gpio_cfgpin(gpio, S3C_GPIO_OUTPUT);
+ gpio_direction_output(gpio, 0);
+
+ gpio_export(gpio, 1);
+ gpio_export_link(&dev->dev, "tsync", gpio);
+ }
+
+ /* input UART pin need to set pull-up to prevent floating */
+ if (gpio_is_valid(pdata->uart_rxd))
+ s3c_gpio_setpull(pdata->uart_rxd, S3C_GPIO_PULL_UP);
+
+ /*
+ * Get regulators.
+ * If always_on power, can remove below.
+ */
+ bd->rtc_xi = regulator_get(&dev->dev, "gps_clk");
+ if (IS_ERR_OR_NULL(bd->rtc_xi)) {
+ dev_err(&dev->dev, "gps_clk regulator_get error\n");
+ ret = -EINVAL;
+ goto err_regulator_1;
+ }
+
+ bd->vdd_18 = regulator_get(&dev->dev, "v_gps_1.8v");
+ if (IS_ERR_OR_NULL(bd->vdd_18)) {
+ dev_err(&dev->dev, "vdd_18 regulator_get error\n");
+ ret = -EINVAL;
+ goto err_regulator_2;
+ }
+
+ /*
+ * Actually, we don't need rfkill becasue most of power sequences to be
+ * done by user level. Just leave this to know user trggers onoff so we
+ * can set pins PDN mode at suspend/resume.
+ */
+ bd->rfk = rfkill_alloc("gsd4t", &dev->dev, RFKILL_TYPE_GPS,
+ &gsd4t_rfkill_ops, bd);
+ if (!bd->rfk) {
+ ret = -ENOMEM;
+ goto err_rfk_alloc;
+ }
+
+ /*
+ * rfkill.h
+ * block true : off
+ * block false : on
+ */
+ rfkill_init_sw_state(bd->rfk, true);
+ bd->in_use = false;
+
+ ret = rfkill_register(bd->rfk);
+ if (ret)
+ goto err_rfkill;
+
+ platform_set_drvdata(dev, bd);
+
+ dev_info(&dev->dev, "ready\n");
+
+ return 0;
+
+err_rfkill:
+ rfkill_destroy(bd->rfk);
+err_rfk_alloc:
+ regulator_put(bd->vdd_18);
+err_regulator_2:
+ regulator_put(bd->rtc_xi);
+err_regulator_1:
+ gpio_unexport(pdata->tsync);
+ gpio_unexport(pdata->onoff);
+err_invalid_pin2:
+ gpio_unexport(pdata->nrst);
+err_invalid_pin1:
+ kfree(bd);
+ return ret;
+}
+
+static int __devexit gsd4t_remove(struct platform_device *dev)
+{
+ struct gsd4t_data *bd = platform_get_drvdata(dev);
+ struct gsd4t_platform_data *pdata = bd->pdata;
+
+ if (bd->in_use)
+ rfkill_init_sw_state(bd->rfk, true);
+
+ rfkill_unregister(bd->rfk);
+ rfkill_destroy(bd->rfk);
+ gpio_unexport(pdata->onoff);
+ gpio_unexport(pdata->nrst);
+ gpio_unexport(pdata->tsync);
+ gpio_free(pdata->onoff);
+ gpio_free(pdata->nrst);
+
+ regulator_put(bd->vdd_18);
+ regulator_put(bd->rtc_xi);
+
+ kfree(bd);
+ platform_set_drvdata(dev, NULL);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int gsd4t_suspend(struct platform_device *dev, pm_message_t stata)
+{
+ struct gsd4t_data *bd = platform_get_drvdata(dev);
+ struct gsd4t_platform_data *pdata = bd->pdata;
+
+ if (bd->in_use)
+ s5p_gpio_set_pd_cfg(pdata->nrst, S5P_GPIO_PD_OUTPUT1);
+ else
+ s5p_gpio_set_pd_cfg(pdata->nrst, S5P_GPIO_PD_OUTPUT0);
+
+ return 0;
+}
+
+static int gsd4t_resume(struct platform_device *dev)
+{
+ return 0;
+}
+#else
+#define gsd4t_suspend NULL
+#define gsd4t_resume NULL
+#endif
+
+static struct platform_driver gsd4t_driver = {
+ .probe = gsd4t_probe,
+ .remove = __devexit_p(gsd4t_remove),
+ .suspend = gsd4t_suspend,
+ .resume = gsd4t_resume,
+ .driver = {
+ .name = "gsd4t",
+ },
+};
+
+static int __init gsd4t_init(void)
+{
+ return platform_driver_register(&gsd4t_driver);
+}
+
+static void __exit gsd4t_exit(void)
+{
+ platform_driver_unregister(&gsd4t_driver);
+}
+
+module_init(gsd4t_init);
+module_exit(gsd4t_exit);
+
+MODULE_AUTHOR("Minho Ban <mhban@samsung.com>");
+MODULE_DESCRIPTION("GSD4T GPS driver");
+MODULE_LICENSE("GPL");