206 lines
4.2 KiB
C
206 lines
4.2 KiB
C
/* Copyright (c) 2008-2009, 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.
|
|
*
|
|
*/
|
|
/*
|
|
* SMD NMEA Driver -- Provides GPS NMEA device to SMD port interface.
|
|
*
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/cdev.h>
|
|
#include <linux/device.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/uaccess.h>
|
|
|
|
#include <mach/msm_smd.h>
|
|
|
|
#define MAX_BUF_SIZE 200
|
|
|
|
static DEFINE_MUTEX(nmea_ch_lock);
|
|
static DEFINE_MUTEX(nmea_rx_buf_lock);
|
|
|
|
static DECLARE_WAIT_QUEUE_HEAD(nmea_wait_queue);
|
|
|
|
struct nmea_device_t {
|
|
struct miscdevice misc;
|
|
|
|
struct smd_channel *ch;
|
|
|
|
unsigned char rx_buf[MAX_BUF_SIZE];
|
|
unsigned int bytes_read;
|
|
};
|
|
|
|
struct nmea_device_t *nmea_devp;
|
|
|
|
static void nmea_work_func(struct work_struct *ws)
|
|
{
|
|
int sz;
|
|
|
|
for (;;) {
|
|
sz = smd_cur_packet_size(nmea_devp->ch);
|
|
if (sz == 0)
|
|
break;
|
|
if (sz > smd_read_avail(nmea_devp->ch))
|
|
break;
|
|
if (sz > MAX_BUF_SIZE) {
|
|
smd_read(nmea_devp->ch, 0, sz);
|
|
continue;
|
|
}
|
|
|
|
mutex_lock(&nmea_rx_buf_lock);
|
|
if (smd_read(nmea_devp->ch, nmea_devp->rx_buf, sz) != sz) {
|
|
mutex_unlock(&nmea_rx_buf_lock);
|
|
printk(KERN_ERR "nmea: not enough data?!\n");
|
|
continue;
|
|
}
|
|
nmea_devp->bytes_read = sz;
|
|
mutex_unlock(&nmea_rx_buf_lock);
|
|
wake_up_interruptible(&nmea_wait_queue);
|
|
}
|
|
}
|
|
|
|
struct workqueue_struct *nmea_wq;
|
|
static DECLARE_WORK(nmea_work, nmea_work_func);
|
|
|
|
static void nmea_notify(void *priv, unsigned event)
|
|
{
|
|
switch (event) {
|
|
case SMD_EVENT_DATA: {
|
|
int sz;
|
|
sz = smd_cur_packet_size(nmea_devp->ch);
|
|
if ((sz > 0) && (sz <= smd_read_avail(nmea_devp->ch)))
|
|
queue_work(nmea_wq, &nmea_work);
|
|
break;
|
|
}
|
|
case SMD_EVENT_OPEN:
|
|
printk(KERN_INFO "nmea: smd opened\n");
|
|
break;
|
|
case SMD_EVENT_CLOSE:
|
|
printk(KERN_INFO "nmea: smd closed\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
static ssize_t nmea_read(struct file *fp, char __user *buf,
|
|
size_t count, loff_t *pos)
|
|
{
|
|
int r;
|
|
int bytes_read;
|
|
|
|
r = wait_event_interruptible(nmea_wait_queue,
|
|
nmea_devp->bytes_read);
|
|
if (r < 0) {
|
|
/* qualify error message */
|
|
if (r != -ERESTARTSYS) {
|
|
/* we get this anytime a signal comes in */
|
|
printk(KERN_ERR "ERROR:%s:%i:%s: "
|
|
"wait_event_interruptible ret %i\n",
|
|
__FILE__,
|
|
__LINE__,
|
|
__func__,
|
|
r
|
|
);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
mutex_lock(&nmea_rx_buf_lock);
|
|
bytes_read = nmea_devp->bytes_read;
|
|
nmea_devp->bytes_read = 0;
|
|
r = copy_to_user(buf, nmea_devp->rx_buf, bytes_read);
|
|
mutex_unlock(&nmea_rx_buf_lock);
|
|
|
|
if (r > 0) {
|
|
printk(KERN_ERR "ERROR:%s:%i:%s: "
|
|
"copy_to_user could not copy %i bytes.\n",
|
|
__FILE__,
|
|
__LINE__,
|
|
__func__,
|
|
r);
|
|
return r;
|
|
}
|
|
|
|
return bytes_read;
|
|
}
|
|
|
|
static int nmea_open(struct inode *ip, struct file *fp)
|
|
{
|
|
int r = 0;
|
|
|
|
mutex_lock(&nmea_ch_lock);
|
|
if (nmea_devp->ch == 0)
|
|
r = smd_open("GPSNMEA", &nmea_devp->ch, nmea_devp, nmea_notify);
|
|
mutex_unlock(&nmea_ch_lock);
|
|
|
|
return r;
|
|
}
|
|
|
|
static int nmea_release(struct inode *ip, struct file *fp)
|
|
{
|
|
int r = 0;
|
|
|
|
mutex_lock(&nmea_ch_lock);
|
|
if (nmea_devp->ch != 0) {
|
|
r = smd_close(nmea_devp->ch);
|
|
nmea_devp->ch = 0;
|
|
}
|
|
mutex_unlock(&nmea_ch_lock);
|
|
|
|
return r;
|
|
}
|
|
|
|
static const struct file_operations nmea_fops = {
|
|
.owner = THIS_MODULE,
|
|
.read = nmea_read,
|
|
.open = nmea_open,
|
|
.release = nmea_release,
|
|
};
|
|
|
|
static struct nmea_device_t nmea_device = {
|
|
.misc = {
|
|
.minor = MISC_DYNAMIC_MINOR,
|
|
.name = "nmea",
|
|
.fops = &nmea_fops,
|
|
}
|
|
};
|
|
|
|
static void __exit nmea_exit(void)
|
|
{
|
|
destroy_workqueue(nmea_wq);
|
|
misc_deregister(&nmea_device.misc);
|
|
}
|
|
|
|
static int __init nmea_init(void)
|
|
{
|
|
int ret;
|
|
|
|
nmea_device.bytes_read = 0;
|
|
nmea_devp = &nmea_device;
|
|
|
|
nmea_wq = create_singlethread_workqueue("nmea");
|
|
if (nmea_wq == 0)
|
|
return -ENOMEM;
|
|
|
|
ret = misc_register(&nmea_device.misc);
|
|
return ret;
|
|
}
|
|
|
|
module_init(nmea_init);
|
|
module_exit(nmea_exit);
|
|
|
|
MODULE_DESCRIPTION("MSM Shared Memory NMEA Driver");
|
|
MODULE_LICENSE("GPL v2");
|