From 73cc765dc643ceb34b1d11b5c7894f046479adb0 Mon Sep 17 00:00:00 2001 From: Hoegeun Kwon Date: Thu, 18 Aug 2016 17:46:19 +0900 Subject: sensors: brcm: bbdpl2: add new version (v2) of the BCM4773 sensorhub Commit, "sensors: add support for the BCM 4773 sensorhub" has added support for the BCM4773. Since then the firmware interface has been updated to a new version: version 2. Add the second version of the BCM4773 sensorhub and rename the previous version configuration flags to VER1. Change-Id: Ie40d4b41fe4c3491567246392d2fee1d7c9cad48 Signed-off-by: Hoegeun Kwon Signed-off-by: Andi Shyti --- drivers/sensors/brcm/Kconfig | 26 +- drivers/sensors/brcm/Makefile | 5 +- drivers/sensors/brcm/bbdpl2/Kconfig | 28 + drivers/sensors/brcm/bbdpl2/Makefile | 4 + drivers/sensors/brcm/bbdpl2/bbd.c | 997 ++++++++++++++++++++++++++++++ drivers/sensors/brcm/bbdpl2/bbd.h | 109 ++++ drivers/sensors/brcm/bbdpl2/bbd_rpc_lh.c | 712 +++++++++++++++++++++ drivers/sensors/brcm/bbdpl2/bcm_dbg.c | 33 + drivers/sensors/brcm/bbdpl2/bcm_gps_i2c.c | 741 ++++++++++++++++++++++ drivers/sensors/brcm/bbdpl2/bcm_gps_i2c.h | 22 + drivers/sensors/brcm/bbdpl2/bcm_gps_spi.c | 968 +++++++++++++++++++++++++++++ 11 files changed, 3641 insertions(+), 4 deletions(-) create mode 100644 drivers/sensors/brcm/bbdpl2/Kconfig create mode 100644 drivers/sensors/brcm/bbdpl2/Makefile create mode 100644 drivers/sensors/brcm/bbdpl2/bbd.c create mode 100644 drivers/sensors/brcm/bbdpl2/bbd.h create mode 100644 drivers/sensors/brcm/bbdpl2/bbd_rpc_lh.c create mode 100644 drivers/sensors/brcm/bbdpl2/bcm_dbg.c create mode 100644 drivers/sensors/brcm/bbdpl2/bcm_gps_i2c.c create mode 100644 drivers/sensors/brcm/bbdpl2/bcm_gps_i2c.h create mode 100644 drivers/sensors/brcm/bbdpl2/bcm_gps_spi.c (limited to 'drivers/sensors/brcm') diff --git a/drivers/sensors/brcm/Kconfig b/drivers/sensors/brcm/Kconfig index c9829e298828..2e1cc096fdd1 100644 --- a/drivers/sensors/brcm/Kconfig +++ b/drivers/sensors/brcm/Kconfig @@ -14,8 +14,30 @@ config SENSORS_SSP_BBD choice depends on SENSORS_SSP_BBD - prompt "Choose target patch" - default SENSORHUB_TM2 + prompt "Choose BBDPL Version" + default SENSORS_SSP_BBD_VER2 + +config SENSORS_SSP_BBD_VER1 + bool "bbdpl1" + depends on SPI + help + Sensorhub bbdpl version 1 for TM2(e). + +config SENSORS_SSP_BBD_VER2 + bool "bbdpl2" + depends on SPI + help + Sensorhub bbdpl version 2 for TM2(e). +endchoice + +if SENSORS_SSP_BBD_VER2 +source "drivers/sensors/brcm/bbdpl2/Kconfig" +endif + +choice + depends on SENSORS_SSP_BBD + prompt "Choose target patch" + default SENSORHUB_TM2 config SENSORHUB_S333 bool "BROADCOM_SENSORHUB_S333" diff --git a/drivers/sensors/brcm/Makefile b/drivers/sensors/brcm/Makefile index 71843a1eebe9..b965e2119334 100644 --- a/drivers/sensors/brcm/Makefile +++ b/drivers/sensors/brcm/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_SENSORS_SSP_TMG399X) += factory/light_tmg399x.o factory/prox_tmg399 obj-$(CONFIG_SENSORS_SSP_BMP182) += factory/pressure_bmp182.o obj-$(CONFIG_SENSORS_SSP_MOBEAM) += factory/barcode_emul_tmg3992.o obj-$(CONFIG_SENSORS_SSP_SENSORHUB) += ssp_sensorhub.o ssp_misc.o -obj-$(CONFIG_SENSORS_SSP_BBD) += ssp_bbd.o factory/mcu_bcm4773.o \ - bbdpl1/ +obj-$(CONFIG_SENSORS_SSP_BBD) += ssp_bbd.o factory/mcu_bcm4773.o +obj-$(CONFIG_SENSORS_SSP_BBD_VER1) += bbdpl1/ +obj-$(CONFIG_SENSORS_SSP_BBD_VER2) += bbdpl2/ obj-$(CONFIG_GPS_BCM47531) += gps/sec_gps_bcm47531.o diff --git a/drivers/sensors/brcm/bbdpl2/Kconfig b/drivers/sensors/brcm/bbdpl2/Kconfig new file mode 100644 index 000000000000..5697dde7f2fd --- /dev/null +++ b/drivers/sensors/brcm/bbdpl2/Kconfig @@ -0,0 +1,28 @@ +choice + depends on SENSORS_SSP_BBD + prompt "Choose serial connection with chip" + default BCM_GPS_SPI_DRIVER + +config BCM_GPS_TTY_DRIVER + bool "TTY" + depends on TTY + help + Support for BRCM GPS UART driver. + +config BCM_GPS_I2C_DRIVER + bool "I2C" + depends on I2C + help + Support for BRCM GPS I2C driver. + +config BCM_GPS_SPI_DRIVER + bool "SPI" + depends on SPI + help + Support for BRCM GPS SPI driver. + +endchoice + + + + diff --git a/drivers/sensors/brcm/bbdpl2/Makefile b/drivers/sensors/brcm/bbdpl2/Makefile new file mode 100644 index 000000000000..feeb3f5445ad --- /dev/null +++ b/drivers/sensors/brcm/bbdpl2/Makefile @@ -0,0 +1,4 @@ +obj-$(CONFIG_BCM_GPS_TTY_DRIVER) += bcm_gps_tty.o +obj-$(CONFIG_BCM_GPS_I2C_DRIVER) += bcm_gps_i2c.o +obj-$(CONFIG_BCM_GPS_SPI_DRIVER) += bcm_gps_spi.o +obj-$(CONFIG_SENSORS_SSP_BBD) += bbd_rpc_lh.o bbd.o diff --git a/drivers/sensors/brcm/bbdpl2/bbd.c b/drivers/sensors/brcm/bbdpl2/bbd.c new file mode 100644 index 000000000000..ee98fb7ae030 --- /dev/null +++ b/drivers/sensors/brcm/bbdpl2/bbd.c @@ -0,0 +1,997 @@ +/* + * Copyright 2014 Broadcom Corporation + * + * 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 (the "GPL"). + * + * 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. + * + * A copy of the GPL is available at + * http://www.broadcom.com/licenses/GPLv2.php, or by writing to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * The BBD (Broadcom Bridge Driver) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "bbd.h" + +/* Needs because SSP is tightly coupled with SPI */ +#ifdef CONFIG_SENSORS_SSP +#include + +extern struct spi_driver *pssp_driver; +extern bool ssp_dbg; +extern bool ssp_pkt_dbg; + +static struct spi_device dummy_spi = { + .dev = { + .init_name = "dummy", + }, +}; +#endif + +#ifdef CONFIG_BCM_GPS_SPI_DRIVER +extern bool ssi_dbg; +#endif + +void bbd_log_hex(const char*, const unsigned char*, unsigned long); + +//-------------------------------------------------------------- +// +// BBD device struct +// +//-------------------------------------------------------------- +#define BBD_BUFF_SIZE (PAGE_SIZE*2) +struct bbd_cdev_priv { + const char* name; + struct cdev dev; /* char device */ + bool busy; + struct circ_buf read_buf; /* LHD reads from BBD */ + struct mutex lock; /* Lock for read_buf */ + char _read_buf[BBD_BUFF_SIZE]; /* LHD reads from BBD */ + char write_buf[BBD_BUFF_SIZE]; /* LHD writes into BBD */ + wait_queue_head_t poll_wait; /* for poll */ +}; + +struct bbd_device { + struct kobject *kobj; /* for sysfs register */ + struct class *class; /* for device_create */ + + struct bbd_cdev_priv priv[BBD_DEVICE_INDEX];/* individual structures */ + struct wake_lock bbd_wake_lock; + bool db; /* debug flag */ + + void *ssp_priv; /* private data pointer */ + bbd_callbacks *ssp_cb; /* callbacks for SSP */ + +}; + +/* + * Character device names of BBD + */ +static const char *bbd_dev_name[BBD_DEVICE_INDEX] = { + "bbd_shmd", + "bbd_sensor", + "bbd_control", + "bbd_patch", + /* "bbd_ssi_spi_debug" */ +}; + +//-------------------------------------------------------------- +// +// Globals +// +//-------------------------------------------------------------- +/* + * The global BBD device which has all necessary information. + * It's not beautiful but useful when we debug by Trace32. + */ +static struct bbd_device bbd; +/* + * Embedded patch file provided as /dev/bbd_patch + */ +static unsigned char bbd_patch[] = +{ +#if defined(CONFIG_SENSORHUB_TM2) +#include "../platform/bbd_tm2_patch_file.h" +#elif defined(CONFIG_SENSORHUB_S333) +#include "../platform/bbd_s333_patch_file.h" +#endif +}; + +/* Function to push read data into any bbd device's read buf */ +ssize_t bbd_on_read(unsigned int minor, const unsigned char *buf, size_t size); + +#ifdef DEBUG_1HZ_STAT + +static const char *bbd_stat_name[STAT_MAX] = { + "tx@lhd", + "tx@ssp", + "tx@rpc", + "tx@tl", + "tx@ssi", + "rx@ssi", + "rx@tl", + "rx@rpc", + "rx@ssp", + "rx@lhd" +}; + +struct bbd_stat stat1hz; + +//-------------------------------------------------------------- +// +// bbd 1hz statistics Functions +// +//-------------------------------------------------------------- +static void bbd_init_stat(void) +{ + memset(&stat1hz, 0, sizeof(stat1hz)); + + stat1hz.min_rx_lat = (u64)-1; + stat1hz.min_rx_dur = (u64)-1; + stat1hz.workq = create_singlethread_workqueue("BBD_1HZ_TICK"); +} + +static void bbd_exit_stat(void) +{ + bbd_disable_stat(); + if (stat1hz.workq) { + flush_workqueue(stat1hz.workq); + destroy_workqueue(stat1hz.workq); + stat1hz.workq = 0; + } +} + +static void bbd_report_stat(struct work_struct *work) +{ + char buf[512]; + char *p = buf; + int i; + + p += sprintf(p, "BBD:"); + for (i = 0; i < STAT_MAX; i++) + p += sprintf(p, " %s=%llu", bbd_stat_name[i], stat1hz.stat[i]); + p += sprintf(p, " rxlat_min=%llu rxlat_max=%llu", + stat1hz.min_rx_lat, stat1hz.max_rx_lat); + p += sprintf(p, " rxdur_min=%llu rxdur_max=%llu", + stat1hz.min_rx_dur, stat1hz.max_rx_dur); + + /* report only in case we had SSI traffic */ + if (stat1hz.stat[STAT_TX_SSI] || stat1hz.stat[STAT_RX_SSI]) + bbd_on_read(BBD_MINOR_CONTROL, buf, strlen(buf)+1); + + for (i = 0; i < STAT_MAX; i++) + stat1hz.stat[i] = 0; + + stat1hz.min_rx_lat = (u64)-1; + stat1hz.min_rx_dur = (u64)-1; + stat1hz.max_rx_lat = 0; + stat1hz.max_rx_dur = 0; +} + +static void bbd_stat_timer_func(unsigned long p) +{ + if (stat1hz.workq) + queue_work(stat1hz.workq, &stat1hz.work); + mod_timer(&stat1hz.timer, jiffies + HZ); +} + +void bbd_update_stat(int idx, unsigned int count) +{ + stat1hz.stat[idx] += count; +} + +void bbd_enable_stat(void) +{ + if (stat1hz.enabled) { + printk("%s() 1HZ stat already enable. skipping.\n", __func__); + return; + } + + INIT_WORK(&stat1hz.work, bbd_report_stat); + setup_timer(&stat1hz.timer, bbd_stat_timer_func, 0); + mod_timer(&stat1hz.timer, jiffies + HZ); + stat1hz.enabled = true; +} + +void bbd_disable_stat(void) +{ + if (!stat1hz.enabled) { + printk("%s() 1HZ stat already disabled. skipping.\n", __func__); + return; + } + del_timer_sync(&stat1hz.timer); + cancel_work_sync(&stat1hz.work); + stat1hz.enabled = false; +} +#endif /* DEBUG_1HZ_STAT */ +//-------------------------------------------------------------- +// +// SHMD Interface Functions +// +//-------------------------------------------------------------- + +/** + * bbd_register - Interface function called from SHMD to register itself to BBD + * + * @priv: SHMD's private data provided back to SHMD as callback argument + * @cb: SHMD's functions to be called + */ +void bbd_register(void *priv, bbd_callbacks *cb) +{ + bbd.ssp_priv = priv; + bbd.ssp_cb = cb; +} +EXPORT_SYMBOL(bbd_register); + +/** + * bbd_send_packet - Interface function called from SHMD to send sensor packet. + * + * The sensor packet is pushed into /dev/bbd_sensor to be read by gpsd/lhd. + * gpsd/lhd wrap the packet into RPC and sends to chip directly. + * + * @buf: buffer containing sensor packet + * @size: size of sensor packet + * @return: pushed data length = success + */ +struct sensor_pkt { + unsigned short size; + unsigned char buf[1022]; /*We assume max SSP packet less than 1KB */ +}__attribute__((__packed__)) ss_pkt; + +ssize_t bbd_send_packet(unsigned char *buf, size_t size) +{ + memset(&ss_pkt, 0, sizeof(ss_pkt)); + ss_pkt.size = (unsigned short)size; + memcpy(ss_pkt.buf, buf, size); + +#ifdef DEBUG_1HZ_STAT + bbd_update_stat(STAT_TX_SSP, size); +#endif + return bbd_on_read(BBD_MINOR_SENSOR, (unsigned char*)&ss_pkt, size+2); /* +2 for pkt.size */ +} +EXPORT_SYMBOL(bbd_send_packet); + + +/** + * bbd_pull_packet - Interface function called from SHMD to read sensor packet. + * + * Read packet consists of sensor packet from gpsd/lhd and from BBD. + * + * @buf: buffer to receive packet + * @size: + * @timeout_ms: if specified, this function waits for sensor packet during given time + * + * @return: popped data length = success + */ +ssize_t bbd_pull_packet(unsigned char *buf, size_t size, unsigned int timeout_ms) +{ + struct circ_buf *circ = &bbd.priv[BBD_MINOR_SHMD].read_buf; + size_t rd_size = 0; + + WARN_ON(!buf); + WARN_ON(!size); + + if (timeout_ms) { + int ret = wait_event_interruptible_timeout( + bbd.priv[BBD_MINOR_SHMD].poll_wait, + circ->head != circ->tail, + msecs_to_jiffies(timeout_ms)); + if (!ret) + return -ETIMEDOUT; + } + + mutex_lock(&bbd.priv[BBD_MINOR_SHMD].lock); + + /* Copy from circ buffer to linear buffer + * Because SHMD's buffer is linear, we may require 2 copies + * from [tail..end] and [start..head] + */ + do { + size_t cnt_to_end = CIRC_CNT_TO_END(circ->head, circ->tail, BBD_BUFF_SIZE); + size_t copied = min(cnt_to_end, size); + + memcpy(buf + rd_size, (void*) circ->buf + circ->tail, copied); + size -= copied; + rd_size += copied; + circ->tail = (circ->tail + copied) & (BBD_BUFF_SIZE -1); + + } while (size>0 && CIRC_CNT(circ->head, circ->tail, BBD_BUFF_SIZE)); + + mutex_unlock(&bbd.priv[BBD_MINOR_SHMD].lock); + + return rd_size; +} +EXPORT_SYMBOL(bbd_pull_packet); + +/** + * bbd_mcu_reset - Interface function called from SHMD to reset chip + * + * BBD pushes reset request into /dev/bbd_control and actual reset is + * done by gpsd/lhd when it reads the request + * + * @return: 0 = success, -1 = failure + */ +int bbd_mcu_reset(void) +{ + pr_info("reset request from sensor hub\n"); + return bbd_on_read(BBD_MINOR_CONTROL, BBD_CTRL_RESET_REQ, + strlen(BBD_CTRL_RESET_REQ)+1); +} +EXPORT_SYMBOL(bbd_mcu_reset); + + + +//-------------------------------------------------------------- +// +// BBD device struct +// +//-------------------------------------------------------------- + +/** + * bbd_control - Handles command string from lhd + * + * + */ +ssize_t bbd_control(const char *buf, ssize_t len) +{ + if (strstr(buf, ESW_CTRL_READY)) { + + if (bbd.ssp_cb && bbd.ssp_cb->on_mcu_ready) + bbd.ssp_cb->on_mcu_ready(bbd.ssp_priv, true); + + } else if (strstr(buf, ESW_CTRL_NOTREADY)) { + struct circ_buf *circ = &bbd.priv[BBD_MINOR_SENSOR].read_buf; + circ->head = circ->tail = 0; + if (bbd.ssp_cb && bbd.ssp_cb->on_mcu_ready) + bbd.ssp_cb->on_mcu_ready(bbd.ssp_priv, false); + } else if (strstr(buf, ESW_CTRL_CRASHED)) { + struct circ_buf *circ = &bbd.priv[BBD_MINOR_SENSOR].read_buf; + circ->head = circ->tail = 0; + + if (bbd.ssp_cb && bbd.ssp_cb->on_mcu_ready) + bbd.ssp_cb->on_mcu_ready(bbd.ssp_priv, false); + + if (bbd.ssp_cb && bbd.ssp_cb->on_control) + bbd.ssp_cb->on_control(bbd.ssp_priv, buf); + } else if (strstr(buf, BBD_CTRL_DEBUG_OFF)) { + bbd.db = false; +#ifdef CONFIG_SENSORS_SSP + } else if (strstr(buf, SSP_DEBUG_ON)) { + ssp_dbg = true; + ssp_pkt_dbg = true; + } else if (strstr(buf, SSP_DEBUG_OFF)) { + ssp_dbg = false; + ssp_pkt_dbg = false; +#endif +#ifdef CONFIG_BCM_GPS_SPI_DRIVER + } else if (strstr(buf, SSI_DEBUG_ON)) { + ssi_dbg = true; + } else if (strstr(buf, SSI_DEBUG_OFF)) { + ssi_dbg = false; +#endif + } else if (bbd.ssp_cb && bbd.ssp_cb->on_control) { + /* Tell SHMD about the unknown control string */ + bbd.ssp_cb->on_control(bbd.ssp_priv, buf); + } + + return len; +} + + + +//-------------------------------------------------------------- +// +// BBD Common File Functions +// +//-------------------------------------------------------------- + +/** + * bbd_common_open - Common open function for BBD devices + * + */ +int bbd_common_open(struct inode *inode, struct file *filp) +{ + unsigned int minor = iminor(inode); + struct circ_buf *circ = &bbd.priv[minor].read_buf; + + if (minor >= BBD_DEVICE_INDEX) + return -ENODEV; + + pr_debug("%s", bbd.priv[minor].name); + + if (bbd.priv[minor].busy && minor!=BBD_MINOR_CONTROL) + return -EBUSY; + + bbd.priv[minor].busy = true; + + /* Reset circ buffer */ + circ->head = circ->tail = 0; + + filp->private_data = &bbd; + + return 0; +} + +/** + * bbd_common_release - Common release function for BBD devices + */ +static int bbd_common_release(struct inode *inode, struct file *filp) +{ + unsigned int minor = iminor(inode); + + + BUG_ON(minor >= BBD_DEVICE_INDEX); + pr_debug("%s", bbd.priv[minor].name); + + bbd.priv[minor].busy = false; + + return 0; +} + +/** + * bbd_common_read - Common read function for BBD devices + * + * lhd reads from BBD devices via this function + * + */ +static ssize_t bbd_common_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos) +{ + unsigned int minor = iminor(filp->f_path.dentry->d_inode); + struct circ_buf *circ = &bbd.priv[minor].read_buf; + size_t rd_size=0; + + BUG_ON(minor >= BBD_DEVICE_INDEX); + + mutex_lock(&bbd.priv[minor].lock); + + /* Copy from circ buffer to lhd + * Because lhd's buffer is linear, we may require 2 copies + * from [tail..end] and [end..head] + */ + do { + size_t cnt_to_end = CIRC_CNT_TO_END(circ->head, circ->tail, BBD_BUFF_SIZE); + size_t copied = min(cnt_to_end, size); + + WARN_ON(copy_to_user(buf + rd_size, (void*) circ->buf + circ->tail, copied)); + size -= copied; + rd_size += copied; + circ->tail = (circ->tail + copied) & (BBD_BUFF_SIZE -1); + + } while (size>0 && CIRC_CNT(circ->head, circ->tail, BBD_BUFF_SIZE)); + + mutex_unlock(&bbd.priv[minor].lock); + + bbd_log_hex(bbd_dev_name[minor], buf, rd_size); + +#ifdef DEBUG_1HZ_STAT + bbd_update_stat(STAT_RX_LHD, rd_size); +#endif + return rd_size; +} + +/** + * bbd_common_write - Common write function for BBD devices * + * lhd writes to BBD devices via this function + */ +static ssize_t bbd_common_write(struct file *filp, const char __user *buf, + size_t size, loff_t *ppos) +{ + unsigned int minor = iminor(filp->f_path.dentry->d_inode); + + BUG_ON(size >= BBD_BUFF_SIZE); + + WARN_ON(copy_from_user(bbd.priv[minor].write_buf, buf, size)); + +#ifdef DEBUG_1HZ_STAT + bbd_update_stat(STAT_TX_LHD, size); +#endif + return size; +} + +/** + * bbd_common_poll - Common poll function for BBD devices + * + */ +static unsigned int bbd_common_poll(struct file *filp, poll_table *wait) +{ + unsigned int minor = iminor(filp->f_path.dentry->d_inode); + struct circ_buf *circ = &bbd.priv[minor].read_buf; + unsigned int mask = 0; + + BUG_ON(minor >= BBD_DEVICE_INDEX); + + poll_wait(filp, &bbd.priv[minor].poll_wait, wait); + + if (CIRC_CNT(circ->head, circ->tail, BBD_BUFF_SIZE)) + mask |= POLLIN; + + return mask; +} + +//-------------------------------------------------------------- +// +// BBD Device Specific File Functions +// +//-------------------------------------------------------------- + +/** + * bbd_sensor_write - BBD's RPC calls this function to send sensor packet + * + * @buf: contains sensor packet coming from gpsd/lhd + * + */ +ssize_t bbd_sensor_write(const char *buf, size_t size) +{ + /* Copies into /dev/bbd_shmd. If SHMD was sleeping in poll_wait, + * bbd_on_read() wakes it up also + */ + bbd_on_read(BBD_MINOR_SHMD, buf, size); + +#ifdef DEBUG_1HZ_STAT + bbd_update_stat(STAT_RX_SSP, size); +#endif + /* OK. Now call pre-registered SHMD callbacks */ + if (bbd.ssp_cb->on_packet) + bbd.ssp_cb->on_packet(bbd.ssp_priv, bbd.priv[BBD_MINOR_SHMD].write_buf, size); + else if (bbd.ssp_cb->on_packet_alarm) + bbd.ssp_cb->on_packet_alarm(bbd.ssp_priv); + else + pr_err("%s no SSP on_packet callback registered. " + "Dropped %u bytes\n", __func__, (unsigned int)size); + + return size; +} + +/** + * bbd_control_write - Write function for BBD control (/dev/bbd_control) + * + * Receives control string from lhd and handles it + * + */ +ssize_t bbd_control_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos) +{ + unsigned int minor = iminor(filp->f_path.dentry->d_inode); + + /* get command string first */ + ssize_t len = bbd_common_write(filp, buf, size, ppos); + if (len <= 0) + return len; + + /* Process received command string */ + return bbd_control(bbd.priv[minor].write_buf, len); +} + +ssize_t bbd_patch_read( struct file *filp, char __user *buf, size_t size, loff_t *ppos) +{ + ssize_t rd_size = size; + size_t offset = filp->f_pos; + + if (offset >= sizeof(bbd_patch)) { /* signal EOF */ + *ppos = 0; + return 0; + } + if (offset+size > sizeof(bbd_patch)) + rd_size = sizeof(bbd_patch) - offset; + if (copy_to_user(buf, bbd_patch + offset, rd_size)) + rd_size = -EFAULT; + else + *ppos = filp->f_pos + rd_size; + + return rd_size; +} + + +//-------------------------------------------------------------- +// +// Sysfs +// +//-------------------------------------------------------------- +static ssize_t store_sysfs_bbd_control(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) +{ + bbd_control(buf, strlen(buf)+1); + return len; +} + +ssize_t bbd_request_mcu(bool on); +static ssize_t show_sysfs_bbd_pl(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return 0; +} + +static DEVICE_ATTR(bbd, 0220, NULL, store_sysfs_bbd_control); +static DEVICE_ATTR(pl, 0440, show_sysfs_bbd_pl, NULL); + +static struct attribute *bbd_attributes[] = { + &dev_attr_bbd.attr, + &dev_attr_pl.attr, + NULL +}; + +static const struct attribute_group bbd_group = { + .attrs = bbd_attributes, +}; + + +//-------------------------------------------------------------- +// +// Misc Functions +// +//-------------------------------------------------------------- +void bbd_log_hex(const char* pIntroduction, + const unsigned char* pData, + unsigned long ulDataLen) +{ + const unsigned char* pDataEnd = pData + ulDataLen; + + if (likely(!bbd.db)) + return; + if (!pIntroduction) pIntroduction = "...unknown..."; + + while (pData < pDataEnd) + { + char buf[128]; + size_t bufsize = sizeof(buf) - 3; + size_t lineLen = pDataEnd - pData; + size_t perLineCount = lineLen; + if (lineLen > 32) { + lineLen = 32; + perLineCount = lineLen; + } + + snprintf(buf, bufsize, "%s [%u] { ", pIntroduction, + (unsigned int)lineLen); + + for (; perLineCount > 0; ++pData, --perLineCount) + { + size_t len = strlen(buf); + snprintf(buf+len, bufsize - len, "%02X ", *pData); + } + printk(KERN_INFO"%s}\n", buf); + } +} + +/** + * + * bbd_on_read - Push data into read buffer of specified char device. + * if minor is bbd_sensor + * + * @buf: linear buffer + */ +ssize_t bbd_on_read(unsigned int minor, const unsigned char *buf, size_t size) +{ + struct circ_buf *circ = &bbd.priv[minor].read_buf; + size_t wr_size = 0; + + bbd_log_hex(bbd_dev_name[minor], buf, size); + + mutex_lock(&bbd.priv[minor].lock); + + /* If there's not enough speace, drop it but try waking up reader */ + if (CIRC_SPACE(circ->head, circ->tail, BBD_BUFF_SIZE)head, circ->tail, BBD_BUFF_SIZE); + size_t copied = min(space_to_end, size); + + memcpy((void*) circ->buf + circ->head, buf + wr_size, copied); + size -= copied; + wr_size += copied; + circ->head = (circ->head + copied) & (BBD_BUFF_SIZE -1); + + } while (size>0 && CIRC_SPACE(circ->head, circ->tail, BBD_BUFF_SIZE)); +skip: + mutex_unlock(&bbd.priv[minor].lock); + + /* Wake up reader */ + wake_up(&bbd.priv[minor].poll_wait); + + + return wr_size; +} + +ssize_t bbd_request_mcu(bool on) +{ + printk("%s(%s) called", __func__, (on)?"On":"Off"); + if (on) + return bbd_on_read(BBD_MINOR_CONTROL, GPSD_SENSOR_ON, strlen(GPSD_SENSOR_ON)+1); + else { + bbd.ssp_cb->on_mcu_ready(bbd.ssp_priv, false); + return bbd_on_read(BBD_MINOR_CONTROL, GPSD_SENSOR_OFF, strlen(GPSD_SENSOR_OFF)+1); + } +} +EXPORT_SYMBOL(bbd_request_mcu); + +//-------------------------------------------------------------- +// +// PM operation +// +//-------------------------------------------------------------- +static int bbd_suspend(pm_message_t state) +{ +#ifdef DEBUG_1HZ_STAT + bbd_disable_stat(); +#endif +#ifdef CONFIG_SENSORS_SSP + /* Call SSP suspend */ + if (pssp_driver->driver.pm && pssp_driver->driver.pm->suspend) + pssp_driver->driver.pm->suspend(&dummy_spi.dev); +#endif + mdelay(20); + + return 0; +} + +static int bbd_resume(void) +{ +#ifdef CONFIG_SENSORS_SSP + /* Call SSP resume */ + if (pssp_driver->driver.pm && pssp_driver->driver.pm->suspend) + pssp_driver->driver.pm->resume(&dummy_spi.dev); +#endif +#ifdef DEBUG_1HZ_STAT + bbd_enable_stat(); +#endif + wake_lock_timeout(&bbd.bbd_wake_lock, HZ/2); + + return 0; +} + +static int bbd_notifier(struct notifier_block *nb, unsigned long event, void * data) +{ + pm_message_t state = {0}; + switch (event) { + case PM_SUSPEND_PREPARE: + printk("%s going to sleep", __func__); + state.event = event; + bbd_suspend(state); + break; + case PM_POST_SUSPEND: + printk("%s waking up", __func__); + bbd_resume(); + break; + } + return NOTIFY_OK; +} + +static struct notifier_block bbd_notifier_block = { + .notifier_call = bbd_notifier, +}; + +//-------------------------------------------------------------- +// +// BBD Device Init and Exit +// +//-------------------------------------------------------------- + + +static const struct file_operations bbd_fops[BBD_DEVICE_INDEX] = { + /* bbd shmd file operations */ + { + .owner = THIS_MODULE, + }, + /* bbd sensor file operations */ + { + .owner = THIS_MODULE, + .open = bbd_common_open, + .release = bbd_common_release, + .read = bbd_common_read, + .write = NULL, + .poll = bbd_common_poll, + }, + /* bbd control file operations */ + { + .owner = THIS_MODULE, + .open = bbd_common_open, + .release = bbd_common_release, + .read = bbd_common_read, + .write = bbd_control_write, + .poll = bbd_common_poll, + }, + /* bbd patch file operations */ + { + .owner = THIS_MODULE, + .open = bbd_common_open, + .release = bbd_common_release, + .read = bbd_patch_read, + .write = NULL, /* /dev/bbd_patch is read-only */ + .poll = NULL, + }, +}; + + +int bbd_init(struct device* dev) +{ + int minor, ret = -ENOMEM; + struct timespec ts1; + unsigned long start, elapsed; + + ts1 = ktime_to_timespec(ktime_get_boottime()); + start = ts1.tv_sec * 1000000000ULL + ts1.tv_nsec; + + + pr_err("BBD start: %s, %d\n", __func__, __LINE__); + + /* Initialize BBD device */ + memset(&bbd, 0, sizeof(bbd)); + wake_lock_init(&bbd.bbd_wake_lock, WAKE_LOCK_SUSPEND, "bbd_wake_lock"); + + /* Create class which is required for device_create() */ + bbd.class = class_create(THIS_MODULE, "bbd"); + if (IS_ERR(bbd.class)) { + WARN("BBD:%s() failed to create class \"bbd\"", __func__); + goto exit; + } + + /* Create BBD char devices */ + for (minor=0; minorowner = THIS_MODULE; + cdev->ops = &bbd_fops[minor]; + ret = cdev_add(cdev, devno, 1); + if (ret) { + pr_err("BBD:%s()) failed to cdev_add() \"%s\", ret=%d", + __func__, name, ret); + unregister_chrdev_region(devno, 1); + goto free_class; + } + + /* Let it show in FS */ + dev = device_create(bbd.class, NULL, devno, NULL, "%s", name); + if (IS_ERR_OR_NULL(dev)) { + pr_err("BBD:%s() failed to device_create() " + "\"%s\", ret=%d", __func__, name, ret); + unregister_chrdev_region(devno, 1); + cdev_del(&bbd.priv[minor].dev); + goto free_class; + } + + /* Done. Put success log and init BBD specific fields */ + pr_debug("BBD:%s(%d,%d) registered /dev/%s\n", + __func__, BBD_DEVICE_MAJOR,minor,name); + + } + + /* Register sysfs entry */ + bbd.kobj = kobject_create_and_add("bbd", NULL); + BUG_ON(!bbd.kobj); + ret = sysfs_create_group(bbd.kobj, &bbd_group); + if (ret < 0) { + pr_err("%s failed to sysfs_create_group \"bbd\", ret = %d", + __func__, ret); + goto free_kobj; + } + + + /* Register PM */ + ret = register_pm_notifier(&bbd_notifier_block); + BUG_ON(ret); + +#ifdef CONFIG_SENSORS_SSP + /* Now, we can initialize SSP */ + BUG_ON(device_register(&dummy_spi.dev)); + { + struct spi_device *spi = to_spi_device(dev); + void *org_priv, *new_priv; + + org_priv = spi_get_drvdata(spi); + pssp_driver->probe(spi); + new_priv = spi_get_drvdata(spi); + spi_set_drvdata(spi, org_priv); + spi_set_drvdata(&dummy_spi, new_priv); + + } +#endif + ts1 = ktime_to_timespec(ktime_get_boottime()); + elapsed = (ts1.tv_sec * 1000000000ULL + ts1.tv_nsec) - start; + pr_debug("BBD:%s %lu nsec elapsed\n", __func__, elapsed); + +#ifdef DEBUG_1HZ_STAT + bbd_init_stat(); +#endif + return 0; + +free_kobj: + kobject_put(bbd.kobj); +free_class: + while (--minor>BBD_MINOR_SHMD) { + dev_t devno = MKDEV(BBD_DEVICE_MAJOR, minor); + struct cdev *cdev = &bbd.priv[minor].dev; + + device_destroy(bbd.class, devno); + cdev_del(cdev); + unregister_chrdev_region(devno, 1); + } + class_destroy(bbd.class); +exit: + return ret; +} + +static void __exit bbd_exit(void) +{ + int minor; + +#ifdef CONFIG_SENSORS_SSP + /* Shutdown SSP first*/ + pssp_driver->shutdown(&dummy_spi); +#endif + + /* Remove sysfs entry */ + sysfs_remove_group(bbd.kobj, &bbd_group); + + /* Remove BBD char devices */ + for (minor=BBD_MINOR_SENSOR; minor +#include +#include +#include +#include +#include +#include "bbd.h" +/* function to inject sensor data into SHMD */ +ssize_t bbd_sensor_write(const char *buf, unsigned int size); +void bcm_on_packet_recieved(void *_priv, unsigned char *data, unsigned int size); + +/* + ******************* CUSTOMIZATION FILE ******************** + * This file can be edited for your project, and you can customize + * your transport layer by changing some of the default value + * Note that you can also define these value from your makefile directly + */ +/* if defined to 1, the reliable channel will be enabled. + * If defined to 0, it will be disabled and compiled out + */ +#ifndef TLCUST_ENABLE_RELIABLE_PL +#define TLCUST_ENABLE_RELIABLE_PL 1 +#endif +/* Defines the max size of outgoing packet. That should match + * the max size of incoming packet from the remote TL + */ +#ifndef TLCUST_MAX_OUTGOING_PACKET_SIZE +#define TLCUST_MAX_OUTGOING_PACKET_SIZE 2048 +#endif +/* Defines the max size of incoming packet. That should match + * the max size of outgoing packet from the remote TL + */ +#ifndef TLCUST_MAX_INCOMING_PACKET_SIZE +#define TLCUST_MAX_INCOMING_PACKET_SIZE 2048 +#endif +/* Defines the number of millisecond before retrying a reliable + * packet when no acknowledgement is received. + */ +#ifndef TLCUST_RELIABLE_RETRY_TIMEOUT_MS +#define TLCUST_RELIABLE_RETRY_TIMEOUT_MS 1000 +#endif +/* Defines the number of retries before declaring a communication error */ +#ifndef TLCUST_RELIABLE_MAX_RETRY +#define TLCUST_RELIABLE_MAX_RETRY 10 +#endif + +/* Defines the maximum number of reliable packets that can be in transit(MAX is 255) */ +#ifndef TLCUST_RELIABLE_MAX_PACKETS +#define TLCUST_RELIABLE_MAX_PACKETS 150 +#endif +/* public constants that are built from the customization file */ +#define MAX_OUTGOING_PACKET_SIZE TLCUST_MAX_OUTGOING_PACKET_SIZE +#define MAX_INCOMING_PACKET_SIZE TLCUST_MAX_INCOMING_PACKET_SIZE +#define RELIABLE_RETRY_TIMEOUT_MS TLCUST_RELIABLE_RETRY_TIMEOUT_MS +#define RELIABLE_MAX_RETRY TLCUST_RELIABLE_MAX_RETRY +#define RELIABLE_MAX_PACKETS TLCUST_RELIABLE_MAX_PACKETS +#define MAX_HEADER_SIZE 14 + +#define _DIM(x) ((unsigned int )(sizeof(x)/sizeof(*(x)))) + +/* + * The following are used for the software flow control (UART) + */ +static const unsigned char XON_CHARACTER = 0x11; +static const unsigned char XOFF_CHARACTER = 0x13; +/* + * The following are used for the protocol. + */ +static const unsigned char ESCAPE_CHARACTER = 0xB0; +static const unsigned char SOP_CHARACTER = 0x00; +static const unsigned char EOP_CHARACTER = 0x01; +static const unsigned char ESCAPED_ESCAPE_CHARACTER = 0x03; +static const unsigned char ESCAPED_XON_CHARACTER = 0x04; +static const unsigned char ESCAPED_XOFF_CHARACTER = 0x05; +/* + * The following are the bit field definition for the flags + */ +static const unsigned short FLAG_PACKET_ACK = (1<<0); /* ACK of a received packet. Flag detail contains the ACK SeqId */ +static const unsigned short FLAG_RELIABLE_PACKET = (1<<1); /* Indicates that this is a reliable packet. Flag detail contains the remote reliable seqId */ +static const unsigned short FLAG_RELIABLE_ACK = (1<<2); /* A reliable SeqId was Acked. Flag detail contains the acked reliable seqId */ +static const unsigned short FLAG_RELIABLE_NACK = (1<<3); /* A reliable SeqId error was detected. Flag detail contains the last remote reliable seqId */ + +static const unsigned short FLAG_MSG_LOST = (1<<4); /* Remote PacketLayer detected lost packets (jumps in SeqId). The flag details contains the last remote seqId */ +static const unsigned short FLAG_MSG_GARBAGE = (1<<5); /* Garbage bytes detected. The Flag details will contains the number of garbage bytes (capped to 255) */ +static const unsigned short FLAG_SIZE_EXTENDED = (1<<6); /* Size of packet will have one byte extension (MSB), contained in the Flags detail */ +static const unsigned short FLAG_EXTENDED = (1<<7); /* If set, then the flag details will contains a Byte representing the MSB of the 16bit flags */ +static const unsigned short FLAG_INTERNAL_PACKET = (1<<8); /* Packet in that message is internal, and should be processed by the TL */ +static const unsigned short FLAG_IGNORE_SEQID = (1<<9); /* Remote side requested to ignore the seqId, i.e. error should not be accounted for in the stats */ +/* Enumeration of all the RPCs for the codec. DO NOT CHANGE THE ORDER, + * DELETE, or INSERT anything to keep backward compatibility. + */ +#define RPC_DEFINITION(klass, method) RPC_##klass##_##method +enum +{ + RPC_DEFINITION(IRpcA, A) + ,RPC_DEFINITION(IRpcA, B) + ,RPC_DEFINITION(IRpcA, C) + ,RPC_DEFINITION(IRpcB, A) + ,RPC_DEFINITION(IRpcB, B) + ,RPC_DEFINITION(IRpcC, A) + ,RPC_DEFINITION(IRpcC, B) + ,RPC_DEFINITION(IRpcC, C) + ,RPC_DEFINITION(IRpcD, A) + ,RPC_DEFINITION(IRpcE, A) + ,RPC_DEFINITION(IRpcE, B) + ,RPC_DEFINITION(IRpcF, A) + ,RPC_DEFINITION(IRpcF, B) + ,RPC_DEFINITION(IRpcF, C) + ,RPC_DEFINITION(IRpcG, A) + ,RPC_DEFINITION(IRpcG, B) + ,RPC_DEFINITION(IRpcG, C) + ,RPC_DEFINITION(IRpcG, D) + ,RPC_DEFINITION(IRpcG, E) + ,RPC_DEFINITION(IRpcH, A) + ,RPC_DEFINITION(IRpcH, B) + ,RPC_DEFINITION(IRpcH, C) + ,RPC_DEFINITION(IRpcH, D) + ,RPC_DEFINITION(IRpcI, A) + ,RPC_DEFINITION(IRpcI, B) + ,RPC_DEFINITION(IRpcI, C) + ,RPC_DEFINITION(IRpcJ, A) + ,RPC_DEFINITION(IRpcJ, B) + ,RPC_DEFINITION(IRpcK, A) + ,RPC_DEFINITION(IRpcK, B) + ,RPC_DEFINITION(IRpcK, C) + ,RPC_DEFINITION(IRpcL, A) + ,RPC_DEFINITION(IRpcSensorRequest, Data) + ,RPC_DEFINITION(IRpcSensorResponse, Data) +}; +/* enumeration for the state */ +enum +{ + WAIT_FOR_ESC_SOP = 0 + ,WAIT_FOR_SOP + ,WAIT_FOR_MESSAGE_COMPLETE + ,WAIT_FOR_EOP +}; + +typedef struct stTransportLayerStats +{ + unsigned int ulRxGarbageBytes; + unsigned int ulRxPacketLost; + unsigned int ulRemotePacketLost; /* this is approximate as it is reported and the report could be lost. */ + unsigned int ulRemoteGarbage; /* this is approximate as it is reported and the report could be lost. */ + unsigned int ulPacketSent; /* number of normal packet sent */ + unsigned int ulPacketReceived; + unsigned int ulAckReceived; + unsigned int ulReliablePacketSent; + unsigned int ulReliableRetransmit; + unsigned int ulReliablePacketReceived; + unsigned int ulMaxRetransmitCount; +}stTransportLayerStats; +static unsigned int m_uiParserState; +static unsigned int m_uiRxLen; +static unsigned int m_uiEscLen; +static unsigned int m_ulByteCntSinceLastValidPacket; +static unsigned char m_aucRxMessageBuf[MAX_INCOMING_PACKET_SIZE+MAX_HEADER_SIZE]; +static unsigned char m_aucRxEscapedBuf[(MAX_INCOMING_PACKET_SIZE+MAX_HEADER_SIZE+2)*2]; +static stTransportLayerStats m_otCurrentStats; +static unsigned char m_ucDelayAckCount; + +static unsigned char m_ucLastRxSeqId; +static unsigned char m_ucLastAckSeqId; +static unsigned char m_ucReliableSeqId; +static unsigned char m_ucReliableCrc; +static unsigned short m_usReliableLen; +static bool m_bOngoingSync; + +/** + * CRC table - from GlUtlCrc::ucCrcTable + * + */ +static const unsigned char crc_table [] = +{ + 0x00,0x4d,0x9a,0xd7,0x79,0x34,0xe3,0xae,0xf2,0xbf,0x68,0x25,0x8b,0xc6,0x11,0x5c, + 0xa9,0xe4,0x33,0x7e,0xd0,0x9d,0x4a,0x07,0x5b,0x16,0xc1,0x8c,0x22,0x6f,0xb8,0xf5, + 0x1f,0x52,0x85,0xc8,0x66,0x2b,0xfc,0xb1,0xed,0xa0,0x77,0x3a,0x94,0xd9,0x0e,0x43, + 0xb6,0xfb,0x2c,0x61,0xcf,0x82,0x55,0x18,0x44,0x09,0xde,0x93,0x3d,0x70,0xa7,0xea, + 0x3e,0x73,0xa4,0xe9,0x47,0x0a,0xdd,0x90,0xcc,0x81,0x56,0x1b,0xb5,0xf8,0x2f,0x62, + 0x97,0xda,0x0d,0x40,0xee,0xa3,0x74,0x39,0x65,0x28,0xff,0xb2,0x1c,0x51,0x86,0xcb, + 0x21,0x6c,0xbb,0xf6,0x58,0x15,0xc2,0x8f,0xd3,0x9e,0x49,0x04,0xaa,0xe7,0x30,0x7d, + 0x88,0xc5,0x12,0x5f,0xf1,0xbc,0x6b,0x26,0x7a,0x37,0xe0,0xad,0x03,0x4e,0x99,0xd4, + 0x7c,0x31,0xe6,0xab,0x05,0x48,0x9f,0xd2,0x8e,0xc3,0x14,0x59,0xf7,0xba,0x6d,0x20, + 0xd5,0x98,0x4f,0x02,0xac,0xe1,0x36,0x7b,0x27,0x6a,0xbd,0xf0,0x5e,0x13,0xc4,0x89, + 0x63,0x2e,0xf9,0xb4,0x1a,0x57,0x80,0xcd,0x91,0xdc,0x0b,0x46,0xe8,0xa5,0x72,0x3f, + 0xca,0x87,0x50,0x1d,0xb3,0xfe,0x29,0x64,0x38,0x75,0xa2,0xef,0x41,0x0c,0xdb,0x96, + 0x42,0x0f,0xd8,0x95,0x3b,0x76,0xa1,0xec,0xb0,0xfd,0x2a,0x67,0xc9,0x84,0x53,0x1e, + 0xeb,0xa6,0x71,0x3c,0x92,0xdf,0x08,0x45,0x19,0x54,0x83,0xce,0x60,0x2d,0xfa,0xb7, + 0x5d,0x10,0xc7,0x8a,0x24,0x69,0xbe,0xf3,0xaf,0xe2,0x35,0x78,0xd6,0x9b,0x4c,0x01, + 0xf4,0xb9,0x6e,0x23,0x8d,0xc0,0x17,0x5a,0x06,0x4b,0x9c,0xd1,0x7f,0x32,0xe5,0xa8 +}; +/** + * crc_calc from GlUtlCrc::GlUtlCrcCalc + * update with new data byte and get result + */ +static inline unsigned char crc_calc(unsigned char *m_ucCrcState, unsigned char ucData) +{ + *m_ucCrcState = crc_table[*m_ucCrcState ^ ucData]; + return *m_ucCrcState; +} + +/** + * crc_calc_many - from GlUtlCrc::GlUtlCrcCalc + * + */ +static unsigned char crc_calc_many(unsigned char *m_ucCrcState, const unsigned char *pucData, unsigned short usLen) +{ + while (usLen--) + { + *m_ucCrcState = crc_table[*m_ucCrcState ^ (*pucData++)]; + } + return *m_ucCrcState; +} + +/* + * BbdBridge_OnRpcReceived - copied from RpcEngine::OnRpcReceived + * + */ +static int BbdBridge_OnRpcReceived(unsigned short usRpcId, unsigned char *pRpcPayload, unsigned short usRpcLen) +{ + if (usRpcId == RPC_DEFINITION(IRpcSensorResponse, Data)) + { + /* Read 2 byte size */ + ssize_t result; + unsigned short size = *pRpcPayload++; + size |= *pRpcPayload++ <<8; + result = bbd_sensor_write(pRpcPayload, size); + WARN_ON( size != usRpcLen-2); + WARN_ON((short) result != size); + return 1; + } + return 0; +} +/* + * BbdBridge_CheckPacketSanity - copied from RpcEngine::OnPacketReceived + * + */ +static bool BbdBridge_CheckPacketSanity(unsigned char *pucData, unsigned short usSize) +{ + long lSize = (long) usSize; + while (lSize > 0) + { + unsigned short usRpcId = *pucData++; + unsigned short usRpcLen; + lSize--; + if (usRpcId&0x80) + { + usRpcId &= ~0x80; + usRpcId <<= 8; + usRpcId |= *pucData++; lSize--; + } + usRpcLen = *pucData++; lSize--; + if (usRpcLen&0x80) + { + usRpcLen &= ~0x80; + usRpcLen <<= 8; + usRpcLen |= *pucData++; lSize--; + } + pucData += usRpcLen; + lSize -= usRpcLen; + } + return lSize == 0; +} +/* + * RpcEngine_OnPacketReceived - copied from RpcEngine::OnPacketReceived + * + */ +static int BbdBridge_OnPacketReceived(unsigned char *pucData, unsigned short usSize) +{ + int sensor = 0, gnss = 0; + if (BbdBridge_CheckPacketSanity(pucData, usSize)) + { + long lSize = (long)usSize; + while (lSize > 0) + { +#ifdef DEBUG_1HZ_STAT + unsigned char *pucDataOrg = pucData; +#endif + unsigned short usRpcId = *pucData++; + unsigned short usRpcLen; + lSize--; + if (usRpcId&0x80) + { + usRpcId &= ~0x80; + usRpcId <<= 8; + usRpcId |= *pucData++; lSize--; + } + usRpcLen = *pucData++; lSize--; + if (usRpcLen&0x80) + { + usRpcLen &= ~0x80; + usRpcLen <<= 8; + usRpcLen |= *pucData++; lSize--; + } + if (BbdBridge_OnRpcReceived(usRpcId, pucData, usRpcLen)) + sensor++; + else + gnss++; +#ifdef DEBUG_1HZ_STAT + bbd_update_stat(STAT_RX_RPC, usRpcLen+pucData-pucDataOrg); +#endif + pucData += usRpcLen; + lSize -= usRpcLen; + } + } + else + { + WARN_ON(1); + } + return (sensor > 0); +} + +static bool TransportLayer_PacketReceived(void *priv) +{ + unsigned char ucCrc = 0; + /* minimum is seqId, payload size, flags, and Crc */ + if (m_uiRxLen >= 4) + { + /* compute CRC */ + /* CRC is not applied on itself, nor on the SeqId */ + ucCrc = crc_calc_many(&ucCrc, &m_aucRxMessageBuf[1], m_uiRxLen-2); + /* CRC has its nibble inverted for stronger CRC (as CRC of + * a packet with itself is always 0, if EoP is not detected, + * that always reset the CRC) + */ + ucCrc = ((ucCrc&0x0F)<<4) | ((ucCrc&0xF0)>>4); + if (ucCrc != m_aucRxMessageBuf[m_uiRxLen-1]) /* CRC is last */ + { + return false; + } + } + else + { + return false; + } +#ifdef DEBUG_1HZ_STAT + bbd_update_stat(STAT_RX_TL, m_uiRxLen); +#endif + /* passed CRC check */ + { + unsigned char *pucData = &m_aucRxMessageBuf[0]; + unsigned short usLen = m_uiRxLen-1; + unsigned char ucSeqId = *pucData++; /* usLen--; */ + unsigned short usPayloadSize = *pucData++; /* usLen--; */ + unsigned short usFlags = *pucData++; /* usLen--; */ + bool bReliablePacket = false; + unsigned char ucReliableSeqId=0; + bool bReliableAckReceived = false; + unsigned char ucReliableAckSeqId=0; + bool bReliableNackReceived = false; + unsigned char ucReliableNackSeqId=0; + bool bAckReceived = false; + bool bInternalPacket = false; + bool bIgnoreSeqId = false; + unsigned short usAckFlags = 0; + unsigned int i=0; + usLen -= 3; + for (i = 0; i < 16 && usFlags != 0 && usLen > 0; ++i) + { + unsigned short usFlagMask = (1 << i); + unsigned short usFlagBit = usFlags & usFlagMask; + unsigned char ucFlagDetail = 0; + if (usFlagBit == 0) + { + continue; + } + usFlags &= ~usFlagBit; /* clear the flag */ + ucFlagDetail = *pucData++; /* extract flag details */ + --usLen; + if (usFlagBit == FLAG_PACKET_ACK) /* acknowledgement */ + { + /* flag detail contain the acknowledged SeqId */ + unsigned char ucReceivedAckSeqId = ucFlagDetail; + m_ucLastAckSeqId = ucReceivedAckSeqId; + bAckReceived = true; + } + else if (usFlagBit == FLAG_RELIABLE_PACKET) + { + /* This is a reliable packet. we need to provide the proper Ack */ + ucReliableSeqId = ucFlagDetail; + bReliablePacket = true; + } + else if (usFlagBit == FLAG_RELIABLE_ACK) + { + bReliableAckReceived = true; + ucReliableAckSeqId = ucFlagDetail; + } + else if (usFlagBit == FLAG_RELIABLE_NACK) + { + bReliableNackReceived = true; + ucReliableNackSeqId = ucFlagDetail; + } + else if (usFlagBit == FLAG_MSG_LOST) + { + /* remote TransportLayer lost had some SeqId jumps */ + m_otCurrentStats.ulRemotePacketLost += ucFlagDetail; + } + else if (usFlagBit == FLAG_MSG_GARBAGE) + { + /* remote TransportLayer detected garbage */ + m_otCurrentStats.ulRemoteGarbage += ucFlagDetail; + } + else if (usFlagBit == FLAG_SIZE_EXTENDED) + { + /* flag detail contains the MSB of the payload size */ + usPayloadSize |= (ucFlagDetail<<8); + } + else if (usFlagBit == FLAG_EXTENDED) + { + /* the flags are extended, which means that the details + * contains the MSB of the 16bit flags + */ + usFlags |= (ucFlagDetail<<8); + } + else if (usFlagBit == FLAG_INTERNAL_PACKET) + { + /* don't care about details */ + bInternalPacket = true; + } + else if (usFlagBit == FLAG_IGNORE_SEQID) + { + /* don't care about details */ + bIgnoreSeqId = true; + } + else + { + /* we did not process the flag, just put it back + * this is an error, so we can break now, as there is no + * point in continuing + */ + usFlags |= usFlagBit; + break; + } + } + /* if flag is not garbage, entire packet should all be consumed */ + /* remaining length of the buffer should be the payload size */ + if (usFlags == 0 && + usPayloadSize == usLen) + { + /* we now have a valid packet (at least it passed all our + * validity checks, so we are going to trust it + */ + unsigned char ucExpectedTxSeqId = (m_ucLastRxSeqId+1)&0xFF; + if (ucSeqId != ucExpectedTxSeqId + && !bIgnoreSeqId + && !m_bOngoingSync) + { + /* Some packets were lost, jump in the RxSeqId */ + m_otCurrentStats.ulRxPacketLost += + ((ucSeqId - ucExpectedTxSeqId)&0xFF); + } + m_ucLastRxSeqId = ucSeqId; /* increase expected SeqId */ + if (!bAckReceived || usLen > 0 ) + { + bool bDelayedEnough = (++m_ucDelayAckCount > 200); + ++m_otCurrentStats.ulPacketReceived; + /* if this is a payload packet, we need to acknowledge it. + * But don't clog the wires with power-hungry simple + * acks unless we have too many (200) outstanding. + */ + if (bDelayedEnough) + { + pr_debug("Skip averted/%d %s %s\n", __LINE__, + (bAckReceived ) ? "ACK" : "!ack", + (bDelayedEnough) ? "ENUF" : "!enuf"); + usAckFlags |= FLAG_PACKET_ACK; + m_ucDelayAckCount = 0; + } + else + { + pr_debug("Skip/%d %s %s %d\n", __LINE__, + (bAckReceived ) ? "ACK" : "!ack", + (bDelayedEnough) ? "ENUF" : "!enuf", + m_ucDelayAckCount); + } + } + else + { + ++m_otCurrentStats.ulAckReceived; + } + { + bool bProcessPacket = bReliablePacket || usLen > 0; + if (bReliablePacket) + { + pr_debug("TransportLayer_Received Reliable(Size %u, SeqId %u)\n", usLen, ucReliableSeqId); + /* if this is a reliable message, we need to make sure + * we didn't received it before! + * if we did, the Host probably didn't received the Ack, + * so let's just send the ack + * Reliable seqId is not enough, so we also use CRC and + * Lenght to confirm this was the same message received! + */ + if (ucReliableSeqId == m_ucReliableSeqId + && ucCrc == m_ucReliableCrc + && usLen == m_usReliableLen) + { + /* already received that packet, remote TransportLayer + * probably lost the Acknowledgement, send it again + */ + usAckFlags |= FLAG_PACKET_ACK | FLAG_RELIABLE_ACK; + bProcessPacket = false; /* we should not process it again */ + } + else if (ucReliableSeqId == ((m_ucReliableSeqId+1)&0xFF)) + { + /* this is a valid message, just do nothing but update + * the reliable info. the message will be processed below + */ + usAckFlags |= FLAG_PACKET_ACK | FLAG_RELIABLE_ACK; + /* already received that packet, remote TransportLayer + * probably lost the Acknowledgement, send it again + */ + m_usReliableLen = usLen; + m_ucReliableSeqId = ucReliableSeqId; + m_ucReliableCrc = ucCrc; + m_otCurrentStats.ulReliablePacketReceived++; + } + else + { + /* we received the wrong reliable SeqId */ + usAckFlags |= FLAG_PACKET_ACK | FLAG_RELIABLE_NACK; + bProcessPacket = false; /* we cannot accept the packet */ + } + } + if (bProcessPacket) + { + if (bInternalPacket) + { + bcm_on_packet_recieved(priv, m_aucRxEscapedBuf, m_uiEscLen); + } + else + { + /* everything good, notify upper layer that we have + * the payload of a packet available + In case of sensor data, we don't want send it to lhd! */ + if (!BbdBridge_OnPacketReceived(pucData, usLen)) + bcm_on_packet_recieved(priv, m_aucRxEscapedBuf, m_uiEscLen); + } + } + else + { + bcm_on_packet_recieved(priv, m_aucRxEscapedBuf, m_uiEscLen); + } + return true; + } + } + else + { + return false; + } + } +} + +/* + * bbd_parse_asic_data - copied from TransportLayer::ParseIncomingData + * + */ +void bbd_parse_asic_data(unsigned char *pucData, unsigned short usLen, void (*to_gpsd)(unsigned char *packet, unsigned short len, void* priv), void* priv) +{ + unsigned short usIdx=0; + while (usIdx != usLen) + { + unsigned char ucData = pucData[usIdx++]; + m_ulByteCntSinceLastValidPacket++; + if (sizeof(m_aucRxEscapedBuf) > m_uiEscLen) + m_aucRxEscapedBuf[m_uiEscLen++] = ucData; + + if (ucData == XON_CHARACTER || ucData == XOFF_CHARACTER) + { + continue; + } + switch(m_uiParserState) + { + case WAIT_FOR_ESC_SOP: + { + if (ucData == ESCAPE_CHARACTER) + { + m_uiParserState = WAIT_FOR_SOP; + m_otCurrentStats.ulRxGarbageBytes += (m_ulByteCntSinceLastValidPacket -1); /* if we had only one byte, then there is no garbage, any extra is garbage */ + m_ulByteCntSinceLastValidPacket = 1; + } + } + break; + case WAIT_FOR_SOP: + { + if (ucData == SOP_CHARACTER) + { + m_uiParserState = WAIT_FOR_MESSAGE_COMPLETE; + m_uiRxLen = 0; + } + else + { + if (ucData != ESCAPE_CHARACTER) + { + m_uiParserState = WAIT_FOR_ESC_SOP; + } + else + { + m_otCurrentStats.ulRxGarbageBytes += 2; + m_ulByteCntSinceLastValidPacket = 1; + } + } + } + break; + case WAIT_FOR_MESSAGE_COMPLETE: + { + if (ucData == ESCAPE_CHARACTER) + { + m_uiParserState = WAIT_FOR_EOP; + } + else if (m_uiRxLen < sizeof(m_aucRxMessageBuf)) + { + m_aucRxMessageBuf[m_uiRxLen++] = ucData; + } + else + { + m_uiParserState = WAIT_FOR_ESC_SOP; + } + } + break; + case WAIT_FOR_EOP: + { + if (ucData == EOP_CHARACTER) + { + if (TransportLayer_PacketReceived(priv)) + { + m_ulByteCntSinceLastValidPacket = 0; /* we had a valid packet, restart the cnt */ + } + m_uiEscLen = 0; + m_uiParserState = WAIT_FOR_ESC_SOP; + } + else if (ucData == ESCAPED_ESCAPE_CHARACTER) + { + if (m_uiRxLen < _DIM(m_aucRxMessageBuf)) + { + m_aucRxMessageBuf[m_uiRxLen++] = ESCAPE_CHARACTER; + m_uiParserState = WAIT_FOR_MESSAGE_COMPLETE; + } + else + { + m_uiParserState = WAIT_FOR_ESC_SOP; + } + } + else if (ucData == ESCAPED_XON_CHARACTER) + { + if (m_uiRxLen < _DIM(m_aucRxMessageBuf)) + { + m_aucRxMessageBuf[m_uiRxLen++] = XON_CHARACTER; + m_uiParserState = WAIT_FOR_MESSAGE_COMPLETE; + } + else + { + m_uiParserState = WAIT_FOR_ESC_SOP; + } + } + else if (ucData == ESCAPED_XOFF_CHARACTER) + { + if (m_uiRxLen < _DIM(m_aucRxMessageBuf)) + { + m_aucRxMessageBuf[m_uiRxLen++] = XOFF_CHARACTER; + m_uiParserState = WAIT_FOR_MESSAGE_COMPLETE; + } + else + { + m_uiParserState = WAIT_FOR_ESC_SOP; + } + } + else if (ucData == SOP_CHARACTER) + { + /* we probably missed the ESC EOP, but we start receiving a new packet! */ + m_uiParserState = WAIT_FOR_MESSAGE_COMPLETE; + /* init the parser! */ + m_uiRxLen = 0; + m_uiEscLen = 0; + + m_otCurrentStats.ulRxGarbageBytes += (m_ulByteCntSinceLastValidPacket-2); /* if we had only one byte, then there is no garbage, any extra is garbage */ + m_ulByteCntSinceLastValidPacket = 2; + } + else if (ucData == ESCAPE_CHARACTER) + { + m_uiParserState = WAIT_FOR_SOP; + } + else + { + m_uiParserState = WAIT_FOR_ESC_SOP; + } + } + break; + } + } +} diff --git a/drivers/sensors/brcm/bbdpl2/bcm_dbg.c b/drivers/sensors/brcm/bbdpl2/bcm_dbg.c new file mode 100644 index 000000000000..97ef8eda8fd7 --- /dev/null +++ b/drivers/sensors/brcm/bbdpl2/bcm_dbg.c @@ -0,0 +1,33 @@ +#include +#include +#include + +#define MAX_DBG_LOG_LEN 60 +#define MAX_DBG_RECORD (4096*128) + +struct dbg_record +{ + unsigned long time; + char log[MAX_DBG_LOG_LEN]; +}; + +static int dbg_idx; +static struct dbg_record rec[MAX_DBG_RECORD]; + +void record(const char *str) +{ + struct timeval tv; + do_gettimeofday(&tv); + rec[dbg_idx].time = tv.tv_sec*1000000UL + tv.tv_usec; + strncpy(rec[dbg_idx].log, str, MAX_DBG_LOG_LEN); + dbg_idx = ++dbg_idx&(MAX_DBG_RECORD-1); +} + +void print_record(void) +{ + int i; + + for (i=0; i\t%s\n", rec[i].time, rec[i].log); + } +} diff --git a/drivers/sensors/brcm/bbdpl2/bcm_gps_i2c.c b/drivers/sensors/brcm/bbdpl2/bcm_gps_i2c.c new file mode 100644 index 000000000000..08a8f9453192 --- /dev/null +++ b/drivers/sensors/brcm/bbdpl2/bcm_gps_i2c.c @@ -0,0 +1,741 @@ +/****************************************************************************** + * Copyright (C) 2013 Broadcom Corporation + * + * + * 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. + ******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "bcm_gps_i2c.h" +#include + +#define GPS_VERSION "2.23" +#define PFX "bcmgps:" + +#ifdef CONFIG_SENSORS_SSP_BBD +void bbd_parse_asic_data(unsigned char *pucData, unsigned short usLen, void (*to_gpsd)(unsigned char *packet, unsigned short len, void* priv), void* priv); +#endif + +/* ring buffer functions */ +typedef struct ring_buffer { + struct mutex *lock; + int start; + int end; + unsigned int size_of_ring_buffer; + unsigned char *buffer; + int (*push_back_byte)(struct ring_buffer *self, unsigned char byte); + int (*pop_front_byte)(struct ring_buffer *self, unsigned char *byte); + int (*push_back)(struct ring_buffer *self, unsigned char *buffer, unsigned int sizeOfBuffer); + int (*pop_front)(struct ring_buffer *self, unsigned char *buffer, unsigned int sizeOfBuffer); + int (*is_empty_internal)(struct ring_buffer *self); + int (*is_full_internal)(struct ring_buffer *self); + int (*is_empty)(struct ring_buffer *self); + int (*is_full)(struct ring_buffer *self); + void (*reset)(struct ring_buffer *self); +} ring_buffer; + +static ring_buffer *ring_buffer_init(int size_of_ring_buffer, struct mutex *lock); +static void ring_buffer_free(ring_buffer *self); +static int ring_buffer_pushBackByte(struct ring_buffer *self, unsigned char byte); +static int ring_buffer_popFrontByte(ring_buffer *self, unsigned char *byte); +static int ring_buffer_pushBack(ring_buffer *self, unsigned char *buffer, unsigned int sizeOfBuffer); +static int ring_buffer_popFront(ring_buffer *self, unsigned char *buffer, unsigned int sizeOfBuffer); +static int ring_buffer_isEmptyInternal(ring_buffer *self); +static int ring_buffer_isFullInternal(ring_buffer *self); +static int ring_buffer_isEmpty(ring_buffer *self); +static int ring_buffer_isFull(ring_buffer *self); +static void ring_buffer_reset(ring_buffer *self); + +#define MAX_RX_PCKT_LEN 256 +#define MAX_TX_PCKT_LEN 256 + +#define MAX_TEMP_READ_BUFFER (256 * 2) + +#define SIZE_OF_READ_RING_BUFFER 256*128 +#define SIZE_OF_WRITE_RING_BUFFER 256*64 + +static DEFINE_MUTEX(gps_lock); +static DEFINE_MUTEX(gps_read_lock); +static DEFINE_MUTEX(gps_write_lock); + +struct gps_irq { + wait_queue_head_t wait; + int irq; + int host_req_pin; + struct miscdevice misc; + struct i2c_client *client; + ring_buffer *read_rbuf; + ring_buffer *write_rbuf; + struct work_struct read_task; + struct work_struct write_task; + int is_opened; +}; + + +bool ssi_dbg; + +static unsigned long init_time = 0; +static unsigned long clock_get_ms(void) +{ + struct timeval t; + unsigned long now; + + do_gettimeofday(&t); + now = t.tv_usec / 1000 + t.tv_sec * 1000; + if ( init_time == 0 ) + init_time = now; + + return now - init_time; +} + +static void pk_log(char* dir, unsigned char* data, int len) +{ + char acB[960]; + char *p = acB; + int the_trans = len; + int i,j,n; + + char ic = 'D'; + char xc = dir[0] == 'r' || dir[0] == 'w' ? 'x':'X'; + + if (likely(!ssi_dbg)) + return; + + n = len; + p += snprintf(acB,sizeof(acB),"#%06ld%c %2s, %5d: ", + clock_get_ms() % 1000000,ic, dir, n); + + for (i=0, n=32; iis_opened == 0) break; + + host_req_gpio_value = gpio_get_value(ac_data->host_req_pin); + if (host_req_gpio_value == 0) break; + + /* 2. Initialize values. */ + size_of_packet = 0; + memset(temp_read_buffer, 0x00, MAX_RX_PCKT_LEN); + + /* 3. Read the length of message. */ + number_of_read = i2c_master_recv(ac_data->client, &size_of_packet, 1); + pk_log("r", &size_of_packet, 1); + + /* 4. check the length, if it is zero, sender doesn't have data anymore. */ + if (size_of_packet == 0) break; + + /* 5. Read message. */ + number_of_read = i2c_master_recv(ac_data->client, temp_read_buffer, (size_of_packet + 1)); + pk_log("r", temp_read_buffer, size_of_packet+1); + + /* 6. push message into ring buffer. */ + /* -- push message without first byte that is the length of packet. */ + ac_data->read_rbuf->push_back(ac_data->read_rbuf, &(temp_read_buffer[1]), size_of_packet); + + /* 6-1. Call BBD */ +#ifdef CONFIG_SENSORS_SSP_BBD + bbd_parse_asic_data(&temp_read_buffer[1], size_of_packet, NULL, NULL); +#endif + + /* 7. wake up the task for poll. */ + wake_up_interruptible(&ac_data->wait); + } +} + +void write_work(struct work_struct *work) +{ + struct gps_irq *ac_data = + container_of(work, struct gps_irq, write_task); + + int number_of_sent = 0; + int size_of_packet = 0; + unsigned char temp_write_buffer[MAX_TX_PCKT_LEN] = {0}; + int number_of_i2c_sent = 0; + + while(1) + { + /* 1. check condition */ + if (ac_data->write_rbuf->is_empty(ac_data->write_rbuf)) break; + if (ac_data->is_opened == 0) break; + + /* 2. initialize values */ + memset(temp_write_buffer, 0x00, MAX_TX_PCKT_LEN); + + /* 3. read data from ring buffer */ + size_of_packet = ac_data->write_rbuf->pop_front(ac_data->write_rbuf, temp_write_buffer, MAX_TX_PCKT_LEN); + + /* 4. send them through i2c */ + number_of_i2c_sent = i2c_master_send(ac_data->client, temp_write_buffer, size_of_packet); + pk_log("w", temp_write_buffer, size_of_packet); + + number_of_sent += size_of_packet; + + } + + pr_debug(PFX PFX "write_work() : addr = 0x%02X, sent %d bytes.\n", ac_data->client->addr, number_of_sent); +} + +irqreturn_t gps_irq_handler(int irq, void *dev_id) +{ + struct gps_irq *ac_data = dev_id; + char gpio_value = 0x00; + + gpio_value = gpio_get_value(ac_data->host_req_pin); + + /* If HOST_REQ is set, start to read data. */ + if (gpio_value) + schedule_work(&ac_data->read_task); + + return IRQ_HANDLED; +} + +static int gps_irq_open(struct inode *inode, struct file *filp) +{ + struct gps_irq *ac_data = container_of(filp->private_data, + struct gps_irq, + misc); + int ret = 0; + + mutex_lock(&gps_lock); + if (ac_data->is_opened == 1) + { + ret = -EBUSY; + } + else + { + ac_data->is_opened = 1; + } + mutex_unlock(&gps_lock); + + if (ret < 0) + { + pr_err(PFX "open error(%d)\n", ret); + return ret; + } + + pr_notice(PFX "gps_irq_open() : 0x%p\n", ac_data); + + filp->private_data = ac_data; + + ac_data->read_rbuf->reset(ac_data->read_rbuf); + ac_data->write_rbuf->reset(ac_data->write_rbuf); + + return ret; +} + +static int gps_irq_release(struct inode *inode, struct file *filp) +{ + struct gps_irq *ac_data = filp->private_data; + + mutex_lock(&gps_lock); + ac_data->is_opened = 0; + mutex_unlock(&gps_lock); + + filp->private_data = ac_data; + + pr_notice(PFX "gps_irq_release() ac_data = 0x%p\n", ac_data); + + return 0; +} + +static unsigned int gps_irq_poll(struct file *file, poll_table *wait) +{ + struct gps_irq *ac_data = file->private_data; + + BUG_ON(!ac_data); + + poll_wait(file, &ac_data->wait, wait); + + if (!(ac_data->read_rbuf->is_empty(ac_data->read_rbuf))) + return POLLIN | POLLRDNORM; + + return 0; +} + +static ssize_t gps_irq_read(struct file *file, char __user *buf, + size_t count, loff_t *offset) +{ + struct gps_irq *ac_data = file->private_data; + int number_of_read = 0; + int size_of_pop = 0; + int number_of_popped = 0; + unsigned char temp_read_buffer[MAX_TEMP_READ_BUFFER] = {0}; + + while (1) + { + /* 1. check condition */ + if (ac_data->read_rbuf->is_empty(ac_data->read_rbuf)) break; /* no more data in ring buffer */ + if (number_of_read >= count) break; /* user buf is full. */ + + /* 2. initialize values */ + memset(temp_read_buffer, 0x00, MAX_RX_PCKT_LEN); + + /* 3. read data from ring buffer */ + size_of_pop = (count - number_of_read) < MAX_TEMP_READ_BUFFER ? (count - number_of_read) : MAX_TEMP_READ_BUFFER; + number_of_popped = ac_data->read_rbuf->pop_front(ac_data->read_rbuf, temp_read_buffer, size_of_pop); + + /* 4. copy data to user */ + if (copy_to_user((buf + number_of_read), temp_read_buffer, number_of_popped)) + { + /* reset ring buffer */ + ac_data->read_rbuf->reset(ac_data->read_rbuf); + return -EFAULT; + } + number_of_read += number_of_popped; + } + + pr_debug(PFX "gps_irq_read() : # of read = %d\n", number_of_read); + + return number_of_read; +} + +static ssize_t gps_irq_write(struct file *file, const char __user *buf, + size_t count, loff_t *offset) +{ + struct gps_irq *ac_data = file->private_data; + + unsigned char temp_write_buffer[MAX_TX_PCKT_LEN] = {0}; + int number_of_sent = 0; + int number_of_push = 0; + int number_of_pushed = 0; + + while (1) + { + /* 1. initialize values */ + memset(temp_write_buffer, 0x00, MAX_TX_PCKT_LEN); + + /* 2. copy buf to kernel */ + number_of_push = (count - number_of_sent) < MAX_TX_PCKT_LEN ? (count - number_of_sent) : MAX_TX_PCKT_LEN; + if (copy_from_user(temp_write_buffer, (buf + number_of_sent), number_of_push)) + { + /* reset write ring buffer. */ + ac_data->write_rbuf->reset(ac_data->write_rbuf); + return -EFAULT; + } + /* 3. push data to ring buffer */ + number_of_pushed = ac_data->write_rbuf->push_back(ac_data->write_rbuf, temp_write_buffer, number_of_push); + number_of_sent += number_of_pushed; + + /* 4. check escape condition */ + if (number_of_sent >= count) break; /* done */ + if (number_of_push != number_of_pushed) break; /* write_rbuf is full. */ + } + + /* 4. schedule write work */ + schedule_work(&ac_data->write_task); + + pr_debug(PFX "gps_irq_write() : writing %d bytes returns %d\n", count, number_of_sent); + + return number_of_sent; +} + + +static long gps_irq_ioctl( struct file *filp, + unsigned int cmd, unsigned long arg) +{ + struct gps_irq *ac_data = filp->private_data; + struct i2c_client *client = ac_data->client; + + BUG_ON(!client); + + switch (cmd) { + case I2C_SLAVE: + case I2C_SLAVE_FORCE: + if (arg > 0x7f) + { + pr_err(PFX "out of range: 0x%x should be less than 0x7f.", (unsigned int)arg); + return -EINVAL; + } + client->addr = arg; + pr_info(PFX "ioctl: client->addr = 0x%x\n", client->addr); + break; + + case I2C_RETRIES: + client->adapter->retries = arg; + pr_info("ioctl, client->adapter->retries = %d\n",client->adapter->retries); + break; + + case I2C_TIMEOUT: + /* For historical reasons, user-space sets the timeout + * value in units of 10 ms. + */ + client->adapter->timeout = msecs_to_jiffies(arg * 10); + pr_info(PFX "ioctl, client->adapter->timeout = %d\n",client->adapter->timeout); + break; + default: + return -ENOTTY; + } + return 0; +} + +static const struct file_operations gps_irq_fops = { + .owner = THIS_MODULE, + .open = gps_irq_open, + .release = gps_irq_release, + .poll = gps_irq_poll, + .read = gps_irq_read, + .write = gps_irq_write, + .unlocked_ioctl = gps_irq_ioctl +}; + +static int gps_hostwake_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct bcm_gps_platform_data *pdata = (struct bcm_gps_platform_data*)client->dev.platform_data; + struct gps_irq *ac_data = kzalloc(sizeof(struct gps_irq), GFP_KERNEL); + + int irq = -1; + int ret = -1; + int err = -1; + unsigned int gps_gpio_i2c = 0; + +#ifdef CONFIG_OF + enum of_gpio_flags flags; + if (!client->dev.of_node) { + pr_err(PFX "of_node of 475x i2c device is NULL\n"); + return -ENODEV; + } + gps_gpio_i2c = of_get_named_gpio_flags(client->dev.of_node, "brcm,irq_gpio", 0, &flags); + if (gps_gpio_i2c < 0) { + pr_err(PFX "475x host wake gpio %d err\n", gps_gpio_i2c); + return -1; + } + pr_notice(PFX "%s pdata->gpio_i2c = %d, ac_data = 0x%p\n",__func__, gps_gpio_i2c, ac_data); +#else + gps_gpio_i2c = pdata->gpio_i2c; + pr_notice(PFX "%s pdata->gpio_i2c = %d, ac_data = 0x%p\n",__func__, pdata->gpio_i2c, ac_data); +#endif + + ac_data->read_rbuf = ring_buffer_init(SIZE_OF_READ_RING_BUFFER, &gps_read_lock); + ac_data->write_rbuf = ring_buffer_init(SIZE_OF_WRITE_RING_BUFFER, &gps_write_lock); + ac_data->is_opened = 0; + + pr_notice(PFX "%s\n",__func__); + + init_waitqueue_head(&ac_data->wait); + + if ((err = gpio_request(gps_gpio_i2c, "gps_irq"))) { + pr_warning(PFX "Can't request HOST_REQ GPIO %d.It may be already registered in init.xyz.3rdparty.rc/init.xyz.rc\n",gps_gpio_i2c); + } + gpio_export(gps_gpio_i2c, 1); + gpio_direction_input(gps_gpio_i2c); + + irq = gpio_to_irq(gps_gpio_i2c); + if (irq < 0) { + pr_err(PFX KERN_ERR "Could not get GPS IRQ = %d!\n",gps_gpio_i2c); + gpio_free(gps_gpio_i2c); + return -EIO; + } + + ac_data->irq = irq; + ac_data->host_req_pin = gps_gpio_i2c; + ret = request_irq(irq, gps_irq_handler, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + /*KOM -- IRQF_TRIGGER_RISING, */ + "gps_interrupt", + ac_data); + + ac_data->client = client; + + ac_data->misc.minor = MISC_DYNAMIC_MINOR; + ac_data->misc.name = "gps_irq"; + ac_data->misc.fops = &gps_irq_fops; + + pr_notice(PFX "%s misc register, name = %s, irq = %d, GPS gpio = %d\n",__func__, ac_data->misc.name, irq, gps_gpio_i2c); + if (0 != (ret = misc_register(&ac_data->misc))) { + pr_err(PFX "cannot register gps miscdev on minor=%d (%d)\n",MISC_DYNAMIC_MINOR, ret); + free_irq(ac_data->irq, ac_data); + gpio_free(gps_gpio_i2c); + return ret; + } + + INIT_WORK(&ac_data->read_task, read_work); + INIT_WORK(&ac_data->write_task, write_work); + + i2c_set_clientdata(client, ac_data); + + pr_notice(PFX "Initialized.\n"); + + return 0; +} + +static int gps_hostwake_remove(struct i2c_client *client) +{ + struct bcm_gps_platform_data *pdata = 0; + struct gps_irq *ac_data = 0; + + pr_notice(PFX " %s : called\n", __func__); + + pdata = client->dev.platform_data; + gpio_free(pdata->gpio_i2c); + + ac_data = i2c_get_clientdata(client); + free_irq(ac_data->irq, ac_data); + misc_deregister(&ac_data->misc); + + cancel_work_sync(&ac_data->read_task); + cancel_work_sync(&ac_data->write_task); + + ring_buffer_free(ac_data->read_rbuf); + ring_buffer_free(ac_data->write_rbuf); + kfree(ac_data); + return 0; +} + +static const struct i2c_device_id gpsi2c_id[] = { + {"gpsi2c", 0}, + {} +}; + +#ifdef CONFIG_OF +static struct of_device_id gps_match_table[] = { + { .compatible = "brcm,gps",}, + {}, +}; +#endif + +static struct i2c_driver gps_driver = { + .id_table = gpsi2c_id, + .probe = gps_hostwake_probe, + .remove = gps_hostwake_remove, + .driver = { + .owner = THIS_MODULE, + .name = "gpsi2c", +#ifdef CONFIG_OF + .of_match_table = gps_match_table, +#endif + }, +}; + +static int gps_irq_init(void) +{ + pr_notice(PFX "Broadcom GPS i2c Driver v%s\n", GPS_VERSION); + return i2c_add_driver(&gps_driver); +} + +static void gps_irq_exit(void) +{ + i2c_del_driver(&gps_driver); +} + +/* ring_buffer implementation */ +static ring_buffer *ring_buffer_init(int size_of_ring_buffer, struct mutex *lock) +{ + ring_buffer *new_rb = 0; + + new_rb = kzalloc(sizeof(ring_buffer), GFP_KERNEL); + + new_rb->lock = lock; + new_rb->push_back_byte = ring_buffer_pushBackByte; + new_rb->pop_front_byte = ring_buffer_popFrontByte; + new_rb->push_back = ring_buffer_pushBack; + new_rb->pop_front = ring_buffer_popFront; + new_rb->is_empty_internal = ring_buffer_isEmptyInternal; + new_rb->is_full_internal = ring_buffer_isFullInternal; + new_rb->is_empty = ring_buffer_isEmpty; + new_rb->is_full = ring_buffer_isFull; + new_rb->reset = ring_buffer_reset; + + new_rb->start = 0; + new_rb->end = 0; + new_rb->size_of_ring_buffer = size_of_ring_buffer; + + new_rb->buffer = kzalloc(new_rb->size_of_ring_buffer, GFP_KERNEL); + + return new_rb; +} + +static void ring_buffer_free(ring_buffer *self) +{ + self->start = 0; + self->end = 0; + self->size_of_ring_buffer = 0; + + kfree(self->buffer); + kfree(self); + + self = 0; +} + +static int ring_buffer_pushBackByte(struct ring_buffer *self, unsigned char byte) +{ + if (self->is_full_internal(self)) return 0; + + self->buffer[self->end] = byte; + self->end = (self->end + 1) % self->size_of_ring_buffer; + + return 1; +} + +static int ring_buffer_popFrontByte(ring_buffer *self, unsigned char *byte) +{ + if (self->is_empty_internal(self)) return 0; + + *byte = self->buffer[self->start]; + self->start = (self->start + 1) % self->size_of_ring_buffer; + + return 1; +} + +static int ring_buffer_pushBack(ring_buffer *self, unsigned char *buffer, unsigned int sizeOfBuffer) +{ + int i = 0; + int nPush = 0; + + if (self->lock) mutex_lock(self->lock); + for (i = 0; i < sizeOfBuffer; i++) + { + if (self->push_back_byte(self, buffer[i]) == 0) break; + ++nPush; + } + if (self->lock) mutex_unlock(self->lock); + + return nPush; +} + +static int ring_buffer_popFront(ring_buffer *self, unsigned char *buffer, unsigned int sizeOfBuffer) +{ + int i = 0; + int nPop = 0; + unsigned char byte; + + if (self->lock) mutex_lock(self->lock); + for (i = 0; i < sizeOfBuffer; i++) + { + if (self->pop_front_byte(self, &byte) == 0) break; + buffer[i] = byte; + ++nPop; + } + if (self->lock) mutex_unlock(self->lock); + + return nPop; +} + +static int ring_buffer_isEmptyInternal(ring_buffer *self) +{ + int ret = 0; + + ret = (self->start == self->end); + + return ret; +} + +static int ring_buffer_isFullInternal(ring_buffer *self) +{ + int ret = 0; + + if ((self->start - 1) > 0) + { + ret = ((self->start - 1) == self->end); + } + else if (self->end == (self->size_of_ring_buffer - 1)) + { + ret = 1; + } + + return ret; +} + +static int ring_buffer_isEmpty(ring_buffer *self) +{ + int ret = 0; + + if (self->lock) mutex_lock(self->lock); + ret = (self->start == self->end); + if (self->lock) mutex_unlock(self->lock); + + return ret; +} + +static int ring_buffer_isFull(ring_buffer *self) +{ + int ret = 0; + + if (self->lock) mutex_lock(self->lock); + if ((self->start - 1) > 0) + { + ret = ((self->start - 1) == self->end); + } + else if (self->end == (self->size_of_ring_buffer - 1)) + { + ret = 1; + } + if (self->lock) mutex_unlock(self->lock); + + return ret; +} + +static void ring_buffer_reset(ring_buffer *self) +{ + if (self->lock) mutex_lock(self->lock); + self->start = 0; + self->end = 0; + if (self->lock) mutex_unlock(self->lock); +} + +module_init(gps_irq_init); +module_exit(gps_irq_exit); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Broadcom GPS Driver with host wake interrupt"); diff --git a/drivers/sensors/brcm/bbdpl2/bcm_gps_i2c.h b/drivers/sensors/brcm/bbdpl2/bcm_gps_i2c.h new file mode 100644 index 000000000000..08c40ca1e854 --- /dev/null +++ b/drivers/sensors/brcm/bbdpl2/bcm_gps_i2c.h @@ -0,0 +1,22 @@ +/****************************************************************************** +* Copyright (C) 2013 Broadcom Corporation +* +* +* 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. +******************************************************************************/ + +#ifndef _BCM_GPS_I2C_H_ +#define _BCM_GPS_I2C_H_ + +struct bcm_gps_platform_data { + unsigned int gpio_i2c; /* HOST_REQ : to indicate that ASIC has data to send. */ +}; + +#endif /* _BCM_GPS_I2C_H_ */ diff --git a/drivers/sensors/brcm/bbdpl2/bcm_gps_spi.c b/drivers/sensors/brcm/bbdpl2/bcm_gps_spi.c new file mode 100644 index 000000000000..8434aa157e11 --- /dev/null +++ b/drivers/sensors/brcm/bbdpl2/bcm_gps_spi.c @@ -0,0 +1,968 @@ +/****************************************************************************** + * Copyright (C) 2015 Broadcom Corporation + * + * + * 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. + ******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bbd.h" + +#define WORD_BURST_SIZE 4 +#define CONFIG_SPI_DMA_BYTES_PER_WORD 4 +#define CONFIG_SPI_DMA_BITS_PER_WORD 32 + +#define SSI_MODE_STREAM 0x00 +#define SSI_MODE_DEBUG 0x80 + +#define SSI_MODE_HALF_DUPLEX 0x00 +#define SSI_MODE_FULL_DUPLEX 0x40 + +#define SSI_WRITE_TRANS 0x00 +#define SSI_READ_TRANS 0x20 + +#define SSI_WRITE_HD (SSI_WRITE_TRANS | SSI_MODE_HALF_DUPLEX) +#define SSI_READ_HD (SSI_READ_TRANS | SSI_MODE_HALF_DUPLEX) + +#define DEBUG_TIME_STAT + +#ifdef CONFIG_SENSORS_SSP_BBD +extern void bbd_parse_asic_data(unsigned char *pucData, unsigned short usLen, void (*to_gpsd)(unsigned char *packet, unsigned short len, void* priv), void* priv); +#endif + +bool ssi_dbg; + +//-------------------------------------------------------------- +// +// Structs +// +//-------------------------------------------------------------- + +#define BCM_SPI_READ_BUF_SIZE (4*PAGE_SIZE) +#define BCM_SPI_WRITE_BUF_SIZE (4*PAGE_SIZE) + + +#define MAX_SPI_FRAME_LEN 254 +struct bcm_ssi_tx_frame +{ + unsigned char cmd; + unsigned char data[MAX_SPI_FRAME_LEN-1]; +} __attribute__((__packed__)); + +struct bcm_ssi_rx_frame +{ + unsigned char status; + unsigned char len; + unsigned char data[MAX_SPI_FRAME_LEN-2]; +} __attribute__((__packed__)); + + +struct bcm_spi_priv +{ + struct spi_device *spi; + + /* Char device stuff */ + struct miscdevice misc; + bool busy; + struct circ_buf read_buf; + struct circ_buf write_buf; + struct mutex rlock; /* Lock for read_buf */ + struct mutex wlock; /* Lock for write_buf */ + char _read_buf[BCM_SPI_READ_BUF_SIZE]; + char _write_buf[BCM_SPI_WRITE_BUF_SIZE]; + wait_queue_head_t poll_wait; /* for poll */ + + /* GPIO pins */ + int host_req; + int mcu_req; + int mcu_resp; + + /* IRQ and its control */ + atomic_t irq_enabled; + spinlock_t irq_lock; + + /* Work */ + struct work_struct rxtx_work; + struct workqueue_struct *serial_wq; + atomic_t suspending; + + /* SPI tx/rx buf */ + struct bcm_ssi_tx_frame *tx_buf; + struct bcm_ssi_rx_frame *rx_buf; + + struct wake_lock bcm_wake_lock; + + struct clk *clk; +}; + +static struct bcm_spi_priv *g_bcm_gps; + +//-------------------------------------------------------------- +// +// File Operations +// +//-------------------------------------------------------------- +static int bcm_spi_open(struct inode *inode, struct file *filp) +{ + /* Initially, file->private_data points device itself and we can get our priv structs from it. */ + struct bcm_spi_priv *priv = container_of(filp->private_data, struct bcm_spi_priv, misc); + unsigned long int flags; + + if (priv->busy) + return -EBUSY; + + priv->busy = true; + + /* Reset circ buffer */ + priv->read_buf.head = priv->read_buf.tail = 0; + priv->write_buf.head = priv->write_buf.tail = 0; + + + /* Enable irq */ + spin_lock_irqsave( &priv->irq_lock, flags); + if (!atomic_xchg(&priv->irq_enabled, 1)) + enable_irq(priv->spi->irq); + + spin_unlock_irqrestore( &priv->irq_lock, flags); + + enable_irq_wake(priv->spi->irq); + + filp->private_data = priv; +#ifdef DEBUG_1HZ_STAT + bbd_enable_stat(); +#endif + return 0; +} + +static int bcm_spi_release(struct inode *inode, struct file *filp) +{ + struct bcm_spi_priv *priv = filp->private_data; + unsigned long int flags; + + priv->busy = false; + +#ifdef DEBUG_1HZ_STAT + bbd_disable_stat(); +#endif + /* Disable irq */ + spin_lock_irqsave( &priv->irq_lock, flags); + if (atomic_xchg(&priv->irq_enabled, 0)) + disable_irq_nosync(priv->spi->irq); + + spin_unlock_irqrestore( &priv->irq_lock, flags); + + disable_irq_wake(priv->spi->irq); + + return 0; +} + +static ssize_t bcm_spi_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos) +{ + struct bcm_spi_priv *priv = filp->private_data; + struct circ_buf *circ = &priv->read_buf; + size_t rd_size=0; + + /* Copy from circ buffer to user + * We may require 2 copies from [tail..end] and [end..head] + */ + do { + size_t cnt_to_end = CIRC_CNT_TO_END(circ->head, circ->tail, BCM_SPI_READ_BUF_SIZE); + size_t copied = min(cnt_to_end, size); + + WARN_ON(copy_to_user(buf + rd_size, (void*) circ->buf + circ->tail, copied)); + size -= copied; + rd_size += copied; + circ->tail = (circ->tail + copied) & (BCM_SPI_READ_BUF_SIZE-1); + + } while (size>0 && CIRC_CNT(circ->head, circ->tail, BCM_SPI_READ_BUF_SIZE)); + +#ifdef DEBUG_1HZ_STAT + bbd_update_stat(STAT_RX_LHD, rd_size); +#endif + + return rd_size; +} + +static ssize_t bcm_spi_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos) +{ + struct bcm_spi_priv *priv = filp->private_data; + struct circ_buf *circ = &priv->write_buf; + size_t wr_size=0; + + /* Copy from user into circ buffer + * We may require 2 copies from [tail..end] and [end..head] + */ + do { + size_t space_to_end = CIRC_SPACE_TO_END(circ->head, circ->tail, BCM_SPI_WRITE_BUF_SIZE); + size_t copied = min(space_to_end, size); + + + WARN_ON(copy_from_user((void*) circ->buf + circ->head, buf + wr_size, copied)); + size -= copied; + wr_size += copied; + circ->head = (circ->head + copied) & (BCM_SPI_WRITE_BUF_SIZE-1); + } while (size>0 && CIRC_SPACE(circ->head, circ->tail, BCM_SPI_WRITE_BUF_SIZE)); + + /* kick start rxtx thread */ + /* we don't want to queue work in suspending and shutdown */ + if (!atomic_read(&priv->suspending)) + queue_work(priv->serial_wq, &(priv->rxtx_work) ); + +#ifdef DEBUG_1HZ_STAT + bbd_update_stat(STAT_TX_LHD, wr_size); +#endif + return wr_size; +} + +static unsigned int bcm_spi_poll(struct file *filp, poll_table *wait) +{ + struct bcm_spi_priv *priv = filp->private_data; + struct circ_buf *rd_circ = &priv->read_buf; + struct circ_buf *wr_circ = &priv->write_buf; + unsigned int mask = 0; + + poll_wait(filp, &priv->poll_wait, wait); + + if (CIRC_CNT(rd_circ->head, rd_circ->tail, BCM_SPI_READ_BUF_SIZE)) + mask |= POLLIN; + + if (CIRC_SPACE(wr_circ->head, wr_circ->tail, BCM_SPI_WRITE_BUF_SIZE)) + mask |= POLLOUT; + + return mask; +} + + +static const struct file_operations bcm_spi_fops = { + .owner = THIS_MODULE, + .open = bcm_spi_open, + .release = bcm_spi_release, + .read = bcm_spi_read, + .write = bcm_spi_write, + .poll = bcm_spi_poll, +}; + + + +//-------------------------------------------------------------- +// +// Misc. functions +// +//-------------------------------------------------------------- + +/* + * bcm4773_hello - wakeup chip by toggling mcu_req while monitoring mcu_resp to check if awake + * + */ +static bool bcm4773_hello(struct bcm_spi_priv *priv) +{ + int count=0, retries=0; + + gpio_set_value(priv->mcu_req, 1); + while (!gpio_get_value(priv->mcu_resp)) { + if (count++ > 100) { + gpio_set_value(priv->mcu_req, 0); + printk("[SSPBBD] MCU_REQ_RESP timeout. MCU_RESP(gpio%d) not responding to MCU_REQ(gpio%d)\n", + priv->mcu_resp, priv->mcu_req); + return false; + } + + mdelay(1); + + /*if awake, done */ + if (gpio_get_value(priv->mcu_resp)) break; + + if (count%20==0 && retries++ < 3) { + gpio_set_value(priv->mcu_req, 0); + mdelay(1); + gpio_set_value(priv->mcu_req, 1); + mdelay(1); + } + } + return true; +} + +/* + * bcm4773_bye - set mcu_req low to let chip go to sleep + * + */ +static void bcm4773_bye(struct bcm_spi_priv *priv) +{ + gpio_set_value(priv->mcu_req, 0); +} + + +static unsigned long init_time = 0; +static unsigned long clock_get_ms(void) +{ + struct timeval t; + unsigned long now; + + do_gettimeofday(&t); + now = t.tv_usec / 1000 + t.tv_sec * 1000; + if ( init_time == 0 ) + init_time = now; + + return now - init_time; +} + + +static void pk_log(char* dir, unsigned char* data, int len) +{ + char acB[960]; + char *p = acB; + int the_trans = len; + int i,j,n; + + char ic = 'D'; + char xc = dir[0] == 'r' || dir[0] == 'w' ? 'x':'X'; + + if (likely(!ssi_dbg)) + return; + + n = len; + p += snprintf(acB,sizeof(acB),"#%06ld%c %2s, %5d: ", + clock_get_ms() % 1000000,ic, dir, n); + + for (i=0, n=32; ispi; + xfer.len = len; + xfer.tx_buf = tx_buf; + xfer.rx_buf = rx_buf; + + /* Sync */ + pk_log("w", (unsigned char *)xfer.tx_buf, len); + ret = spi_sync(msg.spi, &msg); + pk_log("r", (unsigned char *)xfer.rx_buf, len); + + if (ret) + pr_err("[SSPBBD} spi_sync error, return=%d\n", ret); + + return ret; +} + + +static int bcm_ssi_tx(struct bcm_spi_priv *priv, int length) +{ + struct bcm_ssi_tx_frame *tx = priv->tx_buf; + struct bcm_ssi_rx_frame *rx = priv->rx_buf; + int bits_per_word = (length >= 255) ? CONFIG_SPI_DMA_BITS_PER_WORD : 8; + int ret; + + tx->cmd = SSI_WRITE_HD; + ret = bcm_spi_sync(priv, tx, rx, length+1, bits_per_word); /*1 for tx cmd */ + + return ret; +} + +static int bcm_ssi_rx( struct bcm_spi_priv *priv, size_t *length) +{ + struct bcm_ssi_tx_frame *tx = priv->tx_buf; + struct bcm_ssi_rx_frame *rx = priv->rx_buf; + + memset(tx, 0, MAX_SPI_FRAME_LEN); + tx->cmd = SSI_READ_HD; + rx->status = 0; + + if (bcm_spi_sync(priv, tx, rx, 2, 8)) /* 2 for rx status + len */ + return -1; + + /* Check Sanity */ + if (rx->status) { + pr_err("[SSPBBD] spi_sync error, status = 0x%02X\n", rx->status); + return -1; + } + + if (rx->len == 0) + { + rx->len = MAX_SPI_FRAME_LEN; + pr_err("[SSPBBD] rx->len is still read to 0. set MAX_SPI_FRAME_LEN\n"); + } + + /* limit max payload to 254 because of exynos3 bug */ + *length = min((unsigned char)(MAX_SPI_FRAME_LEN-2), rx->len); + if (bcm_spi_sync(priv, tx, rx, *length+2, 8)) /* 2 for rx status + len */ + return -1; + + /* Check Sanity */ + if (rx->status) { + pr_err("[SSPBBD] spi_sync error, status = 0x%02X\n", rx->status); + return -1; + } + + if (rx->len < *length) { + *length = rx->len; /* workaround */ + } + +#ifdef DEBUG_1HZ_STAT + bbd_update_stat(STAT_RX_SSI, *length); +#endif + return 0; +} + +void bcm_on_packet_recieved(void *_priv, unsigned char *data, unsigned int size) +{ + struct bcm_spi_priv *priv = (struct bcm_spi_priv *)_priv; + struct circ_buf *rd_circ = &priv->read_buf; + size_t written=0, avail = size; + + /* Copy into circ buffer */ + mutex_lock(&priv->rlock); + do { + size_t space_to_end = CIRC_SPACE_TO_END(rd_circ->head, rd_circ->tail, BCM_SPI_READ_BUF_SIZE); + size_t copied = min(space_to_end, avail); + + memcpy((void*) rd_circ->buf + rd_circ->head, data + written, copied); + avail -= copied; + written += copied; + rd_circ->head = (rd_circ->head + copied) & (BCM_SPI_READ_BUF_SIZE-1); + + } while (avail>0 && CIRC_SPACE(rd_circ->head, rd_circ->tail, BCM_SPI_READ_BUF_SIZE)); + mutex_unlock(&priv->rlock); + wake_up(&priv->poll_wait); + + if (avail>0) + pr_err("[SSPBBD]: input overrun error by %zd bytes!\n", avail); +} + +static void bcm_rxtx_work( struct work_struct *work ) +{ +#ifdef DEBUG_1HZ_STAT + u64 ts_rx_start = 0; + u64 ts_rx_end = 0; +#endif + struct bcm_spi_priv *priv = container_of(work, struct bcm_spi_priv, rxtx_work); + struct circ_buf *wr_circ = &priv->write_buf; + + if (!bcm4773_hello(priv)) { + pr_err("[SSPBBD]: %s timeout!!\n", __func__); + return; + } + + do { + /* Read first */ + if (gpio_get_value(priv->host_req)) { + size_t avail; +#ifdef DEBUG_1HZ_STAT + struct timespec ts; + if (stat1hz.ts_irq) { + ts = ktime_to_timespec(ktime_get_boottime()); + ts_rx_start = ts.tv_sec * 1000000000ULL + + ts.tv_nsec; + } +#endif + /* Receive SSI frame */ + if (bcm_ssi_rx(priv, &avail)) + break; + +#ifdef DEBUG_1HZ_STAT + bbd_update_stat(STAT_RX_SSI, avail); + if (ts_rx_start && !gpio_get_value(priv->host_req)) { + ts = ktime_to_timespec(ktime_get_boottime()); + ts_rx_end = ts.tv_sec * 1000000000ULL + + ts.tv_nsec; + } +#endif + /* Call BBD */ + bbd_parse_asic_data(priv->rx_buf->data, avail, NULL, priv); + } + + /* Next, write */ + if (CIRC_CNT(wr_circ->head, wr_circ->tail, BCM_SPI_WRITE_BUF_SIZE)) { + size_t written=0, avail=0; + + avail = CIRC_CNT(wr_circ->head, wr_circ->tail, BCM_SPI_WRITE_BUF_SIZE); + /* For big packet, we should align xfer size to DMA word size and burst size. + * That is, SSI payload + one byte command should be multiple of (DMA word size * burst size) + */ + if (avail > MAX_SPI_FRAME_LEN - 1) + avail = MAX_SPI_FRAME_LEN - 1; + + /* Copy from wr_circ the data */ + do { + size_t cnt_to_end = CIRC_CNT_TO_END(wr_circ->head, wr_circ->tail, BCM_SPI_WRITE_BUF_SIZE); + size_t copied = min(cnt_to_end, avail); + + memcpy(priv->tx_buf->data + written, wr_circ->buf + wr_circ->tail, copied); + avail -= copied; + written += copied; + wr_circ->tail = (wr_circ->tail + copied) & (BCM_SPI_WRITE_BUF_SIZE-1); + } while (avail>0); + + /* Transmit SSI frame */ + if (bcm_ssi_tx(priv, written)) + break; + + wake_up(&priv->poll_wait); +#ifdef DEBUG_1HZ_STAT + bbd_update_stat(STAT_TX_SSI, written); +#endif + } + + } while (gpio_get_value(priv->host_req) || CIRC_CNT(wr_circ->head, wr_circ->tail, BCM_SPI_WRITE_BUF_SIZE)); + + bcm4773_bye(priv); + + /* Enable irq */ + { + unsigned long int flags; + spin_lock_irqsave( &priv->irq_lock, flags); + + /* we dont' want to enable irq when going to suspending */ + if (!atomic_read(&priv->suspending)) + if (!atomic_xchg(&priv->irq_enabled, 1)) + enable_irq(priv->spi->irq); + + spin_unlock_irqrestore( &priv->irq_lock, flags); + } + +#ifdef DEBUG_1HZ_STAT + if (stat1hz.ts_irq && ts_rx_start && ts_rx_end) { + u64 lat = ts_rx_start - stat1hz.ts_irq; + u64 dur = ts_rx_end - ts_rx_start; + stat1hz.min_rx_lat = (lat < stat1hz.min_rx_lat) ? + lat : stat1hz.min_rx_lat; + stat1hz.max_rx_lat = (lat > stat1hz.max_rx_lat) ? + lat : stat1hz.max_rx_lat; + stat1hz.min_rx_dur = (dur < stat1hz.min_rx_dur) ? + dur : stat1hz.min_rx_dur; + stat1hz.max_rx_dur = (dur > stat1hz.max_rx_dur) ? + dur : stat1hz.max_rx_dur; + stat1hz.ts_irq = 0; + } +#endif +} + + +//-------------------------------------------------------------- +// +// IRQ Handler +// +//-------------------------------------------------------------- +static irqreturn_t bcm_irq_handler(int irq, void *pdata) +{ + struct bcm_spi_priv *priv = (struct bcm_spi_priv *) pdata; + + if (!gpio_get_value(priv->host_req)) + return IRQ_HANDLED; +#ifdef DEBUG_1HZ_STAT + { + struct timespec ts; + ts = ktime_to_timespec(ktime_get_boottime()); + stat1hz.ts_irq = ts.tv_sec * 1000000000ULL + ts.tv_nsec; + } +#endif + /* Disable irq */ + spin_lock(&priv->irq_lock); + if (atomic_xchg(&priv->irq_enabled, 0)) + disable_irq_nosync(priv->spi->irq); + + spin_unlock(&priv->irq_lock); + + /* we don't want to queue work in suspending and shutdown */ + if (!atomic_read(&priv->suspending)) + queue_work(priv->serial_wq, &priv->rxtx_work); + + wake_lock_timeout(&priv->bcm_wake_lock, HZ/2); + return IRQ_HANDLED; +} + + +//-------------------------------------------------------------- +// +// SPI driver operations +// +//-------------------------------------------------------------- + +static void bcm_spi_shutdown(struct spi_device *spi) +{ + struct bcm_spi_priv *priv = (struct bcm_spi_priv*) spi_get_drvdata(spi); + unsigned long int flags; + + printk("[SSPBBD]: %s ++ \n", __func__); + + atomic_set(&priv->suspending, 1); + + /* Disable irq */ + spin_lock_irqsave( &priv->irq_lock, flags); + if (atomic_xchg(&priv->irq_enabled, 0)) + disable_irq_nosync(spi->irq); + + spin_unlock_irqrestore( &priv->irq_lock, flags); + + printk("[SSPBBD]: %s ** \n", __func__); + + flush_workqueue(priv->serial_wq ); + destroy_workqueue( priv->serial_wq ); + + printk("[SSPBBD]: %s -- \n", __func__); +} + +static int bcm_spi_probe(struct spi_device *spi) +{ + int host_req, mcu_req, mcu_resp; + struct bcm_spi_priv *priv; + int ret; + + /* Check GPIO# */ +#ifndef CONFIG_OF + pr_err("[SSPBBD]: Check platform_data for bcm device\n"); +#else + if (!spi->dev.of_node) { + pr_err("[SSPBBD]: Failed to find of_node\n"); + goto err_exit; + } +#endif + + host_req = of_get_named_gpio(spi->dev.of_node, "ssp-irq", 0); + mcu_req = of_get_named_gpio(spi->dev.of_node, "ssp-mcu-req", 0); + mcu_resp = of_get_named_gpio(spi->dev.of_node, "ssp-mcu-resp", 0); + + pr_info("[SSPBBD] ssp-host-req=%d, ssp-mcu_req=%d, ssp-mcu-resp=%d\n", host_req, mcu_req, mcu_resp); + if (host_req<0 || mcu_req<0 || mcu_resp<0) { + pr_err("[SSPBBD]: GPIO value not correct\n"); + goto err_exit; + } + + /* Check IRQ# */ + spi->irq = gpio_to_irq(host_req); + if (spi->irq < 0) { + pr_err("[SSPBBD]: irq=%d for host_req=%d not correct\n", spi->irq, host_req); + goto err_exit; + } + + /* Config GPIO */ + ret = gpio_request(mcu_req, "MCU REQ"); + if (ret){ + pr_err("[SSPBBD]: failed to request MCU REQ, ret:%d", ret); + goto err_exit; + } + ret = gpio_direction_output(mcu_req, 0); + if (ret) { + pr_err("[SSPBBD]: failed set MCU REQ as input mode, ret:%d", ret); + goto err_exit; + } + ret = gpio_request(mcu_resp, "MCU RESP"); + if (ret){ + pr_err("[SSPBBD]: failed to request MCU RESP, ret:%d", ret); + goto err_exit; + } + ret = gpio_direction_input(mcu_resp); + if (ret) { + pr_err("[SSPBBD]: failed set MCU RESP as input mode, ret:%d", ret); + goto err_exit; + } + + /* Alloc everything */ + priv = (struct bcm_spi_priv*) kmalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + pr_err("[SSPBBD]: Failed to allocate memory for the BCM SPI driver\n"); + goto err_exit; + } + + memset(priv, 0, sizeof(*priv)); + + priv->spi = spi; + + priv->tx_buf = kmalloc(sizeof(struct bcm_ssi_tx_frame), GFP_KERNEL); + priv->rx_buf = kmalloc(sizeof(struct bcm_ssi_rx_frame), GFP_KERNEL); + if (!priv->tx_buf || !priv->rx_buf) { + pr_err("[SSPBBD]: Failed to allocate xfer buffer. tx_buf=%p, rx_buf=%p\n", + priv->tx_buf, priv->rx_buf); + goto free_mem; + } + + priv->clk = devm_clk_get(&spi->dev, "xtal"); + if (IS_ERR(priv->clk)) { + pr_err("[SSPBBD] failed to get clock\n"); + goto free_mem; + } + + ret = clk_prepare_enable(priv->clk); + if (ret) { + goto free_mem; + pr_err("[SSPBBD] failed to enable clock\n"); + } + + priv->serial_wq = alloc_workqueue("bcm4773_wq", WQ_HIGHPRI|WQ_UNBOUND|WQ_MEM_RECLAIM, 1); + if (!priv->serial_wq) { + pr_err("[SSPBBD]: Failed to allocate workqueue\n"); + goto free_mem; + } + + /* Request IRQ */ + ret = request_irq(spi->irq, bcm_irq_handler, IRQF_TRIGGER_HIGH, "ttyBCM", priv); + if (ret) { + pr_err("[SSPBBD]: Failed to register BCM4774 SPI TTY IRQ %d.\n",spi->irq); + goto free_wq; + } + + disable_irq(spi->irq); + + pr_notice("[SSPBBD]: Probe OK. ssp-host-req=%d, irq=%d, priv=0x%p\n", host_req, spi->irq, priv); + + /* Register misc device */ + priv->misc.minor = MISC_DYNAMIC_MINOR; + priv->misc.name = "ttyBCM"; + priv->misc.fops = &bcm_spi_fops; + + ret = misc_register(&priv->misc); + if (ret) { + pr_err("[SSPBBD]: Failed to register bcm_gps_spi's misc dev. err=%d\n", ret); + goto free_irq; + } + + /* Set driver data */ + spi_set_drvdata(spi, priv); + + /* Init - miscdev stuff */ + init_waitqueue_head(&priv->poll_wait); + priv->read_buf.buf = priv->_read_buf; + priv->write_buf.buf = priv->_write_buf; + mutex_init(&priv->rlock); + mutex_init(&priv->wlock); + priv->busy = false; + + /* Init - work */ + INIT_WORK(&priv->rxtx_work, bcm_rxtx_work); + + /* Init - irq stuff */ + spin_lock_init(&priv->irq_lock); + atomic_set(&priv->irq_enabled, 0); + atomic_set(&priv->suspending, 0); + + /* Init - gpios */ + priv->host_req = host_req; + priv->mcu_req = mcu_req; + priv->mcu_resp = mcu_resp; + + /* Init - etc */ + wake_lock_init(&priv->bcm_wake_lock, WAKE_LOCK_SUSPEND, "bcm_spi_wake_lock"); + + g_bcm_gps = priv; + /* Init BBD & SSP */ + bbd_init(&spi->dev); + + return 0; + +free_irq: + if (spi->irq) + free_irq( spi->irq, priv ); +free_wq: + if (priv->serial_wq) + destroy_workqueue(priv->serial_wq); +free_mem: + if (priv->tx_buf) + kfree(priv->tx_buf); + if (priv->rx_buf) + kfree(priv->rx_buf); + if (priv) + kfree(priv); +err_exit: + return -ENODEV; +} + + +static int bcm_spi_remove(struct spi_device *spi) +{ + struct bcm_spi_priv *priv = (struct bcm_spi_priv*) spi_get_drvdata(spi); + unsigned long int flags; + + pr_notice("[SSPBBD]: %s : called\n", __func__); + + atomic_set(&priv->suspending, 1); + + /* Disable irq */ + spin_lock_irqsave(&priv->irq_lock, flags); + if (atomic_xchg(&priv->irq_enabled, 0)) + disable_irq_nosync(spi->irq); + + spin_unlock_irqrestore( &priv->irq_lock, flags); + + /* Flush work */ + flush_workqueue(priv->serial_wq); + destroy_workqueue(priv->serial_wq); + + /* Free everything */ + free_irq( spi->irq, priv ); + kfree(priv->tx_buf); + kfree(priv->rx_buf); + kfree( priv ); + + g_bcm_gps = NULL; + + return 0; +} + +void bcm4773_debug_info(void) +{ + int pin_ttyBCM, pin_MCU_REQ, pin_MCU_RESP; + int irq_enabled, irq_count; + + pin_ttyBCM = gpio_get_value(g_bcm_gps->host_req); + pin_MCU_REQ = gpio_get_value(g_bcm_gps->mcu_req); + pin_MCU_RESP = gpio_get_value(g_bcm_gps->mcu_resp); + + irq_enabled = atomic_read(&g_bcm_gps->irq_enabled); + irq_count = kstat_irqs_cpu(g_bcm_gps->spi->irq, 0); + + printk("[SSPBBD]: %s pin_ttyBCM:%d, pin_MCU_REQ:%d, pin_MCU_RESP:%d\n", + __func__, pin_ttyBCM, pin_MCU_REQ, pin_MCU_RESP); + printk("[SSPBBD]: %s irq_enabled:%d, irq_count:%d\n", + __func__, irq_enabled, irq_count); +} + + +static const struct spi_device_id bcm_spi_id[] = { + {"ssp", 0}, + {} +}; +MODULE_DEVICE_TABLE(spi, bcm_spi_id); + +#ifdef CONFIG_OF +static struct of_device_id match_table[] = { + { .compatible = "ssp,BCM4773",}, + { .compatible = "ssp,BCM4774",}, + {}, +}; +#endif + +#ifdef CONFIG_PM_SLEEP +static int bcm_spi_suspend(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + struct bcm_spi_priv *priv = (struct bcm_spi_priv*) spi_get_drvdata(spi); + unsigned long int flags; + + printk("[SSPBBD]: %s ++ \n", __func__); + + atomic_set(&priv->suspending, 1); + + /* Disable irq */ + spin_lock_irqsave(&priv->irq_lock, flags); + if (atomic_xchg(&priv->irq_enabled, 0)) + disable_irq_nosync(spi->irq); + + spin_unlock_irqrestore(&priv->irq_lock, flags); + + flush_workqueue(priv->serial_wq); + + return 0; +} + +static int bcm_spi_resume(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + struct bcm_spi_priv *priv = (struct bcm_spi_priv*) spi_get_drvdata(spi); + unsigned long int flags; + + pr_info("[SSPBBD]: %s ++ \n", __func__); + atomic_set(&priv->suspending, 0); + + /* Enable irq */ + spin_lock_irqsave(&priv->irq_lock, flags); + if (!atomic_xchg(&priv->irq_enabled, 1)) + enable_irq(spi->irq); + + spin_unlock_irqrestore( &priv->irq_lock, flags); + + pr_info("[SSPBBD]: %s -- \n", __func__); + return 0; +} + +static const struct dev_pm_ops bcm_spi_pm = { + SET_SYSTEM_SLEEP_PM_OPS(bcm_spi_suspend, bcm_spi_resume) +}; +#endif /* CONFIG_PM_SLEEP */ + +static struct spi_driver bcm_spi_driver = +{ + .id_table = bcm_spi_id, + .probe = bcm_spi_probe, + .remove = bcm_spi_remove, + .shutdown = bcm_spi_shutdown, + .driver = { + .name = "ssp", + .owner = THIS_MODULE, + .pm = &bcm_spi_pm, +#ifdef CONFIG_OF + .of_match_table = match_table +#endif + }, +}; + + +module_spi_driver(bcm_spi_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("BCM SPI/SSI Driver"); -- cgit v1.2.3