summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCheng Chang <chengcha@google.com>2022-08-08 10:00:56 +0000
committerCheng Chang <chengcha@google.com>2022-08-30 08:36:19 +0000
commit21049f89bd1a3459b29c5b11f8c6feb4292d5673 (patch)
tree320f287f642382201f623555760223986256be8b
parent354991cb719be04b2bd5ca2953f7766291bb3cd2 (diff)
downloadbcm47765-21049f89bd1a3459b29c5b11f8c6feb4292d5673.tar.gz
bcm47765: add bcm47765 driver
move from kernel tree kernel/drivers/misc/bbdpl to new repository out-of-tree google-modules/gps/broadcom/bcm47765 Commit-Topic: extern_gps_module Bug: 238390923 Test: factory functions are workable Signed-off-by: Cheng Chang <chengcha@google.com> Change-Id: I5955ecfae3b0cdabe47e319f456502791078e325
-rw-r--r--Kbuild2
-rw-r--r--Kconfig6
-rw-r--r--Makefile24
-rw-r--r--bbd.c884
-rw-r--r--bbd.h203
-rw-r--r--bcm_gps_regs.c323
-rw-r--r--bcm_gps_spi.c1566
-rw-r--r--bcm_gps_spi.h237
8 files changed, 3245 insertions, 0 deletions
diff --git a/Kbuild b/Kbuild
new file mode 100644
index 0000000..df32ab5
--- /dev/null
+++ b/Kbuild
@@ -0,0 +1,2 @@
+obj-$(CONFIG_BCM_GPS_SPI_DRIVER) += bcm47765.o
+bcm47765-objs+= bcm_gps_regs.o bcm_gps_spi.o bbd.o \ No newline at end of file
diff --git a/Kconfig b/Kconfig
new file mode 100644
index 0000000..e8bd67c
--- /dev/null
+++ b/Kconfig
@@ -0,0 +1,6 @@
+config BCM_GPS_SPI_DRIVER
+ tristate "BRCM GPS SPI driver"
+ depends on SPI
+ help
+ Support for BRCM GPS SPI driver.
+
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..03800f5
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,24 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for gps/broadcom/bcm47765 devices
+#
+
+KERNEL_SRC ?= /lib/modules/$(shell uname -r)/build
+M ?= $(shell pwd)
+
+
+KBUILD_OPTIONS += CONFIG_BCM_GPS_SPI_DRIVER=m
+EXTRA_CFLAGS += -DCONFIG_BCM_GPS_SPI_DRIVER
+
+include $(KERNEL_SRC)/../private/google-modules/soc/gs/Makefile.include
+
+all:
+ $(MAKE) -C $(KERNEL_SRC) M=$(M) \
+ $(KBUILD_OPTIONS) EXTRA_CFLAGS="$(EXTRA_CFLAGS)" KBUILD_EXTRA_SYMBOLS="$(EXTRA_SYMBOLS)" modules
+
+modules_install:
+ @echo "$(MAKE) INSTALL_MOD_STRIP=1 M=$(M) -C $(KERNEL_SRC) modules_install"
+ @$(MAKE) INSTALL_MOD_STRIP=1 M=$(M) -C $(KERNEL_SRC) modules_install
+
+clean:
+ $(MAKE) -C $(KERNEL_SRC) M=$(M) clean $(KBUILD_OPTIONS)
diff --git a/bbd.c b/bbd.c
new file mode 100644
index 0000000..b5e0f7e
--- /dev/null
+++ b/bbd.c
@@ -0,0 +1,884 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright 2014 Broadcom Corporation
+ *
+ * The BBD (Broadcom Bridge Driver)
+ *
+ */
+
+/* TODO: Use dev_*() calls instead */
+#define pr_fmt(fmt) "GPSBBD: " fmt
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/sched.h>
+#include <linux/poll.h>
+#include <linux/wait.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/suspend.h>
+#include <linux/notifier.h>
+#include <linux/of.h>
+#include "bbd.h"
+
+#if IS_ENABLED(CONFIG_SENSORS_SSP)
+#include <linux/spi/spi.h> /* Needs because SSP is tightly coupled with SPI */
+extern struct spi_driver *pssp_driver;
+
+static const struct spi_device dummy_spi = {
+ .dev = {
+ .init_name = "mock",
+ },
+};
+#endif
+
+/* Character device names of BBD */
+static const char *bbd_dev_name[BBD_DEVICE_INDEX] = {
+ "bbd_shmd",
+ "bbd_sensor",
+ "bbd_control",
+ "bbd_patch",
+#ifdef BBD_PWR_STATUS
+ "bbd_pwrstat",
+#endif /* BBD_PWR_STATUS */
+};
+
+/* Embedded patch file provided as /dev/bbd_patch */
+static const unsigned char bbd_patch[] = {
+};
+
+#ifdef CONFIG_SENSORS_BBD_LEGACY_PATCH
+static const unsigned char legacy_bbd_patch[] = {
+#include "legacy_bbd_patch_file.h"
+};
+#else
+static const unsigned char legacy_bbd_patch[] = {
+ "mock",
+};
+#endif
+
+/* Function to push read data into any bbd device's read buf */
+ssize_t bbd_on_read(struct bbd_device *bbd, 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"
+};
+
+/*
+ * BBD 1hz Statistics Functions
+ */
+
+static void bbd_init_stat(struct bbd_device *bbd)
+{
+ struct bbd_stat *stat1hz = &bbd->stat1hz;
+
+ memset(stat1hz, 0, sizeof(*stat1hz));
+
+ stat1hz->bbd = bbd;
+ 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(struct bbd_device *bbd)
+{
+ struct bbd_stat *stat1hz = &bbd->stat1hz;
+
+ bbd_disable_stat(bbd);
+ if (stat1hz->workq) {
+ flush_workqueue(stat1hz->workq);
+ destroy_workqueue(stat1hz->workq);
+ stat1hz->workq = 0;
+ }
+}
+
+static void bbd_report_stat(struct work_struct *work)
+{
+ const int MAX_SIZE = 512;
+ char *buf;
+ int i;
+ int count = 0;
+ struct bbd_stat *stat1hz = container_of(work, struct bbd_stat, work);
+
+ buf = kvmalloc_array(MAX_SIZE, sizeof(char), GFP_KERNEL);
+ if (!buf)
+ return;
+
+ count += scnprintf(buf + count, MAX_SIZE - count, "BBD:");
+ for (i = 0; i < STAT_MAX; i++) {
+ count += scnprintf(buf + count, MAX_SIZE - count, " %s=%llu",
+ bbd_stat_name[i], stat1hz->stat[i]);
+ }
+ count += scnprintf(buf + count, MAX_SIZE - count,
+ " rxlat_min=%llu rxlat_max=%llu",
+ stat1hz->min_rx_lat, stat1hz->max_rx_lat);
+ count += scnprintf(buf + count, MAX_SIZE - count,
+ " 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(stat1hz->bbd, BBD_MINOR_CONTROL, buf, count);
+
+ 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;
+
+ kvfree(buf);
+}
+
+static void bbd_stat_timer_func(struct timer_list *t)
+{
+ struct bbd_stat *stat1hz = container_of(t, struct bbd_stat, timer);
+ if (stat1hz->workq)
+ queue_work(stat1hz->workq, &stat1hz->work);
+ mod_timer(&stat1hz->timer, jiffies + HZ);
+}
+
+void bbd_update_stat(struct bbd_device *bbd,
+ int idx, unsigned int count)
+{
+ struct bbd_stat *stat1hz = &bbd->stat1hz;
+ stat1hz->stat[idx] += count;
+}
+EXPORT_SYMBOL_GPL(bbd_update_stat);
+
+void bbd_enable_stat(struct bbd_device *bbd)
+{
+ struct bbd_stat *stat1hz = &bbd->stat1hz;
+ if (stat1hz->enabled) {
+ dev_dbg(bbd->dev, "1HZ stat already enable. skipping.\n");
+ return;
+ }
+
+ INIT_WORK(&stat1hz->work, bbd_report_stat);
+ timer_setup(&stat1hz->timer, bbd_stat_timer_func, 0);
+ mod_timer(&stat1hz->timer, jiffies + HZ);
+ stat1hz->enabled = true;
+}
+EXPORT_SYMBOL_GPL(bbd_enable_stat);
+
+void bbd_disable_stat(struct bbd_device *bbd)
+{
+ struct bbd_stat *stat1hz = &bbd->stat1hz;
+ if (!stat1hz->enabled) {
+ dev_dbg(bbd->dev, "1HZ stat already disabled. skipping.\n");
+ return;
+ }
+ del_timer_sync(&stat1hz->timer);
+ cancel_work_sync(&stat1hz->work);
+ stat1hz->enabled = false;
+}
+EXPORT_SYMBOL_GPL(bbd_disable_stat);
+#endif /* DEBUG_1HZ_STAT */
+
+
+static void bbd_log_hex(bool log_enabled, const char *prefix_str,
+ const unsigned char *buf, size_t len)
+{
+ if (likely(!log_enabled))
+ return;
+
+ if (!prefix_str)
+ prefix_str = "...unknown...";
+
+ print_hex_dump(KERN_INFO, prefix_str, DUMP_PREFIX_NONE, 32, 1,
+ buf, len, false);
+}
+
+/**
+ * bbd_control - Handles command string from lhd
+ */
+static ssize_t bbd_control(struct bbd_device *bbd, const char *buf, ssize_t len)
+{
+#ifdef DEBUG_1HZ_STAT
+ pr_info("%s\n", buf);
+#endif
+
+ if (strcmp(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 (strcmp(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 (strcmp(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 (strcmp(buf, BBD_CTRL_DEBUG_OFF)) {
+ bbd->db = false;
+#if IS_ENABLED(CONFIG_SENSORS_SSP)
+ } else if (!strcmp(buf, SSP_DEBUG_ON)) {
+ bbd->ssp_dbg = true;
+ bbd->ssp_pkt_dbg = true;
+ } else if (!strstr(buf, SSP_DEBUG_OFF)) {
+ bbd->ssp_dbg = false;
+ bbd->ssp_pkt_dbg = false;
+#endif
+ } else if (strcmp(buf, SSI_DEBUG_ON)) {
+ bcm_ssi_debug(bbd->dev, 0, true);
+ } else if (strcmp(buf, SSI_DEBUG_OFF)) {
+ bcm_ssi_debug(bbd->dev, 0, false);
+ } else if (strcmp(buf, PZC_DEBUG_ON)) {
+ bcm_ssi_debug(bbd->dev, 1, true);
+ } else if (strcmp(buf, PZC_DEBUG_OFF)) {
+ bcm_ssi_debug(bbd->dev, 1, false);
+ } else if (strcmp(buf, RNG_DEBUG_ON)) {
+ bcm_ssi_debug(bbd->dev, 2, true);
+ } else if (strcmp(buf, RNG_DEBUG_OFF)) {
+ bcm_ssi_debug(bbd->dev, 2, false);
+#ifdef BBD_PWR_STATUS
+ } else if (!strcmp(buf, GPSD_CORE_ON)) {
+ u64 now = ktime_to_us(ktime_get_boottime());
+ struct gnss_pwrstats *pwrstats =
+ &bbd->priv[BBD_MINOR_PWRSTAT].pwrstats;
+
+ mutex_lock(&bbd->priv[BBD_MINOR_PWRSTAT].lock);
+
+ pwrstats->gps_stat = STAT_GPS_ON;
+ pwrstats->gps_on_cnt++;
+ pwrstats->gps_on_entry = now;
+ pwrstats->gps_off_exit = now;
+ pwrstats->gps_off_duration +=
+ pwrstats->gps_off_exit - pwrstats->gps_off_entry;
+
+ mutex_unlock(&bbd->priv[BBD_MINOR_PWRSTAT].lock);
+ } else if (!strcmp(buf, GPSD_CORE_OFF)) {
+ u64 now = ktime_to_us(ktime_get_boottime());
+ struct gnss_pwrstats *pwrstats =
+ &bbd->priv[BBD_MINOR_PWRSTAT].pwrstats;
+
+ mutex_lock(&bbd->priv[BBD_MINOR_PWRSTAT].lock);
+
+ pwrstats->gps_stat = STAT_GPS_OFF;
+ pwrstats->gps_off_cnt++;
+ pwrstats->gps_off_entry = now;
+ pwrstats->gps_on_exit = now;
+ pwrstats->gps_on_duration +=
+ pwrstats->gps_on_exit - pwrstats->gps_on_entry;
+
+ mutex_unlock(&bbd->priv[BBD_MINOR_PWRSTAT].lock);
+#endif /* BBD_PWR_STATUS */
+ } 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
+ */
+static int bbd_common_open(struct inode *inode, struct file *filp)
+{
+ struct cdev *cdev = inode->i_cdev;
+ struct bbd_cdev_priv *bbd_cdev =
+ container_of(cdev, struct bbd_cdev_priv, cdev);
+
+ unsigned int minor = iminor(inode);
+ struct circ_buf *circ = &bbd_cdev->read_buf;
+
+ if (minor >= BBD_DEVICE_INDEX)
+ return -ENODEV;
+
+ if (bbd_cdev->busy && minor != BBD_MINOR_CONTROL)
+ return -EBUSY;
+
+ bbd_cdev->busy = true;
+
+ /* Reset circ buffer */
+ circ->head = circ->tail = 0;
+
+ filp->private_data = bbd_cdev->bbd;
+
+ return 0;
+}
+
+/**
+ * bbd_common_release - Common release function for BBD devices
+ */
+static int bbd_common_release(struct inode *inode, struct file *filp)
+{
+ struct bbd_device *bbd = filp->private_data;
+ unsigned int minor = iminor(inode);
+
+ if (minor >= BBD_DEVICE_INDEX) {
+ WARN_ON(minor >= BBD_DEVICE_INDEX);
+ return 0;
+ }
+#ifdef DEBUG_1HZ_STAT
+ pr_info("%s", bbd_dev_name[minor]);
+#endif
+
+ 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)
+{
+ struct bbd_device *bbd = filp->private_data;
+ unsigned int minor = iminor(filp->f_path.dentry->d_inode);
+ struct circ_buf *circ = &bbd->priv[minor].read_buf;
+ size_t rd_size = 0;
+
+ if (minor >= BBD_DEVICE_INDEX) {
+ WARN_ON(minor >= BBD_DEVICE_INDEX);
+ goto out;
+ }
+
+ 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);
+
+ if (copy_to_user(buf + rd_size,
+ (void *) circ->buf + circ->tail, copied)) {
+ mutex_unlock(&bbd->priv[minor].lock);
+ rd_size = -EFAULT;
+ goto out;
+ }
+
+ 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->db, bbd_dev_name[minor], buf, rd_size);
+
+#ifdef DEBUG_1HZ_STAT
+ bbd_update_stat(bbd, STAT_RX_LHD, rd_size);
+#endif
+out:
+ 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)
+{
+ struct bbd_device *bbd = filp->private_data;
+ unsigned int minor = iminor(filp->f_path.dentry->d_inode);
+
+ if (size >= BBD_BUFF_SIZE)
+ return -EFAULT;
+
+ if (copy_from_user(bbd->priv[minor].write_buf, buf, size)) {
+ pr_err("failed to copy from user.\n");
+ return -EFAULT;
+ }
+
+#ifdef DEBUG_1HZ_STAT
+ bbd_update_stat(bbd, 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)
+{
+ struct bbd_device *bbd = filp->private_data;
+ unsigned int minor = iminor(filp->f_path.dentry->d_inode);
+ struct circ_buf *circ = &bbd->priv[minor].read_buf;
+ unsigned int mask = 0;
+
+ if (minor >= BBD_DEVICE_INDEX)
+ return POLLNVAL;
+
+ 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_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)
+{
+ struct bbd_device *bbd = filp->private_data;
+ 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, 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;
+ struct bbd_device *bbd = filp->private_data;
+ const unsigned char *curr_bbd_patch;
+ size_t bbd_patch_sz;
+
+ if (bbd->legacy_patch) {
+ curr_bbd_patch = legacy_bbd_patch;
+ bbd_patch_sz = sizeof(legacy_bbd_patch);
+ } else {
+ curr_bbd_patch = bbd_patch;
+ bbd_patch_sz = sizeof(bbd_patch);
+ }
+
+ if (offset >= bbd_patch_sz) { /* signal EOF */
+ *ppos = 0;
+ return 0;
+ }
+ if (offset+size > bbd_patch_sz)
+ rd_size = bbd_patch_sz - offset;
+ if (copy_to_user(buf, curr_bbd_patch + offset, rd_size))
+ rd_size = -EFAULT;
+ else
+ *ppos = filp->f_pos + rd_size;
+
+ return rd_size;
+}
+
+#ifdef BBD_PWR_STATUS
+#define BBD_MAX_PWRSTAT_SIZE 512
+
+ssize_t bbd_pwrstat_read(
+ struct file *filp, char __user *buf, size_t size, loff_t *ppos)
+{
+ const int MAX_SIZE = BBD_MAX_PWRSTAT_SIZE;
+ char buf2[BBD_MAX_PWRSTAT_SIZE];
+ int ret = 0;
+ u64 now, gps_on_dur, gps_off_dur;
+ struct bbd_device *bbd = filp->private_data;
+ struct gnss_pwrstats *pwrstats = &bbd->priv[BBD_MINOR_PWRSTAT].pwrstats;
+
+ mutex_lock(&bbd->priv[BBD_MINOR_PWRSTAT].lock);
+
+ now = ktime_to_us(ktime_get_boottime());
+ if (pwrstats->gps_stat == STAT_GPS_ON) {
+ gps_on_dur = pwrstats->gps_on_duration +
+ (now - pwrstats->gps_on_entry);
+ gps_off_dur = pwrstats->gps_off_duration;
+ } else {
+ gps_on_dur = pwrstats->gps_on_duration;
+ gps_off_dur = pwrstats->gps_off_duration +
+ (now - pwrstats->gps_off_entry);
+ }
+
+ ret += scnprintf(buf2 + ret, MAX_SIZE - ret, "GPS_ON:\n");
+ ret += scnprintf(buf2 + ret, MAX_SIZE - ret,
+ "count: 0x%0llx\n", pwrstats->gps_on_cnt);
+ ret += scnprintf(buf2 + ret, MAX_SIZE - ret,
+ "duration_usec: 0x%0llx\n", gps_on_dur);
+ ret += scnprintf(buf2 + ret, MAX_SIZE - ret,
+ "last_entry_timestamp_usec: 0x%0llx\n",
+ pwrstats->gps_on_entry);
+ ret += scnprintf(buf2 + ret, MAX_SIZE - ret,
+ "last_exit_timestamp_usec: %0lld\n",
+ pwrstats->gps_on_exit);
+
+ ret += scnprintf(buf2 + ret, MAX_SIZE - ret, "GPS_OFF:\n");
+ ret += scnprintf(buf2 + ret, MAX_SIZE - ret,
+ "count: 0x%0llx\n", pwrstats->gps_off_cnt);
+ ret += scnprintf(buf2 + ret, MAX_SIZE - ret,
+ "duration_usec: 0x%0llx\n", gps_off_dur);
+ ret += scnprintf(buf2 + ret, MAX_SIZE - ret,
+ "last_entry_timestamp_usec: 0x%0llx\n",
+ pwrstats->gps_off_entry);
+ ret += scnprintf(buf2 + ret, MAX_SIZE - ret,
+ "last_exit_timestamp_usec: 0x%0llx\n",
+ pwrstats->gps_off_exit);
+
+ mutex_unlock(&bbd->priv[BBD_MINOR_PWRSTAT].lock);
+
+ return simple_read_from_buffer(buf, size, ppos, buf2, ret);
+}
+#endif /* BBD_PWR_STATUS */
+/**
+ *
+ * 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(struct bbd_device *bbd, 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->db, 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) < size) {
+ pr_err("%s read buffer full. Dropping %zd bytes\n",
+ bbd_dev_name[minor], size);
+ goto skip;
+ }
+
+ /*
+ * Copy into circ buffer from linear buffer
+ * We may require 2 copies from [head..end] and [start..head]
+ */
+ do {
+ size_t space_to_end = CIRC_SPACE_TO_END(
+ circ->head, circ->tail, BBD_BUFF_SIZE);
+ size_t copied = min(space_to_end, size);
+
+ memcpy(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;
+}
+
+/*
+ * PM Operation Functions
+ */
+
+static int bbd_suspend(struct bbd_device *bbd, pm_message_t state)
+{
+
+#ifdef DEBUG_1HZ_STAT
+ bbd_disable_stat(bbd);
+#endif
+#if IS_ENABLED(CONFIG_SENSORS_SSP)
+ /* Call SSP suspend */
+ if (pssp_driver->driver.pm && pssp_driver->driver.pm->suspend)
+ pssp_driver->driver.pm->suspend(&dummy_spi.dev);
+ /* Per (b/203008378#comment3), the following delay is specified to the
+ * chips sensor hub system. Moving the delay to within the define removes
+ * it when the features is not used. Updating to a non-blocking sleep
+ * instead of a busy wait.
+ */
+ msleep(20);
+#endif
+
+ return 0;
+}
+
+static int bbd_resume(struct bbd_device *bbd)
+{
+#if IS_ENABLED(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(bbd);
+#endif
+ return 0;
+}
+
+static int bbd_notifier(
+ struct notifier_block *nb, unsigned long event, void *data)
+{
+ struct bbd_device *bbd = container_of(nb, struct bbd_device, notifier);
+ pm_message_t state = {0};
+
+ switch (event) {
+ case PM_SUSPEND_PREPARE:
+ state.event = event;
+ bbd_suspend(bbd, state);
+ break;
+ case PM_POST_SUSPEND:
+ bbd_resume(bbd);
+ break;
+ }
+ return NOTIFY_OK;
+}
+
+/*
+ * BBD Device Init and Exit Functions
+ */
+
+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,
+ },
+#ifdef BBD_PWR_STATUS
+ /* bbd power file operations */
+ {
+ .owner = THIS_MODULE,
+ .open = bbd_common_open,
+ .release = bbd_common_release,
+ .read = bbd_pwrstat_read,
+ .write = NULL,
+ .poll = NULL,
+ },
+#endif /* BBD_PWR_STATUS */
+};
+
+
+struct bbd_device *bbd_init(struct device *dev, bool legacy_patch)
+{
+ int minor, ret = -ENOMEM;
+ struct timespec64 ts1;
+ unsigned long start, elapsed;
+ struct bbd_device *bbd;
+
+ ts1 = ktime_to_timespec64(ktime_get_boottime());
+ start = ts1.tv_sec * 1000000000ULL + ts1.tv_nsec;
+
+ /* Initialize BBD device */
+ bbd = kvzalloc(sizeof(struct bbd_device), GFP_KERNEL);
+ if (!bbd) {
+ ret = -ENOMEM;
+ goto exit;
+ }
+
+ bbd->dev = dev;
+ bbd->legacy_patch = legacy_patch;
+
+ /*
+ * Allocate device major number for this BBD device
+ * Starts minor number from 1 to ignore BBD SHMD device
+ */
+ ret = alloc_chrdev_region(&bbd->dev_num, 1, BBD_DEVICE_INDEX, "bbd");
+ if (ret) {
+ pr_err("failed to alloc_chrdev_region(), ret=%d", ret);
+ goto exit;
+ }
+
+ /* Create class which is required for device_create() */
+ bbd->class = class_create(THIS_MODULE, "bbd");
+ if (IS_ERR(bbd->class)) {
+ pr_err("failed to create class bbd\n");
+ goto exit;
+ }
+
+ /* Create BBD char devices */
+ for (minor = 0; minor < BBD_DEVICE_INDEX; minor++) {
+ struct bbd_cdev_priv *bbd_cdev = &bbd->priv[minor];
+
+ /* Init buf, waitqueue, mutex, etc. */
+ bbd_cdev->bbd = bbd;
+ bbd_cdev->devno = MKDEV(MAJOR(bbd->dev_num), minor);
+ bbd_cdev->read_buf.buf = bbd_cdev->_read_buf;
+
+ init_waitqueue_head(&bbd_cdev->poll_wait);
+ mutex_init(&bbd_cdev->lock);
+
+#ifdef BBD_PWR_STATUS
+ /* Initial power stats */
+ memset(&bbd->priv[minor].pwrstats, 0, sizeof(struct gnss_pwrstats));
+ bbd->priv[minor].pwrstats.gps_off_cnt = 1;
+#endif /* BBD_PWR_STATUS */
+
+ /* Don't register /dev/bbd_shmd */
+ if (minor == BBD_MINOR_SHMD)
+ continue;
+
+ /*
+ * Register cdev which relates above
+ * device number with this BBD device
+ */
+ cdev_init(&bbd_cdev->cdev, &bbd_fops[minor]);
+ bbd_cdev->cdev.owner = THIS_MODULE;
+ bbd_cdev->cdev.ops = &bbd_fops[minor];
+ ret = cdev_add(&bbd_cdev->cdev, bbd_cdev->devno, 1);
+ if (ret) {
+ pr_err("failed to cdev_add() \"%s\", ret=%d",
+ bbd_dev_name[minor], ret);
+ unregister_chrdev_region(bbd_cdev->devno, 1);
+ goto free_class;
+ }
+
+ /* Let it show in FS */
+ bbd_cdev->dev = device_create(bbd->class, NULL,
+ bbd_cdev->devno, bbd, "%s", bbd_dev_name[minor]);
+ if (IS_ERR_OR_NULL(dev)) {
+ pr_err("failed to device_create() \"%s\", ret = %d",
+ bbd_dev_name[minor], ret);
+ unregister_chrdev_region(bbd_cdev->devno, 1);
+ cdev_del(&bbd_cdev->cdev);
+ goto free_class;
+ }
+
+ /* Done. Put success log and init BBD specific fields */
+ pr_info("(%d,%d) registered /dev/%s\n",
+ MAJOR(bbd->dev_num), minor, bbd_dev_name[minor]);
+ }
+
+ bbd->notifier.notifier_call = bbd_notifier;
+
+ /* Register PM */
+ ret = register_pm_notifier(&bbd->notifier);
+ if (ret)
+ goto free_class;
+
+#if IS_ENABLED(CONFIG_SENSORS_SSP)
+ /* Now, we can initialize SSP */
+ if (device_register(&dummy_spi.dev))
+ goto free_class;
+
+ 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_timespec64(ktime_get_boottime());
+ elapsed = (ts1.tv_sec * 1000000000ULL + ts1.tv_nsec) - start;
+ pr_info("%lu nsec elapsed\n", elapsed);
+
+#ifdef DEBUG_1HZ_STAT
+ bbd_init_stat(bbd);
+#endif
+ return bbd;
+
+free_class:
+ while (--minor > BBD_MINOR_SHMD) {
+ dev_t devno = MKDEV(MAJOR(bbd->dev_num), minor);
+ struct cdev *cdev = &bbd->priv[minor].cdev;
+
+ device_destroy(bbd->class, devno);
+ cdev_del(cdev);
+ unregister_chrdev_region(devno, 1);
+ }
+ class_destroy(bbd->class);
+exit:
+ kvfree(bbd);
+ return NULL;
+}
+
+EXPORT_SYMBOL_GPL(bbd_init);
+
+void bbd_exit(struct device *dev)
+{
+ struct bbd_device *bbd = dev_get_drvdata(dev);
+ int minor;
+
+#if IS_ENABLED(CONFIG_SENSORS_SSP)
+ /* Shutdown SSP first*/
+ pssp_driver->shutdown(&dummy_spi);
+#endif
+
+ /* Remove BBD char devices */
+ for (minor = BBD_MINOR_SENSOR; minor < BBD_DEVICE_INDEX; minor++) {
+ struct bbd_cdev_priv *bbd_cdev = &bbd->priv[minor];
+
+ device_destroy(bbd->class, bbd_cdev->devno);
+ cdev_del(&bbd_cdev->cdev);
+ unregister_chrdev_region(bbd_cdev->devno, 1);
+
+ pr_info("(%d,%d) unregistered /dev/%s\n",
+ MAJOR(bbd->dev_num), minor, bbd_dev_name[minor]);
+ }
+
+#ifdef DEBUG_1HZ_STAT
+ bbd_exit_stat(bbd);
+#endif
+ /* Remove class */
+ class_destroy(bbd->class);
+ kvfree(bbd);
+}
+
+EXPORT_SYMBOL_GPL(bbd_exit);
+
+MODULE_AUTHOR("Broadcom");
+MODULE_LICENSE("GPL v2");
diff --git a/bbd.h b/bbd.h
new file mode 100644
index 0000000..5be6993
--- /dev/null
+++ b/bbd.h
@@ -0,0 +1,203 @@
+/* SPDX-License-Identifier: GPL-2.0
+ * Copyright 2014 Broadcom Corporation
+ *
+ * The BBD (Broadcom Bridge Driver)
+ *
+ */
+
+#ifndef __BBD_H__
+#define __BBD_H__
+
+#include <linux/device.h>
+#include <linux/cdev.h>
+#include <linux/circ_buf.h>
+
+#define BBD_PWR_STATUS
+
+union long_union_t {
+ u8 uc[sizeof(u32)];
+ u32 ul;
+} __packed __aligned(4);
+
+union short_union_t {
+ u8 uc[sizeof(u16)];
+ u16 us;
+} __packed __aligned(4);
+
+enum {
+ BBD_MINOR_SHMD = 0,
+ BBD_MINOR_SENSOR = 1,
+ BBD_MINOR_CONTROL = 2,
+ BBD_MINOR_PATCH = 3,
+#ifdef BBD_PWR_STATUS
+ BBD_MINOR_PWRSTAT = 4,
+#endif
+ /* BBD_MINOR_SSI_SPI_DEBUG = 5, */ /* NOT supported yet */
+ BBD_DEVICE_INDEX
+};
+
+#define BBD_MAX_DATA_SIZE 4096 /* max data size for transition */
+
+#define BBD_CTRL_RESET_REQ "BBD:RESET_REQ"
+#define ESW_CTRL_READY "ESW:READY"
+#define ESW_CTRL_NOTREADY "ESW:NOTREADY"
+#define ESW_CTRL_CRASHED "ESW:CRASHED"
+#define BBD_CTRL_DEBUG_ON "BBD:DEBUG=1"
+#define BBD_CTRL_DEBUG_OFF "BBD:DEBUG=0"
+#define SSP_DEBUG_ON "SSP:DEBUG=1"
+#define SSP_DEBUG_OFF "SSP:DEBUG=0"
+#define SSI_DEBUG_ON "SSI:DEBUG=1"
+#define SSI_DEBUG_OFF "SSI:DEBUG=0"
+#define PZC_DEBUG_ON "PZC:DEBUG=1"
+#define PZC_DEBUG_OFF "PZC:DEBUG=0"
+#define RNG_DEBUG_ON "RNG:DEBUG=1"
+#define RNG_DEBUG_OFF "RNG:DEBUG=0"
+#define BBD_CTRL_SSI_PATCH_BEGIN "SSI:PatchBegin"
+#define BBD_CTRL_SSI_PATCH_END "SSI:PatchEnd"
+#define GPSD_SENSOR_ON "GPSD:SENSOR_ON"
+#define GPSD_SENSOR_OFF "GPSD:SENSOR_OFF"
+#define GPSD_CORE_ON "GPSD:CORE_ON"
+#define GPSD_CORE_OFF "GPSD:CORE_OFF"
+
+//#define DEBUG_1HZ_STAT
+
+#define HSI_ERROR_STATUS 0x2C
+#define HSI_ERROR_STATUS_LPBK_ERROR 0x01
+#define HSI_ERROR_STATUS_STRM_FIFO_OVFL 0x02
+#define HSI_ERROR_STATUS_AHB_BUS_ERROR 0x04
+#define HSI_ERROR_STATUS_PATCH_ERROR 0x10
+#define HSI_ERROR_STATUS_ALL_ERRORS (HSI_ERROR_STATUS_LPBK_ERROR|\
+ HSI_ERROR_STATUS_STRM_FIFO_OVFL|HSI_ERROR_STATUS_AHB_BUS_ERROR)
+
+#define HSI_STATUS 0x30
+#define HSI_INTR_MASK 0x40104034
+#define HSI_RNGDMA_RX_BASE_ADDR 0x40104040
+#define HSI_RNGDMA_RX_SW_ADDR_OFFSET 0x40104050
+#define HSI_RNGDMA_TX_BASE_ADDR 0x40104060
+#define HSI_RNGDMA_TX_SW_ADDR_OFFSET 0x40104070
+#define HSI_CTRL 0x40104080
+#define HSI_RESETN 0x40104090
+#define HSI_ADL_ABR_CONTROL 0x401040a0
+#define HSI_STRM_FIFO_STATUS 0x40104100
+#define HSI_CMND_FIFO_STATUS 0x40104104
+
+#define BBD_BUFF_SIZE (PAGE_SIZE * 2)
+
+struct bbd_device;
+
+#ifdef DEBUG_1HZ_STAT
+enum {
+ STAT_TX_LHD = 0,
+ STAT_TX_SSP,
+ STAT_TX_RPC,
+ STAT_TX_TL,
+ STAT_TX_SSI,
+
+ STAT_RX_SSI,
+ STAT_RX_TL,
+ STAT_RX_RPC,
+ STAT_RX_SSP,
+ STAT_RX_LHD,
+
+ STAT_MAX
+};
+
+struct bbd_stat {
+ struct bbd_device *bbd;
+ bool enabled;
+
+ u64 ts_irq;
+
+ u64 min_rx_lat; /* = (u64)-1 */
+ u64 min_rx_dur; /* = (u64)-1 */
+
+ u64 max_rx_lat; /* = 0 */
+ u64 max_rx_dur; /* = 0 */
+
+ u64 stat[STAT_MAX];
+
+ struct timer_list timer;
+ struct work_struct work;
+ struct workqueue_struct *workq;
+};
+
+void bbd_update_stat(struct bbd_device *bbd, int index, unsigned int count);
+void bbd_enable_stat(struct bbd_device *bbd);
+void bbd_disable_stat(struct bbd_device *bbd);
+#endif
+
+#ifdef BBD_PWR_STATUS
+enum {
+ STAT_GPS_OFF = 0,
+ STAT_GPS_ON,
+ STAT_GPS_MAX
+};
+
+struct gnss_pwrstats {
+ bool gps_stat;
+ u64 gps_on_cnt;
+ u64 gps_on_duration;
+ u64 gps_on_entry;
+ u64 gps_on_exit;
+ u64 gps_off_cnt;
+ u64 gps_off_duration;
+ u64 gps_off_entry;
+ u64 gps_off_exit;
+};
+#endif /* BBD_PWR_STATUS */
+
+struct bbd_cdev_priv {
+ struct bbd_device *bbd;
+ struct cdev cdev; /* char device */
+ struct device *dev;
+ dev_t devno;
+ 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 */
+#ifdef BBD_PWR_STATUS
+ struct gnss_pwrstats pwrstats; /* GNSS power state */
+#endif /* BBD_PWR_STATUS */
+};
+
+struct bbd_device {
+ struct device *dev;
+ struct class *class; /* for device_create */
+
+#ifdef DEBUG_1HZ_STAT
+ struct bbd_stat stat1hz;
+#endif
+ struct notifier_block notifier;
+
+ struct bbd_cdev_priv priv[BBD_DEVICE_INDEX];/* individual structures */
+
+ bool db; /* debug flag */
+#ifdef CONFIG_SENSORS_SSP
+ bool ssp_dbg;
+ bool ssp_pkt_dbg;
+#endif
+ void *ssp_priv; /* private data pointer */
+ struct bbd_callbacks *ssp_cb; /* callbacks for SSP */
+
+ bool legacy_patch; /* check for using legacy_bbd_patch */
+ dev_t dev_num; /* device number */
+};
+
+
+/** callback for incoming data from 477x to senser hub driver **/
+struct bbd_callbacks {
+ int (*on_packet)(void *ssh_data, const char *buf, size_t size);
+ int (*on_packet_alarm)(void *ssh_data);
+ int (*on_control)(void *ssh_data, const char *str_ctrl);
+ int (*on_mcu_ready)(void *ssh_data, bool ready);
+};
+
+extern void bbd_register(void *ext_data, struct bbd_callbacks *pcallbacks);
+extern int bbd_mcu_reset(void);
+extern struct bbd_device *bbd_init(struct device *dev, bool legacy_patch);
+extern void bbd_exit(struct device *dev);
+extern void bcm_ssi_debug(struct device *dev, int type, bool value);
+
+#endif /* __BBD_H__ */
diff --git a/bcm_gps_regs.c b/bcm_gps_regs.c
new file mode 100644
index 0000000..682352c
--- /dev/null
+++ b/bcm_gps_regs.c
@@ -0,0 +1,323 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright 2015 Broadcom Corporation
+ *
+ * The Broadcom GPS SPI driver
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/ioport.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/sysrq.h>
+#include <linux/console.h>
+#include <linux/delay.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spidev.h>
+#include <linux/kthread.h>
+#include <linux/circ_buf.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/poll.h>
+#include <linux/uaccess.h>
+#include <linux/suspend.h>
+#include <linux/kernel.h>
+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
+#include <linux/miscdevice.h>
+#include <linux/time.h>
+#include <linux/io.h>
+#include <asm/irq.h>
+#include <linux/kernel_stat.h>
+
+#include "bbd.h"
+#include "bcm_gps_spi.h"
+
+
+/**
+ * bcm_dreg_write - write to direct addressable registers in command controller
+ * @priv: @bcm_spi_priv structure data
+ * @id: name of indirect register, just for debug print
+ * @offset: the command offset of the direct addressable register
+ * @buf: pointer of data to be written to the register
+ * @size: number of bytes to write
+ * Return: upon successful completion, this function returns number of
+ * written bytes otherwise exit with specific error code <0
+ */
+int bcm_dreg_write(struct bcm_spi_priv *priv, char *id, u8 offset, u8 *buf, u8 size)
+{
+
+ int i;
+ struct bcm_ssi_tx_frame *tx = priv->tx_buf;
+ struct bcm_ssi_rx_frame *rx = priv->rx_buf;
+
+ if (size > MAX_SPI_DREG_FRAME_LEN)
+ return -1;
+
+ /*
+ * writing in 1 transaction
+ * 0x80 : < CHWXX > = half-duplex, SPI write command packet
+ */
+ tx->cmd = SSI_MODE_DEBUG | SSI_MODE_HALF_DUPLEX | SSI_WRITE_TRANS;
+
+ /*
+ * bit-8 is a write transaction and the lower 7-bits is
+ * the command offset of the
+ */
+ tx->data[0] = offset & 0x7F;
+ /* the number of valid bytes available on MOSI following this byte */
+ tx->data[1] = size;
+ memcpy(&tx->data[2], buf, size);
+ rx->status = 0;
+
+ if (bcm_spi_sync(priv, tx, rx, size + 3, size + 3))
+ return -1;
+
+ for (i = 0; i < size; i++) {
+ dev_dbg(&priv->spi->dev, "regW REG %s @ [%02X]: %08X", id,
+ offset, buf[i]);
+ }
+
+ return size;
+}
+
+
+/**
+ * bcm_dreg_read - read direct addressable registers in command controller
+ * @priv: @bcm_spi_priv structure data
+ * @offset: the command offset of the direct addressable register.
+ * @buf pointer data to be read from the register
+ * @size number of bytes to read
+ * Return: upon successful completion, this function returns number of
+ * read bytes otherwise exit with specific error code <0
+ */
+int bcm_dreg_read(struct bcm_spi_priv *priv, char *id, u8 offset, u8 *buf, u8 size)
+{
+ /* Reading in 2 transactions */
+ int i = 0;
+ /* int status; */
+ struct bcm_ssi_tx_frame *tx = priv->tx_buf;
+ struct bcm_ssi_rx_frame *rx = priv->rx_buf;
+
+ if (size > MAX_SPI_DREG_FRAME_LEN)
+ return -1;
+
+ /*
+ * First transaction will setup SPI Command Logic
+ * to Read Data from Register.
+ * 0x80 : < CHWXX > = half-duplex, SPI write command packet
+ */
+ tx->cmd = SSI_MODE_DEBUG | SSI_MODE_HALF_DUPLEX | SSI_WRITE_TRANS;
+
+ /*
+ * <8?h1000_0100> = Bit-8 is a read transaction and the lower 7-bits
+ * is the command offset of the register
+ */
+ tx->data[0] = 0x80 | (offset & 0x7F);
+ /*
+ * The number of bytes host will read from this offset
+ * in next packet
+ */
+ tx->data[1] = size;
+ rx->status = 0;
+
+ if (bcm_spi_sync(priv, tx, rx, 3, 3))
+ return -1;
+
+ dev_dbg(&priv->spi->dev, "regR: REG(W) %s @ [%02X]: %08X ", id,
+ offset, size);
+
+ /*
+ * Second Transaction will read data
+ * 0xa0 : < CHRXX > = half-duplex, SPI read command packet
+ */
+ tx->cmd = SSI_MODE_DEBUG | SSI_MODE_HALF_DUPLEX | SSI_READ_TRANS;
+ /*
+ * READ : the number of valid read bytes available plus one
+ * to account for the read offset address that accompanies
+ * the read data == <cmdByteNum+1>
+ */
+ tx->data[0] = 0;
+ /* READ : the command offset of the register == <cmdRegOffset>. */
+ tx->data[1] = 0;
+ rx->status = 0;
+
+ memset(&tx->data[2], 0, size);
+
+ if (bcm_spi_sync(priv, tx, rx, size + 3, size + 3))
+ return -1;
+
+ memcpy(buf, &rx->data[2], size);
+
+ for (i = 0 ; i < size ; i++)
+ dev_dbg(&priv->spi->dev, "regR: REG(R) %s @ [%02X]: %08X", id,
+ offset, buf[i]);
+
+ return size;
+}
+
+
+/**
+ * bcm_ireg_write - write to indirect addressable register
+ * @priv: @bcm_spi_priv structure data
+ * @id: name of indirect register, just for debug print
+ * @regaddr: the stream address of the indirect addressable register.
+ * @regval: pointer data to be read from the register
+ * Return: upon successful completion, this function returns number
+ * of written bytes otherwise exit with specific error code <0
+ */
+int bcm_ireg_write(struct bcm_spi_priv *priv, char *id, u32 regaddr, u32 regval)
+{
+ union long_union_t swap_addr, swap_reg;
+ struct bcm_ssi_tx_frame *tx = priv->tx_buf;
+ struct bcm_ssi_rx_frame *rx = priv->rx_buf;
+
+ /*
+ * Writing in 2 transactions
+ * First transaction will set up the SPI Debug Logic to Write Data
+ * into Configuration Register or Memory Location.
+ *
+ * 0x80 : <Command Byte>
+ * 0xD1 : <SPI Address Left Shifted with Write Bit as LSB>
+ * 0x00 : < SPI Offset of DMA Start Addr >
+ * 0x00, 0x00, 0x00, 0x00 : < Start Address>,
+ * Should be set from 'regaddr'
+ * 0x04, 0x00 : <Number of bytes to write>
+ * 0x01 : <Write Enable>
+ *
+ * uint8_t transaction_1st[13] = {
+ * 0x80, 0xD1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x03 };
+ * uint8_t transaction_1st[19] = {
+ * 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ * 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+ */
+
+ swap_addr.ul = regaddr;
+ swap_reg.ul = regval;
+
+ /*
+ * First transaction will setup SPI Command Logic
+ * to Read Data from Register.
+ * HSI_MOSI_COMMAND_PCKT | HSI_MOSI_HALF_DUPLEX | HSI_MOSI_WRITE_TRANS;
+ * 0x80 : < CHWXX > = half-duplex, SPI write command packet
+ */
+ tx->cmd = 0x80;
+ /*
+ * <8’h0100_0000> = Bit-8 is a write transaction.
+ * The lower 7-bits is the command offset of
+ */
+ tx->data[0] = 0x20;
+ /*
+ * The register (CONFIG_REG_DATA) the host is starting to write from.
+ * the number of valid bytes available on MOSI following this byte
+ */
+ tx->data[1] = 9;
+
+ tx->data[2] = swap_reg.uc[0];
+ tx->data[3] = swap_reg.uc[1];
+ tx->data[4] = swap_reg.uc[2];
+ tx->data[5] = swap_reg.uc[3];
+
+ tx->data[6] = swap_addr.uc[0];
+ tx->data[7] = swap_addr.uc[1];
+ tx->data[8] = swap_addr.uc[2];
+ tx->data[9] = swap_addr.uc[3];
+
+ tx->data[10] = 0x03;
+ tx->data[11] = 0x00;
+ tx->data[12] = 0x00;
+ tx->data[13] = 0x00;
+ rx->status = 0;
+
+ if (bcm_spi_sync(priv, tx, rx, 15, 15))
+ return -1;
+
+ if (id)
+ dev_dbg(&priv->spi->dev, "reg32w: %s @ : [%08X] %08X ", id,
+ (unsigned int)swap_addr.ul,
+ (unsigned int)swap_reg.ul);
+
+ return 1;
+}
+
+/**
+ * bcm_ireg_read - read indirect addressable register
+ * @priv: @bcm_spi_priv structure data
+ * @id: the name of indirect register. Just for debug print.
+ * @regaddr: the stream address of the indirect addressable register.
+ * @regval: the pointer data to be read from the register
+ * Return: upon successful completion, this function returns number of
+ * read bytes otherwise exit with specific error code <0
+ */
+int bcm_ireg_read(struct bcm_spi_priv *priv, char *id, u32 regaddr,
+ u32 *regval, s32 n)
+{
+ s32 i;
+ union long_union_t swap_addr, swap_reg;
+ union long_union_t swap_addr2;
+ struct bcm_ssi_tx_frame *tx = priv->tx_buf;
+ struct bcm_ssi_rx_frame *rx = priv->rx_buf;
+
+ for (i = 0; i < n; i++) {
+ swap_addr.ul = regaddr + (i * 4);
+
+ /*
+ * First transaction will setup SPI Command Logic
+ * to Read Data from Register.
+ * HSI_MOSI_COMMAND_PCKT |
+ * HSI_MOSI_HALF_DUPLEX |
+ * HSI_MOSI_WRITE_TRANS;
+ * 0x80 : < CHWXX > = half-duplex, SPI write command packet
+ */
+ tx->cmd = 0x80;
+
+ /*
+ * <8?h0100_0000> = Bit-8 is a write transaction.
+ * The lower 7-bits is the command offset of
+ */
+ tx->data[0] = 0x24;
+ /*
+ * The register (RFIFO Read DATA) the host is
+ * starting to write from. the number of valid bytes
+ * available on MOSI following this byte
+ */
+ tx->data[1] = 5;
+ tx->data[2] = swap_addr.uc[0];
+ tx->data[3] = swap_addr.uc[1];
+ tx->data[4] = swap_addr.uc[2];
+ tx->data[5] = swap_addr.uc[3];
+ tx->data[6] = 0x02; /* AHB read transaction */
+ rx->status = 0;
+
+ if (bcm_spi_sync(priv, tx, rx, 8, 8))
+ return -1;
+
+ tx->cmd = 0xa0;
+ memset(tx->data, 0, 11);
+ rx->status = 0;
+
+ if (bcm_spi_sync(priv, tx, rx, 12, 8))
+ return -1;
+
+ swap_addr2.uc[0] = rx->data[1];
+ swap_addr2.uc[1] = rx->data[2];
+ swap_addr2.uc[2] = rx->data[3];
+ swap_addr2.uc[3] = rx->data[4];
+
+ swap_reg.uc[0] = rx->data[5];
+ swap_reg.uc[1] = rx->data[6];
+ swap_reg.uc[2] = rx->data[7];
+ swap_reg.uc[3] = rx->data[8];
+
+ if (id)
+ dev_dbg(&priv->spi->dev, "reg32r: %s @ : [%08X] %08X ", id,
+ (unsigned int)swap_addr2.ul,
+ (unsigned int)swap_reg.ul);
+
+ if (regval)
+ *regval++ = swap_reg.ul;
+
+ }
+
+ return i;
+}
diff --git a/bcm_gps_spi.c b/bcm_gps_spi.c
new file mode 100644
index 0000000..5d2a9e1
--- /dev/null
+++ b/bcm_gps_spi.c
@@ -0,0 +1,1566 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright 2015 Broadcom Corporation
+ *
+ * The Broadcom GPS SPI driver
+ *
+ */
+
+/* TODO: Use dev_*() calls instead */
+#define pr_fmt(fmt) "GPSREGS: " fmt
+
+#include <linux/module.h>
+#include <linux/ioport.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/sysrq.h>
+#include <linux/console.h>
+#include <linux/delay.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spidev.h>
+#include <linux/kthread.h>
+#include <linux/circ_buf.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/of_gpio.h>
+#include <linux/poll.h>
+#include <linux/uaccess.h>
+#include <linux/suspend.h>
+#include <linux/kernel.h>
+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
+#include <linux/miscdevice.h>
+#include <linux/timekeeping.h>
+#include <linux/io.h>
+#include <asm/irq.h>
+#include <linux/kernel_stat.h>
+#include <linux/pm_runtime.h>
+
+#include "bbd.h"
+#include "bcm_gps_spi.h"
+
+/* 0 - Half Duplex, 1 - Full Duplex */
+#define SSI_MODE 1
+
+/* 1 = 1B, 2 = 2B */
+#define SSI_LEN 2
+
+/*
+ * TODO: Need to read bitrate from bus driver spi.c.
+ * Just for startup info notification.
+ */
+#define BCM_BITRATE 12000
+
+static void bcm_on_packet_received(
+ void *_priv, unsigned char *data, unsigned int size);
+
+
+static ssize_t nstandby_show(
+ struct device *dev, struct device_attribute *attr, char *buf)
+{
+ int value = 0;
+ struct spi_device *spi = to_spi_device(dev);
+ struct bcm_spi_priv *priv = spi_get_drvdata(spi);
+ value = gpio_get_value(priv->nstandby);
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", value);
+}
+
+static ssize_t nstandby_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ struct bcm_spi_priv *priv = spi_get_drvdata(spi);
+
+#ifdef DEBUG_1HZ_STAT
+ dev_dbg(dev, "nstandby, buf is %s\n", buf);
+#endif
+
+ if (buf[0] == '0')
+ gpio_set_value(priv->nstandby, 0);
+ else
+ gpio_set_value(priv->nstandby, 1);
+
+ return count;
+}
+
+static DEVICE_ATTR_RW(nstandby);
+
+static ssize_t sspmcureq_show(
+ struct device *dev, struct device_attribute *attr, char *buf)
+{
+ int value = 0;
+ struct spi_device *spi = to_spi_device(dev);
+ struct bcm_spi_priv *priv = spi_get_drvdata(spi);
+ value = gpio_get_value(priv->mcu_req);
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", value);
+}
+
+static ssize_t sspmcureq_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ struct bcm_spi_priv *priv = spi_get_drvdata(spi);
+
+ dev_dbg(dev, "sspmcureq, buf is %s\n", buf);
+
+ if (buf[0] == '0')
+ gpio_set_value(priv->mcu_req, 0);
+ else
+ gpio_set_value(priv->mcu_req, 1);
+
+ return count;
+}
+
+static DEVICE_ATTR_RW(sspmcureq);
+
+#ifdef CONFIG_TRANSFER_STAT
+void bcm_ssi_clear_trans_stat(struct bcm_spi_priv *priv)
+{
+ memset(priv->trans_stat, 0, sizeof(priv->trans_stat));
+}
+
+void bcm_ssi_print_trans_stat(struct bcm_spi_priv *priv)
+{
+ struct bcm_spi_transfer_stat *trans = &priv->trans_stat[0];
+
+ dev_info(&priv->spi->dev, "DBG SPI @ TX: <255B = %d, <1K = %d, <2K = %d, <4K = %d, <8K = %d, <16K = %d, <32K = %d, <64K = %d, total = %ld, min = %ld, max = %ld",
+ trans->len_255, trans->len_1K, trans->len_2K,
+ trans->len_4K, trans->len_8K, trans->len_16K,
+ trans->len_32K, trans->len_64K, trans->len_total,
+ trans->len_min, trans->len_max);
+
+ trans = &priv->trans_stat[1];
+ dev_info(&priv->spi->dev, "DBG SPI @ RX: <255B = %d, <1K = %d, <2K = %d, <4K = %d, <8K = %d, <16K = %d, <32K = %d, <64K = %d, total = %ld, min = %ld, max = %ld",
+ trans->len_255, trans->len_1K, trans->len_2K,
+ trans->len_4K, trans->len_8K, trans->len_16K,
+ trans->len_32K, trans->len_64K, trans->len_total,
+ trans->len_min, trans->len_max);
+
+ dev_info(&priv->spi->dev, "DBG SPI @ PZC: retries = %d, delays = %d",
+ priv->ssi_tx_pzc_retries, priv->ssi_tx_pzc_retry_delays);
+}
+
+static void bcm_ssi_calc_trans_stat(
+ struct bcm_spi_transfer_stat *trans, unsigned short length)
+{
+ if (length <= 255)
+ trans->len_255++;
+ else if (length <= 1024)
+ trans->len_1K++;
+ else if (length <= (2 * 1024))
+ trans->len_2K++;
+ else if (length <= (4 * 1024))
+ trans->len_4K++;
+ else if (length <= (8 * 1024))
+ trans->len_8K++;
+ else if (length <= (16 * 1024))
+ trans->len_16K++;
+ else if (length <= (32 * 1024))
+ trans->len_32K++;
+ else
+ trans->len_64K++;
+
+ if (length > trans->len_max)
+ trans->len_max = length;
+
+ if (trans->len_min == 0 || length < trans->len_min)
+ trans->len_min = length;
+
+ trans->len_total += length;
+}
+#endif /* CONFIG_TRANSFER_STAT */
+
+static const unsigned long m_ulRxBufferBlockSize[4] = {32, 256, 1024 * 2, 1024 * 16};
+
+static unsigned long bcm_ssi_chk_pzc(struct bcm_spi_priv *priv,
+ unsigned char stat_byte, bool bprint)
+{
+ unsigned long rx_buffer_blk_bytes =
+ m_ulRxBufferBlockSize[(
+ stat_byte & HSI_F_MOSI_CTRL_SZE_MASK) >>
+ HSI_F_MOSI_CTRL_SZE_SHIFT];
+ unsigned long rx_buffer_blk_counts =
+ (unsigned long)((stat_byte & HSI_F_MOSI_CTRL_CNT_MASK) >>
+ HSI_F_MOSI_CTRL_CNT_SHIFT);
+
+ priv->rx_buffer_avail_bytes = rx_buffer_blk_bytes * rx_buffer_blk_counts;
+
+ if (!bprint)
+ return priv->rx_buffer_avail_bytes;
+
+ if (stat_byte & HSI_F_MOSI_CTRL_PE_MASK) {
+ dev_dbg(&priv->spi->dev, "DBG SPI @ PZC: rx stat 0x%02x%s avail %lu",
+ stat_byte,
+ stat_byte & HSI_F_MOSI_CTRL_PE_MASK ? "(PE)" : " ",
+ priv->rx_buffer_avail_bytes);
+ }
+
+#ifdef CONFIG_REG_IO
+ if (stat_byte & HSI_F_MOSI_CTRL_PE_MASK) {
+ u8 regval;
+
+ bcm_dreg_read(priv, "HSI_ERROR_STATUS(R) ",
+ HSI_ERROR_STATUS, &regval, 1);
+
+ if (regval & HSI_ERROR_STATUS_STRM_FIFO_OVFL)
+ dev_err(&priv->spi->dev, "(rx_strm_fifo_ovfl)");
+
+ regval = HSI_ERROR_STATUS_ALL_ERRORS;
+ bcm_dreg_write(priv, "HSI_ERROR_STATUS(W) ",
+ HSI_ERROR_STATUS, &regval, 1);
+ }
+#endif /* CONFIG_REG_IO */
+
+ return priv->rx_buffer_avail_bytes;
+}
+
+
+/**********************************
+ *
+ * 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);
+ struct bcm_spi_strm_protocol *strm;
+ unsigned long flags;
+ unsigned char fc_mask, len_mask, duplex_mask;
+
+#ifdef CONFIG_REG_IO
+ u8 regval8[2];
+ u32 regval32[16];
+#endif
+
+ 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;
+
+ priv->packet_received = 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);
+
+ priv->irq_wakeup_enabled = (enable_irq_wake(priv->spi->irq) == 0);
+
+ filp->private_data = priv;
+#ifdef DEBUG_1HZ_STAT
+ bbd_enable_stat(priv->bbd);
+#endif
+
+ strm = &priv->tx_strm;
+ strm->pckt_len = SSI_LEN == 2 ? 2 : 1;
+ len_mask = SSI_LEN == 2 ? SSI_PCKT_2B_LENGTH : SSI_PCKT_1B_LENGTH;
+ duplex_mask = SSI_MODE != 0 ? SSI_MODE_FULL_DUPLEX : SSI_MODE_HALF_DUPLEX;
+
+ fc_mask = SSI_FLOW_CONTROL_DISABLED;
+ strm->fc_len = 0;
+ if (SSI_MODE == 0) {
+ /* SSI_MODE_HALF_DUPLEX; */
+ strm->pckt_len = 0;
+ }
+ /* 1 for tx cmd byte */
+ strm->ctrl_len = strm->pckt_len + strm->fc_len + 1;
+
+ strm->frame_len = SSI_LEN == 2 ? MAX_SPI_FRAME_LEN : MAX_SPI_DREG_FRAME_LEN;
+ strm->ctrl_byte = duplex_mask | SSI_MODE_STREAM |
+ len_mask | SSI_WRITE_TRANS | fc_mask;
+
+ /* TX SPI Streaming Protocol in details */
+#ifdef DEBUG_1HZ_STAT
+ dev_info(&priv->spi->dev, "tx ctrl %02X: total %d = len %d + fc %d + cmd 1",
+ strm->ctrl_byte, strm->ctrl_len,
+ strm->pckt_len, strm->fc_len);
+#endif
+
+ strm = &priv->rx_strm;
+ strm->pckt_len = SSI_LEN == 2 ? 2 : 1;
+ strm->fc_len = 0;
+ /* 1 for rx stat byte */
+ strm->ctrl_len = strm->pckt_len + strm->fc_len + 1;
+ strm->frame_len = SSI_LEN == 2 ? MAX_SPI_FRAME_LEN : MAX_SPI_DREG_FRAME_LEN;
+ strm->ctrl_byte = duplex_mask | SSI_MODE_STREAM | len_mask |
+ (SSI_MODE == SSI_MODE_FULL_DUPLEX ?
+ SSI_WRITE_TRANS : SSI_READ_TRANS);
+
+ /* RX SPI Streaming Protocol in details */
+#ifdef DEBUG_1HZ_STAT
+ dev_info(&priv->spi->dev, "rx ctrl %02X: total %d = len %d + fc %d + stat 1\n",
+ strm->ctrl_byte, strm->ctrl_len,
+ strm->pckt_len, strm->fc_len);
+
+ dev_info(&priv->spi->dev, "SPI @ %d: %s Duplex Strm mode, %dB Len, w/o FC, Frame Len %u : tx ctrl %02X, rx ctrl %02X\n",
+ BCM_BITRATE,
+ SSI_MODE != 0 ? "Full" : "Half",
+ SSI_LEN == 2 ? 2 : 1,
+ strm->frame_len,
+ priv->tx_strm.ctrl_byte,
+ priv->rx_strm.ctrl_byte);
+#endif
+
+#ifdef CONFIG_REG_IO
+ bcm_dreg_read(priv, "HSI_STATUS ",
+ HSI_STATUS, regval8, 1);
+ bcm_dreg_read(priv, "HSI_ERROR_STATUS(R) ",
+ HSI_ERROR_STATUS, regval8, 1);
+ bcm_ireg_read(priv, "INTR_MASK/STAT ",
+ HSI_INTR_MASK, regval32, 2);
+ bcm_ireg_read(priv, "RNGDMA_RX ",
+ HSI_RNGDMA_RX_BASE_ADDR, regval32, 8);
+ bcm_ireg_read(priv, "RNGDMA_TX ",
+ HSI_RNGDMA_TX_BASE_ADDR, regval32, 8);
+ bcm_ireg_read(priv, "HSI_CTRL ",
+ HSI_CTRL, regval32, 4);
+ bcm_ireg_read(priv, "ADL_ABR ",
+ HSI_ADL_ABR_CONTROL, regval32, 4);
+ bcm_ireg_read(priv, "RSTN/STBY/EN ",
+ HSI_RESETN, regval32, 4);
+ bcm_ireg_read(priv, "STRM/CMND ",
+ HSI_STRM_FIFO_STATUS, regval32, 2);
+#endif
+
+#ifdef CONFIG_TRANSFER_STAT
+ bcm_ssi_print_trans_stat(priv);
+ bcm_ssi_clear_trans_stat(priv);
+#endif
+ priv->ssi_tx_fail = 0;
+ priv->ssi_tx_pzc_retries = 0;
+ priv->ssi_tx_pzc_retry_delays = 0;
+ priv->ssi_pm_semaphore = 0;
+ priv->rx_buffer_avail_bytes = HSI_PZC_MAX_RX_BUFFER;
+
+ return 0;
+}
+
+static int bcm_spi_release(struct inode *inode, struct file *filp)
+{
+ struct bcm_spi_priv *priv = filp->private_data;
+ unsigned long flags;
+
+ priv->busy = false;
+
+#ifdef CONFIG_TRANSFER_STAT
+ bcm_ssi_print_trans_stat(priv);
+#endif
+
+
+#ifdef DEBUG_1HZ_STAT
+ bbd_disable_stat(priv->bbd);
+#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);
+
+ if (priv->irq_wakeup_enabled)
+ 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;
+
+ mutex_lock(&priv->rlock);
+
+ /*
+ * 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);
+
+ if (copy_to_user(buf + rd_size,
+ circ->buf + circ->tail, copied)){
+ dev_err(&priv->spi->dev, "failed to copy to user.\n");
+ mutex_unlock(&priv->rlock);
+ return -EFAULT;
+ }
+ 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));
+ mutex_unlock(&priv->rlock);
+
+#ifdef DEBUG_1HZ_STAT
+ bbd_update_stat(priv->bbd, 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;
+
+ mutex_lock(&priv->wlock);
+ /*
+ * 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);
+
+ if (copy_from_user(circ->buf + circ->head,
+ buf + wr_size, copied)){
+ dev_err(&priv->spi->dev, "failed to copy from user.\n");
+ mutex_unlock(&priv->wlock);
+ return -EFAULT;
+ }
+ 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));
+ mutex_unlock(&priv->wlock);
+
+ /*
+ * 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,
+ (struct work_struct *)&priv->rxtx_work);
+
+#ifdef DEBUG_1HZ_STAT
+ bbd_update_stat(priv->bbd, 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 */
+
+unsigned long bcm_clock_get_ms(void)
+{
+ struct timespec64 t;
+ unsigned long now;
+ static unsigned long init_time;
+
+ ktime_get_real_ts64(&t);
+ now = t.tv_nsec / 1000000 + t.tv_sec * 1000;
+ if (init_time == 0)
+ init_time = now;
+
+ return now - init_time;
+}
+
+void wait1secDelay(unsigned int count)
+{
+ if (count <= 100)
+ usleep_range(1000, 2000);
+ else
+ usleep_range(20000, 30000);
+}
+
+#ifdef CONFIG_MCU_WAKEUP
+/**
+ * bcm4773_hello - wakeup chip by toggling mcu_req
+ * while monitoring mcu_resp to check if awake
+ */
+static bool bcm477x_hello(struct bcm_spi_priv *priv)
+{
+ int count = 0;
+#define MAX_RESP_CHECK_COUNT 100 /* 100 msec */
+
+ unsigned long start_time, delta;
+
+ start_time = bcm_clock_get_ms();
+ gpio_set_value(priv->mcu_req, 1);
+ while (!gpio_get_value(priv->mcu_resp)) {
+ if (count++ > MAX_RESP_CHECK_COUNT) {
+ gpio_set_value(priv->mcu_req, 0);
+#ifdef DEBUG_1HZ_STAT
+ dev_err(&priv->spi->dev, " MCU_REQ_RESP timeout. MCU_RESP(gpio%d) not responding to MCU_REQ(gpio%d)\n",
+ priv->mcu_resp, priv->mcu_req);
+#endif
+ return false;
+ }
+
+ wait1secDelay(count);
+
+ /*if awake, done */
+ if (gpio_get_value(priv->mcu_resp))
+ break;
+
+ if (count % 20 == 0) {
+ gpio_set_value(priv->mcu_req, 0);
+ usleep_range(1000, 2000);
+ gpio_set_value(priv->mcu_req, 1);
+ usleep_range(1000, 2000);
+ }
+ }
+
+ delta = bcm_clock_get_ms() - start_time;
+
+ if (count > 100)
+ dev_err(&priv->spi->dev, "hello consumed %lu = clock_get_ms() - start_time; msec",
+ delta);
+
+ return true;
+}
+#endif
+
+/**
+ * bcm4773_bye - set mcu_req low to let chip go to sleep
+ */
+static void bcm477x_bye(struct bcm_spi_priv *priv)
+{
+ gpio_set_value(priv->mcu_req, 0);
+}
+
+static void pk_log(struct bcm_spi_priv *priv, char *dir,
+ unsigned char *data, int len)
+{
+ const char ic = 'D';
+
+ if (likely(!priv->ssi_dbg))
+ return;
+
+ /*
+ * TODO: There is print issue. Printing 7 digits instead of 6
+ * when clock is over 1000000. "% 1000000" added
+ * E.g.
+ * #999829D w 0x68, 1: A2
+ * #999829D r 0x68, 34: 8D 00 01 52 5F B0 01 B0 00 8E 00 01 53 8B
+ * B0 01 B0 00 8F 00 01 54 61 B0 01 B0 00 90 00 01 55 B5
+ * r B0 01
+ * #1000001D w 0x68, 1: A1
+ * #1000001D r 0x68, 1: 00
+ */
+ dev_info(&priv->spi->dev, "#%06ld%c %2s,\t %5d: ",
+ bcm_clock_get_ms() % 1000000, ic, dir, len);
+
+ print_hex_dump(KERN_INFO, dir[0] == 'r' ? "r " : "w ",
+ DUMP_PREFIX_NONE, 32, 1, data, len, false);
+}
+
+void bcm_ssi_debug(struct device *dev, int type, bool value)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ struct bcm_spi_priv *priv = spi_get_drvdata(spi);
+
+ switch (type) {
+ case 0:
+ priv->ssi_dbg = value;
+ break;
+ case 1:
+ priv->ssi_dbg_pzc = value;
+ break;
+ case 2:
+ priv->ssi_dbg_rng = value;
+ break;
+ }
+}
+EXPORT_SYMBOL_GPL(bcm_ssi_debug);
+
+/* SSI tx/rx functions */
+
+static unsigned short bcm_ssi_get_len(
+ unsigned char ctrl_byte, unsigned char *data)
+{
+ if (ctrl_byte & SSI_PCKT_2B_LENGTH)
+ return ((unsigned short)data[0] +
+ ((unsigned short)data[1] << 8));
+
+ return (unsigned short)data[0];
+}
+
+static void bcm_ssi_set_len(
+ unsigned char ctrl_byte, unsigned char *data, unsigned short len)
+{
+ if (ctrl_byte & SSI_PCKT_2B_LENGTH) {
+ data[0] = (unsigned char)(len & 0xff);
+ data[1] = (unsigned char)((len >> 8) & 0xff);
+ } else {
+ data[0] = (unsigned char)len;
+ }
+}
+
+static void bcm_ssi_clr_len(unsigned char ctrl_byte, unsigned char *data)
+{
+ bcm_ssi_set_len(ctrl_byte, data, 0);
+}
+
+
+int bcm_spi_sync(struct bcm_spi_priv *priv, void *tx_buf,
+ void *rx_buf, int len, int bits_per_word)
+{
+ struct spi_message msg;
+ struct spi_transfer xfer;
+ int ret;
+
+ /* Init */
+ spi_message_init(&msg);
+ memset(&xfer, 0, sizeof(xfer));
+ spi_message_add_tail(&xfer, &msg);
+
+ /* Setup */
+ msg.spi = priv->spi;
+ xfer.len = len;
+ xfer.tx_buf = tx_buf;
+ xfer.rx_buf = rx_buf;
+ xfer.bits_per_word = bits_per_word;
+
+ /* Sync */
+ pk_log(priv, "w", (unsigned char *)xfer.tx_buf, len);
+ ret = spi_sync(msg.spi, &msg);
+ pk_log(priv, "r", (unsigned char *)xfer.rx_buf, len);
+
+ if (ret)
+ dev_err(&priv->spi->dev, "spi_sync error for cmd:0x%x, return=%d\n",
+ ((struct bcm_ssi_tx_frame *)xfer.tx_buf)->cmd, 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;
+ struct bcm_spi_strm_protocol *strm = &priv->tx_strm;
+ int bits_per_word = (length + strm->ctrl_len >= MIN_DMA_SIZE) ?
+ CONFIG_SPI_DMA_BITS_PER_WORD : 8;
+ int ret;
+ unsigned short m_write;
+ unsigned short bytes_to_write = (unsigned short)length;
+ unsigned short bytes_written = 0;
+ unsigned short n_read = 0; /* for Full Duplex only */
+ unsigned short frame_data_size = strm->frame_len - strm->ctrl_len;
+
+ m_write = bytes_to_write;
+
+ tx->cmd = strm->ctrl_byte; /* SSI_WRITE_HD etc. */
+
+ bytes_to_write = max(m_write, n_read);
+
+ if (strm->pckt_len != 0)
+ bcm_ssi_set_len(strm->ctrl_byte, tx->data, m_write);
+
+ /* ctrl_len is for tx len + fc */
+ ret = bcm_spi_sync(priv, tx, rx, bytes_to_write +
+ strm->ctrl_len, bits_per_word);
+
+ if (ret) {
+ priv->ssi_tx_fail++;
+ return ret;
+ /* TODO: failure, operation should gets 0 to continue */
+ }
+
+ if (strm->pckt_len != 0) {
+ unsigned short m_write =
+ bcm_ssi_get_len(strm->ctrl_byte, tx->data);
+ /* Just for understanding SPI Streaming Protocol */
+ if (m_write > frame_data_size) {
+ /* The h/w malfunctioned ? */
+ dev_err(&priv->spi->dev, "@ TX m_write %d is h/w overflowed of frame %d...Fail\n",
+ m_write, frame_data_size);
+ }
+ }
+
+ if (strm->ctrl_byte & SSI_MODE_FULL_DUPLEX) {
+ unsigned char *data_p = rx->data + strm->pckt_len;
+
+ n_read = bcm_ssi_get_len(strm->ctrl_byte, rx->data);
+ if (n_read > frame_data_size) {
+ dev_err(&priv->spi->dev, "@ FD n_read %d is h/w overflowed of frame %d...Fail\n",
+ n_read, frame_data_size);
+ n_read = frame_data_size;
+ }
+
+ if (m_write < n_read) {
+ /* Call BBD */
+ bcm_on_packet_received(priv, data_p, m_write);
+ /* 1/2 bytes for len */
+ n_read -= m_write;
+ bytes_to_write -= m_write;
+ data_p += (m_write + strm->fc_len);
+ } else {
+ bytes_to_write = n_read;
+ /* No data available next time */
+ n_read = 0;
+ }
+
+ /* Call BBD */
+ if (bytes_to_write != 0) {
+ bcm_on_packet_received(
+ priv, data_p, bytes_to_write);
+ }
+ }
+
+ bytes_written += bytes_to_write;
+
+#ifdef CONFIG_TRANSFER_STAT
+ bcm_ssi_calc_trans_stat(&priv->trans_stat[0], bytes_written);
+#endif
+
+ bcm_ssi_chk_pzc(priv, rx->status, priv->ssi_dbg_pzc);
+
+ 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;
+ struct circ_buf *rd_circ = &priv->read_buf;
+ struct bcm_spi_strm_protocol *strm = &priv->rx_strm;
+ unsigned short ctrl_len = strm->pckt_len + 1; /* +1 for rx status */
+ unsigned short payload_len;
+ int bits_per_word = 8;
+ size_t sz_to_recv = 0;
+
+#ifdef CONFIG_REG_IO
+ if (likely(priv->ssi_dbg_rng) &&
+ (priv->packet_received > CONFIG_PACKET_RECEIVED)) {
+ u32 regval32[8];
+
+ bcm_ireg_read(priv, "RNGDMA_TX ",
+ HSI_RNGDMA_TX_SW_ADDR_OFFSET, regval32, 3);
+ }
+#endif
+
+ /* TODO:: Check 1B or 2B mode */
+
+ bcm_ssi_clr_len(strm->ctrl_byte, tx->data);
+ /* tx and rx ctrl_byte(s) are same */
+ tx->cmd = strm->ctrl_byte;
+ /* SSI_READ_HD etc. */
+ rx->status = 0;
+
+ if (bcm_spi_sync(priv, tx, rx, ctrl_len, 8))
+ return -1;
+
+ bcm_ssi_chk_pzc(priv, rx->status, priv->ssi_dbg_pzc);
+
+ payload_len = bcm_ssi_get_len(strm->ctrl_byte, rx->data);
+
+ if (payload_len == 0) {
+ /*
+ * TODO: payload_len = MIN_SPI_FRAME_LEN;
+ * Needn't to use MAX_SPI_FRAME_LEN because don't
+ * know how many bytes is ready to really read
+ */
+ dev_err(&priv->spi->dev, "@ RX length is still read to 0. Set %d\n", payload_len);
+ return -1;
+ }
+
+ *length = min((unsigned short)(strm->frame_len - ctrl_len), payload_len);
+
+ /* SWGNSSGLL-24487 : slowing down read speed if buffer is half full */
+ if (CIRC_CNT(rd_circ->head, rd_circ->tail, BCM_SPI_READ_BUF_SIZE) >
+ BCM_SPI_READ_BUF_SIZE / 2) {
+ msleep(DELAY_FOR_SYSTEM_OVERLOADED_MS);
+ if (*length >= READ_SIZE_FOR_SYSTEM_OVERLOADED)
+ *length = READ_SIZE_FOR_SYSTEM_OVERLOADED;
+ }
+
+ sz_to_recv = *length + ctrl_len;
+ if (sz_to_recv >= MIN_DMA_SIZE) {
+ bits_per_word = CONFIG_SPI_DMA_BITS_PER_WORD;
+ if (sz_to_recv & (CONFIG_SPI_DMA_BYTES_PER_WORD - 1))
+ *length = (sz_to_recv & ~(CONFIG_SPI_DMA_BYTES_PER_WORD - 1))
+ - ctrl_len;
+ }
+ memset(tx->data, 0, *length + ctrl_len - 1); /* -1 for status byte */
+
+ if (bcm_spi_sync(priv, tx, rx, *length+ctrl_len, bits_per_word))
+ return -1;
+
+ payload_len = bcm_ssi_get_len(strm->ctrl_byte, rx->data);
+ if (payload_len < *length)
+ *length = payload_len;
+
+ return 0;
+}
+
+static void bcm_check_overrun(struct bcm_spi_priv *priv, size_t avail)
+{
+ const long THRESHOLD_MS = 100;
+ unsigned long curr_tick = bcm_clock_get_ms();
+
+ if (!avail)
+ return;
+
+ if (curr_tick - priv->last_tick < THRESHOLD_MS) {
+ priv->skip_count++;
+ return;
+ }
+
+ if (priv->skip_count)
+ dev_err(&priv->spi->dev, "%ld messages are skipped!\n", priv->skip_count);
+
+ dev_err(&priv->spi->dev, "input overrun error by %zu bytes.\n", avail);
+ priv->skip_count = 0;
+ priv->last_tick = curr_tick;
+
+}
+
+static void bcm_on_packet_received(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;
+
+#ifdef DEBUG_1HZ_STAT
+ bbd_update_stat(priv->bbd, STAT_RX_SSI, size);
+#endif
+#ifdef CONFIG_TRANSFER_STAT
+ bcm_ssi_calc_trans_stat(&priv->trans_stat[1], size);
+#endif
+
+ /* 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(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));
+
+ priv->packet_received += size;
+ mutex_unlock(&priv->rlock);
+ wake_up(&priv->poll_wait);
+ bcm_check_overrun(priv, avail);
+}
+
+#ifdef DEBUG_1HZ_STAT
+void bcm477x_debug_info(struct bcm_spi_priv *priv)
+{
+ int pin_ttyBCM, pin_MCU_REQ, pin_MCU_RESP;
+ int irq_enabled;
+
+ if (!priv)
+ return;
+
+ pin_ttyBCM = gpio_get_value(priv->host_req);
+ pin_MCU_REQ = gpio_get_value(priv->mcu_req);
+ pin_MCU_RESP = gpio_get_value(priv->mcu_resp);
+
+ irq_enabled = atomic_read(&priv->irq_enabled);
+
+ dev_info(&priv->spi->dev, "pin_ttyBCM:%d, pin_MCU_REQ:%d, pin_MCU_RESP:%d\n",
+ pin_ttyBCM, pin_MCU_REQ, pin_MCU_RESP);
+ dev_info(&priv->spi->dev, "irq_enabled:%d\n", irq_enabled);
+}
+#endif
+
+static void bcm_rxtx_work_func(struct work_struct *work)
+{
+ struct bcm_spi_priv *priv = container_of(work,
+ struct bcm_spi_priv, rxtx_work);
+ struct circ_buf *rd_circ = &priv->read_buf;
+ struct circ_buf *wr_circ = &priv->write_buf;
+ struct bcm_spi_strm_protocol *strm = &priv->tx_strm;
+ unsigned short rx_pckt_len = priv->rx_strm.pckt_len;
+ int wait_for_pzc = 0;
+ unsigned long flags;
+
+#ifdef DEBUG_1HZ_STAT
+ u64 ts_rx_start = 0;
+ u64 ts_rx_end = 0;
+ struct timespec64 ts;
+ struct bbd_device *bbd = priv->bbd;
+#endif
+
+#ifdef CONFIG_MCU_WAKEUP
+ if (!bcm477x_hello(priv)) {
+#ifdef DEBUG_1HZ_STAT
+ dev_err(&priv->spi->dev, "hello timeout!!\n");
+ bcm477x_debug_info(priv);
+#endif
+ return;
+ }
+#endif
+
+ do {
+ int ret = 0;
+ size_t avail = 0;
+ size_t written = 0;
+ size_t sz_to_send = 0;
+
+ /* Read first */
+ if (!gpio_is_valid(priv->host_req)) {
+ dev_err(&priv->spi->dev, "gpio host_req is invalid, return\n");
+ return;
+ }
+ ret = gpio_get_value(priv->host_req);
+
+ if (ret || wait_for_pzc) {
+ wait_for_pzc = 0;
+#ifdef DEBUG_1HZ_STAT
+ if (bbd->stat1hz.ts_irq) {
+ ts = ktime_to_timespec64(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
+ if (ts_rx_start && !gpio_get_value(priv->host_req)) {
+ ts = ktime_to_timespec64(ktime_get_boottime());
+ ts_rx_end = ts.tv_sec * 1000000000ULL
+ + ts.tv_nsec;
+ }
+#endif
+ /* Call BBD */
+ bcm_on_packet_received(priv,
+ priv->rx_buf->data + rx_pckt_len, avail);
+ }
+
+ /* Next, write */
+ avail = CIRC_CNT(wr_circ->head, wr_circ->tail,
+ BCM_SPI_WRITE_BUF_SIZE);
+
+ if(!avail)
+ continue;
+
+ mutex_lock(&priv->wlock);
+ /*
+ * 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 > (strm->frame_len - strm->ctrl_len))
+ avail = strm->frame_len - strm->ctrl_len;
+
+ ret = 0;
+
+ /*
+ * SWGNSSGLL-15521 : Sometimes LHD does not write data
+ * because the following code blocks sending data to MCU
+ * Code is commented out because
+ * 'rx_buffer_avail_bytes'(PZC) is calculated in
+ * bcm_ssi_tx() inside loop in work queue
+ * (bcm_rxtx_work_func) below this code.
+ * It means 'rx_buffer_avail_bytes' doesn't reflect
+ * real available bytes in RX DMA RING buffer when
+ * work queue will be restarted
+ * because MCU is working independently from host.
+ * The 'rx_buffer_avail_bytes' can be tested inside
+ * bcm_ssi_tx but it may not guarantee correct
+ * condition also.
+ * SWGNSSGLL-16290 : FC detecting was broken when buffer
+ * is overflow Using PZC for a software workaround to
+ * not get into fifo overflow condition.
+ */
+ if (avail > priv->rx_buffer_avail_bytes) {
+ priv->rx_buffer_avail_bytes ? priv->ssi_tx_pzc_retries++ :
+ priv->ssi_tx_pzc_retry_delays++;
+ dev_dbg(&priv->spi->dev, "%d PZC %s, wr CIRC_CNT %lu, RNGDMA_RX %lu\n",
+ priv->rx_buffer_avail_bytes ?
+ priv->ssi_tx_pzc_retries : priv->ssi_tx_pzc_retry_delays,
+ priv->rx_buffer_avail_bytes ? "writes":"delays",
+ avail,
+ priv->rx_buffer_avail_bytes);
+ if (priv->rx_buffer_avail_bytes == 0) {
+ /*
+ *RNGDMA_RX is full ?
+ * If it's YES keep reading
+ */
+ u32 regval32[8];
+
+ bcm_ireg_read(priv, "RNGDMA_RX ",
+ HSI_RNGDMA_RX_SW_ADDR_OFFSET,
+ regval32, 3);
+ }
+ avail = priv->rx_buffer_avail_bytes;
+ usleep_range(1000, 2000);
+ /*
+ * TODO: increase delay for waiting for
+ * draining RNGDMA_RX on MCU side ?
+ */
+ wait_for_pzc = 1;
+ /*
+ * This case is for when RNGDMA_RX is
+ * full and HOST_REQ is low
+ */
+ }
+
+ /* we should align xfer size to DMA word size. */
+ sz_to_send = avail + strm->ctrl_len;
+ if (sz_to_send >= MIN_DMA_SIZE &&
+ sz_to_send & (CONFIG_SPI_DMA_BYTES_PER_WORD - 1))
+ avail = (sz_to_send & ~(CONFIG_SPI_DMA_BYTES_PER_WORD - 1))
+ - strm->ctrl_len;
+
+ /* Copy from wr_circ the data */
+ while (avail > 0) {
+ 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 + strm->pckt_len +
+ 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);
+ }
+
+ /* Transmit SSI frame */
+ if (written)
+ ret = bcm_ssi_tx(priv, written);
+
+ mutex_unlock(&priv->wlock);
+
+ if (ret)
+ break;
+
+ /*
+ * SWGNSSAND-2159 While looping,
+ * wake up lhd only if rx ring is more than 12.5% full
+ */
+ if (CIRC_CNT(rd_circ->head, rd_circ->tail, BCM_SPI_READ_BUF_SIZE) >
+ BCM_SPI_READ_BUF_SIZE / 8) {
+ wake_up(&priv->poll_wait);
+ }
+#ifdef DEBUG_1HZ_STAT
+ bbd_update_stat(bbd, STAT_TX_SSI, written);
+#endif
+
+ } while (!atomic_read(&priv->suspending) &&
+ (gpio_get_value(priv->host_req) ||
+ CIRC_CNT(wr_circ->head, wr_circ->tail, BCM_SPI_WRITE_BUF_SIZE)));
+
+ bcm477x_bye(priv);
+
+ wake_up(&priv->poll_wait);
+
+ /* Enable irq */
+ spin_lock_irqsave(&priv->irq_lock, flags);
+
+ /* we don't 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 (bbd->stat1hz.ts_irq && ts_rx_start && ts_rx_end) {
+ u64 lat = ts_rx_start - bbd->stat1hz.ts_irq;
+ u64 dur = ts_rx_end - ts_rx_start;
+
+ bbd->stat1hz.min_rx_lat = (lat < bbd->stat1hz.min_rx_lat) ?
+ lat : bbd->stat1hz.min_rx_lat;
+ bbd->stat1hz.max_rx_lat = (lat > bbd->stat1hz.max_rx_lat) ?
+ lat : bbd->stat1hz.max_rx_lat;
+ bbd->stat1hz.min_rx_dur = (dur < bbd->stat1hz.min_rx_dur) ?
+ dur : bbd->stat1hz.min_rx_dur;
+ bbd->stat1hz.max_rx_dur = (dur > bbd->stat1hz.max_rx_dur) ?
+ dur : bbd->stat1hz.max_rx_dur;
+ bbd->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 bbd_device *bbd = priv->bbd;
+
+ struct timespec64 ts;
+
+ ts = ktime_to_timespec64(ktime_get_boottime());
+ bbd->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,
+ (struct work_struct *)&priv->rxtx_work);
+
+ return IRQ_HANDLED;
+}
+
+static int gps_initialize_pinctrl(struct bcm_spi_priv *data)
+{
+ int ret = 0;
+ struct device *dev = &data->spi->dev;
+
+ /* Get pinctrl if target uses pinctrl */
+ data->ts_pinctrl = devm_pinctrl_get(dev);
+ if (IS_ERR_OR_NULL(data->ts_pinctrl)) {
+ dev_err(&data->spi->dev, "Target does not use pinctrl\n");
+ ret = PTR_ERR(data->ts_pinctrl);
+ data->ts_pinctrl = NULL;
+ return ret;
+ }
+
+ data->gpio_state_active
+ = pinctrl_lookup_state(data->ts_pinctrl, "gps_active");
+ if (IS_ERR_OR_NULL(data->gpio_state_active)) {
+ dev_err(&data->spi->dev, "Can not get ts default pinstate\n");
+ ret = PTR_ERR(data->gpio_state_active);
+ data->ts_pinctrl = NULL;
+ return ret;
+ }
+
+ data->gpio_state_suspend
+ = pinctrl_lookup_state(data->ts_pinctrl, "gps_suspend");
+ if (IS_ERR_OR_NULL(data->gpio_state_suspend)) {
+ dev_err(&data->spi->dev, "Can not get ts sleep pinstate\n");
+ ret = PTR_ERR(data->gpio_state_suspend);
+ data->ts_pinctrl = NULL;
+ return ret;
+ }
+
+ return ret;
+}
+
+static int gps_pinctrl_select(struct bcm_spi_priv *data, bool on)
+{
+ int ret = 0;
+ struct pinctrl_state *pins_state;
+
+ pins_state = on ? data->gpio_state_active : data->gpio_state_suspend;
+ if (!IS_ERR_OR_NULL(pins_state)) {
+ ret = pinctrl_select_state(data->ts_pinctrl, pins_state);
+ if (ret) {
+ dev_err(&data->spi->dev, "can not set %s pins\n",
+ on ? "gps_active" : "gps_suspend");
+ return ret;
+ }
+ } else {
+ dev_err(&data->spi->dev, "not a valid '%s' pinstate\n",
+ on ? "gps_active" : "gps_suspend");
+ }
+
+ return ret;
+}
+
+
+
+/* SPI driver operations */
+
+static int bcm_spi_suspend(struct device *dev)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ struct bcm_spi_priv *priv = spi_get_drvdata(spi);
+ unsigned long flags;
+
+ 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);
+
+ if (priv->serial_wq)
+ flush_workqueue(priv->serial_wq);
+
+ priv->ssi_pm_semaphore++;
+ return 0;
+}
+
+static int bcm_spi_resume(struct device *dev)
+{
+ struct spi_device *spi = to_spi_device(dev);
+ struct bcm_spi_priv *priv = spi_get_drvdata(spi);
+ unsigned long flags;
+
+ 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);
+
+ priv->ssi_pm_semaphore--;
+ return 0;
+}
+
+static void bcm_spi_shutdown(struct spi_device *spi)
+{
+ struct bcm_spi_priv *priv = spi_get_drvdata(spi);
+ unsigned long flags;
+
+#ifdef CONFIG_TRANSFER_STAT
+ bcm_ssi_print_trans_stat(priv);
+#endif
+
+ 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);
+ destroy_workqueue(priv->serial_wq);
+ priv->serial_wq = NULL;
+}
+
+static int bcm_spi_probe(struct spi_device *spi)
+{
+ int host_req, mcu_req, mcu_resp;
+ int gps_power;
+ int nstandby;
+ struct bcm_spi_priv *priv;
+ bool skip_validity_check;
+ bool legacy_patch = false;
+ int ret;
+ int error = 0;
+
+ /* Check GPIO# */
+#ifndef CONFIG_OF
+ dev_err(&spi->dev, "Check platform_data for bcm device\n");
+#else
+ if (!spi->dev.of_node) {
+ dev_err(&spi->dev, "Failed to find of_node\n");
+ goto err_exit;
+ }
+#endif
+
+ host_req = of_get_named_gpio(spi->dev.of_node, "host-req-gpios", 0);
+ mcu_req = of_get_named_gpio(spi->dev.of_node, "mcu-req-gpios", 0);
+ mcu_resp = of_get_named_gpio(spi->dev.of_node, "mcu-resp-gpios", 0);
+ nstandby = of_get_named_gpio(spi->dev.of_node, "nstandby-gpios", 0);
+ skip_validity_check = of_property_read_bool(spi->dev.of_node,
+ "ssp-skip-validity-check");
+#ifdef CONFIG_SENSORS_BBD_LEGACY_PATCH
+ legacy_patch = of_property_read_bool(spi->dev.of_node,
+ "ssp-legacy-patch");
+#endif
+
+ dev_info(&spi->dev, "ssp-host-req=%d, ssp-mcu_req=%d, ssp-mcu-resp=%d nstandby=%d \n",
+ host_req, mcu_req, mcu_resp, nstandby);
+ if (host_req < 0 || mcu_req < 0 || mcu_resp < 0 || nstandby < 0) {
+ dev_err(&spi->dev, "GPIO value not correct\n");
+ goto err_exit;
+ }
+
+ /* Check IRQ# */
+ ret = gpio_request(host_req, "HOST REQ");
+ if (ret) {
+ dev_err(&spi->dev, "failed to request HOST REQ, ret:%d", ret);
+ goto err_exit;
+ }
+ spi->irq = gpio_to_irq(host_req);
+ if (spi->irq < 0) {
+ dev_err(&spi->dev, "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) {
+ dev_err(&spi->dev, "failed to request MCU REQ, ret:%d", ret);
+ goto err_exit;
+ }
+ ret = gpio_direction_output(mcu_req, 0);
+ if (ret) {
+ dev_err(&spi->dev, "failed set MCU REQ as input mode, ret:%d",
+ ret);
+ goto err_exit;
+ }
+ ret = gpio_request(mcu_resp, "MCU RESP");
+ if (ret) {
+ dev_err(&spi->dev, "failed to request MCU RESP, ret:%d", ret);
+ goto err_exit;
+ }
+ ret = gpio_direction_input(mcu_resp);
+ if (ret) {
+ dev_err(&spi->dev, "failed set MCU RESP as input mode, ret:%d",
+ ret);
+ goto err_exit;
+ }
+
+ ret = gpio_request(nstandby, "GPS NSTANDBY");
+ if (ret) {
+ dev_err(&spi->dev, "failed to request GPS NSTANDBY, ret:%d", ret);
+ goto err_exit;
+ }
+ ret = gpio_direction_output(nstandby, 0);
+ if (ret) {
+ dev_err(&spi->dev, "failed set GPS NSTANDBY as out mode, ret:%d",
+ ret);
+ goto err_exit;
+ }
+
+ /* enable gps_power */
+ gps_power = of_get_named_gpio(spi->dev.of_node, "gps-power-enable", 0);
+ if (gps_power >= 0) {
+ ret = gpio_request(gps_power, "GPS POWER");
+ if (ret) {
+ dev_err(&spi->dev, "failed to request GPS POWER, ret:%d", ret);
+ goto err_exit;
+ }
+ ret = gpio_direction_output(gps_power, 1);
+ if (ret) {
+ dev_err(&spi->dev, "failed set GPS POWER as out mode, ret:%d", ret);
+ goto err_exit;
+ }
+ }
+
+ /* Alloc everything */
+ priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ goto err_exit;
+
+ priv->skip_validity_check = skip_validity_check;
+ priv->spi = spi;
+ priv->tx_buf = devm_kmalloc(&spi->dev,
+ sizeof(struct bcm_ssi_tx_frame), GFP_KERNEL);
+ priv->rx_buf = devm_kmalloc(&spi->dev,
+ sizeof(struct bcm_ssi_rx_frame), GFP_KERNEL);
+ if (!priv->tx_buf || !priv->rx_buf)
+ goto err_exit;
+
+ priv->serial_wq = alloc_workqueue("bcm477x_wq",
+ WQ_HIGHPRI|WQ_UNBOUND|WQ_MEM_RECLAIM, 1);
+ if (!priv->serial_wq) {
+ dev_err(&spi->dev, "Failed to allocate workqueue\n");
+ goto err_exit;
+ }
+ /* Init - pinctrl */
+ error = gps_initialize_pinctrl(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) {
+ dev_err(&spi->dev, "Failed to register bcm_gps_spi's misc dev. err=%d\n", ret);
+ goto free_wq;
+ }
+
+ /* 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((struct work_struct *)&priv->rxtx_work, bcm_rxtx_work_func);
+
+ /* 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;
+ priv->nstandby = nstandby;
+
+ /* Init BBD & SSP */
+ priv->bbd = bbd_init(&spi->dev, legacy_patch);
+ if (priv->bbd == NULL)
+ goto free_wq;
+
+ if (device_create_file(&priv->spi->dev, &dev_attr_nstandby))
+ dev_err(&spi->dev, "Unable to create sysfs 4775 nstandby entry");
+
+ if (device_create_file(&priv->spi->dev, &dev_attr_sspmcureq))
+ dev_err(&spi->dev, "Unable to create sysfs 4775 sspmcureq entry");
+
+ /* Request IRQ */
+ ret = devm_request_irq(&spi->dev, spi->irq, bcm_irq_handler,
+ IRQF_TRIGGER_HIGH, "ttyBCM", priv);
+ if (ret) {
+ dev_err(&spi->dev, "Failed to register BCM477x SPI TTY IRQ %d.\n",
+ spi->irq);
+ goto free_wq;
+ }
+ disable_irq(spi->irq);
+
+ dev_info(&spi->dev, "Probe OK. ssp-host-req=%d, irq=%d, priv=0x%pK\n",
+ host_req, spi->irq, priv);
+
+ return 0;
+
+free_wq:
+ if (priv->serial_wq)
+ destroy_workqueue(priv->serial_wq);
+err_exit:
+ return -ENODEV;
+}
+
+
+static int bcm_spi_remove(struct spi_device *spi)
+{
+ struct bcm_spi_priv *priv = spi_get_drvdata(spi);
+ unsigned long flags;
+
+ 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);
+ if (priv->ts_pinctrl) {
+ if (gps_pinctrl_select(priv, false) < 0)
+ dev_err(&priv->spi->dev, "Cannot get idle pinctrl state\n");
+ }
+
+ /* Free everything */
+ bbd_exit(&spi->dev);
+
+ device_remove_file(&priv->spi->dev, &dev_attr_nstandby);
+ device_remove_file(&priv->spi->dev, &dev_attr_sspmcureq);
+ return 0;
+}
+
+static const struct spi_device_id bcm_spi_id[] = {
+ {"ssp-spi", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(spi, bcm_spi_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id match_table[] = {
+ { .compatible = "ssp-spi,bcm4775",},
+ {},
+};
+#endif
+
+static const struct dev_pm_ops bcm_spi_pm_ops = {
+ .suspend = bcm_spi_suspend,
+ .resume = bcm_spi_resume,
+};
+
+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 = "brcm gps spi",
+ .owner = THIS_MODULE,
+#ifdef CONFIG_OF
+ .of_match_table = match_table,
+#endif
+ .pm = &bcm_spi_pm_ops,
+ },
+};
+
+
+/* Module init/exit */
+static int __init bcm_spi_init(void)
+{
+ return spi_register_driver(&bcm_spi_driver);
+}
+
+static void __exit bcm_spi_exit(void)
+{
+ spi_unregister_driver(&bcm_spi_driver);
+}
+
+module_init(bcm_spi_init);
+module_exit(bcm_spi_exit);
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("BCM SPI/SSI Driver");
diff --git a/bcm_gps_spi.h b/bcm_gps_spi.h
new file mode 100644
index 0000000..3ed34e5
--- /dev/null
+++ b/bcm_gps_spi.h
@@ -0,0 +1,237 @@
+/* SPDX-License-Identifier: GPL-2.0
+ * Copyright 2015 Broadcom Corporation
+ *
+ * The Broadcom GPS SPI driver
+ *
+ */
+
+#ifndef __BCM_GPS_SPI_H__
+#define __BCM_GPS_SPI_H__
+
+#define WORD_BURST_SIZE 4
+#define CONFIG_SPI_DMA_BYTES_PER_WORD 4
+#define CONFIG_SPI_DMA_BITS_PER_WORD (CONFIG_SPI_DMA_BYTES_PER_WORD * 8)
+#define MIN_DMA_SIZE 64
+#define DELAY_FOR_SYSTEM_OVERLOADED_MS 100
+#define READ_SIZE_FOR_SYSTEM_OVERLOADED 1024
+
+#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_PCKT_1B_LENGTH 0
+#define SSI_PCKT_2B_LENGTH 0x10
+
+#define SSI_FLOW_CONTROL_DISABLED 0
+#define SSI_FLOW_CONTROL_ENABLED 0x08
+
+#define SSI_WRITE_HD (SSI_WRITE_TRANS | SSI_MODE_HALF_DUPLEX)
+#define SSI_READ_HD (SSI_READ_TRANS | SSI_MODE_HALF_DUPLEX)
+
+#define HSI_F_MOSI_CTRL_CNT_SHIFT 0
+#define HSI_F_MOSI_CTRL_CNT_SIZE 3
+#define HSI_F_MOSI_CTRL_CNT_MASK 0x07
+
+#define HSI_F_MOSI_CTRL_SZE_SHIFT 3
+#define HSI_F_MOSI_CTRL_SZE_SIZE 2
+#define HSI_F_MOSI_CTRL_SZE_MASK 0x18
+
+#define HSI_F_MOSI_CTRL_RSV_SHIFT 5
+#define HSI_F_MOSI_CTRL_RSV_SIZE 1
+#define HSI_F_MOSI_CTRL_RSV_MASK 0x20
+
+#define HSI_F_MOSI_CTRL_PE_SHIFT 6
+#define HSI_F_MOSI_CTRL_PE_SIZE 1
+#define HSI_F_MOSI_CTRL_PE_MASK 0x40
+
+#define HSI_F_MOSI_CTRL_PZC_SHIFT 0
+#define HSI_F_MOSI_CTRL_PZC_SIZE (HSI_F_MOSI_CTRL_CNT_SIZE + \
+ HSI_F_MOSI_CTRL_SZE_SIZE + HSI_F_MOSI_CTRL_PE_SIZE)
+#define HSI_F_MOSI_CTRL_PZC_MASK (HSI_F_MOSI_CTRL_CNT_MASK | \
+ HSI_F_MOSI_CTRL_SZE_MASK | HSI_F_MOSI_CTRL_PE_MASK)
+
+/*
+ * Transport receive buffer size, bytes
+ * #define TRANSPORT_RX_BUFFER_SIZE 6144
+ */
+#define HSI_PZC_MAX_RX_BUFFER 6144
+
+#define DEBUG_TIME_STAT
+/* #define CONFIG_TRANSFER_STAT */
+/* #define CONFIG_REG_IO */
+#define CONFIG_PACKET_RECEIVED (31*1024)
+/* rngdma_tx_end_addr_ptr */
+#define CONFIG_RNGDMA_TX_END_ADDR_PTR_MIN (128)
+#define WORK_TYPE_RX 1
+#define WORK_TYPE_TX 2
+#define WORK_TYPE_RXTX 3
+
+#define CONFIG_MCU_WAKEUP
+
+/* TODO: Use proper kerenl type */
+struct bcm_spi_strm_protocol {
+ int pckt_len;
+ int fc_len;
+ int ctrl_len;
+ unsigned char ctrl_byte;
+ unsigned short frame_len;
+} __attribute__((__packed__));
+
+
+#ifdef CONFIG_TRANSFER_STAT
+struct bcm_spi_transfer_stat {
+ int len_255;
+ int len_1K;
+ int len_2K;
+ int len_4K;
+ int len_8K;
+ int len_16K;
+ int len_32K;
+ int len_64K;
+ unsigned long len_total;
+ unsigned long len_max;
+ unsigned long len_min;
+} __attribute__((__packed__));
+#endif
+
+
+/* class FailSafe : struct DataRecordList */
+struct bcm_failsafe_data_recordlist {
+ unsigned long start_addr;
+ unsigned int size;
+ int mem_type;
+};
+
+/*******************
+ *
+ * Structs
+ *
+ *******************/
+
+#define BCM_SPI_READ_BUF_SIZE (16 * PAGE_SIZE)
+#define BCM_SPI_WRITE_BUF_SIZE (16 * PAGE_SIZE)
+
+/* TODO: limit max payload to 254 because of exynos3 bug */
+#define MAX_SPI_DREG_FRAME_LEN 254
+
+/*
+ * TODO: MAX_SPI_FRAME_LEN = 8K should be less TRANSPORT_RX_BUFFER_SIZE,
+ * Now it is 12K (SWGNSSAND-1647)
+ * #define MAX_SPI_FRAME_LEN (BCM_SPI_READ_BUF_SIZE / 8)
+ *
+ * TODO : hardcoded because of Tizen has PAGE_SIZE == 8K not 4K
+ * as it is expected. Need to confirm.
+ */
+#define MAX_SPI_FRAME_LEN (1024 * 8)
+
+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 data[MAX_SPI_FRAME_LEN-1];
+} __attribute__((__packed__));
+
+struct bbd_device;
+
+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;
+ int nstandby;
+
+ /* 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;
+
+ /* 4775 SPI tx/rx strm protocol */
+ struct bcm_spi_strm_protocol tx_strm;
+ struct bcm_spi_strm_protocol rx_strm;
+
+#ifdef CONFIG_TRANSFER_STAT
+ struct bcm_spi_transfer_stat trans_stat[2];
+#endif
+ struct pinctrl *ts_pinctrl;
+ struct pinctrl_state *gpio_state_active;
+ struct pinctrl_state *gpio_state_suspend;
+
+ /* some chip-set(BCM4775) needs to skip validity check */
+ bool skip_validity_check;
+
+ bool irq_wakeup_enabled;
+ /* struct wake_lock bcm_wake_lock; */
+
+ /* To make sure that interface is detected on chip side !=0 */
+ unsigned long packet_received;
+
+ /* Suspend/Resume semaphore */
+ int ssi_pm_semaphore;
+
+ /* Overrun counter */
+ unsigned long skip_count;
+ unsigned long last_tick;
+
+ bool ssi_dbg;
+ bool ssi_dbg_pzc;
+ bool ssi_dbg_rng;
+
+ /*
+ * Calculating TX transfer failure operation in bus driver,
+ * reset in bcm_ssi_open()
+ */
+ int ssi_tx_fail;
+ /* Calculating TX pzc retries, reset in bcm_ssi_open() */
+ int ssi_tx_pzc_retries;
+ /* Calculating TX pzc retry delays, reset in bcm_ssi_open() */
+ int ssi_tx_pzc_retry_delays;
+
+ unsigned long rx_buffer_avail_bytes;// = HSI_PZC_MAX_RX_BUFFER;
+ /* Should be more MAX_SPI_FRAME_LEN. See below */
+ struct bbd_device *bbd;
+};
+
+/* bcm_gps_regs.cpp */
+int bcm_dreg_write(struct bcm_spi_priv *priv, char *id, u8 offset, u8 *buf,
+ u8 size);
+int bcm_dreg_read(struct bcm_spi_priv *priv, char *id, u8 offset, u8 *buf,
+ u8 size);
+int bcm_ireg_write(struct bcm_spi_priv *priv, char *id, unsigned int regaddr,
+ unsigned int regval);
+int bcm_ireg_read(struct bcm_spi_priv *priv, char *id, unsigned int regaddr,
+ unsigned int *regval, int n);
+
+/* bcm_gps_spi.cpp */
+int bcm_spi_sync(struct bcm_spi_priv *priv, void *tx_buf, void *rx_buf,
+ int len, int bits_per_word);
+
+#endif /* __BBD_H__ */