blob: 1634807df9016f862df74b1c6cfb97f8abf85bf5 [file] [log] [blame]
/******************************************************************************
* Copyright (C) 2013 Broadcom Corporation
*
* @file /kernel/drivers/misc/bcm-gps.c
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation version 2.
*
* This program is distributed "as is" WITHOUT ANY WARRANTY of any
* kind, whether express or implied; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
******************************************************************************/
/*
* Broadcom GPS driver which supports Geo-fence.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/gpio.h>
#include <linux/types.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <mach/io_map.h>
#include <linux/version.h>
#include <linux/interrupt.h>
#include <asm/irq.h>
#include <linux/uaccess.h>
#include <linux/serial_core.h>
#include <linux/tty.h>
#include <linux/delay.h>
#include <linux/pm_wakeup.h>
#include <linux/spinlock.h>
#include <mach/chip_pinmux.h>
#include <mach/pinmux.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/of_device.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#ifndef BCM_GPS_GEO_FENCE_HOST_WAKE_ASSERT
#define BCM_GPS_GEO_FENCE_HOST_WAKE_ASSERT 1
#endif
#ifndef BCM_GPS_GEO_FENCE_HOST_WAKE_DEASSERT
#define BCM_GPS_GEO_FENCE_HOST_WAKE_DEASSERT \
(!(BCM_GPS_GEO_FENCE_HOST_WAKE_ASSERT))
#endif
#define BRCM_GPS_KERN_MAGIC 'G'
/* IOCTL requests */
#define GPS_CMD_GET_PORT_NAME _IO(BRCM_GPS_KERN_MAGIC, 0x1)
#define GPS_CMD_GET_BAUD_RATE _IO(BRCM_GPS_KERN_MAGIC, 0x2)
#define GPS_CMD_VREG_PWR_ON _IO(BRCM_GPS_KERN_MAGIC, 0x3)
#define GPS_CMD_VREG_PWR_OFF _IO(BRCM_GPS_KERN_MAGIC, 0x4)
#define GPS_CMD_GEOFENCE_ON _IO(BRCM_GPS_KERN_MAGIC, 0x5)
#define GPS_CMD_GEOFENCE_OFF _IO(BRCM_GPS_KERN_MAGIC, 0x6)
#define GPS_PORT_NAME_SIZE 128
#define GPS_VREG_ON 1
#define GPS_VREG_OFF 0
/* #define pr_debug pr_err */
struct bcm_gps_struct {
struct miscdevice misc;
spinlock_t bcm_gps_lock;
int host_irq;
};
struct bcm_gps_platform_data {
int host_wake_gpio;
long int baudrate;
char port_name[GPS_PORT_NAME_SIZE];
char gpio_n_stdby_path[GPS_PORT_NAME_SIZE];
int gpio_regpu;
};
struct bcm_gps_entry_struct {
struct bcm_gps_platform_data *pdata;
struct bcm_gps_struct *pgps;
struct platform_device *pdev;
struct wakeup_source *host_wake_ws;
int gps_geofence_started;
};
struct bcm_gps_entry_struct *gps_g;
static struct pin_config uartb3_config[2] = {
{
.name = PN_UBRTSN,
.func = PF_GPIO47,
.reg.b = {
.drv_sth = 3,
.input_dis = 0,
.slew_rate_ctrl = 0,
.pull_up = 0,
.pull_dn = 0,
.hys_en = 0,
},
},
{
.name = PN_UBRTSN,
.func = PF_UB3RTSN,
.reg.b = {
.drv_sth = 3,
.input_dis = 0,
.slew_rate_ctrl = 0,
.pull_up = 1,
.pull_dn = 0,
.hys_en = 0,
},
},
};
/* Structure to exchange data between driver and the user */
struct command_par {
unsigned long param1; /**< extra parameter for the target */
int error; /**< return value. This can be enum later */
void *buf; /**< buffer */
int size; /* buffer size */
unsigned long retval; /**< actual return value */
unsigned long retval2; /**< actual return value (2) */
/* If we see the data is more, we can go with a pointer which need
* to be allocated by the user and filled up the kernel.
*/
};
static irqreturn_t bcm_gps_host_wake_isr(int irq, void *dev)
{
unsigned int host_wake;
unsigned long flags;
struct bcm_gps_entry_struct *priv;
priv = (struct bcm_gps_entry_struct *)dev;
if (priv == NULL) {
pr_err(
"%s GPS: Error data pointer is null\n",
__func__);
return IRQ_HANDLED;
}
spin_lock_irqsave(&priv->pgps->bcm_gps_lock, flags);
host_wake = gpio_get_value(
priv->pdata->host_wake_gpio);
if (BCM_GPS_GEO_FENCE_HOST_WAKE_ASSERT == host_wake) {
__pm_stay_awake(priv->host_wake_ws);
pinmux_set_pin_config(&uartb3_config[1]);
} else {
__pm_relax(priv->host_wake_ws);
}
spin_unlock_irqrestore(&priv->pgps->bcm_gps_lock, flags);
pr_debug("%s GPS:Exiting.\n", __func__);
return IRQ_HANDLED;
}
/*static int bcm_gps_init_hostwake(struct bcm_gps_entry_struct *priv)
{
int rc = -1;
pr_debug("%s GPS:Entering.\n", __func__);
if ((priv == NULL) ||
(priv->pdata->host_wake_gpio < 0)) {
pr_err("%s GPS: invalid host-wake-gpio.\n",
__func__);
return -EFAULT;
}
rc = devm_gpio_request_one(&priv->pdev->dev,
priv->pdata->host_wake_gpio,
GPIOF_OUT_INIT_LOW,
"host_wake_gpio");
if (rc) {
pr_err
("%s: failed to configure host-wake-gpio err=%d\n",
__func__, rc);
return rc;
}
gpio_direction_input(priv->pdata->host_wake_gpio);
pr_debug("%s GPS:Exiting.\n", __func__);
return rc;
}
*/
static void bcm_gps_clean_host_wake(
struct bcm_gps_entry_struct *priv)
{
pr_debug("%s GPS:Entering.\n", __func__);
if ((priv == NULL) ||
(priv->pdata->host_wake_gpio < 0)) {
pr_err("%s GPS: NULL ptr or invalid hw gpio.\n",
__func__);
return;
}
free_irq(priv->pgps->host_irq, gps_g);
pr_debug("%s GPS:Exiting.\n", __func__);
}
void bcm_gps_start(void)
{
int rc = -1;
pr_debug("%s GPS: Entering.\n", __func__);
if (gps_g != NULL && gps_g->gps_geofence_started) {
pr_debug("%s GPS: geofence already started.\n", __func__);
return;
}
if (gps_g != NULL) {
pr_debug(
"%s: GPS: data pointer is valid\n", __func__);
rc = gpio_to_irq(
gps_g->pdata->host_wake_gpio);
if (rc < 0) {
pr_err
("%s: failed to configure GPS Host Mgmt err=%d\n",
__func__, rc);
return;
}
gps_g->pgps->host_irq = rc;
pr_debug("%s: GPS: request host_wake_irq=%d\n",
__func__, gps_g->pgps->host_irq);
rc = request_irq(
gps_g->pgps->host_irq,
bcm_gps_host_wake_isr,
(IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING |
IRQF_NO_SUSPEND), "gps_host_wake", gps_g);
if (rc) {
pr_err("%s: request irq failed: err=%d\n",
__func__, rc);
}
rc = irq_set_irq_wake(gps_g->pgps->host_irq, 1);
if (rc) {
pr_err("%s: irq_set_irq_wake failed: err=%d\n",
__func__, rc);
}
gps_g->gps_geofence_started = 1;
} else {
pr_err("%s: GPS:data ptr null, Uninitialized\n",
__func__);
return;
}
pr_debug("%s GPS:Exiting.\n", __func__);
return;
}
static long bcm_gps_ker_unlocked_ioctl(struct file *filp,
unsigned int cmd,
unsigned long arg)
{
int ret = 0;
int size = 0;
void __user *argp = (void __user *)arg;
struct command_par *user_ptr = (struct command_par *) argp;
pr_debug("ioctl in gps : 0x%x\n", (unsigned int)user_ptr);
switch (cmd) {
case GPS_CMD_GET_PORT_NAME:
if (copy_to_user((char *)user_ptr->buf,
&gps_g->pdata->port_name,
strlen(gps_g->pdata->port_name))) {
pr_err("GPS:%s copy_to_user failed for Port name\n",
__func__);
ret = -1;
}
size = strlen(gps_g->pdata->port_name);
if (copy_to_user((int *)&user_ptr->size, &size, sizeof(int))) {
pr_err("GPS:%s copy_to_user failed for Port name\n",
__func__);
ret = -1;
}
pr_err("%s: port_name = %s", __func__, gps_g->pdata->port_name);
break;
case GPS_CMD_GET_BAUD_RATE:
pr_err("%s: baudrate =%ld", __func__, gps_g->pdata->baudrate);
if (copy_to_user((unsigned long *)&user_ptr->retval,
&gps_g->pdata->baudrate,
sizeof(unsigned long))) {
pr_err("GPS:%s copy_to_user failed for baudrate\n",
__func__);
ret = -1;
}
break;
case GPS_CMD_VREG_PWR_ON:
gpio_set_value(gps_g->pdata->gpio_regpu, GPS_VREG_ON);
break;
case GPS_CMD_VREG_PWR_OFF:
gpio_set_value(gps_g->pdata->gpio_regpu, GPS_VREG_OFF);
break;
case GPS_CMD_GEOFENCE_ON:
bcm_gps_start();
break;
case GPS_CMD_GEOFENCE_OFF:
/*bcm_gps_stop();*/
break;
default:
pr_debug("%s: GPS: default ioctl\n", __func__);
break;
}
return ret;
}
static int my_open(struct inode *i, struct file *f)
{
return 0;
}
static int my_close(struct inode *i, struct file *f)
{
return 0;
}
static const struct of_device_id bcm_gps_of_match[] = {
{ .compatible = "bcm,bcm-gps",},
{ /* Sentinel */ },
};
MODULE_DEVICE_TABLE(of, bcm_gps_of_match);
static const struct file_operations bcm_gps_fops = {
.owner = THIS_MODULE,
.open = my_open,
.release = my_close,
.unlocked_ioctl = bcm_gps_ker_unlocked_ioctl,
};
static int bcm_gps_probe(struct platform_device *pdev)
{
u32 val;
int rc = -EINVAL;
const struct of_device_id *match = NULL;
struct bcm_gps_platform_data *pdata = NULL;
const char *prop;
pr_err("%s: Enter gps_probe\n", __func__);
match = of_match_device(bcm_gps_of_match, &pdev->dev);
if (!match)
pr_err("%s: **ERROR** No matcing device found\n", __func__);
if (!gps_g) {
gps_g = devm_kzalloc(&pdev->dev,
sizeof(*gps_g),
GFP_KERNEL);
if (gps_g == NULL) {
pr_err("%s: gps_g =NULL", __func__);
return -ENOMEM;
}
gps_g->pdata = devm_kzalloc(&pdev->dev,
sizeof(
struct bcm_gps_platform_data
),
GFP_KERNEL);
gps_g->pgps = devm_kzalloc(&pdev->dev,
sizeof(struct bcm_gps_struct),
GFP_KERNEL);
if (gps_g->pgps == NULL)
return -ENOMEM;
gps_g->host_wake_ws = devm_kzalloc(&pdev->dev,
sizeof(struct wakeup_source),
GFP_KERNEL);
if (gps_g->host_wake_ws == NULL)
return -ENOMEM;
spin_lock_init(&gps_g->pgps->bcm_gps_lock);
gps_g->gps_geofence_started = 0;
gps_g->pdev = pdev;
}
if (!match && pdev->dev.platform_data) {
pdata = pdev->dev.platform_data;
strncpy(gps_g->pdata->port_name, pdata->port_name,
strlen(pdata->port_name));
pr_err("%s: port_name=%s", __func__, pdata->port_name);
gps_g->pdata->baudrate = pdata->baudrate;
gps_g->pdata->gpio_regpu = pdata->gpio_regpu;
gps_g->pdata->host_wake_gpio = pdata->host_wake_gpio;
pr_err("%s: platform_data YES", __func__);
} else if (pdev->dev.of_node) {
pr_err("%s: device node", __func__);
gps_g->pdata->host_wake_gpio =
of_get_named_gpio(pdev->dev.of_node,
"bcm-gps-hostwake", 0);
if (!gpio_is_valid(gps_g->pdata->host_wake_gpio)) {
pr_err("%s: invalid host_wake_gpio ", __func__);
return -ENODEV;
}
gps_g->pdata->gpio_regpu =
of_get_named_gpio(pdev->dev.of_node,
"bcm-gps-regpu-gpio", 0);
if (!gpio_is_valid(gps_g->pdata->gpio_regpu)) {
pr_err("%s: invalid regpu gpio", __func__);
return -ENODEV;
}
gps_g->pdev = pdev;
} else {
pr_err("%s: **ERROR** NO platform data available\n",
__func__);
rc = -ENODEV;
goto out;
}
/*pr_info("%s: 2 - host_wake_gpio=%d\n",
__func__,
gps_g->pdata->host_wake_gpio);
pr_err("%s: Ignore hostwake: no geofence for now", __func__);
bcm_gps_init_hostwake(gps_g);*/
gps_g->host_wake_ws = wakeup_source_register("bcm-gps-hostwake-ws");
if (gps_g->host_wake_ws == NULL) {
pr_err(
"%s: failed to register host-wake wakeup source\n",
__func__);
return -ENODEV;
}
bcm_gps_start();
gps_g->pgps->misc.minor = MISC_DYNAMIC_MINOR;
gps_g->pgps->misc.name = "bcm-gps";
gps_g->pgps->misc.fops = &bcm_gps_fops;
pr_err("%s, now register gps driver", __func__);
rc = misc_register(&gps_g->pgps->misc);
if (rc) {
pr_err("gps misc_register failed\n");
goto error_exit;
}
pr_debug("%s GPS:Exiting.\n", __func__);
return rc;
error_exit:
kfree(gps_g->pgps);
kfree(gps_g->pdata);
kfree(gps_g);
out:
pr_debug("%s GPS:Exiting after cleaning.\n",
__func__);
return rc;
}
static int bcm_gps_remove(struct platform_device *pdev)
{
if (gps_g == NULL)
return 0;
if (gps_g->pdata) {
bcm_gps_clean_host_wake(gps_g);
wakeup_source_unregister(gps_g->host_wake_ws);
}
if (gps_g != NULL) {
if ((pdev->dev.of_node != NULL) &&
(gps_g->pdata != NULL)) {
kfree(gps_g->pdata);
kfree(gps_g->pgps);
kfree(gps_g->host_wake_ws);
}
kfree(gps_g);
gps_g = NULL;
}
pinmux_set_pin_config(&uartb3_config[0]);
pr_debug("%s GPS:Exiting.\n", __func__);
return 0;
}
static struct platform_driver bcm_gps_platform_driver = {
.probe = bcm_gps_probe,
.remove = bcm_gps_remove,
.driver = {
.name = "bcm-gps",
.owner = THIS_MODULE,
.of_match_table = bcm_gps_of_match,
},
};
static int __init bcm_gps_init(void)
{
int rc = -1;
pr_debug("%s GPS: Entering.\n", __func__);
rc = platform_driver_register(&bcm_gps_platform_driver);
if (rc)
pr_err(
"GPS: driver register failed err=%d, Exiting %s.\n",
rc, __func__);
else
pr_debug(
"GPS: Init success. Exiting %s.\n", __func__);
return rc;
}
static void __exit bcm_gps_exit(void)
{
platform_driver_unregister(&bcm_gps_platform_driver);
pr_debug("%s GPS:Exiting.\n", __func__);
}
module_init(bcm_gps_init);
module_exit(bcm_gps_exit);
MODULE_DESCRIPTION("bcm-gps");
MODULE_AUTHOR("Broadcom");
MODULE_LICENSE("GPL");