From 516d7137abfb1f11c36ffb809d4dd46e0e1e13da Mon Sep 17 00:00:00 2001 From: Taejin Kim Date: Mon, 14 May 2018 20:31:32 +0900 Subject: [PATCH] [COMMON] drivers: misc: gnss_if: Apply GNSS driver files GNSS drvier filese were came from branch android-exynos-4.9-ww-9610-stable Change-Id: Ifb9c15ed8dee6051cd5c52312a7325793a7b3c79 Signed-off-by: Taejin Kim --- drivers/misc/Kconfig | 1 + drivers/misc/Makefile | 1 + drivers/misc/gnss_if/Kconfig | 6 + drivers/misc/gnss_if/Makefile | 11 + drivers/misc/gnss_if/gnss_io_device.c | 833 +++++++++++++++ drivers/misc/gnss_if/gnss_keplerctl_device.c | 643 ++++++++++++ .../misc/gnss_if/gnss_link_device_memory.c | 400 +++++++ .../misc/gnss_if/gnss_link_device_memory.h | 215 ++++ drivers/misc/gnss_if/gnss_link_device_shmem.c | 978 ++++++++++++++++++ drivers/misc/gnss_if/gnss_link_device_shmem.h | 444 ++++++++ drivers/misc/gnss_if/gnss_main.c | 518 ++++++++++ drivers/misc/gnss_if/gnss_prj.h | 367 +++++++ drivers/misc/gnss_if/gnss_utils.c | 128 +++ drivers/misc/gnss_if/gnss_utils.h | 52 + drivers/misc/gnss_if/include/exynos_ipc.h | 155 +++ drivers/misc/gnss_if/include/gnss.h | 200 ++++ drivers/misc/gnss_if/pmu-gnss.c | 471 +++++++++ drivers/misc/gnss_if/pmu-gnss.h | 105 ++ 18 files changed, 5528 insertions(+) create mode 100644 drivers/misc/gnss_if/Kconfig create mode 100644 drivers/misc/gnss_if/Makefile create mode 100644 drivers/misc/gnss_if/gnss_io_device.c create mode 100644 drivers/misc/gnss_if/gnss_keplerctl_device.c create mode 100644 drivers/misc/gnss_if/gnss_link_device_memory.c create mode 100644 drivers/misc/gnss_if/gnss_link_device_memory.h create mode 100644 drivers/misc/gnss_if/gnss_link_device_shmem.c create mode 100644 drivers/misc/gnss_if/gnss_link_device_shmem.h create mode 100644 drivers/misc/gnss_if/gnss_main.c create mode 100644 drivers/misc/gnss_if/gnss_prj.h create mode 100644 drivers/misc/gnss_if/gnss_utils.c create mode 100644 drivers/misc/gnss_if/gnss_utils.h create mode 100755 drivers/misc/gnss_if/include/exynos_ipc.h create mode 100755 drivers/misc/gnss_if/include/gnss.h create mode 100644 drivers/misc/gnss_if/pmu-gnss.c create mode 100644 drivers/misc/gnss_if/pmu-gnss.h diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index d3ba3917e316..5eac86fde8ab 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -541,4 +541,5 @@ source "drivers/misc/echo/Kconfig" source "drivers/misc/cxl/Kconfig" source "drivers/misc/mcu_ipc/Kconfig" source "drivers/misc/modem_if/Kconfig" +source "drivers/misc/gnss_if/Kconfig" endmenu diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 15c802c065a8..b6ed9020be50 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -60,6 +60,7 @@ obj-$(CONFIG_PCI_ENDPOINT_TEST) += pci_endpoint_test.o obj-$(CONFIG_UID_SYS_STATS) += uid_sys_stats.o obj-$(CONFIG_MCU_IPC) += mcu_ipc/ obj-$(CONFIG_SEC_MODEM_IF) += modem_if/ +obj-$(CONFIG_GNSS_SHMEM_IF) += gnss_if/ obj-$(CONFIG_MEMORY_STATE_TIME) += memory_state_time.o lkdtm-$(CONFIG_LKDTM) += lkdtm_core.o diff --git a/drivers/misc/gnss_if/Kconfig b/drivers/misc/gnss_if/Kconfig new file mode 100644 index 000000000000..5027e9d49e55 --- /dev/null +++ b/drivers/misc/gnss_if/Kconfig @@ -0,0 +1,6 @@ +config GNSS_SHMEM_IF + bool "Samsung Shared memory Interface for GNSS" + depends on MCU_IPC + default n + ---help--- + Samsung Shared Memory Interface for GNSS. diff --git a/drivers/misc/gnss_if/Makefile b/drivers/misc/gnss_if/Makefile new file mode 100644 index 000000000000..ba3d9cc3972b --- /dev/null +++ b/drivers/misc/gnss_if/Makefile @@ -0,0 +1,11 @@ +# Makefile of gnss_if + +# obj-$(CONFIG_GNSS_SHMEM_IF) += gnss_main.o gnss_io_device.o gnss_link_device_shmem.o \ +# gnss_keplerctl_device.o gnss_utils.o + +obj-$(CONFIG_GNSS_SHMEM_IF) += gnss_main.o gnss_io_device.o \ + gnss_keplerctl_device.o \ + gnss_link_device_shmem.o \ + gnss_link_device_memory.o \ + pmu-gnss.o \ + gnss_utils.o diff --git a/drivers/misc/gnss_if/gnss_io_device.c b/drivers/misc/gnss_if/gnss_io_device.c new file mode 100644 index 000000000000..f48bf1e1941e --- /dev/null +++ b/drivers/misc/gnss_if/gnss_io_device.c @@ -0,0 +1,833 @@ +/* + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gnss_prj.h" +#include "gnss_utils.h" + +#define WAKE_TIME (HZ/2) /* 500 msec */ + +static void exynos_build_header(struct io_device *iod, struct link_device *ld, + u8 *buff, u16 cfg, u8 ctl, size_t count); + +static inline void iodev_lock_wlock(struct io_device *iod) +{ + if (iod->waketime > 0 && !wake_lock_active(&iod->wakelock)) + wake_lock_timeout(&iod->wakelock, iod->waketime); +} + +static inline int queue_skb_to_iod(struct sk_buff *skb, struct io_device *iod) +{ + struct sk_buff_head *rxq = &iod->sk_rx_q; + + skb_queue_tail(rxq, skb); + + if (rxq->qlen > MAX_IOD_RXQ_LEN) { + gif_err("%s: %s application may be dead (rxq->qlen %d > %d)\n", + iod->name, iod->app ? iod->app : "corresponding", + rxq->qlen, MAX_IOD_RXQ_LEN); + skb_queue_purge(rxq); + return -ENOSPC; + } else { + gif_debug("%s: rxq->qlen = %d\n", iod->name, rxq->qlen); + wake_up(&iod->wq); + return 0; + } +} + +static inline int rx_frame_with_link_header(struct sk_buff *skb) +{ + struct exynos_link_header *hdr; + + /* Remove EXYNOS link header */ + hdr = (struct exynos_link_header *)skb->data; + skb_pull(skb, EXYNOS_HEADER_SIZE); + +#ifdef DEBUG_GNSS_IPC_PKT + /* Print received data from GNSS */ + gnss_log_ipc_pkt(skb, RX); +#endif + + return queue_skb_to_iod(skb, skbpriv(skb)->iod); +} + +static int rx_fmt_ipc(struct sk_buff *skb) +{ + return rx_frame_with_link_header(skb); +} + +static int rx_demux(struct link_device *ld, struct sk_buff *skb) +{ + struct io_device *iod; + + iod = ld->iod; + if (unlikely(!iod)) { + gif_err("%s: ERR! no iod!\n", ld->name); + return -ENODEV; + } + + skbpriv(skb)->ld = ld; + skbpriv(skb)->iod = iod; + + if (atomic_read(&iod->opened) <= 0) { + gif_err_limited("%s: ERR! %s is not opened\n", ld->name, iod->name); + return -ENODEV; + } + + return rx_fmt_ipc(skb); +} + +static int rx_frame_done(struct io_device *iod, struct link_device *ld, + struct sk_buff *skb) +{ + /* Cut off the padding of the current frame */ + skb_trim(skb, exynos_get_frame_len(skb->data)); + gif_debug("%s->%s: frame length = %d\n", ld->name, iod->name, skb->len); + + return rx_demux(ld, skb); +} + +static int recv_frame_from_skb(struct io_device *iod, struct link_device *ld, + struct sk_buff *skb) +{ + struct sk_buff *clone; + unsigned int rest; + unsigned int rcvd; + unsigned int tot; /* total length including padding */ + int err = 0; + + /* + ** If there is only one EXYNOS frame in @skb, receive the EXYNOS frame and + ** return immediately. In this case, the frame verification must already + ** have been done at the link device. + */ + if (skbpriv(skb)->single_frame) { + err = rx_frame_done(iod, ld, skb); + if (err < 0) + goto exit; + return 0; + } + + /* + ** The routine from here is used only if there may be multiple EXYNOS + ** frames in @skb. + */ + + /* Check the config field of the first frame in @skb */ + if (!exynos_start_valid(skb->data)) { + gif_err("%s->%s: ERR! INVALID config 0x%02X\n", + ld->name, iod->name, skb->data[0]); + err = -EINVAL; + goto exit; + } + + /* Get the total length of the frame with a padding */ + tot = exynos_get_total_len(skb->data); + + /* Verify the total length of the first frame */ + rest = skb->len; + if (unlikely(tot > rest)) { + gif_err("%s->%s: ERR! tot %d > skb->len %d)\n", + ld->name, iod->name, tot, rest); + err = -EINVAL; + goto exit; + } + + /* If there is only one EXYNOS frame in @skb, */ + if (likely(tot == rest)) { + /* Receive the EXYNOS frame and return immediately */ + err = rx_frame_done(iod, ld, skb); + if (err < 0) + goto exit; + return 0; + } + + /* + ** This routine is used only if there are multiple EXYNOS frames in @skb. + */ + rcvd = 0; + while (rest > 0) { + clone = skb_clone(skb, GFP_ATOMIC); + if (unlikely(!clone)) { + gif_err("%s->%s: ERR! skb_clone fail\n", + ld->name, iod->name); + err = -ENOMEM; + goto exit; + } + + /* Get the start of an EXYNOS frame */ + skb_pull(clone, rcvd); + if (!exynos_start_valid(clone->data)) { + gif_err("%s->%s: ERR! INVALID config 0x%02X\n", + ld->name, iod->name, clone->data[0]); + dev_kfree_skb_any(clone); + err = -EINVAL; + goto exit; + } + + /* Get the total length of the current frame with a padding */ + tot = exynos_get_total_len(clone->data); + if (unlikely(tot > rest)) { + gif_err("%s->%s: ERR! dirty frame (tot %d > rest %d)\n", + ld->name, iod->name, tot, rest); + dev_kfree_skb_any(clone); + err = -EINVAL; + goto exit; + } + + /* Cut off the padding of the current frame */ + skb_trim(clone, exynos_get_frame_len(clone->data)); + + /* Demux the frame */ + err = rx_demux(ld, clone); + if (err < 0) { + gif_err("%s->%s: ERR! rx_demux fail (err %d)\n", + ld->name, iod->name, err); + dev_kfree_skb_any(clone); + goto exit; + } + + /* Calculate the start of the next frame */ + rcvd += tot; + + /* Calculate the rest size of data in @skb */ + rest -= tot; + } + +exit: + dev_kfree_skb_any(skb); + return err; +} + +/* called from link device when a packet arrives for this io device */ +static int io_dev_recv_skb_from_link_dev(struct io_device *iod, + struct link_device *ld, struct sk_buff *skb) +{ + int err; + + iodev_lock_wlock(iod); + + err = recv_frame_from_skb(iod, ld, skb); + if (err < 0) { + gif_err("%s->%s: ERR! recv_frame_from_skb fail(err %d)\n", + ld->name, iod->name, err); + } + + return err; +} + +/* called from link device when a packet arrives fo this io device */ +static int io_dev_recv_skb_single_from_link_dev(struct io_device *iod, + struct link_device *ld, struct sk_buff *skb) +{ + int err; + + iodev_lock_wlock(iod); + + if (skbpriv(skb)->lnk_hdr) + skb_trim(skb, exynos_get_frame_len(skb->data)); + + err = rx_demux(ld, skb); + if (err < 0) + gif_err_limited("%s<-%s: ERR! rx_demux fail (err %d)\n", + iod->name, ld->name, err); + + return err; +} + +static int misc_open(struct inode *inode, struct file *filp) +{ + struct io_device *iod = to_io_device(filp->private_data); + struct link_device *ld; + int ref_cnt; + filp->private_data = (void *)iod; + + ld = iod->ld; + + ref_cnt = atomic_inc_return(&iod->opened); + + gif_err("%s (opened %d) by %s\n", iod->name, ref_cnt, current->comm); + + return 0; +} + +static int misc_release(struct inode *inode, struct file *filp) +{ + struct io_device *iod = (struct io_device *)filp->private_data; + int ref_cnt; + + skb_queue_purge(&iod->sk_rx_q); + + ref_cnt = atomic_dec_return(&iod->opened); + + gif_err("%s (opened %d) by %s\n", iod->name, ref_cnt, current->comm); + + return 0; +} + +static unsigned int misc_poll(struct file *filp, struct poll_table_struct *wait) +{ + struct io_device *iod = (struct io_device *)filp->private_data; + struct gnss_ctl *gc = iod->gc; + poll_wait(filp, &iod->wq, wait); + + if (!skb_queue_empty(&iod->sk_rx_q) && gc->gnss_state != STATE_OFFLINE) + return POLLIN | POLLRDNORM; + + if (gc->gnss_state == STATE_OFFLINE || gc->gnss_state == STATE_FAULT) { + gif_err("POLL wakeup in abnormal state!!!\n"); + return POLLHUP; + } else { + return 0; + } +} + +static int valid_cmd_arg(unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case GNSS_IOCTL_RESET: + case GNSS_IOCTL_LOAD_FIRMWARE: + case GNSS_IOCTL_REQ_FAULT_INFO: + case GNSS_IOCTL_REQ_BCMD: + return access_ok(VERIFY_READ, (const void *)arg, sizeof(arg)); + case GNSS_IOCTL_READ_FIRMWARE: + return access_ok(VERIFY_WRITE, (const void *)arg, sizeof(arg)); + default: + return true; + } +} + +static int send_bcmd(struct io_device *iod, unsigned long arg) +{ + struct gnss_ctl *gc = iod->gc; + struct kepler_bcmd_args bcmd_args; + int err = 0; + + memset(&bcmd_args, 0, sizeof(struct kepler_bcmd_args)); + err = copy_from_user(&bcmd_args, (const void __user *)arg, + sizeof(struct kepler_bcmd_args)); + if (err) { + gif_err("copy_from_user fail(to get structure)\n"); + err = -EFAULT; + goto bcmd_exit; + } + + if (!gc->ops.req_bcmd) { + gif_err("%s: !ld->req_bcmd\n", iod->name); + err = -EFAULT; + goto bcmd_exit; + } + + gif_info("flags : %d, cmd_id : %d, param1 : %d, param2 : %d(0x%x)\n", + bcmd_args.flags, bcmd_args.cmd_id, bcmd_args.param1, + bcmd_args.param2, bcmd_args.param2); + err = gc->ops.req_bcmd(gc, bcmd_args.cmd_id, bcmd_args.flags, + bcmd_args.param1, bcmd_args.param2); + if (err == -EIO) { /* BCMD timeout */ + gif_err("BCMD timeout cmd_id : %d\n", bcmd_args.cmd_id); + } else { + bcmd_args.ret_val = err; + err = copy_to_user((void __user *)arg, + (void *)&bcmd_args, sizeof(bcmd_args)); + if (err) { + gif_err("copy_to_user fail(to send bcmd params)\n"); + err = -EFAULT; + } + } + +bcmd_exit: + return err; +} + +static int gnss_load_firmware(struct io_device *iod, + struct kepler_firmware_args firmware_arg) +{ + struct link_device *ld = iod->ld; + int err = 0; + + gif_info("Load Firmware - fw size : %d, fw_offset : %d\n", + firmware_arg.firmware_size, firmware_arg.offset); + + if (!ld->copy_reserved_from_user) { + gif_err("No copy_reserved_from_user method\n"); + err = -EFAULT; + goto load_firmware_exit; + } + + err = ld->copy_reserved_from_user(iod->ld, firmware_arg.offset, + firmware_arg.firmware_bin, firmware_arg.firmware_size); + if (err) { + gif_err("Unable to load firmware\n"); + err = -EFAULT; + goto load_firmware_exit; + } + +load_firmware_exit: + return err; +} + +static int parsing_load_firmware(struct io_device *iod, unsigned long arg) +{ + struct kepler_firmware_args firmware_arg; + int err = 0; + + memset(&firmware_arg, 0, sizeof(struct kepler_firmware_args)); + err = copy_from_user(&firmware_arg, (const void __user *)arg, + sizeof(struct kepler_firmware_args)); + if (err) { + gif_err("copy_from_user fail(to get structure)\n"); + err = -EFAULT; + return err; + } + + return gnss_load_firmware(iod, firmware_arg); +} + +static int gnss_read_firmware(struct io_device *iod, + struct kepler_firmware_args firmware_arg) +{ + struct link_device *ld = iod->ld; + int err = 0; + + gif_debug("Read Firmware - fw size : %d, fw_offset : %d\n", + firmware_arg.firmware_size, firmware_arg.offset); + + if (!ld->copy_reserved_to_user) { + gif_err("No copy_reserved_to_user method\n"); + err = -EFAULT; + goto read_firmware_exit; + } + + err = ld->copy_reserved_to_user(iod->ld, firmware_arg.offset, + firmware_arg.firmware_bin, firmware_arg.firmware_size); + if (err) { + gif_err("Unable to read firmware\n"); + err = -EFAULT; + goto read_firmware_exit; + } + +read_firmware_exit: + return err; +} + +static int parsing_read_firmware(struct io_device *iod, unsigned long arg) +{ + struct kepler_firmware_args firmware_arg; + int err = 0; + + memset(&firmware_arg, 0, sizeof(struct kepler_firmware_args)); + err = copy_from_user(&firmware_arg, (const void __user *)arg, + sizeof(struct kepler_firmware_args)); + if (err) { + gif_err("copy_from_user fail(to get structure)\n"); + err = -EFAULT; + return err; + } + + return gnss_read_firmware(iod, firmware_arg); +} + +static int change_tcxo_mode(struct gnss_ctl *gc, unsigned long arg) +{ + enum gnss_tcxo_mode tcxo_mode; + int ret; + + ret = copy_from_user(&tcxo_mode, (const void __user *)arg, + sizeof(enum gnss_tcxo_mode)); + if (ret) { + gif_err("copy_from_user fail(to get tcxo mode)\n"); + ret = -EFAULT; + goto change_mode_exit; + } + + if (!gc->pmu_ops->change_tcxo_mode) { + gif_err("!gc->pmu_ops->change_tcxo_mode\n"); + ret = -EFAULT; + goto change_mode_exit; + } + + ret = gc->pmu_ops->change_tcxo_mode(tcxo_mode); + +change_mode_exit: + return ret; +} + +static int set_sensor_power(struct gnss_ctl *gc, unsigned long arg) +{ + enum sensor_power sensor_power_en; + int ret; + + ret = copy_from_user(&sensor_power_en, (const void __user *)arg, + sizeof(enum sensor_power)); + if (ret) { + gif_err("copy_from_user fail(to get sensor power setting)\n"); + ret = -EFAULT; + goto set_sensor_power_exit; + } + + if (!gc->ops.set_sensor_power) { + gif_err("!gc->ops.set_sensor_power\n"); + ret = -EFAULT; + goto set_sensor_power_exit; + } + + ret = gc->ops.set_sensor_power(gc, sensor_power_en); + +set_sensor_power_exit: + return ret; +} + +static long misc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct io_device *iod = (struct io_device *)filp->private_data; + struct link_device *ld = iod->ld; + struct gnss_ctl *gc = iod->gc; + int err = 0; + int size; + + if (!valid_cmd_arg(cmd, arg)) + return -ENOTTY; + + switch (cmd) { + case GNSS_IOCTL_RESET: + gif_err("%s: GNSS_IOCTL_RESET\n", iod->name); + + if (!gc->ops.gnss_hold_reset) { + gif_err("%s: !gc->ops.gnss_reset\n", iod->name); + return -EINVAL; + } + gc->ops.gnss_hold_reset(gc); + skb_queue_purge(&iod->sk_rx_q); + return 0; + + case GNSS_IOCTL_REQ_FAULT_INFO: + gif_err("%s: GNSS_IOCTL_REQ_FAULT_INFO\n", iod->name); + + if (!gc->ops.gnss_req_fault_info) { + gif_err("%s: !gc->ops.req_fault_info\n", iod->name); + return -EFAULT; + } + size = gc->ops.gnss_req_fault_info(gc); + + gif_err("gnss_req_fault_info returned %d\n", size); + + if (size < 0) { + gif_err("Can't get fault info from Kepler\n"); + return -EFAULT; + } + + if (size > 0) { + err = ld->dump_fault_to_user(ld, (void __user *)arg, size); + if (err) { + gif_err("copy_to_user fail(to copy fault info)\n"); + return -EFAULT; + } + } + return size; + + case GNSS_IOCTL_REQ_BCMD: + gif_info("%s: GNSS_IOCTL_REQ_BCMD\n", iod->name); + return send_bcmd(iod, arg); + + case GNSS_IOCTL_LOAD_FIRMWARE: + gif_info("%s: GNSS_IOCTL_LOAD_FIRMWARE\n", iod->name); + return parsing_load_firmware(iod, arg); + + case GNSS_IOCTL_READ_FIRMWARE: + gif_debug("%s: GNSS_IOCTL_READ_FIRMWARE\n", iod->name); + return parsing_read_firmware(iod, arg); + + case GNSS_IOCTL_CHANGE_SENSOR_GPIO: + gif_err("%s: GNSS_IOCTL_CHANGE_SENSOR_GPIO\n", iod->name); + + if (!gc->ops.change_sensor_gpio) { + gif_err("%s: !gc->ops.change_sensor_gpio\n", iod->name); + return -EFAULT; + } + return gc->ops.change_sensor_gpio(gc); + + case GNSS_IOCTL_CHANGE_TCXO_MODE: + gif_err("%s: GNSS_IOCTL_CHANGE_TCXO_MODE\n", iod->name); + return change_tcxo_mode(gc, arg); + + case GNSS_IOCTL_SET_SENSOR_POWER: + gif_err("%s: GNSS_IOCTL_SENSOR_POWER\n", iod->name); + return set_sensor_power(gc, arg); + + default: + + gif_err("%s: ERR! undefined cmd 0x%X\n", iod->name, cmd); + return -EINVAL; + } + + return 0; +} + +#ifdef CONFIG_COMPAT +static int parsing_load_firmware32(struct io_device *iod, unsigned long arg) +{ + struct kepler_firmware_args firmware_arg; + struct kepler_firmware_args32 arg32; + int err = 0; + + memset(&firmware_arg, 0, sizeof(firmware_arg)); + err = copy_from_user(&arg32, (const void __user *)arg, + sizeof(struct kepler_firmware_args32)); + if (err) { + gif_err("copy_from_user fail(to get structure)\n"); + err = -EFAULT; + + return err; + } + + firmware_arg.firmware_size = arg32.firmware_size; + firmware_arg.offset = arg32.offset; + firmware_arg.firmware_bin = compat_ptr(arg32.firmware_bin); + + return gnss_load_firmware(iod, firmware_arg); +} + +static int parsing_read_firmware32(struct io_device *iod, unsigned long arg) +{ + struct kepler_firmware_args firmware_arg; + struct kepler_firmware_args32 arg32; + int err = 0; + + memset(&firmware_arg, 0, sizeof(firmware_arg)); + err = copy_from_user(&arg32, (const void __user *)arg, + sizeof(struct kepler_firmware_args32)); + if (err) { + gif_err("copy_from_user fail(to get structure)\n"); + err = -EFAULT; + + return err; + } + + firmware_arg.firmware_size = arg32.firmware_size; + firmware_arg.offset = arg32.offset; + firmware_arg.firmware_bin = compat_ptr(arg32.firmware_bin); + + return gnss_read_firmware(iod, firmware_arg); +} + +static long misc_compat_ioctl(struct file *filp, + unsigned int cmd, unsigned long arg) +{ + struct io_device *iod = (struct io_device *)filp->private_data; + unsigned long realarg = (unsigned long)compat_ptr(arg); + + if (!valid_cmd_arg(cmd, realarg)) + return -ENOTTY; + + switch (cmd) { + case GNSS_IOCTL_LOAD_FIRMWARE: + gif_info("%s: GNSS_IOCTL_LOAD_FIRMWARE (32-bit)\n", iod->name); + return parsing_load_firmware32(iod, realarg); + case GNSS_IOCTL_READ_FIRMWARE: + gif_info("%s: GNSS_IOCTL_READ_FIRMWARE (32-bit)\n", iod->name); + return parsing_read_firmware32(iod, realarg); + } + return misc_ioctl(filp, cmd, realarg); +} +#endif + +static ssize_t misc_write(struct file *filp, const char __user *data, + size_t count, loff_t *fpos) +{ + struct io_device *iod = (struct io_device *)filp->private_data; + struct link_device *ld = iod->ld; + struct sk_buff *skb; + u8 *buff; + int ret; + size_t headroom; + size_t tailroom; + size_t tx_bytes; + u16 fr_cfg; + + fr_cfg = EXYNOS_SINGLE_MASK << 8; + headroom = EXYNOS_HEADER_SIZE; + tailroom = exynos_calc_padding_size(EXYNOS_HEADER_SIZE + count); + + tx_bytes = headroom + count + tailroom; + + skb = alloc_skb(tx_bytes, GFP_KERNEL); + if (!skb) { + gif_err("%s: ERR! alloc_skb fail (tx_bytes:%ld)\n", + iod->name, tx_bytes); + return -ENOMEM; + } + + /* Store the IO device, the link device, etc. */ + skbpriv(skb)->iod = iod; + skbpriv(skb)->ld = ld; + + skbpriv(skb)->lnk_hdr = iod->link_header; + skbpriv(skb)->exynos_ch = 0; /* Single channel should be 0. */ + + /* Build EXYNOS link header */ + if (fr_cfg) { + buff = skb_put(skb, headroom); + exynos_build_header(iod, ld, buff, fr_cfg, 0, count); + } + + /* Store IPC message */ + buff = skb_put(skb, count); + if (copy_from_user(buff, data, count)) { + gif_err("%s->%s: ERR! copy_from_user fail (count %ld)\n", + iod->name, ld->name, count); + dev_kfree_skb_any(skb); + return -EFAULT; + } + + /* Apply padding */ + if (tailroom) + skb_put(skb, tailroom); + + /* send data with sk_buff, link device will put sk_buff + * into the specific sk_buff_q and run work-q to send data + */ + skbpriv(skb)->iod = iod; + skbpriv(skb)->ld = ld; + + ret = ld->send(ld, iod, skb); + if (ret < 0) { + gif_err("%s->%s: ERR! ld->send fail (err %d, tx_bytes %ld)\n", + iod->name, ld->name, ret, tx_bytes); + return ret; + } + + if (ret != tx_bytes) { + gif_debug("%s->%s: WARNING! ret %d != tx_bytes %ld (count %ld)\n", + iod->name, ld->name, ret, tx_bytes, count); + } + + return count; +} + +static ssize_t misc_read(struct file *filp, char *buf, size_t count, + loff_t *fpos) +{ + struct io_device *iod = (struct io_device *)filp->private_data; + struct sk_buff_head *rxq = &iod->sk_rx_q; + struct sk_buff *skb; + int copied = 0; + + if (skb_queue_empty(rxq)) { + gif_debug("%s: ERR! no data in rxq\n", iod->name); + return 0; + } + + skb = skb_dequeue(rxq); + if (unlikely(!skb)) { + gif_debug("%s: No data in RXQ\n", iod->name); + return 0; + } + + copied = skb->len > count ? count : skb->len; + + if (copy_to_user(buf, skb->data, copied)) { + gif_err("%s: ERR! copy_to_user fail\n", iod->name); + dev_kfree_skb_any(skb); + return -EFAULT; + } + + gif_debug("%s: data:%d copied:%d qlen:%d\n", + iod->name, skb->len, copied, rxq->qlen); + + if (skb->len > count) { + skb_pull(skb, count); + skb_queue_head(rxq, skb); + } else { + dev_kfree_skb_any(skb); + } + + return copied; +} + +static const struct file_operations misc_io_fops = { + .owner = THIS_MODULE, + .open = misc_open, + .release = misc_release, + .poll = misc_poll, + .unlocked_ioctl = misc_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = misc_compat_ioctl, +#endif + .write = misc_write, + .read = misc_read, +}; + +static void exynos_build_header(struct io_device *iod, struct link_device *ld, + u8 *buff, u16 cfg, u8 ctl, size_t count) +{ + u16 *exynos_header = (u16 *)(buff + EXYNOS_START_OFFSET); + u16 *frame_seq = (u16 *)(buff + EXYNOS_FRAME_SEQ_OFFSET); + u16 *frag_cfg = (u16 *)(buff + EXYNOS_FRAG_CONFIG_OFFSET); + u16 *size = (u16 *)(buff + EXYNOS_LEN_OFFSET); + struct exynos_seq_num *seq_num = &(iod->seq_num); + + *exynos_header = EXYNOS_START_MASK; + *frame_seq = ++seq_num->frame_cnt; + *frag_cfg = cfg; + *size = (u16)(EXYNOS_HEADER_SIZE + count); + buff[EXYNOS_CH_ID_OFFSET] = 0; /* single channel, should be 0. */ + + if (cfg == EXYNOS_SINGLE_MASK) + *frag_cfg = cfg; + + buff[EXYNOS_CH_SEQ_OFFSET] = ++seq_num->ch_cnt[0]; +} + +int exynos_init_gnss_io_device(struct io_device *iod) +{ + int ret = 0; + + /* Matt - GNSS uses link headers; placeholder code */ + iod->link_header = true; + + /* Get data from link device */ + gif_info("%s: init\n", iod->name); + iod->recv_skb = io_dev_recv_skb_from_link_dev; + iod->recv_skb_single = io_dev_recv_skb_single_from_link_dev; + + /* Register misc device */ + init_waitqueue_head(&iod->wq); + skb_queue_head_init(&iod->sk_rx_q); + + iod->miscdev.minor = MISC_DYNAMIC_MINOR; + iod->miscdev.name = iod->name; + iod->miscdev.fops = &misc_io_fops; + iod->waketime = WAKE_TIME; + wake_lock_init(&iod->wakelock, WAKE_LOCK_SUSPEND, iod->name); + + ret = misc_register(&iod->miscdev); + if (ret) + gif_err("%s: ERR! misc_register failed\n", iod->name); + + return ret; +} diff --git a/drivers/misc/gnss_if/gnss_keplerctl_device.c b/drivers/misc/gnss_if/gnss_keplerctl_device.c new file mode 100644 index 000000000000..8ef8802b11b3 --- /dev/null +++ b/drivers/misc/gnss_if/gnss_keplerctl_device.c @@ -0,0 +1,643 @@ +/* + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "gnss_prj.h" +#include "gnss_link_device_shmem.h" +#include "pmu-gnss.h" + +#define BCMD_WAKELOCK_TIMEOUT (HZ / 10) /* 100 msec */ +#define REQ_BCMD_TIMEOUT (200) /* ms */ +#define REQ_INIT_TIMEOUT (1000) /* ms */ + +static void gnss_state_changed(struct gnss_ctl *gc, enum gnss_state state) +{ + struct io_device *iod = gc->iod; + int old_state = gc->gnss_state; + + if (old_state != state) { + gc->gnss_state = state; + gif_err("%s state changed (%s -> %s)\n", gc->name, + get_gnss_state_str(old_state), get_gnss_state_str(state)); + } + + if (state == STATE_OFFLINE || state == STATE_FAULT) + wake_up(&iod->wq); +} + +static irqreturn_t kepler_req_init_isr(int irq, void *arg) +{ + struct gnss_ctl *gc = (struct gnss_ctl *)arg; + + gif_err("REQ_INIT Interrupt occurred!\n"); + + disable_irq_nosync(gc->req_init_irq); + complete(&gc->req_init_cmpl); + + return IRQ_HANDLED; +} + +static irqreturn_t kepler_active_isr(int irq, void *arg) +{ + struct gnss_ctl *gc = (struct gnss_ctl *)arg; + struct io_device *iod = gc->iod; + + gif_err("ACTIVE Interrupt occurred!\n"); + + if (!wake_lock_active(&gc->gc_fault_wake_lock)) + wake_lock_timeout(&gc->gc_fault_wake_lock, HZ); + + gnss_state_changed(gc, STATE_FAULT); + wake_up(&iod->wq); + + gc->pmu_ops->clear_int(GNSS_INT_ACTIVE_CLEAR); + + return IRQ_HANDLED; +} + +static irqreturn_t kepler_wdt_isr(int irq, void *arg) +{ + struct gnss_ctl *gc = (struct gnss_ctl *)arg; + struct io_device *iod = gc->iod; + + gif_err("WDT Interrupt occurred!\n"); + + if (!wake_lock_active(&gc->gc_fault_wake_lock)) + wake_lock_timeout(&gc->gc_fault_wake_lock, HZ); + + gnss_state_changed(gc, STATE_FAULT); + wake_up(&iod->wq); + + gc->pmu_ops->clear_int(GNSS_INT_WDT_RESET_CLEAR); + return IRQ_HANDLED; +} + +static irqreturn_t kepler_wakelock_isr(int irq, void *arg) +{ + struct gnss_ctl *gc = (struct gnss_ctl *)arg; + struct gnss_mbox *mbx = gc->gnss_data->mbx; + struct link_device *ld = gc->iod->ld; + struct shmem_link_device *shmd = to_shmem_link_device(ld); + /* + u32 rx_tail, rx_head, tx_tail, tx_head, gnss_ipc_msg, ap_ipc_msg; + */ + +#ifdef USE_SIMPLE_WAKE_LOCK + gif_err("Unexpected interrupt occurred(%s)!!!!\n", __func__); + return IRQ_HANDLED; +#endif + + /* This is for debugging + tx_head = get_txq_head(shmd); + tx_tail = get_txq_tail(shmd); + rx_head = get_rxq_head(shmd); + rx_tail = get_rxq_tail(shmd); + gnss_ipc_msg = mbox_get_value(MCU_GNSS, shmd->irq_gnss2ap_ipc_msg); + ap_ipc_msg = read_int2gnss(shmd); + + gif_err("RX_H[0x%x], RX_T[0x%x], TX_H[0x%x], TX_T[0x%x],\ + AP_IPC[0x%x], GNSS_IPC[0x%x]\n", + rx_head, rx_tail, tx_head, tx_tail, ap_ipc_msg, gnss_ipc_msg); + */ + + /* Clear wake_lock */ + if (wake_lock_active(&shmd->wlock)) + wake_unlock(&shmd->wlock); + + gif_info("Wake Lock ISR!!!!\n"); + gif_err(">>>>DBUS_SW_WAKE_INT\n"); + + /* 1. Set wake-lock-timeout(). */ + if (!wake_lock_active(&gc->gc_wake_lock)) + wake_lock_timeout(&gc->gc_wake_lock, HZ); /* 1 sec */ + + /* 2. Disable DBUS_SW_WAKE_INT interrupts. */ + disable_irq_nosync(gc->wake_lock_irq); + + /* 3. Write 0x1 to MBOX_reg[6]. */ + /* MBOX_req[6] is WAKE_LOCK */ + if (gnss_read_reg(shmd, GNSS_REG_WAKE_LOCK) == 0X1) { + gif_err("@@ reg_wake_lock is already 0x1!!!!!!\n"); + return IRQ_HANDLED; + } else { + gnss_write_reg(shmd, GNSS_REG_WAKE_LOCK, 0x1); + } + + /* 4. Send interrupt MBOX1[3]. */ + /* Interrupt MBOX1[3] is RSP_WAKE_LOCK_SET */ + mbox_set_interrupt(mbx->id, mbx->int_ap2gnss_ack_wake_set); + + return IRQ_HANDLED; +} + +static void kepler_irq_bcmd_handler(void *data) +{ + struct gnss_ctl *gc = (struct gnss_ctl *)data; + + /* Signal kepler_req_bcmd */ + complete(&gc->bcmd_cmpl); +} + +#ifdef USE_SIMPLE_WAKE_LOCK +static void mbox_kepler_simple_lock(void *arg) +{ + struct gnss_ctl *gc = (struct gnss_ctl *)arg; + struct gnss_mbox *mbx = gc->gnss_data->mbx; + + gif_info("[GNSS] WAKE interrupt(Mbox15) occurred\n"); + mbox_set_interrupt(mbx->id, mbx->int_ap2gnss_ack_wake_set); + gc->pmu_ops->clear_int(GNSS_INT_WAKEUP_CLEAR); +} +#endif + +static void mbox_kepler_wake_clr(void *arg) +{ + struct gnss_ctl *gc = (struct gnss_ctl *)arg; + struct gnss_mbox *mbx = gc->gnss_data->mbx; + struct link_device *ld = gc->iod->ld; + struct shmem_link_device *shmd = to_shmem_link_device(ld); + + /* + struct link_device *ld = gc->iod->ld; + struct shmem_link_device *shmd = to_shmem_link_device(ld); + u32 rx_tail, rx_head, tx_tail, tx_head, gnss_ipc_msg, ap_ipc_msg; + */ +#ifdef USE_SIMPLE_WAKE_LOCK + gif_err("Unexpected interrupt occurred(%s)!!!!\n", __func__); + return ; +#endif + /* + tx_head = get_txq_head(shmd); + tx_tail = get_txq_tail(shmd); + rx_head = get_rxq_head(shmd); + rx_tail = get_rxq_tail(shmd); + gnss_ipc_msg = mbox_get_value(MCU_GNSS, shmd->irq_gnss2ap_ipc_msg); + ap_ipc_msg = read_int2gnss(shmd); + + gif_eff("RX_H[0x%x], RX_T[0x%x], TX_H[0x%x], TX_T[0x%x], AP_IPC[0x%x], GNSS_IPC[0x%x]\n", + rx_head, rx_tail, tx_head, tx_tail, ap_ipc_msg, gnss_ipc_msg); + */ + gc->pmu_ops->clear_int(GNSS_INT_WAKEUP_CLEAR); + + gif_info("Wake Lock Clear!!!!\n"); + gif_err(">>>>DBUS_SW_WAKE_INT CLEAR\n"); + + wake_unlock(&gc->gc_wake_lock); + enable_irq(gc->wake_lock_irq); + if (gnss_read_reg(shmd, GNSS_REG_WAKE_LOCK) == 0X0) { + gif_err("@@ reg_wake_lock is already 0x0!!!!!!\n"); + return ; + } + gnss_write_reg(shmd, GNSS_REG_WAKE_LOCK, 0x0); + mbox_set_interrupt(mbx->id, mbx->int_ap2gnss_ack_wake_clr); + +} + +static void mbox_kepler_rsp_fault_info(void *arg) +{ + struct gnss_ctl *gc = (struct gnss_ctl *)arg; + + complete(&gc->fault_cmpl); +} + +static DEFINE_MUTEX(reset_lock); + +static int kepler_hold_reset(struct gnss_ctl *gc) +{ + gif_err("%s+++\n", __func__); + + if (gc->gnss_state == STATE_OFFLINE) { + gif_err("Current Kerpler status is OFFLINE, so it will be ignored\n"); + return 0; + } + + mutex_lock(&reset_lock); + + gnss_state_changed(gc, STATE_HOLD_RESET); + + if (gc->ccore_qch_lh_gnss) { + clk_disable_unprepare(gc->ccore_qch_lh_gnss); + gif_err("Disabled GNSS Qch\n"); + } + + gc->pmu_ops->hold_reset(); + mbox_sw_reset(gc->gnss_data->mbx->id); + + mutex_unlock(&reset_lock); + + gif_err("%s---\n", __func__); + + return 0; +} + +static int kepler_release_reset(struct gnss_ctl *gc) +{ + int ret; + unsigned long timeout = msecs_to_jiffies(REQ_INIT_TIMEOUT); + + gif_err("%s+++\n", __func__); + + gnss_state_changed(gc, STATE_ONLINE); + mcu_ipc_clear_all_interrupt(MCU_GNSS); + + gc->pmu_ops->release_reset(); + + if (gc->ccore_qch_lh_gnss) { + ret = clk_prepare_enable(gc->ccore_qch_lh_gnss); + if (!ret) + gif_err("GNSS Qch enabled\n"); + else + gif_err("Could not enable Qch (%d)\n", ret); + } + + enable_irq(gc->req_init_irq); + ret = wait_for_completion_timeout(&gc->req_init_cmpl, timeout); + if (ret == 0) { + gif_err("%s: req_init_cmpl TIMEOUT!\n", gc->name); + disable_irq_nosync(gc->req_init_irq); + return -EIO; + } + ret = gc->pmu_ops->req_security(); + if (ret != 0) { + gif_err("req_security error! %d\n", ret); + return ret; + } + gc->pmu_ops->req_baaw(); + + gif_err("%s---\n", __func__); + + return 0; +} + +static int kepler_power_on(struct gnss_ctl *gc) +{ + int ret; + unsigned long timeout = msecs_to_jiffies(REQ_INIT_TIMEOUT); + + gif_err("%s+++\n", __func__); + + gnss_state_changed(gc, STATE_ONLINE); + mcu_ipc_clear_all_interrupt(MCU_GNSS); + + gc->pmu_ops->power_on(GNSS_POWER_ON); + + if (gc->ccore_qch_lh_gnss) { + ret = clk_prepare_enable(gc->ccore_qch_lh_gnss); + if (!ret) + gif_err("GNSS Qch enabled\n"); + else + gif_err("Could not enable Qch (%d)\n", ret); + } + + enable_irq(gc->req_init_irq); + ret = wait_for_completion_timeout(&gc->req_init_cmpl, timeout); + if (ret == 0) { + gif_err("%s: req_init_cmpl TIMEOUT!\n", gc->name); + disable_irq_nosync(gc->req_init_irq); + return -EIO; + } + ret = gc->pmu_ops->req_security(); + if (ret != 0) { + gif_err("req_security error! %d\n", ret); + return ret; + } + gc->pmu_ops->req_baaw(); + + gif_err("%s---\n", __func__); + + return 0; +} + +static int kepler_req_fault_info(struct gnss_ctl *gc) +{ + int ret; + struct gnss_data *pdata; + struct gnss_mbox *mbx; + unsigned long timeout = msecs_to_jiffies(1000); + u32 size = 0; + + mutex_lock(&reset_lock); + + if (!gc) { + gif_err("No gnss_ctl info!\n"); + ret = -ENODEV; + goto req_fault_exit; + } + + pdata = gc->gnss_data; + mbx = pdata->mbx; + + mbox_set_interrupt(mbx->id, mbx->int_ap2gnss_req_fault_info); + + ret = wait_for_completion_timeout(&gc->fault_cmpl, timeout); + if (ret == 0) { + gif_err("Req Fault Info TIMEOUT!\n"); + ret = -EIO; + goto req_fault_exit; + } + + switch (pdata->fault_info.device) { + case GNSS_IPC_MBOX: + size = pdata->fault_info.size * sizeof(u32); + ret = size; + break; + case GNSS_IPC_SHMEM: + size = mbox_get_value(mbx->id, mbx->reg_bcmd_ctrl[CTRL3]); + ret = size; + break; + default: + gif_err("No device for fault dump!\n"); + ret = -ENODEV; + } + +req_fault_exit: + wake_unlock(&gc->gc_fault_wake_lock); + mutex_unlock(&reset_lock); + + return ret; +} + + +static int kepler_suspend(struct gnss_ctl *gc) +{ + return 0; +} + +static int kepler_resume(struct gnss_ctl *gc) +{ +#ifdef USE_SIMPLE_WAKE_LOCK + gc->pmu_ops->clear_int(GNSS_INT_WAKEUP_CLEAR); +#endif + + return 0; +} + +static int kepler_change_gpio(struct gnss_ctl *gc) +{ + int status = 0; + + gif_err("Change GPIO for sensor\n"); + if (!IS_ERR(gc->gnss_sensor_gpio)) { + status = pinctrl_select_state(gc->gnss_gpio, gc->gnss_sensor_gpio); + if (status) { + gif_err("Can't change sensor GPIO(%d)\n", status); + } + } else { + gif_err("gnss_sensor_gpio is not valid(0x%p)\n", gc->gnss_sensor_gpio); + status = -EIO; + } + + return status; +} + +static int kepler_set_sensor_power(struct gnss_ctl *gc, enum sensor_power reg_en) +{ + int ret; + + if (reg_en == SENSOR_OFF) { + ret = regulator_disable(gc->vdd_sensor_reg); + if (ret != 0) + gif_err("Failed : Disable sensor power.\n"); + else + gif_err("Success : Disable sensor power.\n"); + } else { + ret = regulator_enable(gc->vdd_sensor_reg); + if (ret != 0) + gif_err("Failed : Enable sensor power.\n"); + else + gif_err("Success : Enable sensor power.\n"); + } + return ret; +} + +static int kepler_req_bcmd(struct gnss_ctl *gc, u16 cmd_id, u16 flags, + u32 param1, u32 param2) +{ + u32 ctrl[BCMD_CTRL_COUNT], ret_val; + unsigned long timeout = msecs_to_jiffies(REQ_BCMD_TIMEOUT); + int ret; + struct gnss_mbox *mbx = gc->gnss_data->mbx; + struct link_device *ld = gc->iod->ld; + + mutex_lock(&reset_lock); + +#if defined(CONFIG_SOC_EXYNOS9610) + if (gc->gnss_state == STATE_OFFLINE) { + gif_debug("Set POWER ON on kepler_req_bcmd!!!!\n"); + kepler_power_on(gc); + } else if (gc->gnss_state == STATE_HOLD_RESET) { + ld->reset_buffers(ld); + gif_debug("Set RELEASE RESET on kepler_req_bcmd!!!!\n"); + kepler_release_reset(gc); + } +#endif + +#ifndef USE_SIMPLE_WAKE_LOCK + wake_lock_timeout(&gc->gc_bcmd_wake_lock, BCMD_WAKELOCK_TIMEOUT); +#endif + + /* Parse arguments */ + /* Flags: Command flags */ + /* Param1/2 : Paramter 1/2 */ + + ctrl[CTRL0] = (flags << 16) + cmd_id; + ctrl[CTRL1] = param1; + ctrl[CTRL2] = param2; + gif_info("%s : set param 0 : 0x%x, 1 : 0x%x, 2 : 0x%x\n", + __func__, ctrl[CTRL0], ctrl[CTRL1], ctrl[CTRL2]); + mbox_set_value(mbx->id, mbx->reg_bcmd_ctrl[CTRL0], ctrl[CTRL0]); + mbox_set_value(mbx->id, mbx->reg_bcmd_ctrl[CTRL1], ctrl[CTRL1]); + mbox_set_value(mbx->id, mbx->reg_bcmd_ctrl[CTRL2], ctrl[CTRL2]); + /* + * 0xff is MAGIC number to avoid confuging that + * register is set from Kepler. + */ + mbox_set_value(mbx->id, mbx->reg_bcmd_ctrl[CTRL3], 0xff); + + mbox_set_interrupt(mbx->id, mbx->int_ap2gnss_bcmd); + +#if !defined(CONFIG_SOC_EXYNOS9610) + if (gc->gnss_state == STATE_OFFLINE) { + gif_info("Set POWER ON!!!!\n"); + kepler_power_on(gc); + } else if (gc->gnss_state == STATE_HOLD_RESET) { + ld->reset_buffers(ld); + gif_info("Set RELEASE RESET!!!!\n"); + kepler_release_reset(gc); + } +#endif + + if (cmd_id == 0x4) { /* BLC_Branch does not have return value */ + mutex_unlock(&reset_lock); + return 0; + } + + ret = wait_for_completion_interruptible_timeout(&gc->bcmd_cmpl, + timeout); + if (ret == 0) { +#ifndef USE_SIMPLE_WAKE_LOCK + wake_unlock(&gc->gc_bcmd_wake_lock); +#endif + gif_err("%s: bcmd TIMEOUT!\n", gc->name); + mutex_unlock(&reset_lock); + return -EIO; + } + + ret_val = mbox_get_value(mbx->id, mbx->reg_bcmd_ctrl[CTRL3]); + gif_info("BCMD cmd_id 0x%x returned 0x%x\n", cmd_id, ret_val); + + mutex_unlock(&reset_lock); + + return ret_val; +} + +static void gnss_get_ops(struct gnss_ctl *gc) +{ + gc->ops.gnss_hold_reset = kepler_hold_reset; + gc->ops.gnss_release_reset = kepler_release_reset; + gc->ops.gnss_power_on = kepler_power_on; + gc->ops.gnss_req_fault_info = kepler_req_fault_info; + gc->ops.suspend_gnss_ctrl = kepler_suspend; + gc->ops.resume_gnss_ctrl = kepler_resume; + gc->ops.change_sensor_gpio = kepler_change_gpio; + gc->ops.set_sensor_power = kepler_set_sensor_power; + gc->ops.req_bcmd = kepler_req_bcmd; +} + +int init_gnssctl_device(struct gnss_ctl *gc, struct gnss_data *pdata) +{ + int ret = 0, irq = 0; + struct platform_device *pdev = NULL; + struct gnss_mbox *mbox = gc->gnss_data->mbx; + gif_err("[GNSS IF] Initializing GNSS Control\n"); + + gnss_get_ops(gc); + gnss_get_pmu_ops(gc); + + dev_set_drvdata(gc->dev, gc); + + wake_lock_init(&gc->gc_fault_wake_lock, + WAKE_LOCK_SUSPEND, "gnss_fault_wake_lock"); + wake_lock_init(&gc->gc_wake_lock, + WAKE_LOCK_SUSPEND, "gnss_wake_lock"); + + init_completion(&gc->fault_cmpl); + init_completion(&gc->bcmd_cmpl); + init_completion(&gc->req_init_cmpl); + + pdev = to_platform_device(gc->dev); + + /* GNSS_ACTIVE */ + irq = platform_get_irq_byname(pdev, "ACTIVE"); + if (irq < 0) + irq = platform_get_irq(pdev, 0); + ret = devm_request_irq(&pdev->dev, irq, kepler_active_isr, 0, + "kepler_active_handler", gc); + if (ret) { + gif_err("Request irq fail - kepler_active_isr(%d)\n", ret); + return ret; + } + enable_irq_wake(irq); + + /* GNSS_WATCHDOG */ + irq = platform_get_irq_byname(pdev, "WATCHDOG"); + if (irq < 0) + irq = platform_get_irq(pdev, 1); + ret = devm_request_irq(&pdev->dev, irq, kepler_wdt_isr, 0, + "kepler_wdt_handler", gc); + if (ret) { + gif_err("Request irq fail - kepler_wdt_isr(%d)\n", ret); + return ret; + } + enable_irq_wake(irq); + + /* GNSS_WAKEUP */ + irq = platform_get_irq_byname(pdev, "WAKEUP"); + if (irq < 0) + irq = platform_get_irq(pdev, 2); + gc->wake_lock_irq = irq; + ret = devm_request_irq(&pdev->dev, gc->wake_lock_irq, kepler_wakelock_isr, + 0, "kepler_wakelock_handler", gc); + + if (ret) { + gif_err("Request irq fail - kepler_wakelock_isr(%d)\n", ret); + return ret; + } + enable_irq_wake(irq); +#ifdef USE_SIMPLE_WAKE_LOCK + disable_irq(gc->wake_lock_irq); + + gif_err("Using simple lock sequence!!!\n"); + mbox_request_irq(gc->gnss_data->mbx->id, 15, mbox_kepler_simple_lock, (void *)gc); + +#endif + + /* GNSS2AP */ + irq = platform_get_irq_byname(pdev, "REQ_INIT"); + if (irq < 0) + irq = platform_get_irq(pdev, 3); + gc->req_init_irq = irq; + ret = devm_request_irq(&pdev->dev, gc->req_init_irq, kepler_req_init_isr, 0, + "kepler_req_init_handler", gc); + if (ret) { + gif_err("Request irq fail - kepler_req_init_isr(%d)\n", ret); + return ret; + } + enable_irq_wake(irq); + disable_irq(gc->req_init_irq); + + /* Initializing Shared Memory for GNSS */ + gif_err("Initializing shared memory for GNSS.\n"); + gc->pmu_ops->init_conf(gc); + gc->gnss_state = STATE_OFFLINE; + + gif_info("[GNSS IF] Register mailbox for GNSS2AP fault handling\n"); + mbox_request_irq(mbox->id, mbox->irq_gnss2ap_req_wake_clr, + mbox_kepler_wake_clr, (void *)gc); + + mbox_request_irq(mbox->id, mbox->irq_gnss2ap_rsp_fault_info, + mbox_kepler_rsp_fault_info, (void *)gc); + + mbox_request_irq(mbox->id, mbox->irq_gnss2ap_bcmd, + kepler_irq_bcmd_handler, (void *)gc); + + gc->gnss_gpio = devm_pinctrl_get(&pdev->dev); + if (IS_ERR(gc->gnss_gpio)) { + gif_err("Can't get gpio for GNSS sensor.\n"); + } else { + gc->gnss_sensor_gpio = pinctrl_lookup_state(gc->gnss_gpio, + "gnss_sensor"); + } + + gc->vdd_sensor_reg = devm_regulator_get(gc->dev, "vdd_sensor_2p85"); + if (IS_ERR(gc->vdd_sensor_reg)) { + gif_err("Cannot get the regulator \"vdd_sensor_2p85\"\n"); + } + + gif_err("---\n"); + + return ret; +} diff --git a/drivers/misc/gnss_if/gnss_link_device_memory.c b/drivers/misc/gnss_if/gnss_link_device_memory.c new file mode 100644 index 000000000000..0da4e266e4b0 --- /dev/null +++ b/drivers/misc/gnss_if/gnss_link_device_memory.c @@ -0,0 +1,400 @@ +/* + * Copyright (C) 2011 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "gnss_prj.h" +#include "gnss_link_device_memory.h" + +void gnss_msq_reset(struct mem_status_queue *msq) +{ + unsigned long flags; + spin_lock_irqsave(&msq->lock, flags); + msq->out = msq->in; + spin_unlock_irqrestore(&msq->lock, flags); +} + +/** + * gnss_msq_get_free_slot + * @trq : pointer to an instance of mem_status_queue structure + * + * Succeeds always by dropping the oldest slot if a "msq" is full. + */ +struct mem_status *gnss_msq_get_free_slot(struct mem_status_queue *msq) +{ + int qsize = MAX_MEM_LOG_CNT; + int in; + int out; + unsigned long flags; + struct mem_status *stat; + + spin_lock_irqsave(&msq->lock, flags); + + in = msq->in; + out = msq->out; + + if (circ_get_space(qsize, in, out) < 1) { + /* Make the oldest slot empty */ + out++; + msq->out = (out == qsize) ? 0 : out; + } + + /* Get a free slot */ + stat = &msq->stat[in]; + + /* Make it as "data" slot */ + in++; + msq->in = (in == qsize) ? 0 : in; + + spin_unlock_irqrestore(&msq->lock, flags); + + return stat; +} + +struct mem_status *gnss_msq_get_data_slot(struct mem_status_queue *msq) +{ + int qsize = MAX_MEM_LOG_CNT; + int in; + int out; + unsigned long flags; + struct mem_status *stat; + + spin_lock_irqsave(&msq->lock, flags); + + in = msq->in; + out = msq->out; + + if (in == out) { + stat = NULL; + goto exit; + } + + /* Get a data slot */ + stat = &msq->stat[out]; + + /* Make it "free" slot */ + out++; + msq->out = (out == qsize) ? 0 : out; + +exit: + spin_unlock_irqrestore(&msq->lock, flags); + return stat; +} + +/** + * gnss_memcpy16_from_io + * @to: pointer to "real" memory + * @from: pointer to IO memory + * @count: data length in bytes to be copied + * + * Copies data from IO memory space to "real" memory space. + */ +void gnss_memcpy16_from_io(const void *to, const void __iomem *from, u32 count) +{ + u16 *d = (u16 *)to; + u16 *s = (u16 *)from; + u32 words = count >> 1; + while (words--) + *d++ = ioread16(s++); +} + +/** + * gnss_memcpy16_to_io + * @to: pointer to IO memory + * @from: pointer to "real" memory + * @count: data length in bytes to be copied + * + * Copies data from "real" memory space to IO memory space. + */ +void gnss_memcpy16_to_io(const void __iomem *to, const void *from, u32 count) +{ + u16 *d = (u16 *)to; + u16 *s = (u16 *)from; + u32 words = count >> 1; + while (words--) + iowrite16(*s++, d++); +} + +/** + * gnss_memcmp16_to_io + * @to: pointer to IO memory + * @from: pointer to "real" memory + * @count: data length in bytes to be compared + * + * Compares data from "real" memory space to IO memory space. + */ +int gnss_memcmp16_to_io(const void __iomem *to, const void *from, u32 count) +{ + u16 *d = (u16 *)to; + u16 *s = (u16 *)from; + int words = count >> 1; + int diff = 0; + int i; + u16 d1; + u16 s1; + + for (i = 0; i < words; i++) { + d1 = ioread16(d); + s1 = *s; + if (d1 != s1) { + diff++; + gif_err("ERR! [%d] d:0x%04X != s:0x%04X\n", i, d1, s1); + } + d++; + s++; + } + + return diff; +} + +/** + * gnss_circ_read16_from_io + * @dst: start address of the destination buffer + * @src: start address of the buffer in a circular queue + * @qsize: size of the circular queue + * @out: offset to read + * @len: length of data to be read + * + * Should be invoked after checking data length + */ +void gnss_circ_read16_from_io(void *dst, void *src, u32 qsize, u32 out, u32 len) +{ + if ((out + len) <= qsize) { + /* ----- (out) (in) ----- */ + /* ----- 7f 00 00 7e ----- */ + gnss_memcpy16_from_io(dst, (src + out), len); + } else { + /* (in) ----------- (out) */ + /* 00 7e ----------- 7f 00 */ + unsigned len1 = qsize - out; + + /* 1) data start (out) ~ buffer end */ + gnss_memcpy16_from_io(dst, (src + out), len1); + + /* 2) buffer start ~ data end (in - 1) */ + gnss_memcpy16_from_io((dst + len1), src, (len - len1)); + } +} + +/** + * gnss_circ_write16_to_io + * @dst: pointer to the start of the circular queue + * @src: pointer to the source + * @qsize: size of the circular queue + * @in: offset to write + * @len: length of data to be written + * + * Should be invoked after checking free space + */ +void gnss_circ_write16_to_io(void *dst, void *src, u32 qsize, u32 in, u32 len) +{ + u32 space; + + if ((in + len) < qsize) { + /* (in) ----------- (out) */ + /* 00 7e ----------- 7f 00 */ + gnss_memcpy16_to_io((dst + in), src, len); + } else { + /* ----- (out) (in) ----- */ + /* ----- 7f 00 00 7e ----- */ + + /* 1) space start (in) ~ buffer end */ + space = qsize - in; + gnss_memcpy16_to_io((dst + in), src, ((len > space) ? space : len)); + + /* 2) buffer start ~ data end */ + if (len > space) + gnss_memcpy16_to_io(dst, (src + space), (len - space)); + } +} + +/** + * gnss_copy_circ_to_user + * @dst: start address of the destination buffer + * @src: start address of the buffer in a circular queue + * @qsize: size of the circular queue + * @out: offset to read + * @len: length of data to be read + * + * Should be invoked after checking data length + */ +int gnss_copy_circ_to_user(void __user *dst, void *src, u32 qsize, u32 out, u32 len) +{ + if ((out + len) <= qsize) { + /* ----- (out) (in) ----- */ + /* ----- 7f 00 00 7e ----- */ + if (copy_to_user(dst, (src + out), len)) { + gif_err("ERR! copy_to_user fail\n", + CALLER); + return -EFAULT; + } + } else { + /* (in) ----------- (out) */ + /* 00 7e ----------- 7f 00 */ + unsigned len1 = qsize - out; + + /* 1) data start (out) ~ buffer end */ + if (copy_to_user(dst, (src + out), len1)) { + gif_err("ERR! copy_to_user fail\n", + CALLER); + return -EFAULT; + } + + /* 2) buffer start ~ data end (in?) */ + if (copy_to_user((dst + len1), src, (len - len1))) { + gif_err("ERR! copy_to_user fail\n", + CALLER); + return -EFAULT; + } + } + + return 0; +} + +/** + * gnss_copy_user_to_circ + * @dst: pointer to the start of the circular queue + * @src: pointer to the source + * @qsize: size of the circular queue + * @in: offset to write + * @len: length of data to be written + * + * Should be invoked after checking free space + */ +int gnss_copy_user_to_circ(void *dst, void __user *src, u32 qsize, u32 in, u32 len) +{ + u32 space; + u32 len1; + + if ((in + len) < qsize) { + /* (in) ----------- (out) */ + /* 00 7e ----------- 7f 00 */ + if (copy_from_user((dst + in), src, len)) { + gif_err("ERR! copy_from_user fail\n", + CALLER); + return -EFAULT; + } + } else { + /* ----- (out) (in) ----- */ + /* ----- 7f 00 00 7e ----- */ + + /* 1) space start (in) ~ buffer end */ + space = qsize - in; + len1 = (len > space) ? space : len; + if (copy_from_user((dst + in), src, len1)) { + gif_err("ERR! copy_from_user fail\n", + CALLER); + return -EFAULT; + } + + /* 2) buffer start ~ data end */ + if (len > len1) { + if (copy_from_user(dst, (src + space), (len - len1))) { + gif_err("ERR! copy_from_user fail\n", + CALLER); + return -EFAULT; + } + } + } + + return 0; +} + +/** + * gnss_capture_mem_dump + * @ld: pointer to an instance of link_device structure + * @base: base virtual address to a memory interface medium + * @size: size of the memory interface medium + * + * Captures a dump for a memory interface medium. + * + * Returns the pointer to a memory dump buffer. + */ +u8 *gnss_capture_mem_dump(struct link_device *ld, u8 *base, u32 size) +{ + u8 *buff = kzalloc(size, GFP_ATOMIC); + if (!buff) { + gif_err("%s: ERR! kzalloc(%d) fail\n", ld->name, size); + return NULL; + } else { + gnss_memcpy16_from_io(buff, base, size); + return buff; + } +} + +/** + * gnss_trq_get_free_slot + * @trq : pointer to an instance of trace_data_queue structure + * + * Succeeds always by dropping the oldest slot if a "trq" is full. + */ +struct trace_data *gnss_trq_get_free_slot(struct trace_data_queue *trq) +{ + int qsize = MAX_TRACE_SIZE; + int in; + int out; + unsigned long flags; + struct trace_data *trd; + + spin_lock_irqsave(&trq->lock, flags); + + in = trq->in; + out = trq->out; + + /* The oldest slot can be dropped. */ + if (circ_get_space(qsize, in, out) < 1) { + /* Free the data buffer in the oldest slot */ + trd = &trq->trd[out]; + kfree(trd->data); + + /* Make the oldest slot empty */ + out++; + trq->out = (out == qsize) ? 0 : out; + } + + /* Get a free slot and make it occupied */ + trd = &trq->trd[in++]; + trq->in = (in == qsize) ? 0 : in; + + spin_unlock_irqrestore(&trq->lock, flags); + + memset(trd, 0, sizeof(struct trace_data)); + + return trd; +} + +struct trace_data *gnss_trq_get_data_slot(struct trace_data_queue *trq) +{ + int qsize = MAX_TRACE_SIZE; + int in; + int out; + unsigned long flags; + struct trace_data *trd; + + spin_lock_irqsave(&trq->lock, flags); + + in = trq->in; + out = trq->out; + + if (circ_get_usage(qsize, in, out) < 1) { + spin_unlock_irqrestore(&trq->lock, flags); + return NULL; + } + + /* Get a data slot and make it empty */ + trd = &trq->trd[out++]; + trq->out = (out == qsize) ? 0 : out; + + spin_unlock_irqrestore(&trq->lock, flags); + + return trd; +} diff --git a/drivers/misc/gnss_if/gnss_link_device_memory.h b/drivers/misc/gnss_if/gnss_link_device_memory.h new file mode 100644 index 000000000000..967a66079623 --- /dev/null +++ b/drivers/misc/gnss_if/gnss_link_device_memory.h @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __GNSS_LINK_DEVICE_MEMORY_H__ +#define __GNSS_LINK_DEVICE_MEMORY_H__ + +#include + +#include "gnss_prj.h" + +#define MAX_TIMEOUT_CNT 1000 + +#define MAX_SKB_TXQ_DEPTH 1024 + +#define MAX_RETRY_CNT 3 + +enum circ_ptr_type { + HEAD, + TAIL, +}; + +static inline bool circ_valid(u32 qsize, u32 in, u32 out) +{ + if (in >= qsize) + return false; + + if (out >= qsize) + return false; + + return true; +} + +static inline u32 circ_get_space(u32 qsize, u32 in, u32 out) +{ + return (in < out) ? (out - in - 1) : (qsize + out - in - 1); +} + +static inline u32 circ_get_usage(u32 qsize, u32 in, u32 out) +{ + return (in >= out) ? (in - out) : (qsize - out + in); +} + +static inline u32 circ_new_pointer(u32 qsize, u32 p, u32 len) +{ + p += len; + return (p < qsize) ? p : (p - qsize); +} + +/** + * circ_read + * @dst: start address of the destination buffer + * @src: start address of the buffer in a circular queue + * @qsize: size of the circular queue + * @out: offset to read + * @len: length of data to be read + * + * Should be invoked after checking data length + */ +static inline void circ_read(void *dst, void *src, u32 qsize, u32 out, u32 len) +{ + unsigned len1; + + if ((out + len) <= qsize) { + /* ----- (out) (in) ----- */ + /* ----- 7f 00 00 7e ----- */ + memcpy(dst, (src + out), len); + } else { + /* (in) ----------- (out) */ + /* 00 7e ----------- 7f 00 */ + + /* 1) data start (out) ~ buffer end */ + len1 = qsize - out; + memcpy(dst, (src + out), len1); + + /* 2) buffer start ~ data end (in?) */ + memcpy((dst + len1), src, (len - len1)); + } +} + +/** + * circ_write + * @dst: pointer to the start of the circular queue + * @src: pointer to the source + * @qsize: size of the circular queue + * @in: offset to write + * @len: length of data to be written + * + * Should be invoked after checking free space + */ +static inline void circ_write(void *dst, void *src, u32 qsize, u32 in, u32 len) +{ + u32 space; + + if ((in + len) <= qsize) { + /* (in) ----------- (out) */ + /* 00 7e ----------- 7f 00 */ + memcpy((dst + in), src, len); + } else { + /* ----- (out) (in) ----- */ + /* ----- 7f 00 00 7e ----- */ + + /* 1) space start (in) ~ buffer end */ + space = qsize - in; + memcpy((dst + in), src, space); + + /* 2) buffer start ~ data end */ + memcpy(dst, (src + space), (len - space)); + } +} + +/** + * circ_dir + * @dir: communication direction (enum direction) + * + * Returns the direction of a circular queue + * + */ +static const inline char *circ_dir(enum direction dir) +{ + if (dir == TX) + return "TXQ"; + else + return "RXQ"; +} + +/** + * circ_ptr + * @ptr: circular queue pointer (enum circ_ptr_type) + * + * Returns the name of a circular queue pointer + * + */ +static const inline char *circ_ptr(enum circ_ptr_type ptr) +{ + if (ptr == HEAD) + return "head"; + else + return "tail"; +} + +void gnss_memcpy16_from_io(const void *to, const void __iomem *from, u32 count); +void gnss_memcpy16_to_io(const void __iomem *to, const void *from, u32 count); +int gnss_memcmp16_to_io(const void __iomem *to, const void *from, u32 count); +void gnss_circ_read16_from_io(void *dst, void *src, u32 qsize, u32 out, u32 len); +void gnss_circ_write16_to_io(void *dst, void *src, u32 qsize, u32 in, u32 len); +int gnss_copy_circ_to_user(void __user *dst, void *src, u32 qsize, u32 out, u32 len); +int gnss_copy_user_to_circ(void *dst, void __user *src, u32 qsize, u32 in, u32 len); + +#define MAX_MEM_LOG_CNT 8192 +#define MAX_TRACE_SIZE 1024 + +struct mem_status { + /* Timestamp */ + struct timespec ts; + + /* Direction (TX or RX) */ + enum direction dir; + + /* The status of memory interface at the time */ + u32 head[MAX_DIR]; + u32 tail[MAX_DIR]; + + u16 int2ap; + u16 int2gnss; +}; + +struct mem_status_queue { + spinlock_t lock; + u32 in; + u32 out; + struct mem_status stat[MAX_MEM_LOG_CNT]; +}; + +struct circ_status { + u8 *buff; + u32 qsize; /* the size of a circular buffer */ + u32 in; + u32 out; + u32 size; /* the size of free space or received data */ +}; + +struct trace_data { + struct timespec ts; + struct circ_status circ_stat; + u8 *data; + u32 size; +}; + +struct trace_data_queue { + spinlock_t lock; + u32 in; + u32 out; + struct trace_data trd[MAX_TRACE_SIZE]; +}; + +void gnss_msq_reset(struct mem_status_queue *msq); +struct mem_status *gnss_msq_get_free_slot(struct mem_status_queue *msq); +struct mem_status *gnss_msq_get_data_slot(struct mem_status_queue *msq); + +u8 *gnss_capture_mem_dump(struct link_device *ld, u8 *base, u32 size); +struct trace_data *gnss_trq_get_free_slot(struct trace_data_queue *trq); +struct trace_data *gnss_trq_get_data_slot(struct trace_data_queue *trq); + +#endif diff --git a/drivers/misc/gnss_if/gnss_link_device_shmem.c b/drivers/misc/gnss_if/gnss_link_device_shmem.c new file mode 100644 index 000000000000..ac911119a7fb --- /dev/null +++ b/drivers/misc/gnss_if/gnss_link_device_shmem.c @@ -0,0 +1,978 @@ +/* + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#ifdef CONFIG_OF_RESERVED_MEM +#include +#endif + +#include + +#include "include/gnss.h" +#include "gnss_link_device_shmem.h" + +#include "gnss_prj.h" +#include "gnss_utils.h" + +void gnss_write_reg(struct shmem_link_device *shmd, + enum gnss_reg_type reg, u32 value) +{ + struct gnss_shared_reg *gnss_reg = shmd->reg[reg]; + if (gnss_reg) { + switch (gnss_reg->device) { + case GNSS_IPC_MBOX: + mbox_set_value(shmd->mbx->id, gnss_reg->value.index, value); + break; + case GNSS_IPC_SHMEM: + iowrite32(value, gnss_reg->value.addr); + break; + default: + gif_err("Don't know where to write register! (%d)\n", + gnss_reg->device); + } + } else { + gif_err("Couldn't find the register node.\n"); + } + return; +} + +u32 gnss_read_reg(struct shmem_link_device *shmd, enum gnss_reg_type reg) +{ + struct gnss_shared_reg *gnss_reg = shmd->reg[reg]; + u32 ret = 0; + + if (gnss_reg) { + switch (gnss_reg->device) { + case GNSS_IPC_MBOX: + ret = mbox_get_value(shmd->mbx->id, gnss_reg->value.index); + break; + case GNSS_IPC_SHMEM: + ret = ioread32(gnss_reg->value.addr); + break; + default: + gif_err("Don't know where to read register from! (%d)\n", + gnss_reg->device); + } + } else { + gif_err("Couldn't find the register node.\n"); + } + + return ret; +} + +/** + * recv_int2ap + * @shmd: pointer to an instance of shmem_link_device structure + * + * Returns the value of the GNSS-to-AP interrupt register. + */ +static inline u16 recv_int2ap(struct shmem_link_device *shmd) +{ + return (u16)mbox_get_value(shmd->mbx->id, shmd->mbx->irq_gnss2ap_ipc_msg); +} + +/** + * send_int2cp + * @shmd: pointer to an instance of shmem_link_device structure + * @mask: value to be written to the AP-to-GNSS interrupt register + */ +static inline void send_int2gnss(struct shmem_link_device *shmd, u16 mask) +{ + gnss_write_reg(shmd, GNSS_REG_TX_IPC_MSG, mask); + mbox_set_interrupt(shmd->mbx->id, shmd->mbx->int_ap2gnss_ipc_msg); +} + +/** + * get_shmem_status + * @shmd: pointer to an instance of shmem_link_device structure + * @dir: direction of communication (TX or RX) + * @mst: pointer to an instance of mem_status structure + * + * Takes a snapshot of the current status of a SHMEM. + */ +static void get_shmem_status(struct shmem_link_device *shmd, + enum direction dir, struct mem_status *mst) +{ + mst->dir = dir; + mst->head[TX] = get_txq_head(shmd); + mst->tail[TX] = get_txq_tail(shmd); + mst->head[RX] = get_rxq_head(shmd); + mst->tail[RX] = get_rxq_tail(shmd); + mst->int2ap = recv_int2ap(shmd); + mst->int2gnss = read_int2gnss(shmd); + + gif_debug("----- %s -----\n", __func__); + gif_debug("%s: mst->dir = %d\n", __func__, mst->dir); + gif_debug("%s: mst->head[TX] = %d\n", __func__, mst->head[TX]); + gif_debug("%s: mst->tail[TX] = %d\n", __func__, mst->tail[TX]); + gif_debug("%s: mst->head[RX] = %d\n", __func__, mst->head[RX]); + gif_debug("%s: mst->tail[RX] = %d\n", __func__, mst->tail[RX]); + gif_debug("%s: mst->int2ap = %d\n", __func__, mst->int2ap); + gif_debug("%s: mst->int2gnss = %d\n", __func__, mst->int2gnss); + gif_debug("----- %s -----\n", __func__); +} + +static inline void update_rxq_tail_status(struct shmem_link_device *shmd, + struct mem_status *mst) +{ + mst->tail[RX] = get_rxq_tail(shmd); +} + +/** + * ipc_rx_work + * @ws: pointer to an instance of work_struct structure + * + * Invokes the recv method in the io_device instance to perform receiving IPC + * messages from each skb. + */ +static void msg_rx_work(struct work_struct *ws) +{ + struct shmem_link_device *shmd; + struct link_device *ld; + struct io_device *iod; + struct sk_buff *skb; + + shmd = container_of(ws, struct shmem_link_device, msg_rx_dwork.work); + ld = &shmd->ld; + + iod = ld->iod; + while (1) { + skb = skb_dequeue(ld->skb_rxq); + if (!skb) + break; + if (iod->recv_skb_single) + iod->recv_skb_single(iod, ld, skb); + else + gif_err("ERR! iod->recv_skb_single undefined!\n"); + } +} + +/** + * rx_ipc_frames + * @shmd: pointer to an instance of shmem_link_device structure + * @mst: pointer to an instance of mem_status structure + * + * Returns + * ret < 0 : error + * ret == 0 : ILLEGAL status + * ret > 0 : valid data + * + * Must be invoked only when there is data in the corresponding RXQ. + * + * Requires a recv_skb method in the io_device instance, so this function must + * be used for only EXYNOS. + */ +static int rx_ipc_frames(struct shmem_link_device *shmd, + struct circ_status *circ) +{ + struct link_device *ld = &shmd->ld; + struct io_device *iod; + struct sk_buff_head *rxq = ld->skb_rxq; + struct sk_buff *skb; + /** + * variables for the status of the circular queue + */ + u8 *src; + u8 hdr[EXYNOS_HEADER_SIZE]; + /** + * variables for RX processing + */ + int qsize; /* size of the queue */ + int rcvd; /* size of data in the RXQ or error */ + int rest; /* size of the rest data */ + int out; /* index to the start of current frame */ + int tot; /* total length including padding data */ + + src = circ->buff; + qsize = circ->qsize; + out = circ->out; + rcvd = circ->size; + + rest = circ->size; + tot = 0; + + while (rest > 0) { + u8 ch; + + /* Copy the header in the frame to the header buffer */ + circ_read(hdr, src, qsize, out, EXYNOS_HEADER_SIZE); + + /* + gif_err("src : 0x%p, out : 0x%x, recvd : 0x%x, qsize : 0x%x\n", + src, out, rcvd, qsize); + */ + + /* Check the config field in the header */ + if (unlikely(!exynos_start_valid(hdr))) { + gif_err("%s: ERR! %s INVALID config 0x%02X (rcvd %d, rest %d)\n", + ld->name, "FMT", hdr[0], + rcvd, rest); + goto bad_msg; + } + + /* Verify the total length of the frame (data + padding) */ + tot = exynos_get_total_len(hdr); + if (unlikely(tot > rest)) { + gif_err("%s: ERR! %s tot %d > rest %d (rcvd %d)\n", + ld->name, "FMT", tot, rest, rcvd); + goto bad_msg; + } + + /* Allocate an skb */ + skb = dev_alloc_skb(tot); + if (!skb) { + gif_err("%s: ERR! %s dev_alloc_skb(%d) fail\n", + ld->name, "FMT", tot); + goto no_mem; + } + + /* Set the attribute of the skb as "single frame" */ + skbpriv(skb)->single_frame = true; + + /* Read the frame from the RXQ */ + circ_read(skb_put(skb, tot), src, qsize, out, tot); + + /* Store the skb to the corresponding skb_rxq */ + skb_queue_tail(rxq, skb); + + ch = exynos_get_ch(skb->data); + iod = ld->iod; + if (!iod) { + gif_err("%s: ERR! no IPC_BOOT iod\n", ld->name); + break; + } + + skbpriv(skb)->lnk_hdr = iod->link_header; + skbpriv(skb)->exynos_ch = ch; + + /* Calculate new out value */ + rest -= tot; + out += tot; + if (unlikely(out >= qsize)) + out -= qsize; + } + + /* Update tail (out) pointer to empty out the RXQ */ + set_rxq_tail(shmd, circ->in); + return rcvd; + +no_mem: + /* Update tail (out) pointer to the frame to be read in the future */ + set_rxq_tail(shmd, out); + rcvd -= rest; + return rcvd; + +bad_msg: + return -EBADMSG; +} + +/** + * msg_handler: receives IPC messages from every RXQ + * @shmd: pointer to an instance of shmem_link_device structure + * @mst: pointer to an instance of mem_status structure + * + * 1) Receives all IPC message frames currently in every IPC RXQ. + * 2) Sends RES_ACK responses if there are REQ_ACK requests from a GNSS. + * 3) Completes all threads waiting for the corresponding RES_ACK from a GNSS if + * there is any RES_ACK response. + */ +static void msg_handler(struct shmem_link_device *shmd, struct mem_status *mst) +{ + struct link_device *ld = &shmd->ld; + struct circ_status circ; + int ret = 0; + + /* + if (!ipc_active(shmd)) { + gif_err("%s: ERR! IPC is NOT ACTIVE!!!\n", ld->name); + trigger_forced_cp_crash(shmd); + return; + } + */ + + /* Skip RX processing if there is no data in the RXQ */ + if (mst->head[RX] == mst->tail[RX]) { + /* Release wakelock */ + /* Write 0x0 to mbox register 6 */ + /* done_req_ack(shmd); */ + return; + + } + + /* Get the size of data in the RXQ */ + ret = get_rxq_rcvd(shmd, mst, &circ); + if (unlikely(ret < 0)) { + gif_err("%s: ERR! get_rxq_rcvd fail (err %d)\n", + ld->name, ret); + return; + } + + /* Read data in the RXQ */ + ret = rx_ipc_frames(shmd, &circ); + if (unlikely(ret < 0)) { + return; + } +} + +/** + * ipc_rx_task: processes a SHMEM command or receives IPC messages + * @shmd: pointer to an instance of shmem_link_device structure + * @mst: pointer to an instance of mem_status structure + * + * Invokes cmd_handler for commands or msg_handler for IPC messages. + */ +static void ipc_rx_task(unsigned long data) +{ + struct shmem_link_device *shmd = (struct shmem_link_device *)data; + + while (1) { + struct mem_status *mst; + + mst = gnss_msq_get_data_slot(&shmd->rx_msq); + if (!mst) + break; + memset(mst, 0, sizeof(struct mem_status)); + + get_shmem_status(shmd, RX, mst); + + /* Update tail variables with the current tail pointers */ + //update_rxq_tail_status(shmd, mst); + + msg_handler(shmd, mst); + + queue_delayed_work(system_wq, &shmd->msg_rx_dwork, 0); + } +} + +/** + * shmem_irq_handler: interrupt handler for a MCU_IPC interrupt + * @data: pointer to a data + * + * 1) Reads the interrupt value + * 2) Performs interrupt handling + * + * Flow for normal interrupt handling: + * shmem_irq_handler -> udl_handler + * shmem_irq_handler -> ipc_rx_task -> msg_handler -> rx_ipc_frames -> ... + */ +static void shmem_irq_msg_handler(void *data) +{ + struct shmem_link_device *shmd = (struct shmem_link_device *)data; + //struct mem_status *mst = gnss_msq_get_free_slot(&shmd->rx_msq); + + gnss_msq_get_free_slot(&shmd->rx_msq); + + /* + intr = recv_int2ap(shmd); + if (unlikely(!INT_VALID(intr))) { + gif_debug("%s: ERR! invalid intr 0x%X\n", ld->name, intr); + return; + } + */ + + tasklet_hi_schedule(&shmd->rx_tsk); +} + +/** + * write_ipc_to_txq + * @shmd: pointer to an instance of shmem_link_device structure + * @circ: pointer to an instance of circ_status structure + * @skb: pointer to an instance of sk_buff structure + * + * Must be invoked only when there is enough space in the TXQ. + */ +static void write_ipc_to_txq(struct shmem_link_device *shmd, + struct circ_status *circ, struct sk_buff *skb) +{ + u32 qsize = circ->qsize; + u32 in = circ->in; + u8 *buff = circ->buff; + u8 *src = skb->data; + u32 len = skb->len; + +#ifdef DEBUG_GNSS_IPC_PKT + /* Print send data to GNSS */ + gnss_log_ipc_pkt(skb, TX); +#endif + + /* Write data to the TXQ */ + circ_write(buff, src, qsize, in, len); + + /* Update new head (in) pointer */ + set_txq_head(shmd, circ_new_pointer(qsize, in, len)); +} + +/** + * xmit_ipc_msg + * @shmd: pointer to an instance of shmem_link_device structure + * + * Tries to transmit IPC messages in the skb_txq of @dev as many as possible. + * + * Returns total length of IPC messages transmit or an error code. + */ +static int xmit_ipc_msg(struct shmem_link_device *shmd) +{ + struct link_device *ld = &shmd->ld; + struct sk_buff_head *txq = ld->skb_txq; + struct sk_buff *skb; + unsigned long flags; + struct circ_status circ; + int space; + int copied = 0; + bool chk_nospc = false; + + /* Acquire the spin lock for a TXQ */ + spin_lock_irqsave(&shmd->tx_lock, flags); + + while (1) { + /* Get the size of free space in the TXQ */ + space = get_txq_space(shmd, &circ); + if (unlikely(space < 0)) { + /* Empty out the TXQ */ + reset_txq_circ(shmd); + copied = -EIO; + break; + } + + skb = skb_dequeue(txq); + if (unlikely(!skb)) + break; + + /* CAUTION : Uplink size is limited to 16KB and + this limitation is used ONLY in North America Prj. + Check the free space size, + - FMT : comparing with skb->len + - RAW : check used buffer size */ + chk_nospc = (space < skb->len) ? true : false; + if (unlikely(chk_nospc)) { + /* Set res_required flag */ + atomic_set(&shmd->res_required, skb->len); + + /* Take the skb back to the skb_txq */ + skb_queue_head(txq, skb); + + gif_err("%s: NOSPC in %s_TXQ {qsize:%u in:%u out:%u} free:%u < len:%u\n", + ld->name, CALLER, "FMT", + circ.qsize, circ.in, circ.out, space, skb->len); + copied = -ENOSPC; + break; + } + + /* TX only when there is enough space in the TXQ */ + write_ipc_to_txq(shmd, &circ, skb); + copied += skb->len; + dev_kfree_skb_any(skb); + } + + /* Release the spin lock */ + spin_unlock_irqrestore(&shmd->tx_lock, flags); + + return copied; +} + +/** + * fmt_tx_work: performs TX for FMT IPC device under SHMEM flow control + * @ws: pointer to an instance of the work_struct structure + * + * 1) Starts waiting for RES_ACK of FMT IPC device. + * 2) Returns immediately if the wait is interrupted. + * 3) Restarts SHMEM flow control if there is a timeout from the wait. + * 4) Otherwise, it performs processing RES_ACK for FMT IPC device. + */ +static void fmt_tx_work(struct work_struct *ws) +{ + struct link_device *ld; + struct shmem_link_device *shmd; + struct circ_status circ; + int space; + int space_needed; + + ld = container_of(ws, struct link_device, fmt_tx_dwork.work); + shmd = to_shmem_link_device(ld); + + space = get_txq_space(shmd, &circ); + space_needed = atomic_read(&shmd->res_required); + + if (unlikely(space_needed < space)) + queue_delayed_work(ld->tx_wq, ld->tx_dwork, + msecs_to_jiffies(1)); + else + atomic_set(&shmd->res_required, 0); + + return; +} + +/** + * shmem_send_ipc + * @shmd: pointer to an instance of shmem_link_device structure + * @skb: pointer to an skb that will be transmitted + * + * 1) Tries to transmit IPC messages in the skb_txq with xmit_ipc_msg(). + * 2) Sends an interrupt to GNSS if there is no error from xmit_ipc_msg(). + * 3) Starts SHMEM flow control if xmit_ipc_msg() returns -ENOSPC. + */ +static int shmem_send_ipc(struct shmem_link_device *shmd) +{ + struct link_device *ld = &shmd->ld; + int ret; + + if (atomic_read(&shmd->res_required) > 0) { + gif_err("%s: %s_TXQ is full\n", ld->name, "FMT"); + return 0; + } + + ret = xmit_ipc_msg(shmd); + if (likely(ret > 0)) { + send_int2gnss(shmd, 0x82); + goto exit; + } + + /* If there was no TX, just exit */ + if (ret == 0) + goto exit; + + /* At this point, ret < 0 */ + if (ret == -ENOSPC || ret == -EBUSY) { + /*----------------------------------------------------*/ + /* shmd->res_required was set in xmit_ipc_msg(). */ + /*----------------------------------------------------*/ + + queue_delayed_work(ld->tx_wq, ld->tx_dwork, + msecs_to_jiffies(1)); + } + +exit: + return ret; +} + +/** + * shmem_try_send_ipc + * @shmd: pointer to an instance of shmem_link_device structure + * @iod: pointer to an instance of the io_device structure + * @skb: pointer to an skb that will be transmitted + * + * 1) Enqueues an skb to the skb_txq for @dev in the link device instance. + * 2) Tries to transmit IPC messages with shmem_send_ipc(). + */ +static void shmem_try_send_ipc(struct shmem_link_device *shmd, + struct io_device *iod, struct sk_buff *skb) +{ + struct link_device *ld = &shmd->ld; + struct sk_buff_head *txq = ld->skb_txq; + int ret; + + if (unlikely(txq->qlen >= MAX_SKB_TXQ_DEPTH)) { + gif_err("%s: %s txq->qlen %d >= %d\n", ld->name, + "FMT", txq->qlen, MAX_SKB_TXQ_DEPTH); + dev_kfree_skb_any(skb); + return; + } + + skb_queue_tail(txq, skb); + + ret = shmem_send_ipc(shmd); + if (ret < 0) { + gif_err("%s->%s: ERR! shmem_send_ipc fail (err %d)\n", + iod->name, ld->name, ret); + } +} + +/** + * shmem_send + * @ld: pointer to an instance of the link_device structure + * @iod: pointer to an instance of the io_device structure + * @skb: pointer to an skb that will be transmitted + * + * Returns the length of data transmitted or an error code. + * + * Normal call flow for an IPC message: + * shmem_try_send_ipc -> shmem_send_ipc -> xmit_ipc_msg -> write_ipc_to_txq + * + * Call flow on congestion in a IPC TXQ: + * shmem_try_send_ipc -> shmem_send_ipc -> xmit_ipc_msg ,,, queue_delayed_work + * => xxx_tx_work -> wait_for_res_ack + * => msg_handler + * => process_res_ack -> xmit_ipc_msg (,,, queue_delayed_work ...) + */ +static int shmem_send(struct link_device *ld, struct io_device *iod, + struct sk_buff *skb) +{ + struct shmem_link_device *shmd = to_shmem_link_device(ld); + int len = skb->len; + +#ifndef USE_SIMPLE_WAKE_LOCK + wake_lock_timeout(&shmd->wlock, IPC_WAKELOCK_TIMEOUT); +#endif + + shmem_try_send_ipc(shmd, iod, skb); + + return len; +} + +static void shmem_remap_ipc_region(struct shmem_link_device *shmd) +{ + struct shmem_ipc_device *dev; + struct gnss_shared_reg *shreg; + u32 tx_size, rx_size, sh_reg_size; + u8 *tmap; + u32 *reg_base; + int i; + + tmap = (u8 *)shmd->ipc_mem.vaddr; + + /* FMT */ + dev = &shmd->ipc_map.dev; + + sh_reg_size = shmd->ipc_reg_cnt * sizeof(u32); + rx_size = shmd->ipc_mem.size / 2; + tx_size = shmd->ipc_mem.size / 2 - sh_reg_size; + + dev->rxq.buff = (u8 __iomem *)(tmap); + dev->rxq.size = rx_size; + + dev->txq.buff = (u8 __iomem *)(tmap + rx_size); + dev->txq.size = tx_size; + + reg_base = (u32 *)(tmap + shmd->ipc_mem.size - sh_reg_size); + + gif_err("RX region : %x @ %p\n", dev->rxq.size, dev->rxq.buff); + gif_err("TX region : %x @ %p\n", dev->txq.size, dev->txq.buff); + + for (i = 0; i < GNSS_REG_COUNT; i++) { + shreg = shmd->reg[i]; + if (shreg && (shreg->device == GNSS_IPC_SHMEM)) { + shreg->value.addr = reg_base + shreg->value.index; + gif_err("Reg %s -> %p\n", shreg->name, shreg->value.addr); + } + } +} + +static int shmem_init_ipc_map(struct shmem_link_device *shmd) +{ + shmem_remap_ipc_region(shmd); + + memset(shmd->ipc_mem.vaddr, 0, shmd->ipc_mem.size); + + shmd->dev = &shmd->ipc_map.dev; + + return 0; +} + +static void shmem_reset_buffers(struct link_device *ld) +{ + struct shmem_link_device *shmd = to_shmem_link_device(ld); + purge_txq(ld); + purge_rxq(ld); + clear_shmem_map(shmd); + + return; +} + +static int shmem_copy_reserved_to_user(struct link_device *ld, u32 offset, + void __user *user_dst, u32 size) +{ + struct shmem_link_device *shmd = to_shmem_link_device(ld); + int err = 0; + + if (offset + size > shmd->res_mem.size) { + gif_err("Unable to read %d bytes @ 0x%p+0x%x\n", size, + shmd->res_mem.vaddr, offset); + err = -EFAULT; + goto shmem_copy_to_fault; + } + + gif_debug("base addr = 0x%p\n", shmd->res_mem.vaddr); + err = copy_to_user(user_dst, (void *)shmd->res_mem.vaddr + offset, + size); + if (err) { + gif_err("copy_to_user fail\n"); + err = -EFAULT; + goto shmem_copy_to_fault; + } + +shmem_copy_to_fault: + return err; +} + +static int shmem_copy_reserved_from_user(struct link_device *ld, u32 offset, + void __user *user_src, u32 size) +{ + struct shmem_link_device *shmd = to_shmem_link_device(ld); + int err = 0; + + if (offset + size > shmd->res_mem.size) { + gif_err("Unable to load %d bytes @ 0x%p+0x%x\n", size, + shmd->res_mem.vaddr, offset); + err = -EFAULT; + goto shmem_copy_from_fault; + } + + gif_info("base addr = 0x%p\n", shmd->res_mem.vaddr); + err = copy_from_user((void *)shmd->res_mem.vaddr + offset, + user_src, size); + if (err) { + gif_err("copy_from_user fail\n"); + err = -EFAULT; + goto shmem_copy_from_fault; + } + +shmem_copy_from_fault: + return err; +} + +static int shmem_dump_fault_mem_to_user(struct link_device *ld, + void __user *user_dst, u32 size) +{ + struct shmem_link_device *shmd = to_shmem_link_device(ld); + int err = 0; + + if (size > shmd->fault_mem.size) { + gif_err("Unable to dump %d bytes (max %d bytes)\n", size, + shmd->fault_mem.size); + err = -EFAULT; + goto shmem_dump_mem_to_fault; + } + + gif_info("fault addr = 0x%p\n", shmd->fault_mem.vaddr); + err = copy_to_user(user_dst, shmd->fault_mem.vaddr, size); + + if (err) { + gif_err("copy_to_user fail\n"); + err = -EFAULT; + goto shmem_dump_mem_to_fault; + } + +shmem_dump_mem_to_fault: + return err; +} + +static int shmem_dump_fault_mbx_to_user(struct link_device *ld, + void __user *user_dst, u32 size) +{ + struct gnss_data *pdata; + struct gnss_mbox *mbx; + u32 *dump_info; + unsigned int max_size; + int reg_cnt; + int err = 0; + int i; + + pdata = ld->gnss_data; + mbx = pdata->mbx; + + reg_cnt = pdata->fault_info.size; + max_size = reg_cnt * sizeof(u32); + + if (size > max_size) { + gif_err("Unable to dump %d bytes (max %d bytes)\n", size, + max_size); + err = -EFAULT; + goto shmem_dump_mbx_to_fault; + } + + dump_info = kzalloc(max_size, GFP_KERNEL); + if (!dump_info) { + gif_err("Could not allocate fault info memory\n"); + err = -ENOMEM; + goto shmem_dump_mbx_to_fault; + } + + for (i = 0; i < reg_cnt; i++) + dump_info[i] = mbox_get_value(mbx->id, + pdata->fault_info.value.index + i); + + err = copy_to_user(user_dst, dump_info, size); + kfree(dump_info); + if (err) { + gif_err("copy_to_user fail\n"); + err = -EFAULT; + goto shmem_dump_mbx_to_fault; + } + +shmem_dump_mbx_to_fault: + return err; +} + +struct link_device *create_link_device_shmem(struct platform_device *pdev) +{ + struct shmem_link_device *shmd = NULL; + struct link_device *ld = NULL; + struct gnss_data *gnss = NULL; + struct device *dev = &pdev->dev; + int err = 0; + + gif_info("+++\n"); + + /* Get the gnss (platform) data */ + gnss = (struct gnss_data *)dev->platform_data; + if (!gnss) { + gif_err("ERR! gnss == NULL\n"); + return NULL; + } + gif_err("%s: %s\n", "SHMEM", gnss->name); + + if (!gnss->mbx) { + gif_err("%s: ERR! %s->mbx == NULL\n", + "SHMEM", gnss->name); + return NULL; + } + + /* Alloc an instance of shmem_link_device structure */ + shmd = devm_kzalloc(dev, sizeof(struct shmem_link_device), GFP_KERNEL); + if (!shmd) { + gif_err("%s: ERR! shmd kzalloc fail\n", "SHMEM"); + goto error; + } + ld = &shmd->ld; + shmd->mbx = gnss->mbx; + + /* Retrieve gnss data and SHMEM control data from the gnss data */ + ld->gnss_data = gnss; + ld->timeout_cnt = 0; + ld->name = "GNSS_SHDMEM"; + + /* Assign reserved memory methods */ + ld->copy_reserved_from_user = shmem_copy_reserved_from_user; + ld->copy_reserved_to_user = shmem_copy_reserved_to_user; + + ld->reset_buffers = shmem_reset_buffers; + + /* Set attributes as a link device */ + ld->send = shmem_send; + + skb_queue_head_init(&ld->sk_fmt_tx_q); + ld->skb_txq = &ld->sk_fmt_tx_q; + + skb_queue_head_init(&ld->sk_fmt_rx_q); + ld->skb_rxq = &ld->sk_fmt_rx_q; + + /* Initialize GNSS Reserved mem */ +#if defined(CONFIG_SOC_EXYNOS9610) + shmd->res_mem.size = gnss->shmem_size; +#else + shmd->res_mem.size = gnss->ipcmem_offset; +#endif + shmd->res_mem.paddr = gnss->shmem_base; + shmd->res_mem.vaddr = shm_request_region(shmd->res_mem.paddr, + shmd->res_mem.size); + if (!shmd->res_mem.vaddr) { + gif_err("%s: ERR! gnss_reserved_region fail\n", ld->name); + goto error; + } + gif_err("%s: Reserved Region " + "phys_addr:0x%08X virt_addr:0x%p size: %d\n", ld->name, + shmd->res_mem.paddr, shmd->res_mem.vaddr, shmd->res_mem.size); + + /* Initialize GNSS fault info area */ + if (gnss->fault_info.device == GNSS_IPC_SHMEM) { + ld->dump_fault_to_user = shmem_dump_fault_mem_to_user; + shmd->fault_mem.size = gnss->fault_info.size; + shmd->fault_mem.paddr = gnss->shmem_base + gnss->fault_info.value.index; + shmd->fault_mem.vaddr = shm_request_region( + shmd->fault_mem.paddr, shmd->fault_mem.size); + if (!shmd->fault_mem.vaddr) { + gif_err("%s: ERR! fault shm_request_region fail\n", + ld->name); + goto error; + } + gif_err("%s: Fault Region " + "phys_addr:0x%08X virt_addr:0x%p size:%d\n", ld->name, + shmd->fault_mem.paddr, shmd->fault_mem.vaddr, + shmd->fault_mem.size); + } else { + ld->dump_fault_to_user = shmem_dump_fault_mbx_to_user; + shmd->fault_mem.size = gnss->fault_info.size; + } + + /* Initialize GNSS IPC region */ + shmd->ipc_reg_cnt = gnss->ipc_reg_cnt; + shmd->reg = gnss->reg; + + shmd->ipc_mem.size = gnss->ipc_size; + shmd->ipc_mem.paddr = gnss->shmem_base + gnss->ipcmem_offset; + shmd->ipc_mem.vaddr = shm_request_region(shmd->ipc_mem.paddr, + shmd->ipc_mem.size); + if (!shmd->ipc_mem.vaddr) { + gif_err("%s: ERR! shm_request_region fail\n", ld->name); + goto error; + } + gif_err("%s: IPC Region " + "phys_addr:0x%08X virt_addr:0x%8p size:%d\n", ld->name, + shmd->ipc_mem.paddr, shmd->ipc_mem.vaddr, shmd->ipc_mem.size); + + /* Initialize SHMEM maps (physical map -> logical map) */ + err = shmem_init_ipc_map(shmd); + if (err < 0) { + gif_err("%s: ERR! shmem_init_ipc_map fail (err %d)\n", + ld->name, err); + goto error; + } + +#ifndef USE_SIMPLE_WAKE_LOCK + /* Initialize locks, completions, and bottom halves */ + snprintf(shmd->wlock_name, MIF_MAX_NAME_LEN, "%s_wlock", ld->name); + wake_lock_init(&shmd->wlock, WAKE_LOCK_SUSPEND, shmd->wlock_name); +#endif + + tasklet_init(&shmd->rx_tsk, ipc_rx_task, (unsigned long)shmd); + INIT_DELAYED_WORK(&shmd->msg_rx_dwork, msg_rx_work); + + spin_lock_init(&shmd->tx_lock); + + ld->tx_wq = create_singlethread_workqueue("shmem_tx_wq"); + if (!ld->tx_wq) { + gif_err("%s: ERR! fail to create tx_wq\n", ld->name); + goto error; + } + + INIT_DELAYED_WORK(&ld->fmt_tx_dwork, fmt_tx_work); + ld->tx_dwork = &ld->fmt_tx_dwork; + + spin_lock_init(&shmd->tx_msq.lock); + spin_lock_init(&shmd->rx_msq.lock); + + /* Register interrupt handlers */ + err = mbox_request_irq(shmd->mbx->id, shmd->mbx->irq_gnss2ap_ipc_msg, + shmem_irq_msg_handler, shmd); + if (err) { + gif_err("%s: ERR! mbox_request_irq fail (err %d)\n", + ld->name, err); + goto error; + } + + gif_info("---\n"); + return ld; + +error: + gif_err("xxx\n"); + devm_kfree(dev, shmd); + return NULL; +} diff --git a/drivers/misc/gnss_if/gnss_link_device_shmem.h b/drivers/misc/gnss_if/gnss_link_device_shmem.h new file mode 100644 index 000000000000..b4fffd9f9257 --- /dev/null +++ b/drivers/misc/gnss_if/gnss_link_device_shmem.h @@ -0,0 +1,444 @@ +/* + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __GNSS_LINK_DEVICE_SHMEM_H__ +#define __GNSS_LINK_DEVICE_SHMEM_H__ + +#include +#include "gnss_link_device_memory.h" + +#define IPC_WAKELOCK_TIMEOUT (HZ) +#define BCMD_WAKELOCK_TIMEOUT (HZ / 10) /* 100 msec */ + +struct shmem_circ { + u32 __iomem *head; + u32 __iomem *tail; + u8 __iomem *buff; + u32 size; +}; + +struct shmem_ipc_device { + struct shmem_circ txq; + struct shmem_circ rxq; +}; + +struct shmem_ipc_map { + struct shmem_ipc_device dev; +}; + +struct shmem_region { + u8 __iomem *vaddr; /* ioremap base address */ + u32 paddr; /* physical base address */ + u32 size; /* region size */ +}; + +struct shmem_link_device { + struct link_device ld; + + struct gnss_mbox *mbx; + struct gnss_shared_reg **reg; + + /* Reserved memory information */ + struct shmem_region res_mem; + + /* Fault area information */ + struct shmem_region fault_mem; + + /* SHMEM (SHARED MEMORY) address, size, IRQ# */ + struct shmem_region ipc_mem; + + u32 ipc_reg_cnt; + + /* IPC device map */ + struct shmem_ipc_map ipc_map; + + /* Pointers (aliases) to IPC device map */ + struct shmem_ipc_device *dev; + + /* MBOX number & IRQ */ + int int_ap2gnss_ipc_msg; + int irq_gnss2ap_ipc_msg; + + /* Wakelock for SHMEM device */ + struct wake_lock wlock; + char wlock_name[GNSS_MAX_NAME_LEN]; + + /* for locking TX process */ + spinlock_t tx_lock; + + /* for retransmission under SHMEM flow control after TXQ full state */ + atomic_t res_required; + //struct completion req_ack_cmpl; + + /* for efficient RX process */ + struct tasklet_struct rx_tsk; + struct delayed_work msg_rx_dwork; + struct io_device *iod; + + /* for logging SHMEM status */ + struct mem_status_queue tx_msq; + struct mem_status_queue rx_msq; + + /* for logging SHMEM dump */ + //struct trace_data_queue trace_list; + + /* to hold/release "cp_wakeup" for PM (power-management) */ + //struct delayed_work cp_sleep_dwork; + atomic_t ref_cnt; + //spinlock_t pm_lock; +}; + +/* converts from struct link_device* to struct xxx_link_device* */ +#define to_shmem_link_device(linkdev) \ + container_of(linkdev, struct shmem_link_device, ld) + +void gnss_write_reg(struct shmem_link_device *, enum gnss_reg_type, u32); +u32 gnss_read_reg(struct shmem_link_device *, enum gnss_reg_type); + +/** + * get_txq_head + * @shmd: pointer to an instance of shmem_link_device structure + * + * Returns the value of a head (in) pointer in a TX queue. + */ +static inline u32 get_txq_head(struct shmem_link_device *shmd) +{ + return gnss_read_reg(shmd, GNSS_REG_TX_HEAD); +} + +/** + * get_txq_tail + * @shmd: pointer to an instance of shmem_link_device structure + * + * Returns the value of a tail (out) pointer in a TX queue. + * + * It is useless for an AP to read a tail pointer in a TX queue twice to verify + * whether or not the value in the pointer is valid, because it can already have + * been updated by a GNSS after the first access from the AP. + */ +static inline u32 get_txq_tail(struct shmem_link_device *shmd) +{ + return gnss_read_reg(shmd, GNSS_REG_TX_TAIL); +} + +/** + * get_txq_buff + * @shmd: pointer to an instance of shmem_link_device structure + * + * Returns the start address of the buffer in a TXQ. + */ +static inline u8 *get_txq_buff(struct shmem_link_device *shmd) +{ + return shmd->dev->txq.buff; +} + +/** + * get_txq_buff_size + * @shmd: pointer to an instance of shmem_link_device structure + * + * Returns the size of the buffer in a TXQ. + */ +static inline u32 get_txq_buff_size(struct shmem_link_device *shmd) +{ + return shmd->dev->txq.size; +} + +/** + * get_rxq_head + * @shmd: pointer to an instance of shmem_link_device structure + * + * Returns the value of a head (in) pointer in an RX queue. + * + * It is useless for an AP to read a head pointer in an RX queue twice to verify + * whether or not the value in the pointer is valid, because it can already have + * been updated by a GNSS after the first access from the AP. + */ +static inline u32 get_rxq_head(struct shmem_link_device *shmd) +{ + return gnss_read_reg(shmd, GNSS_REG_RX_HEAD); +} + +/** + * get_rxq_tail + * @shmd: pointer to an instance of shmem_link_device structure + * + * Returns the value of a tail (in) pointer in an RX queue. + */ +static inline u32 get_rxq_tail(struct shmem_link_device *shmd) +{ + return gnss_read_reg(shmd, GNSS_REG_RX_TAIL); +} + +/** + * get_rxq_buff + * @shmd: pointer to an instance of shmem_link_device structure + * + * Returns the start address of the buffer in an RXQ. + */ +static inline u8 *get_rxq_buff(struct shmem_link_device *shmd) +{ + return shmd->dev->rxq.buff; +} + +/** + * get_rxq_buff_size + * @shmd: pointer to an instance of shmem_link_device structure + * + * Returns the size of the buffer in an RXQ. + */ +static inline u32 get_rxq_buff_size(struct shmem_link_device *shmd) +{ + return shmd->dev->rxq.size; +} + +/** + * set_txq_head + * @shmd: pointer to an instance of shmem_link_device structure + * @in: value to be written to the head pointer in a TXQ + */ +static inline void set_txq_head(struct shmem_link_device *shmd, u32 in) +{ + gnss_write_reg(shmd, GNSS_REG_TX_HEAD, in); +} + +/** + * set_txq_tail + * @shmd: pointer to an instance of shmem_link_device structure + * @out: value to be written to the tail pointer in a TXQ + */ +static inline void set_txq_tail(struct shmem_link_device *shmd, u32 out) +{ + gnss_write_reg(shmd, GNSS_REG_TX_TAIL, out); +} + +/** + * set_rxq_head + * @shmd: pointer to an instance of shmem_link_device structure + * @in: value to be written to the head pointer in an RXQ + */ +static inline void set_rxq_head(struct shmem_link_device *shmd, u32 in) +{ + gnss_write_reg(shmd, GNSS_REG_RX_HEAD, in); +} + +/** + * set_rxq_tail + * @shmd: pointer to an instance of shmem_link_device structure + * @out: value to be written to the tail pointer in an RXQ + */ +static inline void set_rxq_tail(struct shmem_link_device *shmd, u32 out) +{ + gnss_write_reg(shmd, GNSS_REG_RX_TAIL, out); +} + +/** + * read_int2gnss + * @shmd: pointer to an instance of shmem_link_device structure + * + * Returns the value of the AP-to-GNSS interrupt register. + */ +static inline u16 read_int2gnss(struct shmem_link_device *shmd) +{ + return mbox_get_value(MCU_GNSS, shmd->int_ap2gnss_ipc_msg); +} + +/** + * reset_txq_circ + * @shmd: pointer to an instance of shmem_link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * Empties a TXQ by resetting the head (in) pointer with the value in the tail + * (out) pointer. + */ +static inline void reset_txq_circ(struct shmem_link_device *shmd) +{ + struct link_device *ld = &shmd->ld; + u32 head = get_txq_head(shmd); + u32 tail = get_txq_tail(shmd); + + gif_err("%s: %s_TXQ: HEAD[%u] <== TAIL[%u]\n", + ld->name, "FMT", head, tail); + + set_txq_head(shmd, tail); +} + +/** + * reset_rxq_circ + * @shmd: pointer to an instance of shmem_link_device structure + * @dev: IPC device (IPC_FMT, IPC_RAW, etc.) + * + * Empties an RXQ by resetting the tail (out) pointer with the value in the head + * (in) pointer. + */ +static inline void reset_rxq_circ(struct shmem_link_device *shmd) +{ + struct link_device *ld = &shmd->ld; + u32 head = get_rxq_head(shmd); + u32 tail = get_rxq_tail(shmd); + + gif_err("%s: %s_RXQ: TAIL[%u] <== HEAD[%u]\n", + ld->name, "FMT", tail, head); + + set_rxq_tail(shmd, head); +} + +/** + * get_rxq_rcvd + * @shmd: pointer to an instance of shmem_link_device structure + * @mst: pointer to an instance of mem_status structure + * OUT @circ: pointer to an instance of circ_status structure + * + * Stores {start address of the buffer in a RXQ, size of the buffer, in & out + * pointer values, size of received data} into the 'circ' instance. + * + * Returns an error code. + */ +static inline int get_rxq_rcvd(struct shmem_link_device *shmd, + struct mem_status *mst, struct circ_status *circ) +{ + struct link_device *ld = &shmd->ld; + + circ->buff = get_rxq_buff(shmd); + circ->qsize = get_rxq_buff_size(shmd); + circ->in = mst->head[RX]; + circ->out = mst->tail[RX]; + circ->size = circ_get_usage(circ->qsize, circ->in, circ->out); + + if (circ_valid(circ->qsize, circ->in, circ->out)) { + gif_debug("%s: %s_RXQ qsize[%u] in[%u] out[%u] rcvd[%u]\n", + ld->name, "FMT", circ->qsize, circ->in, + circ->out, circ->size); + return 0; + } else { + gif_err("%s: ERR! %s_RXQ invalid (qsize[%d] in[%d] out[%d])\n", + ld->name, "FMT", circ->qsize, circ->in, + circ->out); + return -EIO; + } +} + +/* + * shmem_purge_rxq + * @ld: pointer to an instance of the link_device structure + * + * Purges pending transfers from the RXQ. + */ +static inline void purge_rxq(struct link_device *ld) +{ + skb_queue_purge(ld->skb_rxq); +} + +/** + * get_txq_space + * @shmd: pointer to an instance of shmem_link_device structure + * OUT @circ: pointer to an instance of circ_status structure + * + * Stores {start address of the buffer in a TXQ, size of the buffer, in & out + * pointer values, size of free space} into the 'circ' instance. + * + * Returns the size of free space in the buffer or an error code. + */ +static inline int get_txq_space(struct shmem_link_device *shmd, + struct circ_status *circ) +{ + struct link_device *ld = &shmd->ld; + int cnt = 0; + u32 qsize; + u32 head; + u32 tail; + int space; + + while (1) { + qsize = get_txq_buff_size(shmd); + head = get_txq_head(shmd); + tail = get_txq_tail(shmd); + space = circ_get_space(qsize, head, tail); + + gif_debug("%s: %s_TXQ{qsize:%u in:%u out:%u space:%u}\n", + ld->name, "FMT", qsize, head, tail, space); + + if (circ_valid(qsize, head, tail)) + break; + + cnt++; + gif_err("%s: ERR! invalid %s_TXQ{qsize:%d in:%d out:%d space:%d}, count %d\n", + ld->name, "FMT", qsize, head, tail, + space, cnt); + if (cnt >= MAX_RETRY_CNT) { + space = -EIO; + break; + } + + udelay(100); + } + + circ->buff = get_txq_buff(shmd); + circ->qsize = qsize; + circ->in = head; + circ->out = tail; + circ->size = space; + + return space; +} + +/** + * shmem_purge_txq + * @ld: pointer to an instance of the link_device structure + * + * Purges pending transfers from the TXQ. + */ +static inline void purge_txq(struct link_device *ld) +{ + struct shmem_link_device *shmd = to_shmem_link_device(ld); + unsigned long flags; + + spin_lock_irqsave(&shmd->tx_lock, flags); + skb_queue_purge(ld->skb_txq); + spin_unlock_irqrestore(&shmd->tx_lock, flags); +} + +/** + * clear_shmem_map + * @shmd: pointer to an instance of shmem_link_device structure + * + * Clears all pointers in every circular queue. + */ +static inline void clear_shmem_map(struct shmem_link_device *shmd) +{ + set_txq_head(shmd, 0); + set_txq_tail(shmd, 0); + set_rxq_head(shmd, 0); + set_rxq_tail(shmd, 0); + + atomic_set(&shmd->res_required, 0); + + memset(shmd->ipc_mem.vaddr, 0x0, shmd->ipc_mem.size); +} + +/** + * reset_shmem_ipc + * @shmd: pointer to an instance of shmem_link_device structure + * + * Reset SHMEM with IPC map. + */ +static inline void reset_shmem_ipc(struct shmem_link_device *shmd) +{ + clear_shmem_map(shmd); + + atomic_set(&shmd->res_required, 0); + + atomic_set(&shmd->ref_cnt, 0); +} +#endif + diff --git a/drivers/misc/gnss_if/gnss_main.c b/drivers/misc/gnss_if/gnss_main.c new file mode 100644 index 000000000000..5e1bd40a3696 --- /dev/null +++ b/drivers/misc/gnss_if/gnss_main.c @@ -0,0 +1,518 @@ +/* linux/drivers/misc/gnss/gnss_main.c + * + * Copyright (C) 2010 Google, Inc. + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_OF +#include +#include +#include +#endif + +#include + +#include "gnss_prj.h" + +static struct gnss_ctl *create_ctl_device(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct gnss_data *pdata = pdev->dev.platform_data; + struct gnss_ctl *gnssctl; + struct clk *qch_clk; + int ret; + + /* create GNSS control device */ + gnssctl = devm_kzalloc(dev, sizeof(struct gnss_ctl), GFP_KERNEL); + if (!gnssctl) { + gif_err("%s: gnssctl devm_kzalloc fail\n", pdata->name); + return NULL; + } + + gnssctl->dev = dev; + gnssctl->gnss_state = STATE_OFFLINE; + + gnssctl->gnss_data = pdata; + gnssctl->name = pdata->name; + + qch_clk = devm_clk_get(dev, "ccore_qch_lh_gnss"); + if (!IS_ERR(qch_clk)) { + gif_err("Found Qch clk!\n"); + gnssctl->ccore_qch_lh_gnss = qch_clk; + } else { + gnssctl->ccore_qch_lh_gnss = NULL; + } + + /* init gnssctl device for getting gnssctl operations */ + ret = init_gnssctl_device(gnssctl, pdata); + if (ret) { + gif_err("%s: init_gnssctl_device fail (err %d)\n", + pdata->name, ret); + devm_kfree(dev, gnssctl); + return NULL; + } + + gif_info("%s is created!!!\n", pdata->name); + + return gnssctl; +} + +static struct io_device *create_io_device(struct platform_device *pdev, + struct gnss_io_t *io_t, struct link_device *ld, + struct gnss_ctl *gnssctl, struct gnss_data *pdata) +{ + int ret; + struct device *dev = &pdev->dev; + struct io_device *iod; + + iod = devm_kzalloc(dev, sizeof(struct io_device), GFP_KERNEL); + if (!iod) { + gif_err("iod is NULL\n"); + return NULL; + } + + iod->name = io_t->name; + iod->app = io_t->app; + atomic_set(&iod->opened, 0); + + /* link between io device and gnss control */ + iod->gc = gnssctl; + gnssctl->iod = iod; + + /* link between io device and link device */ + iod->ld = ld; + ld->iod = iod; + + /* register misc device */ + ret = exynos_init_gnss_io_device(iod); + if (ret) { + devm_kfree(dev, iod); + gif_err("exynos_init_gnss_io_device fail (%d)\n", ret); + return NULL; + } + + gif_info("%s created\n", iod->name); + return iod; +} + +#ifdef CONFIG_OF_RESERVED_MEM +static int gnss_dma_device_init(struct reserved_mem *rmem, struct device *dev) +{ + struct gnss_data *pdata; + if (!dev && !dev->platform_data) + return -ENODEV; + + // Save reserved memory information. + pdata = (struct gnss_data *)dev->platform_data; + pdata->shmem_base = rmem->base; + pdata->shmem_size = rmem->size; + + return 0; +} + +static void gnss_dma_device_release(struct reserved_mem *rmem, struct device *dev) +{ + return; +} + +static const struct reserved_mem_ops gnss_dma_ops = { + .device_init = gnss_dma_device_init, + .device_release = gnss_dma_device_release, +}; + +static int __init gnss_if_reserved_mem_setup(struct reserved_mem *remem) +{ + gif_info("%s: memory reserved: paddr=%#lx, t_size=%zd\n", + __func__, (unsigned long)remem->base, (size_t)remem->size); + remem->ops = &gnss_dma_ops; + + return 0; +} +RESERVEDMEM_OF_DECLARE(gnss_if, "exynos,gnss_if", gnss_if_reserved_mem_setup); +#endif + +#ifdef CONFIG_OF +static int parse_dt_common_pdata(struct device_node *np, + struct gnss_data *pdata) +{ + gif_dt_read_string(np, "shmem,name", pdata->name); + gif_dt_read_string(np, "shmem,device_node_name", pdata->device_node_name); + gif_dt_read_u32(np, "shmem,ipc_offset", pdata->ipcmem_offset); + gif_dt_read_u32(np, "shmem,ipc_size", pdata->ipc_size); + gif_dt_read_u32(np, "shmem,ipc_reg_cnt", pdata->ipc_reg_cnt); + + return 0; +} + +static int parse_dt_mbox_pdata(struct device *dev, struct device_node *np, + struct gnss_data *pdata) +{ + struct gnss_mbox *mbox = pdata->mbx; + struct device_node *mbox_info; + + mbox = devm_kzalloc(dev, sizeof(struct gnss_mbox), GFP_KERNEL); + if (!mbox) { + gif_err("mbox: failed to alloc memory\n"); + return -ENOMEM; + } + pdata->mbx = mbox; + + mbox_info = of_parse_phandle(np, "mbox_info", 0); + if (IS_ERR(mbox_info)) { + mbox->id = MCU_GNSS; + } else { + gif_dt_read_u32(mbox_info, "mcu,id", mbox->id); + of_node_put(mbox_info); + } + + gif_dt_read_u32(np, "mbx,int_ap2gnss_bcmd", mbox->int_ap2gnss_bcmd); + gif_dt_read_u32(np, "mbx,int_ap2gnss_req_fault_info", + mbox->int_ap2gnss_req_fault_info); + gif_dt_read_u32(np, "mbx,int_ap2gnss_ipc_msg", mbox->int_ap2gnss_ipc_msg); + gif_dt_read_u32(np, "mbx,int_ap2gnss_ack_wake_set", + mbox->int_ap2gnss_ack_wake_set); + gif_dt_read_u32(np, "mbx,int_ap2gnss_ack_wake_clr", + mbox->int_ap2gnss_ack_wake_clr); + + gif_dt_read_u32(np, "mbx,irq_gnss2ap_bcmd", mbox->irq_gnss2ap_bcmd); + gif_dt_read_u32(np, "mbx,irq_gnss2ap_rsp_fault_info", + mbox->irq_gnss2ap_rsp_fault_info); + gif_dt_read_u32(np, "mbx,irq_gnss2ap_ipc_msg", mbox->irq_gnss2ap_ipc_msg); + gif_dt_read_u32(np, "mbx,irq_gnss2ap_req_wake_clr", + mbox->irq_gnss2ap_req_wake_clr); + + gif_dt_read_u32_array(np, "mbx,reg_bcmd_ctrl", mbox->reg_bcmd_ctrl, + BCMD_CTRL_COUNT); + + return 0; +} + +static int alloc_gnss_reg(struct device *dev, struct gnss_shared_reg **areg, + const char *reg_name, u32 reg_device, u32 reg_value) +{ + struct gnss_shared_reg *ret = NULL; + if (!(*areg)) { + ret = devm_kzalloc(dev, sizeof(struct gnss_shared_reg), GFP_KERNEL); + if (ret) { + ret->name = reg_name; + ret->device = reg_device; + ret->value.index = reg_value; + *areg = ret; + } + } else { + gif_err("Register %s is already allocated!\n", reg_name); + } + return (*areg == NULL); +} + +const char *dt_reg_prop_table[GNSS_REG_COUNT] = { + [GNSS_REG_RX_IPC_MSG] = "reg_rx_ipc_msg", + [GNSS_REG_TX_IPC_MSG] = "reg_tx_ipc_msg", + [GNSS_REG_WAKE_LOCK] = "reg_wake_lock", + [GNSS_REG_RX_HEAD] = "reg_rx_head", + [GNSS_REG_RX_TAIL] = "reg_rx_tail", + [GNSS_REG_TX_HEAD] = "reg_tx_head", + [GNSS_REG_TX_TAIL] = "reg_tx_tail", +}; + +static int parse_dt_reg_mbox_pdata(struct device *dev, struct gnss_data *pdata) +{ + int i; + unsigned int err; + struct device_node *np = dev->of_node; + u32 val[2]; + + for (i = 0; i < GNSS_REG_COUNT; i++) { + err = of_property_read_u32_array(np, dt_reg_prop_table[i], + val, 2); + if (ERR_PTR(err)) + continue; + + err = alloc_gnss_reg(dev, &pdata->reg[i], dt_reg_prop_table[i], + val[0], val[1]); + if (err) + goto parse_dt_reg_nomem; + } + + return 0; + +parse_dt_reg_nomem: + for (i = 0; i < GNSS_REG_COUNT; i++) + if (pdata->reg[i]) + devm_kfree(dev, pdata->reg[i]); + + gif_err("reg: could not allocate register memory\n"); + return -ENOMEM; +} + +static int parse_dt_fault_pdata(struct device *dev, struct gnss_data *pdata) +{ + struct device_node *np = dev->of_node; + u32 tmp[3]; + + if (!of_property_read_u32_array(np, "fault_info", tmp, 3)) { + (pdata)->fault_info.name = "gnss_fault_info"; + (pdata)->fault_info.device = tmp[0]; + (pdata)->fault_info.value.index = tmp[1]; + (pdata)->fault_info.size = tmp[2]; + } else { + return -EINVAL; + } + return 0; +} + +static struct gnss_data *gnss_if_parse_dt_pdata(struct device *dev) +{ + struct gnss_data *pdata; + int i; + u32 ret; + + pdata = devm_kzalloc(dev, sizeof(struct gnss_data), GFP_KERNEL); + if (!pdata) { + gif_err("gnss_data: alloc fail\n"); + return ERR_PTR(-ENOMEM); + } + dev->platform_data = pdata; + + ret = of_reserved_mem_device_init(dev); + if (ret != 0) { + gif_err("Failed to parse reserved memory\n"); + goto parse_dt_pdata_err; + } + + ret = parse_dt_common_pdata(dev->of_node, pdata); + if (ret != 0) { + gif_err("Failed to parse common pdata.\n"); + goto parse_dt_pdata_err; + } + + ret = parse_dt_mbox_pdata(dev, dev->of_node, pdata); + if (ret != 0) { + gif_err("Failed to parse mailbox pdata.\n"); + goto parse_dt_pdata_err; + } + + ret = parse_dt_reg_mbox_pdata(dev, pdata); + if (ret != 0) { + gif_err("Failed to parse mbox register pdata.\n"); + goto parse_dt_pdata_err; + } + + ret = parse_dt_fault_pdata(dev, pdata); + if (ret != 0) { + gif_err("Failed to parse fault info pdata.\n"); + goto parse_dt_pdata_err; + } + + for (i = 0; i < GNSS_REG_COUNT; i++) { + if (pdata->reg[i]) + gif_err("Found reg: [%d:%d] %s\n", + pdata->reg[i]->device, + pdata->reg[i]->value.index, + pdata->reg[i]->name); + } + + gif_err("Fault info: %s [%d:%d:%d]\n", + pdata->fault_info.name, + pdata->fault_info.device, + pdata->fault_info.value.index, + pdata->fault_info.size); + + gif_info("DT parse complete!\n"); + return pdata; + +parse_dt_pdata_err: + if (pdata) + devm_kfree(dev, pdata); + + if (dev->platform_data) + dev->platform_data = NULL; + + return ERR_PTR(-EINVAL); +} + +static const struct of_device_id sec_gnss_match[] = { + { .compatible = "samsung,gnss_shdmem_if", }, + {}, +}; +MODULE_DEVICE_TABLE(of, sec_gnss_match); +#else /* !CONFIG_OF */ +static struct gnss_data *gnss_if_parse_dt_pdata(struct device *dev) +{ + return ERR_PTR(-ENODEV); +} +#endif /* CONFIG_OF */ + +static int gnss_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct gnss_data *pdata = dev->platform_data; + struct gnss_ctl *ctl; + struct io_device *iod; + struct link_device *ld; + unsigned size; + + gif_err("%s: +++\n", pdev->name); + + if (!dev->of_node) { + gif_err("No DT data!\n"); + goto probe_fail; + } + + pdata = gnss_if_parse_dt_pdata(dev); + if (IS_ERR(pdata)) { + gif_err("DT parse error!\n"); + return PTR_ERR(pdata); + } + + /* allocate iodev */ + size = sizeof(struct gnss_io_t); + pdata->iodev = devm_kzalloc(dev, size, GFP_KERNEL); + if (!pdata->iodev) { + gif_err("iodev: failed to alloc memory\n"); + return PTR_ERR(pdata); + } + + /* GNSS uses one IO device and does not need to be parsed from DT. */ + pdata->iodev->name = pdata->device_node_name; + pdata->iodev->id = 0; /* Fixed channel 0. */ + pdata->iodev->app = "SLL"; + + /* create control device */ + ctl = create_ctl_device(pdev); + if (!ctl) { + gif_err("%s: Could not create CTL\n", pdata->name); + goto probe_fail; + } + + /* create link device */ + ld = create_link_device_shmem(pdev); + if (!ld) { + gif_err("%s: Could not create LD\n", pdata->name); + goto free_gc; + } + + ld->gc = ctl; + + /* create io device and connect to ctl device */ + iod = create_io_device(pdev, pdata->iodev, ld, ctl, pdata); + if (!iod) { + gif_err("%s: Could not create IOD\n", pdata->name); + goto free_iod; + } + + /* attach device */ + gif_info("set %s->%s\n", iod->name, ld->name); + set_current_link(iod, iod->ld); + + platform_set_drvdata(pdev, ctl); + + gif_err("%s: ---\n", pdata->name); + + return 0; + +free_iod: + devm_kfree(dev, iod); + +free_gc: + devm_kfree(dev, ctl); + +probe_fail: + + gif_err("%s: xxx\n", pdata->name); + + return -ENOMEM; +} + +static void gnss_shutdown(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct gnss_ctl *gc = dev_get_drvdata(dev); + + /* Matt - Implement Shutdown */ + gc->gnss_state = STATE_OFFLINE; +} + +#ifdef CONFIG_PM +static int gnss_suspend(struct device *pdev) +{ + struct gnss_ctl *gc = dev_get_drvdata(pdev); + + /* Matt - Implement Suspend */ + if (gc->ops.suspend_gnss_ctrl != NULL) { + gif_err("%s: pd_active:0\n", gc->name); + gc->ops.suspend_gnss_ctrl(gc); + } + + return 0; +} + +static int gnss_resume(struct device *pdev) +{ + struct gnss_ctl *gc = dev_get_drvdata(pdev); + + /* Matt - Implement Resume */ + if (gc->ops.resume_gnss_ctrl != NULL) { + gif_err("%s: pd_active:1\n", gc->name); + gc->ops.resume_gnss_ctrl(gc); + } + + return 0; +} +#else +#define gnss_suspend NULL +#define gnss_resume NULL +#endif + +static const struct dev_pm_ops gnss_pm_ops = { + .suspend = gnss_suspend, + .resume = gnss_resume, +}; + +static struct platform_driver gnss_driver = { + .probe = gnss_probe, + .shutdown = gnss_shutdown, + .driver = { + .name = "gif_exynos", + .owner = THIS_MODULE, + .pm = &gnss_pm_ops, +#ifdef CONFIG_OF + .of_match_table = of_match_ptr(sec_gnss_match), +#endif + }, +}; + +module_platform_driver(gnss_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Samsung GNSS Interface Driver"); diff --git a/drivers/misc/gnss_if/gnss_prj.h b/drivers/misc/gnss_if/gnss_prj.h new file mode 100644 index 000000000000..48d1d8b2f41e --- /dev/null +++ b/drivers/misc/gnss_if/gnss_prj.h @@ -0,0 +1,367 @@ +/* + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __GNSS_PRJ_H__ +#define __GNSS_PRJ_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "include/gnss.h" +#include "include/exynos_ipc.h" +#include "pmu-gnss.h" + +#define CALLER (__builtin_return_address(0)) + +#define MAX_IOD_RXQ_LEN 2048 + +#define GNSS_IOC_MAGIC ('K') + +#define GNSS_IOCTL_RESET _IO(GNSS_IOC_MAGIC, 0x00) +#define GNSS_IOCTL_LOAD_FIRMWARE _IO(GNSS_IOC_MAGIC, 0x01) +#define GNSS_IOCTL_REQ_FAULT_INFO _IO(GNSS_IOC_MAGIC, 0x02) +#define GNSS_IOCTL_REQ_BCMD _IO(GNSS_IOC_MAGIC, 0x03) +#define GNSS_IOCTL_READ_FIRMWARE _IO(GNSS_IOC_MAGIC, 0x04) +#define GNSS_IOCTL_CHANGE_SENSOR_GPIO _IO(GNSS_IOC_MAGIC, 0x05) +#define GNSS_IOCTL_CHANGE_TCXO_MODE _IO(GNSS_IOC_MAGIC, 0x06) +#define GNSS_IOCTL_SET_SENSOR_POWER _IO(GNSS_IOC_MAGIC, 0x07) + +enum sensor_power { + SENSOR_OFF, + SENSOR_ON, +}; + +#define USE_SIMPLE_WAKE_LOCK + +struct kepler_bcmd_args { + u16 flags; + u16 cmd_id; + u32 param1; + u32 param2; + u32 ret_val; +}; + +struct kepler_firmware_args { + u32 firmware_size; + u32 offset; + char *firmware_bin; +}; + +struct kepler_fault_args { + u32 dump_size; + char *dumped_data; +}; + +#ifdef CONFIG_COMPAT +struct kepler_firmware_args32 { + u32 firmware_size; + u32 offset; + compat_uptr_t firmware_bin; +}; + +struct kepler_fault_args32 { + u32 dump_size; + compat_uptr_t dumped_data; +}; +#endif + +/* gnss status */ +#define HDLC_HEADER_MAX_SIZE 6 /* fmt 3, raw 6, rfs 6 */ + +#define GNSS_MAX_NAME_LEN 64 + +#define MAX_HEX_LEN 16 +#define MAX_NAME_LEN 64 +#define MAX_PREFIX_LEN 128 +#define MAX_STR_LEN 256 + +/* Does gnss ctl structure will use state ? or status defined below ?*/ +enum gnss_state { + STATE_OFFLINE, + STATE_FIRMWARE_DL, /* no firmware */ + STATE_ONLINE, + STATE_HOLD_RESET, + STATE_FAULT, /* ACTIVE/WDT */ +}; + +static const char const *gnss_state_str[] = { + [STATE_OFFLINE] = "OFFLINE", + [STATE_FIRMWARE_DL] = "FIRMWARE_DL", + [STATE_ONLINE] = "ONLINE", + [STATE_HOLD_RESET] = "HOLD_RESET", + [STATE_FAULT] = "FAULT", +}; + +enum direction { + TX = 0, + AP2GNSS = 0, + RX = 1, + GNSS2AP = 1, + MAX_DIR = 2 +}; + +/** + @brief return the gnss_state string + @param state the state of a GNSS + */ +static const inline char *get_gnss_state_str(int state) +{ + return gnss_state_str[state]; +} + +struct header_data { + char hdr[HDLC_HEADER_MAX_SIZE]; + u32 len; + u32 frag_len; + char start; /*hdlc start header 0x7F*/ +}; + +struct fmt_hdr { + u16 len; + u8 control; +} __packed; + +/* for fragmented data from link devices */ +struct fragmented_data { + struct sk_buff *skb_recv; + struct header_data h_data; + struct exynos_frame_data f_data; + /* page alloc fail retry*/ + unsigned realloc_offset; +}; +#define fragdata(iod, ld) (&(iod)->fragments) + +/** struct skbuff_priv - private data of struct sk_buff + * this is matched to char cb[48] of struct sk_buff + */ +struct skbuff_private { + struct io_device *iod; + struct link_device *ld; + struct io_device *real_iod; /* for rx multipdp */ + + /* for time-stamping */ + struct timespec ts; + + u32 lnk_hdr:1, + reserved:15, + exynos_ch:8, + frm_ctrl:8; + + /* for indicating that thers is only one IPC frame in an skb */ + bool single_frame; +} __packed; + +static inline struct skbuff_private *skbpriv(struct sk_buff *skb) +{ + BUILD_BUG_ON(sizeof(struct skbuff_private) > sizeof(skb->cb)); + return (struct skbuff_private *)&skb->cb; +} + +struct io_device { + /* Name of the IO device */ + char *name; + + /* Link to link device */ + struct link_device *ld; + + /* Reference count */ + atomic_t opened; + + /* Wait queue for the IO device */ + wait_queue_head_t wq; + + /* Misc and net device structures for the IO device */ + struct miscdevice miscdev; + + /* The name of the application that will use this IO device */ + char *app; + + bool link_header; + + /* Rx queue of sk_buff */ + struct sk_buff_head sk_rx_q; + + /* + ** work for each io device, when delayed work needed + ** use this for private io device rx action + */ + struct delayed_work rx_work; + + struct fragmented_data fragments; + + /* called from linkdevice when a packet arrives for this iodevice */ + int (*recv_skb)(struct io_device *iod, struct link_device *ld, + struct sk_buff *skb); + int (*recv_skb_single)(struct io_device *iod, struct link_device *ld, + struct sk_buff *skb); + + struct gnss_ctl *gc; + + struct wake_lock wakelock; + long waketime; + + struct exynos_seq_num seq_num; + + /* DO NOT use __current_link directly + * you MUST use skbpriv(skb)->ld in mc, link, etc.. + */ + struct link_device *__current_link; +}; +#define to_io_device(misc) container_of(misc, struct io_device, miscdev) + +/* get_current_link, set_current_link don't need to use locks. + * In ARM, set_current_link and get_current_link are compiled to + * each one instruction (str, ldr) as atomic_set, atomic_read. + * And, the order of set_current_link and get_current_link is not important. + */ +#define get_current_link(iod) ((iod)->__current_link) +#define set_current_link(iod, ld) ((iod)->__current_link = (ld)) + +struct link_device { + struct list_head list; + char *name; + + /* GNSS data */ + struct gnss_data *gnss_data; + + /* GNSS control */ + struct gnss_ctl *gc; + + /* link to io device */ + struct io_device *iod; + + /* TX queue of socket buffers */ + struct sk_buff_head sk_fmt_tx_q; + struct sk_buff_head *skb_txq; + + /* RX queue of socket buffers */ + struct sk_buff_head sk_fmt_rx_q; + struct sk_buff_head *skb_rxq; + + int timeout_cnt; + + struct workqueue_struct *tx_wq; + struct work_struct tx_work; + struct delayed_work tx_delayed_work; + + struct delayed_work *tx_dwork; + struct delayed_work fmt_tx_dwork; + + struct workqueue_struct *rx_wq; + struct work_struct rx_work; + struct delayed_work rx_delayed_work; + + /* called by an io_device when it has a packet to send over link + * - the io device is passed so the link device can look at id and + * format fields to determine how to route/format the packet + */ + int (*send)(struct link_device *ld, struct io_device *iod, + struct sk_buff *skb); + + /* Method to clear RX/TX buffers before reset */ + void (*reset_buffers)(struct link_device *ld); + + /* Methods for copying to/from reserved memory */ + int (*copy_reserved_from_user)(struct link_device *ld, u32 offset, \ + void __user *user_src, u32 size); + int (*copy_reserved_to_user)(struct link_device *ld, u32 offset, \ + void __user *user_dst, u32 size); + + /* Method to dump fault info to user */ + int (*dump_fault_to_user)(struct link_device *ld, \ + void __user *user_dst, u32 size); +}; + +/** rx_alloc_skb - allocate an skbuff and set skb's iod, ld + * @length: length to allocate + * @iod: struct io_device * + * @ld: struct link_device * + * + * %NULL is returned if there is no free memory. + */ +static inline struct sk_buff *rx_alloc_skb(unsigned int length, + struct io_device *iod, struct link_device *ld) +{ + struct sk_buff *skb; + + skb = alloc_skb(length, GFP_ATOMIC); + + if (likely(skb)) { + skbpriv(skb)->iod = iod; + skbpriv(skb)->ld = ld; + } + return skb; +} + +enum gnss_mode; +enum gnss_int_clear; +enum gnss_tcxo_mode; + +struct gnssctl_ops { + int (*gnss_hold_reset)(struct gnss_ctl *); + int (*gnss_release_reset)(struct gnss_ctl *); + int (*gnss_power_on)(struct gnss_ctl *); + int (*gnss_req_fault_info)(struct gnss_ctl *); + int (*suspend_gnss_ctrl)(struct gnss_ctl *); + int (*resume_gnss_ctrl)(struct gnss_ctl *); + int (*change_sensor_gpio)(struct gnss_ctl *); + int (*set_sensor_power)(struct gnss_ctl *, enum sensor_power); + int (*req_bcmd)(struct gnss_ctl *, u16, u16, u32, u32); +}; + +struct gnss_ctl { + struct device *dev; + char *name; + struct gnss_data *gnss_data; + enum gnss_state gnss_state; + + struct clk *ccore_qch_lh_gnss; + + struct delayed_work dwork; + struct work_struct work; + + struct gnssctl_ops ops; + struct gnssctl_pmu_ops *pmu_ops; + struct io_device *iod; + + /* Wakelock for gnss_ctl */ + struct wake_lock gc_fault_wake_lock; + struct wake_lock gc_wake_lock; + struct wake_lock gc_bcmd_wake_lock; + + int wake_lock_irq; + int req_init_irq; + struct completion fault_cmpl; + struct completion bcmd_cmpl; + struct completion req_init_cmpl; + + struct pinctrl *gnss_gpio; + struct pinctrl_state *gnss_sensor_gpio; + + struct regulator *vdd_sensor_reg; +}; + +extern int exynos_init_gnss_io_device(struct io_device *iod); + +int init_gnssctl_device(struct gnss_ctl *mc, struct gnss_data *pdata); +struct link_device *create_link_device_shmem(struct platform_device *pdev); + +#endif diff --git a/drivers/misc/gnss_if/gnss_utils.c b/drivers/misc/gnss_if/gnss_utils.c new file mode 100644 index 000000000000..f3c860cd4db1 --- /dev/null +++ b/drivers/misc/gnss_if/gnss_utils.c @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2011 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gnss_prj.h" +#include "gnss_utils.h" + +static const char *hex = "0123456789abcdef"; + +/* dump2hex + * dump data to hex as fast as possible. + * the length of @buff must be greater than "@len * 3" + * it need 3 bytes per one data byte to print. + */ +static inline int dump2hex(char *buff, const char *data, size_t len) +{ + char *dest = buff; + int i; + + for (i = 0; i < len; i++) { + *dest++ = hex[(data[i] >> 4) & 0xf]; + *dest++ = hex[data[i] & 0xf]; + *dest++ = ' '; + } + if (likely(len > 0)) + dest--; /* last space will be overwrited with null */ + + *dest = '\0'; + + return dest - buff; +} + +static inline void pr_ipc_msg(int level, u8 ch, const char *prefix, + const u8 *msg, unsigned int len) +{ + size_t offset; + char str[MAX_STR_LEN] = {0, }; + + if (prefix) + snprintf(str, MAX_STR_LEN, "%s", prefix); + + offset = strlen(str); + dump2hex((str + offset), msg, (len > MAX_HEX_LEN ? MAX_HEX_LEN : len)); + + gif_err("%s\n", str); +} + +void gnss_log_ipc_pkt(struct sk_buff *skb, enum direction dir) +{ + struct io_device *iod; + struct link_device *ld; + char prefix[MAX_PREFIX_LEN] = {0, }; + unsigned int hdr_len; + unsigned int msg_len; + u8 *msg; + u8 *hdr; + u8 ch; + + /* + if (!log_info.debug_log) + return; + */ + + iod = skbpriv(skb)->iod; + ld = skbpriv(skb)->ld; + ch = skbpriv(skb)->exynos_ch; + + /** + * Make a string of the route + */ + snprintf(prefix, MAX_PREFIX_LEN, "%s %s: %s: ", + "LNK", dir_str(dir), ld->name); + + hdr = skbpriv(skb)->lnk_hdr ? skb->data : NULL; + hdr_len = hdr ? EXYNOS_HEADER_SIZE : 0; + if (hdr_len > 0) { + char *separation = " | "; + size_t offset = strlen(prefix); + + dump2hex((prefix + offset), hdr, hdr_len); + strncat(prefix, separation, strlen(separation)); + } + + /** + * Print an IPC message with the prefix + */ + msg = skb->data + hdr_len; + msg_len = (skb->len - hdr_len); + + pr_ipc_msg(log_info.fmt_msg, ch, prefix, msg, msg_len); +} + diff --git a/drivers/misc/gnss_if/gnss_utils.h b/drivers/misc/gnss_if/gnss_utils.h new file mode 100644 index 000000000000..105773e1c8de --- /dev/null +++ b/drivers/misc/gnss_if/gnss_utils.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2011 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __GNSS_UTILS_H__ +#define __GNSS_UTILS_H__ + +#include "gnss_prj.h" + +/* #define DEBUG_GNSS_IPC_PKT 1 */ + +struct __packed gnss_log { + u8 fmt_msg; + u8 boot_msg; + u8 dump_msg; + u8 rfs_msg; + u8 log_msg; + u8 ps_msg; + u8 router_msg; + u8 debug_log; +}; + +extern struct gnss_log log_info; + +static const char const *direction_string[] = { + [TX] = "TX", + [RX] = "RX" +}; + +static const inline char *dir_str(enum direction dir) +{ + if (unlikely(dir >= MAX_DIR)) + return "INVALID"; + else + return direction_string[dir]; +} + +/* print IPC message packet */ +void gnss_log_ipc_pkt(struct sk_buff *skb, enum direction dir); + +#endif/*__GNSS_UTILS_H__*/ + diff --git a/drivers/misc/gnss_if/include/exynos_ipc.h b/drivers/misc/gnss_if/include/exynos_ipc.h new file mode 100755 index 000000000000..ba6741995b95 --- /dev/null +++ b/drivers/misc/gnss_if/include/exynos_ipc.h @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2010 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __EXYNOS_IPC_H__ +#define __EXYNOS_IPC_H__ + +#include +#include "gnss.h" + +#define EXYNOS_SINGLE_MASK (0b11000000) +#define EXYNOS_MULTI_START_MASK (0b10000000) +#define EXYNOS_MULTI_LAST_MASK (0b01000000) + +#define EXYNOS_START_MASK 0xABCD +#define EXYNOS_START_OFFSET 0 +#define EXYNOS_START_SIZE 2 + +#define EXYNOS_FRAME_SEQ_OFFSET 2 +#define EXYNOS_FRAME_SIZE 2 + +#define EXYNOS_FRAG_CONFIG_OFFSET 4 +#define EXYNOS_FRAG_CONFIG_SIZE 2 + +#define EXYNOS_LEN_OFFSET 6 +#define EXYNOS_LEN_SIZE 2 + +#define EXYNOS_CH_ID_OFFSET 8 +#define EXYNOS_CH_SIZE 1 + +#define EXYNOS_CH_SEQ_OFFSET 9 +#define EXYNOS_CH_SEQ_SIZE 1 + +#define EXYNOS_HEADER_SIZE 12 + +#define EXYNOS_DATA_LOOPBACK_CHANNEL 82 + +#define EXYNOS_FMT_NUM 1 +#define EXYNOS_RFS_NUM 10 + +struct __packed frag_config { + u8 frame_first:1, + frame_last:1, + packet_index:6; + u8 frame_index; +}; + +/* EXYNOS link-layer header */ +struct __packed exynos_link_header { + u16 seq; + struct frag_config cfg; + u16 len; + u16 reserved_1; + u8 ch_id; + u8 ch_seq; + u16 reserved_2; +}; + +struct __packed exynos_seq_num { + u16 frame_cnt; + u8 ch_cnt[255]; +}; + +struct exynos_frame_data { + /* Frame length calculated from the length fields */ + unsigned int len; + + /* The length of link layer header */ + unsigned int hdr_len; + + /* The length of received header */ + unsigned int hdr_rcvd; + + /* The length of link layer payload */ + unsigned int pay_len; + + /* The length of received data */ + unsigned int pay_rcvd; + + /* The length of link layer padding */ + unsigned int pad_len; + + /* The length of received padding */ + unsigned int pad_rcvd; + + /* Header buffer */ + u8 hdr[EXYNOS_HEADER_SIZE]; +}; + +static inline bool exynos_start_valid(u8 *frm) +{ + u16 cfg = *(u16 *)(frm + EXYNOS_START_OFFSET); + + return cfg == EXYNOS_START_MASK ? true : false; +} + +static inline bool exynos_multi_start_valid(u8 *frm) +{ + u16 cfg = *(u16 *)(frm + EXYNOS_FRAG_CONFIG_OFFSET); + return ((cfg >> 8) & EXYNOS_MULTI_START_MASK) == EXYNOS_MULTI_START_MASK; +} + +static inline bool exynos_multi_last_valid(u8 *frm) +{ + u16 cfg = *(u16 *)(frm + EXYNOS_FRAG_CONFIG_OFFSET); + return ((cfg >> 8) & EXYNOS_MULTI_LAST_MASK) == EXYNOS_MULTI_LAST_MASK; +} + +static inline bool exynos_single_frame(u8 *frm) +{ + u16 cfg = *(u16 *)(frm + EXYNOS_FRAG_CONFIG_OFFSET); + return ((cfg >> 8) & EXYNOS_SINGLE_MASK) == EXYNOS_SINGLE_MASK; +} + +static inline u8 exynos_get_ch(u8 *frm) +{ + return frm[EXYNOS_CH_ID_OFFSET]; +} + +static inline unsigned int exynos_calc_padding_size(unsigned int len) +{ + unsigned int residue = len & 0x3; + return residue ? (4 - residue) : 0; +} + +static inline unsigned int exynos_get_frame_len(u8 *frm) +{ + return (unsigned int)*(u16 *)(frm + EXYNOS_LEN_OFFSET); +} + +static inline unsigned int exynos_get_total_len(u8 *frm) +{ + unsigned int len; + unsigned int pad; + + len = exynos_get_frame_len(frm); + pad = exynos_calc_padding_size(len) ? exynos_calc_padding_size(len) : 0; + return len + pad; +} + +static inline bool exynos_padding_exist(u8 *frm) +{ + return exynos_calc_padding_size(exynos_get_frame_len(frm)) ? true : false; +} +#endif diff --git a/drivers/misc/gnss_if/include/gnss.h b/drivers/misc/gnss_if/include/gnss.h new file mode 100755 index 000000000000..17ca259c37f0 --- /dev/null +++ b/drivers/misc/gnss_if/include/gnss.h @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2014 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __GNSS_IF_H__ +#define __GNSS_IF_H__ + +#include +#include + +/** + * struct gnss_io_t - declaration for io_device + * @name: device name + * @id: for SIPC4, contains format & channel information + * (id & 11100000b)>>5 = format (eg, 0=FMT, 1=RAW, 2=RFS) + * (id & 00011111b) = channel (valid only if format is RAW) + * for SIPC5, contains only 8-bit channel ID + * @format: device format + * @io_type: type of this io_device + * @links: list of link_devices to use this io_device + * for example, if you want to use DPRAM and USB in an io_device. + * .links = LINKTYPE(LINKDEV_DPRAM) | LINKTYPE(LINKDEV_USB) + * @tx_link: when you use 2+ link_devices, set the link for TX. + * If define multiple link_devices in @links, + * you can receive data from them. But, cannot send data to all. + * TX is only one link_device. + * @app: the name of the application that will use this IO device + * + */ +struct gnss_io_t { + char *name; + int id; + char *app; +}; + +enum gnss_bcmd_ctrl { + CTRL0, + CTRL1, + CTRL2, + CTRL3, + BCMD_CTRL_COUNT, +}; + +enum gnss_reg_type { + GNSS_REG_RX_IPC_MSG, + GNSS_REG_TX_IPC_MSG, + GNSS_REG_WAKE_LOCK, + GNSS_REG_RX_HEAD, + GNSS_REG_RX_TAIL, + GNSS_REG_TX_HEAD, + GNSS_REG_TX_TAIL, + GNSS_REG_COUNT, +}; + +enum gnss_ipc_vector { + GNSS_IPC_MBOX, + GNSS_IPC_SHMEM, + GNSS_IPC_COUNT, +}; + +struct gnss_mbox { + int int_ap2gnss_bcmd; + int int_ap2gnss_req_fault_info; + int int_ap2gnss_ipc_msg; + int int_ap2gnss_ack_wake_set; + int int_ap2gnss_ack_wake_clr; + + int irq_gnss2ap_bcmd; + int irq_gnss2ap_rsp_fault_info; + int irq_gnss2ap_ipc_msg; + int irq_gnss2ap_req_wake_clr; + + unsigned reg_bcmd_ctrl[BCMD_CTRL_COUNT]; + + int id; +}; + +struct gnss_shared_reg_value { + int index; + u32 __iomem *addr; +}; + +struct gnss_shared_reg { + const char *name; + struct gnss_shared_reg_value value; + u32 device; +}; + +struct gnss_fault_data_area_value { + u32 index; + u8 __iomem *addr; +}; + +struct gnss_fault_data_area { + const char *name; + struct gnss_fault_data_area_value value; + u32 size; + u32 device; +}; + +/* platform data */ +struct gnss_data { + char *name; + char *device_node_name; + + int irq_gnss_active; + int irq_gnss_wdt; + int irq_gnss_wakeup; + + struct gnss_mbox *mbx; + + struct gnss_shared_reg *reg[GNSS_REG_COUNT]; + + struct gnss_fault_data_area fault_info; + + /* Information of IO devices */ + struct gnss_io_t *iodev; + + /* SHDMEM ADDR */ + u32 shmem_base; + u32 shmem_size; + u32 ipcmem_offset; + u32 ipc_size; + u32 ipc_reg_cnt; + + u8 __iomem *gnss_base; + u8 __iomem *ipc_base; +}; + +struct shmem_conf { + u32 shmem_base; + u32 shmem_size; +}; + + +#ifdef CONFIG_OF +#define gif_dt_read_enum(np, prop, dest) \ + do { \ + u32 val; \ + if (of_property_read_u32(np, prop, &val)) \ + return -EINVAL; \ + dest = (__typeof__(dest))(val); \ + } while (0) + +#define gif_dt_read_bool(np, prop, dest) \ + do { \ + u32 val; \ + if (of_property_read_u32(np, prop, &val)) \ + return -EINVAL; \ + dest = val ? true : false; \ + } while (0) + +#define gif_dt_read_string(np, prop, dest) \ + do { \ + if (of_property_read_string(np, prop, \ + (const char **)&dest)) \ + return -EINVAL; \ + } while (0) + +#define gif_dt_read_u32(np, prop, dest) \ + do { \ + u32 val; \ + if (of_property_read_u32(np, prop, &val)) \ + return -EINVAL; \ + dest = val; \ + } while (0) +#define gif_dt_read_u32_array(np, prop, dest, sz) \ + do { \ + if (of_property_read_u32_array(np, prop, dest, (sz))) \ + return -EINVAL; \ + } while (0) +#endif + +#define LOG_TAG "gif: " +#define CALLEE (__func__) +#define CALLER (__builtin_return_address(0)) + +#define gif_err_limited(fmt, ...) \ + printk_ratelimited(KERN_ERR "%s: " pr_fmt(fmt), __func__, ##__VA_ARGS__) +#define gif_err(fmt, ...) \ + pr_err(LOG_TAG "%s: " pr_fmt(fmt), __func__, ##__VA_ARGS__) +#define gif_debug(fmt, ...) \ + pr_debug(LOG_TAG "%s: " pr_fmt(fmt), __func__, ##__VA_ARGS__) +#define gif_info(fmt, ...) \ + pr_info(LOG_TAG "%s: " pr_fmt(fmt), __func__, ##__VA_ARGS__) +#define gif_trace(fmt, ...) \ + printk(KERN_DEBUG "gif: %s: %d: called(%pF): " fmt, \ + __func__, __LINE__, __builtin_return_address(0), ##__VA_ARGS__) + +#endif diff --git a/drivers/misc/gnss_if/pmu-gnss.c b/drivers/misc/gnss_if/pmu-gnss.c new file mode 100644 index 000000000000..b295e6df54df --- /dev/null +++ b/drivers/misc/gnss_if/pmu-gnss.c @@ -0,0 +1,471 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../soc/samsung/cal-if/acpm_dvfs.h" +#include "pmu-gnss.h" +#include "gnss_prj.h" + +/* For connectivity I/F */ +//#define SMC_CMD_CONN_IF (0x82000710) +/* Connectivity sub system */ +#define EXYNOS_GNSS (0) +/* Target to set */ +#define EXYNOS_SET_CONN_TZPC (0) + +static int gnss_request_tzpc(void); +static void gnss_request_gnss2ap_baaw(void); + +#ifdef USE_IOREMAP_NOPMU +#if defined(CONFIG_SOC_EXYNOS7870) +#define PMU_ADDR (0x10480000) +#define PMU_SIZE (SZ_64K) +#elif defined(CONFIG_SOC_EXYNOS7880) +#define PMU_ADDR (0x106B0000) +#define PMU_SIZE (SZ_64K) +#elif defined(CONFIG_SOC_EXYNOS7570) +#define PMU_ADDR (0x11C80000) +#define PMU_SIZE (SZ_64K) +#elif defined(CONFIG_SOC_EXYNOS7872) +#define PMU_ADDR (0x11C80000) +#define PMU_SIZE (SZ_64K) +#elif defined(CONFIG_SOC_EXYNOS9610) +#define PMU_ADDR (0x11860000) +#define PMU_SIZE (SZ_64K) +#endif + +static void __iomem *pmu_reg; +static int gnss_nopmu_read(unsigned int reg_offset, unsigned int *ret) +{ + *ret = __raw_readl(pmu_reg + reg_offset); + return 0; +} + +static int gnss_nopmu_write(unsigned int reg_offset, unsigned int val) +{ + unsigned int tmp, tmp2; + tmp = __raw_readl(pmu_reg + reg_offset); + __raw_writel(val, pmu_reg + reg_offset); + tmp2 = __raw_readl(pmu_reg + reg_offset); + + return (tmp == tmp2) ? 0 : -EINVAL; +} + +static int gnss_nopmu_update(unsigned int reg_offset, unsigned int mask, + unsigned int val) +{ + unsigned int memcfg_val; + unsigned int tmp, tmp2; + tmp = __raw_readl(pmu_reg + reg_offset); + memcfg_val = tmp; + memcfg_val &= ~mask; + memcfg_val |= val; + __raw_writel(memcfg_val, pmu_reg + reg_offset); + tmp2 = __raw_readl(pmu_reg + reg_offset); + + return (memcfg_val == tmp2) ? 0 : -EINVAL; +} + +#define gnss_pmu_read gnss_nopmu_read +#define gnss_pmu_write gnss_nopmu_write +#define gnss_pmu_update gnss_nopmu_update + +#else + +#define gnss_pmu_read exynos_pmu_read +#define gnss_pmu_write exynos_pmu_write +#define gnss_pmu_update exynos_pmu_update + +#endif /* USE_IOREMAP_NOPMU */ + + + +#if defined(CONFIG_SOC_EXYNOS9610) +static u32 g_shmem_size; +static u32 g_shmem_base; + +#define BAAW_GNSS_CMGP_ADDR (0x13EE0000) +#define BAAW_GNSS_CMGP_SIZE (SZ_64K) + +#define BAAW_GNSS_DBUS_ADDR (0x13ED0000) +#define BAAW_GNSS_DBUS_SIZE (SZ_64K) + +static void __iomem *baaw_cmgp_reg; +static void __iomem *baaw_dbus_reg; + +int gnss_cmgp_read(unsigned int reg_offset, unsigned int *ret) +{ + if (baaw_cmgp_reg == NULL) + return -EIO; + + *ret = __raw_readl(baaw_cmgp_reg + reg_offset); + return 0; +} + +int gnss_cmgp_write(unsigned int reg_offset, unsigned int val) +{ + if (baaw_cmgp_reg == NULL) + return -EIO; + + __raw_writel(val, baaw_cmgp_reg + reg_offset); + gif_info("ADDR:%08X DATA:%08X => Read to verify:%08X\n", BAAW_GNSS_CMGP_ADDR + reg_offset, val, __raw_readl(baaw_cmgp_reg + reg_offset)); + + return 0; +} + +int gnss_dbus_read(unsigned int reg_offset, unsigned int *ret) +{ + if (baaw_dbus_reg == NULL) + return -EIO; + + *ret = __raw_readl(baaw_dbus_reg + reg_offset); + return 0; +} + +int gnss_dbus_write(unsigned int reg_offset, unsigned int val) +{ + if (baaw_dbus_reg == NULL) + return -EIO; + + __raw_writel(val, baaw_dbus_reg + reg_offset); + gif_info("ADDR:%08X DATA:%08X => Read to verify:%08X\n", BAAW_GNSS_DBUS_ADDR + reg_offset, val, __raw_readl(baaw_dbus_reg + reg_offset)); + + return 0; +} +#endif + +void __set_shdmem_size(u32 reg_offset, u32 memsz) +{ + memsz /= MEMSIZE_RES; + gnss_pmu_update(reg_offset, MEMSIZE_MASK, memsz << MEMSIZE_OFFSET); +} + +void set_shdmem_size(u32 memsz) +{ + gif_info("Set shared mem size: %dB\n", memsz); + + __set_shdmem_size(EXYNOS_PMU_GNSS2AP_MEM_CONFIG0, memsz); +} + +void __set_shdmem_base(u32 reg_offset, u32 shmem_base) +{ + u32 base_addr; + base_addr = (shmem_base >> MEMBASE_ADDR_SHIFT); + + gnss_pmu_update(reg_offset, MEMBASE_ADDR_MASK << MEMBASE_ADDR_OFFSET, + base_addr << MEMBASE_ADDR_OFFSET); +} + +void set_shdmem_base(u32 shmem_base) +{ + gif_info("Set shared mem baseaddr : 0x%x\n", shmem_base); + + __set_shdmem_base(EXYNOS_PMU_GNSS2AP_MEM_CONFIG1, shmem_base); +} + +void exynos_sys_powerdown_conf_gnss(void) +{ + gnss_pmu_write(EXYNOS_PMU_RESET_AHEAD_GNSS_SYS_PWR_REG, 0); + gnss_pmu_write(EXYNOS_PMU_CLEANY_BUS_SYS_PWR_REG, 0); + gnss_pmu_write(EXYNOS_PMU_LOGIC_RESET_GNSS_SYS_PWR_REG, 0); + gnss_pmu_write(EXYNOS_PMU_TCXO_GATE_GNSS_SYS_PWR_REG, 0); + gnss_pmu_write(EXYNOS_PMU_GNSS_DISABLE_ISO_SYS_PWR_REG, 1); + gnss_pmu_write(EXYNOS_PMU_GNSS_RESET_ISO_SYS_PWR_REG, 0); + gnss_pmu_write(EXYNOS_PMU_CENTRAL_SEQ_GNSS_CONFIGURATION, 0); +} + +static int gnss_pmu_clear_interrupt(enum gnss_int_clear gnss_int) +{ + int ret = 0; + + gif_info("gnss_int = %d\n", gnss_int); + switch (gnss_int) { + case GNSS_INT_WAKEUP_CLEAR: + ret = gnss_pmu_update(EXYNOS_PMU_GNSS_CTRL_NS, + GNSS_WAKEUP_REQ_CLR, GNSS_WAKEUP_REQ_CLR); + break; + case GNSS_INT_ACTIVE_CLEAR: + ret = gnss_pmu_update(EXYNOS_PMU_GNSS_CTRL_NS, + GNSS_ACTIVE_REQ_CLR, GNSS_ACTIVE_REQ_CLR); + break; + case GNSS_INT_WDT_RESET_CLEAR: + ret = gnss_pmu_update(EXYNOS_PMU_GNSS_CTRL_NS, + GNSS_RESET_REQ_CLR, GNSS_RESET_REQ_CLR); + break; + default: + gif_err("Unexpected interrupt value!\n"); + return -EIO; + } + + if (ret < 0) { + gif_err("ERR! GNSS Reset Fail: %d\n", ret); + return -EIO; + } + + return ret; +} + +static int gnss_pmu_release_reset(void) +{ + u32 __maybe_unused gnss_ctrl = 0; + int ret = 0; + +#if defined(CONFIG_SOC_EXYNOS7872) + pr_err("%s: call exynos_acpm_set_flag(MASTER_ID_GNSS, FLAG_LOCK) before release GNSS\n", __func__); + exynos_acpm_set_flag(MASTER_ID_GNSS, FLAG_LOCK); +#endif + +#ifdef CONFIG_GNSS_PMUCAL + cal_gnss_reset_release(); +#else + gnss_pmu_read(EXYNOS_PMU_GNSS_CTRL_NS, &gnss_ctrl); + if (!(gnss_ctrl & GNSS_PWRON)) { + ret = gnss_pmu_update(EXYNOS_PMU_GNSS_CTRL_NS, GNSS_PWRON, + GNSS_PWRON); + if (ret < 0) { + gif_err("ERR! write Fail: %d\n", ret); + ret = -EIO; + } + } + ret = gnss_pmu_update(EXYNOS_PMU_GNSS_CTRL_S, GNSS_START, GNSS_START); + if (ret < 0) { + gif_err("ERR! GNSS Release Fail: %d\n", ret); + } else { + gnss_pmu_read(EXYNOS_PMU_GNSS_CTRL_S, &gnss_ctrl); + gif_info("PMU_GNSS_CTRL_S[0x%08x]\n", gnss_ctrl); + ret = -EIO; + } +#endif + +#if defined(CONFIG_SOC_EXYNOS7872) + pr_err("%s: call exynos_acpm_set_flag(MASTER_ID_GNSS, FLAG_UNLOCK) after release GNSS\n", __func__); + exynos_acpm_set_flag(MASTER_ID_GNSS, FLAG_UNLOCK); +#endif + + return ret; +} + +static int gnss_pmu_hold_reset(void) +{ + int ret = 0; + +#if defined(CONFIG_SOC_EXYNOS7872) + pr_err("%s: call exynos_acpm_set_flag(MASTER_ID_GNSS, FLAG_LOCK) before reset GNSS\n", __func__); + exynos_acpm_set_flag(MASTER_ID_GNSS, FLAG_LOCK); +#endif + +#ifdef CONFIG_GNSS_PMUCAL + cal_gnss_reset_assert(); + mdelay(50); +#else + /* set sys_pwr_cfg registers */ + exynos_sys_powerdown_conf_gnss(); + + ret = gnss_pmu_update(EXYNOS_PMU_GNSS_CTRL_NS, GNSS_RESET_SET, + GNSS_RESET_SET); + if (ret < 0) { + gif_err("ERR! GNSS Reset Fail: %d\n", ret); + ret = -1; + goto exit; + } + + /* some delay */ + cpu_relax(); + usleep_range(80, 100); +exit: +#endif + +#if defined(CONFIG_SOC_EXYNOS7872) + pr_err("%s: call exynos_acpm_set_flag(MASTER_ID_GNSS, FLAG_UNLOCK) after reset GNSS\n", __func__); + exynos_acpm_set_flag(MASTER_ID_GNSS, FLAG_UNLOCK); +#endif + + return ret; +} + +static int gnss_request_tzpc(void) +{ + int ret; + + ret = exynos_smc(SMC_CMD_CONN_IF, (EXYNOS_GNSS << 31) | + EXYNOS_SET_CONN_TZPC, 0, 0); + if (ret) + gif_err("ERR: fail to TZPC setting - %X\n", ret); + + return ret; +} + +static void gnss_request_gnss2ap_baaw(void) +{ + gif_info("Config GNSS2AP BAAW\n"); + + gif_info("ap mailbox configuration\n"); + gnss_cmgp_write(0x40, 0x000b4190); + gnss_cmgp_write(0x44, 0x000b41a0); + gnss_cmgp_write(0x48, 0x00011a00); + gnss_cmgp_write(0x4C, 0x80000003); + + gif_info("DRAM Configuration\n"); + gnss_dbus_write(0x0, (MEMBASE_GNSS_ADDR >> MEMBASE_ADDR_SHIFT)); + gnss_dbus_write(0x4, (MEMBASE_GNSS_ADDR >> MEMBASE_ADDR_SHIFT) + + (g_shmem_size >> MEMBASE_ADDR_SHIFT)); + gnss_dbus_write(0x8, (g_shmem_base >> MEMBASE_ADDR_SHIFT)); + gnss_dbus_write(0xC, 0x80000003); + + gnss_dbus_write(0x10, (MEMBASE_GNSS_ADDR_2ND >> MEMBASE_ADDR_SHIFT)); + gnss_dbus_write(0x14, (MEMBASE_GNSS_ADDR_2ND >> MEMBASE_ADDR_SHIFT) + + (g_shmem_size >> MEMBASE_ADDR_SHIFT)); + gnss_dbus_write(0x18, (g_shmem_base >> MEMBASE_ADDR_SHIFT)); + gnss_dbus_write(0x1C, 0x80000003); + + gif_info("apm2gnss mailbox configuration\n"); + gnss_cmgp_write(0x0, 0x000b4120); + gnss_cmgp_write(0x4, 0x000b4130); + gnss_cmgp_write(0x8, 0x000119f0); + gnss_cmgp_write(0xC, 0x80000003); + + gif_info("cp2gnss mailbox configuration\n"); + gnss_cmgp_write(0x10, 0x000b4150); + gnss_cmgp_write(0x14, 0x000b4160); + gnss_cmgp_write(0x18, 0x00011940); + gnss_cmgp_write(0x1C, 0x80000003); + + gif_info("chub mailbox configuration\n"); + gnss_cmgp_write(0x20, 0x000b4160); + gnss_cmgp_write(0x24, 0x000b4170); + gnss_cmgp_write(0x28, 0x00011990); + gnss_cmgp_write(0x2C, 0x80000003); + + gif_info("wifi mailbox configuration\n"); + gnss_cmgp_write(0x30, 0x000b4170); + gnss_cmgp_write(0x34, 0x000b4180); + gnss_cmgp_write(0x38, 0x000119e0); + gnss_cmgp_write(0x3C, 0x80000003); + + gif_info("chub iram configuration\n"); + gnss_cmgp_write(0x50, 0x000b3900); + gnss_cmgp_write(0x54, 0x000b3a00); + gnss_cmgp_write(0x58, 0x00011200); + gnss_cmgp_write(0x5C, 0x80000003); + + gif_info("CMGP PERIS configuration\n"); + gnss_cmgp_write(0x60, 0x000b4200); + gnss_cmgp_write(0x64, 0x000b4400); + gnss_cmgp_write(0x68, 0x00011c00); + gnss_cmgp_write(0x6C, 0x80000003); +} + +static int gnss_pmu_power_on(enum gnss_mode mode) +{ + u32 __maybe_unused gnss_ctrl; + int ret = 0; + + gif_err("mode[%d]\n", mode); + +#ifdef CONFIG_GNSS_PMUCAL + if (mode == GNSS_POWER_ON) { + if (cal_gnss_status() > 0) { + gif_err("GNSS is already Power on, try reset\n"); + cal_gnss_reset_assert(); + } else { + gif_info("GNSS Power On\n"); + cal_gnss_init(); + } + } else { + cal_gnss_reset_release(); + } +#else + gnss_pmu_read(EXYNOS_PMU_GNSS_CTRL_NS, &gnss_ctrl); + if (mode == GNSS_POWER_ON) { + if (!(gnss_ctrl & GNSS_PWRON)) { + ret = gnss_pmu_update(EXYNOS_PMU_GNSS_CTRL_NS, + GNSS_PWRON, GNSS_PWRON); + if (ret < 0) + gif_err("ERR! write Fail: %d\n", ret); + } + + ret = gnss_pmu_update(EXYNOS_PMU_GNSS_CTRL_S, GNSS_START, + GNSS_START); + if (ret < 0) + gif_err("ERR! write Fail: %d\n", ret); + } else { + ret = gnss_pmu_update(EXYNOS_PMU_GNSS_CTRL_NS, GNSS_PWRON, 0); + if (ret < 0) { + gif_err("ERR! write Fail: %d\n", ret); + return ret; + } + /* set sys_pwr_cfg registers */ + exynos_sys_powerdown_conf_gnss(); + } +#endif + return ret; +} + +static int gnss_pmu_init_conf(struct gnss_ctl *gc) +{ + u32 __maybe_unused shmem_size, shmem_base; + +#ifdef USE_IOREMAP_NOPMU + pmu_reg = devm_ioremap(gc->dev, PMU_ADDR, PMU_SIZE); + if (pmu_reg == NULL) + gif_err("%s: pmu ioremap failed.\n", gc->gnss_data->name); + else + gif_err("pmu_reg : 0x%p\n", pmu_reg); +#endif + +#if defined(CONFIG_SOC_EXYNOS9610) + baaw_cmgp_reg = devm_ioremap(gc->dev, BAAW_GNSS_CMGP_ADDR, BAAW_GNSS_CMGP_SIZE); + if (baaw_cmgp_reg == NULL) { + gif_err("%s: pmu ioremap failed.\n", gc->gnss_data->name); + return -EIO; + } else + gif_err("baaw_cmgp_reg : 0x%p\n", baaw_cmgp_reg); + + baaw_dbus_reg = devm_ioremap(gc->dev, BAAW_GNSS_DBUS_ADDR, BAAW_GNSS_DBUS_SIZE); + if (baaw_dbus_reg == NULL) { + gif_err("%s: pmu ioremap failed.\n", gc->gnss_data->name); + return -EIO; + } else + gif_err("baaw_dbus_reg : 0x%p\n", baaw_dbus_reg); + + g_shmem_size = gc->gnss_data->shmem_size; + g_shmem_base = gc->gnss_data->shmem_base; + + gif_info("GNSS SHM address:%X size:%X\n", g_shmem_base, g_shmem_size); +#else + shmem_size = gc->gnss_data->shmem_size; + shmem_base = gc->gnss_data->shmem_base; + + set_shdmem_size(shmem_size); + set_shdmem_base(shmem_base); + + /* set access window for GNSS */ + gnss_pmu_write(EXYNOS_PMU_GNSS2AP_MIF_ACCESS_WIN0, 0x0); + gnss_pmu_write(EXYNOS_PMU_GNSS2AP_PERI_ACCESS_WIN0, 0x0); + gnss_pmu_write(EXYNOS_PMU_GNSS2AP_PERI_ACCESS_WIN1, 0x0); +#endif + + return 0; +} + +static struct gnssctl_pmu_ops pmu_ops = { + .init_conf = gnss_pmu_init_conf, + .hold_reset = gnss_pmu_hold_reset, + .release_reset = gnss_pmu_release_reset, + .power_on = gnss_pmu_power_on, + .clear_int = gnss_pmu_clear_interrupt, + .req_security = gnss_request_tzpc, + .req_baaw = gnss_request_gnss2ap_baaw, +}; + +void gnss_get_pmu_ops(struct gnss_ctl *gc) +{ + gc->pmu_ops = &pmu_ops; + return; +} diff --git a/drivers/misc/gnss_if/pmu-gnss.h b/drivers/misc/gnss_if/pmu-gnss.h new file mode 100644 index 000000000000..c0c9f75e4faa --- /dev/null +++ b/drivers/misc/gnss_if/pmu-gnss.h @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2015 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * EXYNOS - PMU(Power Management Unit) support + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#ifndef __EXYNOS_PMU_GNSS_H +#define __EXYNOS_PMU_GNSS_H __FILE__ + +/* BLK_ALIVE: GNSS related SFRs */ +#define EXYNOS_PMU_GNSS_CTRL_NS 0x0040 +#define EXYNOS_PMU_GNSS_CTRL_S 0x0044 +#define EXYNOS_PMU_GNSS_STAT 0x0048 +#define EXYNOS_PMU_GNSS_DEBUG 0x004C +#define EXYNOS_PMU_GNSS2AP_MEM_CONFIG0 0x7250 /* GNSS_MEM_SIZE0 */ +#define EXYNOS_PMU_GNSS2AP_MEM_CONFIG1 0x7254 /* GNSS_MEM_BA0 */ +#define EXYNOS_PMU_GNSS2AP_MIF_ACCESS_WIN0 0X7260 +#define EXYNOS_PMU_GNSS2AP_PERI_ACCESS_WIN0 0x7278 +#define EXYNOS_PMU_GNSS2AP_PERI_ACCESS_WIN1 0x727C +#define EXYNOS_PMU_GNSS_BOOT_TEST_RST_CONFIG 0x7290 + +#define EXYNOS_PMU_CENTRAL_SEQ_GNSS_CONFIGURATION 0x02C0 +#define EXYNOS_PMU_RESET_AHEAD_GNSS_SYS_PWR_REG 0x1340 +#define EXYNOS_PMU_CLEANY_BUS_SYS_PWR_REG 0x1344 +#define EXYNOS_PMU_LOGIC_RESET_GNSS_SYS_PWR_REG 0x1348 +#define EXYNOS_PMU_TCXO_GATE_GNSS_SYS_PWR_REG 0x134C +#define EXYNOS_PMU_GNSS_DISABLE_ISO_SYS_PWR_REG 0X1350 +#define EXYNOS_PMU_GNSS_RESET_ISO_SYS_PWR_REG 0X1354 + +/* GNSS PMU */ +/* For EXYNOS_PMU_GNSS_CTRL Register */ +#define GNSS_PWRON BIT(1) +#define GNSS_RESET_SET BIT(2) +#define GNSS_START BIT(3) +#define GNSS_ACTIVE_REQ_EN BIT(5) +#define GNSS_ACTIVE_REQ_CLR BIT(6) +#define GNSS_RESET_REQ_EN BIT(7) +#define GNSS_RESET_REQ_CLR BIT(8) +#define MASK_GNSS_PWRDN_DONE BIT(9) +#define RTC_OUT_EN BIT(10) +#define TCXO_ENABLE_SW BIT(11) +#define MASK_SLEEP_START_REQ BIT(12) +#define SET_SW_SLEEP_START_REQ BIT(13) +#define GNSS_WAKEUP_REQ_EN BIT(14) +#define GNSS_WAKEUP_REQ_CLR BIT(15) +#define CLEANY_BYPASS_END BIT(16) +#define SFR_SERIALIZER_DUR_DATA2REQ GENMASK(21, 20) + +#define MEMSIZE_MASK (GENMASK(19, 0)) +#define MEMSIZE_OFFSET 0 +#define MEMSIZE_RES (SZ_4K) + +#define MEMBASE_ADDR_SHIFT 12 +#define MEMBASE_ADDR_MASK (GENMASK(23, 0)) +#define MEMBASE_ADDR_OFFSET 0 + +/* Base address in the point of GNSS view */ +#define MEMBASE_GNSS_ADDR (0x60000000) +#define MEMBASE_GNSS_ADDR_2ND (0xA0000000) + +#ifndef CONFIG_ARCH_EXYNOS +/* Exynos PMU API functions are only available when ARCH_EXYNOS is defined. + * Otherwise, we must hardcode the PMU address for setting the PMU registers. + */ +#define USE_IOREMAP_NOPMU +#endif + +enum gnss_mode { + GNSS_POWER_OFF, + GNSS_POWER_ON, + GNSS_RESET, + NUM_GNSS_MODE, +}; + +enum gnss_int_clear { + GNSS_INT_WDT_RESET_CLEAR, + GNSS_INT_ACTIVE_CLEAR, + GNSS_INT_WAKEUP_CLEAR, +}; + +enum gnss_tcxo_mode { + TCXO_SHARED_MODE = 0, + TCXO_NON_SHARED_MODE = 1, +}; + +struct gnss_ctl; + +struct gnssctl_pmu_ops { + int (*init_conf)(struct gnss_ctl *); + int (*hold_reset)(void); + int (*release_reset)(void); + int (*power_on)(enum gnss_mode); + int (*clear_int)(enum gnss_int_clear); + int (*change_tcxo_mode)(enum gnss_tcxo_mode); + int (*req_security)(void); + void (*req_baaw)(void); +}; + +void gnss_get_pmu_ops(struct gnss_ctl *); +#endif /* __EXYNOS_PMU_GNSS_H */ -- 2.20.1