diff options
author | Cheng Chang <chengcha@google.com> | 2022-08-08 10:00:56 +0000 |
---|---|---|
committer | Cheng Chang <chengcha@google.com> | 2022-08-30 08:36:19 +0000 |
commit | 21049f89bd1a3459b29c5b11f8c6feb4292d5673 (patch) | |
tree | 320f287f642382201f623555760223986256be8b | |
parent | 354991cb719be04b2bd5ca2953f7766291bb3cd2 (diff) | |
download | bcm47765-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-- | Kbuild | 2 | ||||
-rw-r--r-- | Kconfig | 6 | ||||
-rw-r--r-- | Makefile | 24 | ||||
-rw-r--r-- | bbd.c | 884 | ||||
-rw-r--r-- | bbd.h | 203 | ||||
-rw-r--r-- | bcm_gps_regs.c | 323 | ||||
-rw-r--r-- | bcm_gps_spi.c | 1566 | ||||
-rw-r--r-- | bcm_gps_spi.h | 237 |
8 files changed, 3245 insertions, 0 deletions
@@ -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 @@ -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) @@ -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"); @@ -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, ®val, 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, ®val, 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__ */ |