text: /* 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");
  