mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-08 17:18:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
6
drivers/misc/gnss_if/Kconfig
Normal file
6
drivers/misc/gnss_if/Kconfig
Normal file
|
@ -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.
|
10
drivers/misc/gnss_if/Makefile
Normal file
10
drivers/misc/gnss_if/Makefile
Normal file
|
@ -0,0 +1,10 @@
|
|||
# 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
|
825
drivers/misc/gnss_if/gnss_io_device.c
Normal file
825
drivers/misc/gnss_if/gnss_io_device.c
Normal file
|
@ -0,0 +1,825 @@
|
|||
/*
|
||||
* 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 <linux/init.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/if_arp.h>
|
||||
#include <linux/ip.h>
|
||||
#include <linux/if_ether.h>
|
||||
#include <linux/etherdevice.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#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);
|
||||
|
||||
/* Print received data from GNSS */
|
||||
/*
|
||||
gnss_log_ipc_pkt(skb, RX);
|
||||
*/
|
||||
|
||||
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 void io_dev_gnss_state_changed(struct io_device *iod,
|
||||
enum gnss_state state)
|
||||
{
|
||||
struct gnss_ctl *gc = iod->gc;
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
|
||||
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 link_device *ld = iod->ld;
|
||||
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 (ld != NULL) {
|
||||
gif_debug("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 = ld->req_bcmd(ld, 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)
|
||||
{
|
||||
int err = 0;
|
||||
gif_debug("Load Firmware - fw size : %d, fw_offset : %d\n",
|
||||
firmware_arg.firmware_size, firmware_arg.offset);
|
||||
|
||||
if (firmware_arg.offset + firmware_arg.firmware_size > SZ_2M) {
|
||||
gif_err("Unacceptable arguments!\n");
|
||||
err = -EFAULT;
|
||||
goto load_firmware_exit;
|
||||
}
|
||||
|
||||
gif_debug("base addr = 0x%p\n", iod->ld->mdm_data->gnss_base);
|
||||
err = copy_from_user(
|
||||
(void *)iod->ld->mdm_data->gnss_base + firmware_arg.offset,
|
||||
(void __user *)firmware_arg.firmware_bin,
|
||||
firmware_arg.firmware_size);
|
||||
if (err) {
|
||||
gif_err("copy_from_user fail(to get fw binary)\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)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
gif_debug("Read Firmware - fw size : %d, fw_offset : %d\n",
|
||||
firmware_arg.firmware_size, firmware_arg.offset);
|
||||
|
||||
if (firmware_arg.offset + firmware_arg.firmware_size > SZ_2M) {
|
||||
gif_err("Unacceptable arguments!\n");
|
||||
err = -EFAULT;
|
||||
goto read_firmware_exit;
|
||||
}
|
||||
|
||||
err = copy_to_user((void __user *)firmware_arg.firmware_bin,
|
||||
(void *)iod->ld->mdm_data->gnss_base + firmware_arg.offset,
|
||||
firmware_arg.firmware_size);
|
||||
if (err) {
|
||||
gif_err("copy_to_user fail(to get fw binary)\n");
|
||||
err = -EFAULT;
|
||||
}
|
||||
|
||||
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) {
|
||||
ret = gc->pmu_ops.change_tcxo_mode(gc, tcxo_mode);
|
||||
}
|
||||
|
||||
change_mode_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;
|
||||
u32 *fault_info_regs;
|
||||
int err = 0;
|
||||
int size;
|
||||
|
||||
if (!valid_cmd_arg(cmd, arg))
|
||||
return -ENOTTY;
|
||||
|
||||
switch (cmd) {
|
||||
case GNSS_IOCTL_RESET:
|
||||
if (gc->ops.gnss_hold_reset) {
|
||||
gif_err("%s: GNSS_IOCTL_RESET\n", iod->name);
|
||||
gc->ops.gnss_hold_reset(gc);
|
||||
skb_queue_purge(&iod->sk_rx_q);
|
||||
return 0;
|
||||
}
|
||||
gif_err("%s: !gc->ops.gnss_reset\n", iod->name);
|
||||
return -EINVAL;
|
||||
|
||||
case GNSS_IOCTL_REQ_FAULT_INFO:
|
||||
if (gc->ops.gnss_req_fault_info) {
|
||||
gif_err("%s: GNSS_IOCTL_REQ_FAULT_INFO\n", iod->name);
|
||||
size = gc->ops.gnss_req_fault_info(gc, &fault_info_regs);
|
||||
|
||||
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 = copy_to_user((void __user *)arg,
|
||||
(void *)fault_info_regs, size);
|
||||
kfree(fault_info_regs);
|
||||
if (err) {
|
||||
gif_err("copy_to_user fail(to copy fault info)\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
gif_err("%s: !gc->ops.req_fault_info\n", iod->name);
|
||||
return -EFAULT;
|
||||
}
|
||||
return size;
|
||||
|
||||
case GNSS_IOCTL_REQ_BCMD:
|
||||
if (ld->req_bcmd) {
|
||||
gif_debug("%s: GNSS_IOCTL_REQ_BCMD\n", iod->name);
|
||||
return send_bcmd(iod, arg);
|
||||
}
|
||||
return 0;
|
||||
|
||||
case GNSS_IOCTL_LOAD_FIRMWARE:
|
||||
gif_debug("%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) {
|
||||
return gc->ops.change_sensor_gpio(gc);
|
||||
}
|
||||
return -EFAULT;
|
||||
|
||||
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:
|
||||
if (gc->ops.set_sensor_power) {
|
||||
gif_err("%s: GNSS_IOCTL_SENSOR_POWER\n", iod->name);
|
||||
return gc->ops.set_sensor_power(gc, arg);
|
||||
}
|
||||
return -EFAULT;
|
||||
|
||||
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_debug("%s: GNSS_IOCTL_LOAD_FIRMWARE (32-bit)\n", iod->name);
|
||||
return parsing_load_firmware32(iod, realarg);
|
||||
case GNSS_IOCTL_READ_FIRMWARE:
|
||||
gif_debug("%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_debug("%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 gnss state from gnss control device */
|
||||
iod->gnss_state_changed = io_dev_gnss_state_changed;
|
||||
|
||||
/* Get data from link device */
|
||||
gif_debug("%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_debug("%s: ERR! misc_register failed\n", iod->name);
|
||||
|
||||
return ret;
|
||||
}
|
486
drivers/misc/gnss_if/gnss_keplerctl_device.c
Normal file
486
drivers/misc/gnss_if/gnss_keplerctl_device.c
Normal file
|
@ -0,0 +1,486 @@
|
|||
/*
|
||||
* 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 <linux/init.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/clk-private.h>
|
||||
#include <linux/mcu_ipc.h>
|
||||
|
||||
#include <asm/cacheflush.h>
|
||||
|
||||
#include "gnss_prj.h"
|
||||
#include "gnss_link_device_shmem.h"
|
||||
#include "pmu-gnss.h"
|
||||
|
||||
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);
|
||||
|
||||
gc->iod->gnss_state_changed(gc->iod, STATE_FAULT);
|
||||
wake_up(&iod->wq);
|
||||
|
||||
gc->pmu_ops.clear_int(gc, 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);
|
||||
|
||||
gc->iod->gnss_state_changed(gc->iod, STATE_FAULT);
|
||||
wake_up(&iod->wq);
|
||||
|
||||
gc->pmu_ops.clear_int(gc, 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_debug("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->reg[GNSS_REG_WAKE_LOCK]) == 0X1) {
|
||||
gif_err("@@ reg_wake_lock is already 0x1!!!!!!\n");
|
||||
return IRQ_HANDLED;
|
||||
} else {
|
||||
gnss_write_reg(shmd->reg[GNSS_REG_WAKE_LOCK], 0x1);
|
||||
}
|
||||
|
||||
/* 4. Send interrupt MBOX1[3]. */
|
||||
/* Interrupt MBOX1[3] is RSP_WAKE_LOCK_SET */
|
||||
mbox_set_interrupt(MCU_GNSS, mbx->int_ap2gnss_ack_wake_set);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
#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_debug("[GNSS] WAKE interrupt(Mbox15) occurred\n");
|
||||
mbox_set_interrupt(MCU_GNSS, mbx->int_ap2gnss_ack_wake_set);
|
||||
gc->pmu_ops.clear_int(gc, 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);
|
||||
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(gc, GNSS_INT_WAKEUP_CLEAR);
|
||||
|
||||
gif_debug("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(gc->gnss_data->reg[GNSS_REG_WAKE_LOCK]) == 0X0) {
|
||||
gif_err("@@ reg_wake_lock is already 0x0!!!!!!\n");
|
||||
return ;
|
||||
}
|
||||
gnss_write_reg(gc->gnss_data->reg[GNSS_REG_WAKE_LOCK], 0x0);
|
||||
mbox_set_interrupt(MCU_GNSS, 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 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;
|
||||
}
|
||||
|
||||
gc->iod->gnss_state_changed(gc->iod, 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(gc);
|
||||
mbox_sw_reset(MCU_GNSS);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kepler_release_reset(struct gnss_ctl *gc)
|
||||
{
|
||||
int ret;
|
||||
gif_err("%s\n", __func__);
|
||||
|
||||
gc->iod->gnss_state_changed(gc->iod, STATE_ONLINE);
|
||||
gc->pmu_ops.release_reset(gc);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kepler_power_on(struct gnss_ctl *gc)
|
||||
{
|
||||
int ret;
|
||||
gif_err("%s\n", __func__);
|
||||
|
||||
gc->iod->gnss_state_changed(gc->iod, STATE_ONLINE);
|
||||
gc->pmu_ops.power_on(gc, 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);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kepler_req_fault_info(struct gnss_ctl *gc, u32 **fault_info_regs)
|
||||
{
|
||||
int ret;
|
||||
struct gnss_data *pdata;
|
||||
struct gnss_mbox *mbx;
|
||||
unsigned long timeout = msecs_to_jiffies(1000);
|
||||
u32 size = 0;
|
||||
|
||||
if (!fault_info_regs) {
|
||||
gif_err("Cannot access fault_info_regs!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!gc) {
|
||||
gif_err("No gnss_ctl info!\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
pdata = gc->gnss_data;
|
||||
mbx = pdata->mbx;
|
||||
|
||||
mbox_set_interrupt(MCU_GNSS, 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");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
switch (pdata->fault_info.device) {
|
||||
case GNSS_IPC_MBOX:
|
||||
size = pdata->fault_info.size * sizeof(u32);
|
||||
if (size == 0) {
|
||||
gif_err("No fault info to read.\n");
|
||||
}
|
||||
else {
|
||||
*fault_info_regs = kmalloc(size, GFP_KERNEL);
|
||||
if (*fault_info_regs) {
|
||||
int i;
|
||||
for (i = 0; i < pdata->fault_info.size; i++) {
|
||||
(*fault_info_regs)[i] = mbox_get_value(MCU_GNSS,
|
||||
pdata->fault_info.value.index + i);
|
||||
}
|
||||
}
|
||||
else {
|
||||
gif_err("Could not allocate fault info\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case GNSS_IPC_SHMEM:
|
||||
size = mbox_get_value(MCU_GNSS, mbx->reg_bcmd_ctrl[CTRL3]);
|
||||
if (size > pdata->fault_info.size) {
|
||||
gif_err("Requested %d bytes when a max of %d bytes is allowed.\n",
|
||||
size, pdata->fault_info.size);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (size == 0) {
|
||||
gif_err("No fault info to read.\n");
|
||||
}
|
||||
else {
|
||||
(*fault_info_regs) = kmalloc(size, GFP_KERNEL);
|
||||
if (*fault_info_regs) {
|
||||
memcpy((*fault_info_regs), pdata->fault_info.value.addr,
|
||||
size);
|
||||
}
|
||||
else {
|
||||
gif_err("Could not allocate fault info\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
gif_err("Don't know where to dump fault info.\n");
|
||||
}
|
||||
wake_unlock(&gc->gc_fault_wake_lock);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
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(gc, 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, unsigned long arg)
|
||||
{
|
||||
int ret;
|
||||
int reg_en = *((enum sensor_power*)arg);
|
||||
|
||||
if (reg_en == 0) {
|
||||
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 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;
|
||||
}
|
||||
|
||||
static void gnss_get_pmu_ops(struct gnss_ctl *gc)
|
||||
{
|
||||
gc->pmu_ops.hold_reset = gnss_pmu_hold_reset;
|
||||
gc->pmu_ops.release_reset = gnss_pmu_release_reset;
|
||||
gc->pmu_ops.power_on = gnss_pmu_power_on;
|
||||
gc->pmu_ops.clear_int = gnss_pmu_clear_interrupt;
|
||||
gc->pmu_ops.init_conf = gnss_pmu_init_conf;
|
||||
gc->pmu_ops.change_tcxo_mode = gnss_change_tcxo_mode;
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
pdev = to_platform_device(gc->dev);
|
||||
|
||||
/* GNSS_ACTIVE */
|
||||
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(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 */
|
||||
gc->wake_lock_irq = platform_get_irq(pdev, 2);
|
||||
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(MCU_GNSS, 15, mbox_kepler_simple_lock, (void *)gc);
|
||||
|
||||
#endif
|
||||
|
||||
/* 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(MCU_GNSS, mbox->irq_gnss2ap_req_wake_clr,
|
||||
mbox_kepler_wake_clr, (void *)gc);
|
||||
|
||||
mbox_request_irq(MCU_GNSS, mbox->irq_gnss2ap_rsp_fault_info,
|
||||
mbox_kepler_rsp_fault_info, (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;
|
||||
}
|
||||
|
415
drivers/misc/gnss_if/gnss_link_device_memory.c
Normal file
415
drivers/misc/gnss_if/gnss_link_device_memory.c
Normal file
|
@ -0,0 +1,415 @@
|
|||
/*
|
||||
* 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 <linux/irq.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/wakelock.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/if_arp.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/kallsyms.h>
|
||||
#include <linux/suspend.h>
|
||||
|
||||
#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! <called by %pf> 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! <called by %pf> 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! <called by %pf> 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! <called by %pf> 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! <called by %pf> 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! <called by %pf> 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;
|
||||
}
|
232
drivers/misc/gnss_if/gnss_link_device_memory.h
Normal file
232
drivers/misc/gnss_if/gnss_link_device_memory.h
Normal file
|
@ -0,0 +1,232 @@
|
|||
/*
|
||||
* 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 <linux/spinlock.h>
|
||||
#include <linux/wakelock.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/notifier.h>
|
||||
#if defined(CONFIG_HAS_EARLYSUSPEND)
|
||||
#include <linux/earlysuspend.h>
|
||||
#elif defined(CONFIG_FB)
|
||||
#include <linux/fb.h>
|
||||
#endif
|
||||
|
||||
#include "gnss_prj.h"
|
||||
|
||||
/* special interrupt cmd indicating gnss boot failure. */
|
||||
#define INT_POWERSAFE_FAIL 0xDEAD
|
||||
|
||||
#define DUMP_TIMEOUT (30 * HZ)
|
||||
#define DUMP_START_TIMEOUT (100 * HZ)
|
||||
#define DUMP_WAIT_TIMEOUT (HZ >> 10) /* 1/1024 second */
|
||||
|
||||
#define REQ_BCMD_TIMEOUT 200 /* 10 ms */
|
||||
#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
|
946
drivers/misc/gnss_if/gnss_link_device_shmem.c
Normal file
946
drivers/misc/gnss_if/gnss_link_device_shmem.c
Normal file
|
@ -0,0 +1,946 @@
|
|||
/*
|
||||
* 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 <linux/irq.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/wakelock.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/if_arp.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/kallsyms.h>
|
||||
#include <linux/suspend.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/smc.h>
|
||||
|
||||
#include <linux/skbuff.h>
|
||||
#ifdef CONFIG_OF_RESERVED_MEM
|
||||
#include <linux/of_reserved_mem.h>
|
||||
#endif
|
||||
|
||||
#include "include/gnss.h"
|
||||
#include "gnss_link_device_shmem.h"
|
||||
#include "../mcu_ipc/mcu_ipc.h"
|
||||
|
||||
#include "gnss_prj.h"
|
||||
|
||||
struct shmem_conf shmem_conf;
|
||||
|
||||
void gnss_write_reg(struct gnss_shared_reg *gnss_reg, u32 value)
|
||||
{
|
||||
if (gnss_reg) {
|
||||
switch(gnss_reg->device) {
|
||||
case GNSS_IPC_MBOX:
|
||||
mbox_set_value(MCU_GNSS, 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 gnss_shared_reg *gnss_reg)
|
||||
{
|
||||
u32 ret = 0;
|
||||
|
||||
if (gnss_reg) {
|
||||
switch(gnss_reg->device) {
|
||||
case GNSS_IPC_MBOX:
|
||||
ret = mbox_get_value(MCU_GNSS, 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(MCU_GNSS, shmd->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->reg[GNSS_REG_TX_IPC_MSG], mask);
|
||||
mbox_set_interrupt(MCU_GNSS, shmd->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);
|
||||
}
|
||||
|
||||
static void shmem_irq_bcmd_handler(void *data)
|
||||
{
|
||||
struct shmem_link_device *shmd = (struct shmem_link_device *)data;
|
||||
struct link_device *ld = (struct link_device *)&shmd->ld;
|
||||
u16 intr;
|
||||
|
||||
#ifndef USE_SIMPLE_WAKE_LOCK
|
||||
if (wake_lock_active(&shmd->wlock))
|
||||
wake_unlock(&shmd->wlock);
|
||||
#endif
|
||||
|
||||
intr = mbox_get_value(MCU_GNSS, shmd->irq_gnss2ap_bcmd);
|
||||
|
||||
/* Signal kepler_req_bcmd */
|
||||
complete(&ld->bcmd_cmpl);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/* Print send data to GNSS */
|
||||
/* gnss_log_ipc_pkt(skb, TX); */
|
||||
|
||||
/* 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, 1);
|
||||
|
||||
/* Take the skb back to the skb_txq */
|
||||
skb_queue_head(txq, skb);
|
||||
|
||||
gif_err("%s: <by %pf> 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;
|
||||
ld = container_of(ws, struct link_device, fmt_tx_dwork.work);
|
||||
|
||||
queue_delayed_work(ld->tx_wq, ld->tx_dwork, 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_data *gnss;
|
||||
u32 tx_size, rx_size, sh_reg_size;
|
||||
u8 *tmap;
|
||||
u32 *reg_base;
|
||||
int i;
|
||||
|
||||
tmap = (u8 *)shmd->base;
|
||||
|
||||
gnss = shmd->ld.mdm_data;
|
||||
|
||||
shmd->ipc_reg_cnt = gnss->ipc_reg_cnt;
|
||||
shmd->reg = gnss->reg;
|
||||
|
||||
/* FMT */
|
||||
dev = &shmd->ipc_map.dev;
|
||||
|
||||
sh_reg_size = shmd->ipc_reg_cnt * sizeof(u32);
|
||||
rx_size = shmd->size / 2;
|
||||
tx_size = shmd->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->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++) {
|
||||
if (shmd->reg[i]) {
|
||||
if (shmd->reg[i]->device == GNSS_IPC_SHMEM) {
|
||||
shmd->reg[i]->value.addr = reg_base + shmd->reg[i]->value.index;
|
||||
gif_err("Reg %s -> %p\n", shmd->reg[i]->name, shmd->reg[i]->value.addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int shmem_init_ipc_map(struct shmem_link_device *shmd)
|
||||
{
|
||||
struct gnss_data *gnss = shmd->ld.mdm_data;
|
||||
int i;
|
||||
|
||||
shmem_remap_ipc_region(shmd);
|
||||
|
||||
memset(shmd->base, 0, shmd->size);
|
||||
|
||||
shmd->dev = &shmd->ipc_map.dev;
|
||||
|
||||
|
||||
/* Retrieve SHMEM MBOX#, IRQ#, etc. */
|
||||
shmd->int_ap2gnss_bcmd = gnss->mbx->int_ap2gnss_bcmd;
|
||||
shmd->int_ap2gnss_ipc_msg = gnss->mbx->int_ap2gnss_ipc_msg;
|
||||
|
||||
shmd->irq_gnss2ap_bcmd = gnss->mbx->irq_gnss2ap_bcmd;
|
||||
shmd->irq_gnss2ap_ipc_msg = gnss->mbx->irq_gnss2ap_ipc_msg;
|
||||
|
||||
for (i = 0; i < BCMD_CTRL_COUNT; i++) {
|
||||
shmd->reg_bcmd_ctrl[i] = gnss->mbx->reg_bcmd_ctrl[i];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void __iomem *gnss_shm_request_region(unsigned int sh_addr,
|
||||
unsigned int size)
|
||||
{
|
||||
int i;
|
||||
struct page **pages;
|
||||
void *pv;
|
||||
|
||||
pages = kmalloc((size >> PAGE_SHIFT) * sizeof(*pages), GFP_KERNEL);
|
||||
if (!pages)
|
||||
return NULL;
|
||||
|
||||
for (i = 0; i < (size >> PAGE_SHIFT); i++) {
|
||||
pages[i] = phys_to_page(sh_addr);
|
||||
sh_addr += PAGE_SIZE;
|
||||
}
|
||||
|
||||
pv = vmap(pages, size >> PAGE_SHIFT, VM_MAP,
|
||||
pgprot_writecombine(PAGE_KERNEL));
|
||||
|
||||
kfree(pages);
|
||||
return (void __iomem *)pv;
|
||||
}
|
||||
|
||||
void gnss_release_sh_region(void *rgn)
|
||||
{
|
||||
vunmap(rgn);
|
||||
}
|
||||
|
||||
int kepler_req_bcmd(struct link_device *ld, u16 cmd_id, u16 flags,
|
||||
u32 param1, u32 param2)
|
||||
{
|
||||
struct shmem_link_device *shmd = to_shmem_link_device(ld);
|
||||
u32 ctrl[BCMD_CTRL_COUNT], ret_val;
|
||||
unsigned long timeout = msecs_to_jiffies(REQ_BCMD_TIMEOUT);
|
||||
int ret;
|
||||
|
||||
#ifndef USE_SIMPLE_WAKE_LOCK
|
||||
wake_lock_timeout(&shmd->wlock, 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_debug("%s : set param 0 : 0x%x, 1 : 0x%x, 2 : 0x%x\n",
|
||||
__func__, ctrl[CTRL0], ctrl[CTRL1], ctrl[CTRL2]);
|
||||
mbox_set_value(MCU_GNSS, shmd->reg_bcmd_ctrl[CTRL0], ctrl[CTRL0]);
|
||||
mbox_set_value(MCU_GNSS, shmd->reg_bcmd_ctrl[CTRL1], ctrl[CTRL1]);
|
||||
mbox_set_value(MCU_GNSS, shmd->reg_bcmd_ctrl[CTRL2], ctrl[CTRL2]);
|
||||
/*
|
||||
* 0xff is MAGIC number to avoid confuging that
|
||||
* register is set from Kepler.
|
||||
*/
|
||||
mbox_set_value(MCU_GNSS, shmd->reg_bcmd_ctrl[CTRL3], 0xff);
|
||||
|
||||
mbox_set_interrupt(MCU_GNSS, shmd->int_ap2gnss_bcmd);
|
||||
|
||||
if (ld->gc->gnss_state == STATE_OFFLINE) {
|
||||
gif_debug("Set POWER ON!!!!\n");
|
||||
ld->gc->ops.gnss_power_on(ld->gc);
|
||||
} else if (ld->gc->gnss_state == STATE_HOLD_RESET) {
|
||||
purge_txq(ld);
|
||||
purge_rxq(ld);
|
||||
clear_shmem_map(shmd);
|
||||
gif_debug("Set RELEASE RESET!!!!\n");
|
||||
ld->gc->ops.gnss_release_reset(ld->gc);
|
||||
}
|
||||
|
||||
if (cmd_id == 0x4) /* BLC_Branch does not have return value */
|
||||
return 0;
|
||||
|
||||
ret = wait_for_completion_interruptible_timeout(&ld->bcmd_cmpl,
|
||||
timeout);
|
||||
if (ret == 0) {
|
||||
#ifndef USE_SIMPLE_WAKE_LOCK
|
||||
wake_unlock(&shmd->wlock);
|
||||
#endif
|
||||
gif_err("%s: bcmd TIMEOUT!\n", ld->name);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
ret_val = mbox_get_value(MCU_GNSS, shmd->reg_bcmd_ctrl[CTRL3]);
|
||||
gif_debug("BCMD cmd_id 0x%x returned 0x%x\n", cmd_id, ret_val);
|
||||
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF_RESERVED_MEM
|
||||
static int __init gnss_if_reserved_mem_setup(struct reserved_mem *remem)
|
||||
{
|
||||
pr_debug("%s: memory reserved: paddr=%#lx, t_size=%zd\n",
|
||||
__func__, (unsigned long)remem->base, (size_t)remem->size);
|
||||
|
||||
shmem_conf.shmem_base = remem->base;
|
||||
shmem_conf.shmem_size = remem->size;
|
||||
|
||||
return 0;
|
||||
}
|
||||
RESERVEDMEM_OF_DECLARE(gnss_if, "exynos,gnss_if", gnss_if_reserved_mem_setup);
|
||||
#endif
|
||||
|
||||
struct link_device *gnss_shmem_create_link_device(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_debug("+++\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;
|
||||
|
||||
/* Retrieve gnss data and SHMEM control data from the gnss data */
|
||||
ld->mdm_data = gnss;
|
||||
ld->timeout_cnt = 0;
|
||||
ld->name = "GNSS_SHDMEM";
|
||||
|
||||
/* Set attributes as a link device */
|
||||
ld->send = shmem_send;
|
||||
ld->req_bcmd = kepler_req_bcmd;
|
||||
|
||||
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 */
|
||||
gnss->gnss_base = gnss_shm_request_region(gnss->shmem_base,
|
||||
gnss->ipcmem_offset);
|
||||
if (!gnss->gnss_base) {
|
||||
gif_err("%s: ERR! gnss_reserved_region fail\n", ld->name);
|
||||
goto error;
|
||||
}
|
||||
gif_err("%s: gnss phys_addr:0x%08X virt_addr:0x%p size: %d\n", ld->name,
|
||||
gnss->shmem_base, gnss->gnss_base, gnss->ipcmem_offset);
|
||||
|
||||
/* Create fault info area */
|
||||
if (gnss->fault_info.device == GNSS_IPC_SHMEM) {
|
||||
gnss->fault_info.value.addr = gnss_shm_request_region(
|
||||
gnss->shmem_base + gnss->fault_info.value.index,
|
||||
gnss->fault_info.size);
|
||||
gif_err("%s: fault phys_addr:0x%08X virt_addr:0x%p size:%d\n",
|
||||
ld->name, gnss->shmem_base + gnss->fault_info.value.index,
|
||||
gnss->fault_info.value.addr, gnss->fault_info.size);
|
||||
}
|
||||
|
||||
shmd->start = gnss->shmem_base + gnss->ipcmem_offset;
|
||||
shmd->size = gnss->ipc_size;
|
||||
shmd->base = gnss_shm_request_region(shmd->start, shmd->size);
|
||||
if (!shmd->base) {
|
||||
gif_err("%s: ERR! gnss_shm_request_region fail\n", ld->name);
|
||||
goto error;
|
||||
}
|
||||
gif_err("%s: phys_addr:0x%08X virt_addr:0x%8p size:%d\n",
|
||||
ld->name, shmd->start, shmd->base, shmd->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
|
||||
|
||||
init_completion(&ld->bcmd_cmpl);
|
||||
|
||||
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(MCU_GNSS, shmd->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;
|
||||
}
|
||||
|
||||
err = mbox_request_irq(MCU_GNSS, shmd->irq_gnss2ap_bcmd,
|
||||
shmem_irq_bcmd_handler, shmd);
|
||||
if (err) {
|
||||
gif_err("%s: ERR! mbox_request_irq fail (err %d)\n",
|
||||
ld->name, err);
|
||||
goto error;
|
||||
}
|
||||
|
||||
gif_debug("---\n");
|
||||
return ld;
|
||||
|
||||
error:
|
||||
gif_err("xxx\n");
|
||||
devm_kfree(dev, shmd);
|
||||
return NULL;
|
||||
}
|
499
drivers/misc/gnss_if/gnss_link_device_shmem.h
Normal file
499
drivers/misc/gnss_if/gnss_link_device_shmem.h
Normal file
|
@ -0,0 +1,499 @@
|
|||
/*
|
||||
* 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 <linux/mcu_ipc.h>
|
||||
#include "gnss_link_device_memory.h"
|
||||
|
||||
/* for checking gnss infomation */
|
||||
#define SHM_2M_FMT_TX_BUFF_SZ (1024 * 1024)
|
||||
#define SHM_2M_FMT_RX_BUFF_SZ (1024 * 1024)
|
||||
|
||||
#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 {
|
||||
u32 __iomem *magic;
|
||||
u32 __iomem *access;
|
||||
|
||||
struct shmem_ipc_device dev;
|
||||
};
|
||||
|
||||
struct shmem_link_device {
|
||||
struct link_device ld;
|
||||
|
||||
struct gnss_mbox *mbx;
|
||||
struct gnss_shared_reg **reg;
|
||||
|
||||
/* SHMEM (SHARED MEMORY) address, size, IRQ# */
|
||||
u32 start; /* physical "start" address of SHMEM */
|
||||
u32 size; /* size of SHMEM */
|
||||
u32 __iomem *base; /* virtual address to the "IPC" region */
|
||||
u32 ipc_reg_cnt;
|
||||
|
||||
/* IPC device map */
|
||||
struct shmem_ipc_map ipc_map;
|
||||
|
||||
/* Pointers (aliases) to IPC device map */
|
||||
u32 __iomem *magic;
|
||||
u32 __iomem *access;
|
||||
struct shmem_ipc_device *dev;
|
||||
|
||||
/* MBOX number & IRQ */
|
||||
int int_ap2gnss_bcmd;
|
||||
int int_ap2gnss_ipc_msg;
|
||||
|
||||
int irq_gnss2ap_bcmd;
|
||||
int irq_gnss2ap_ipc_msg;
|
||||
|
||||
unsigned reg_bcmd_ctrl[BCMD_CTRL_COUNT];
|
||||
|
||||
/* 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 gnss_shared_reg *gnss_reg, u32 value);
|
||||
u32 gnss_read_reg(struct gnss_shared_reg *gnss_reg);
|
||||
|
||||
/**
|
||||
* 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->reg[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->reg[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->reg[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->reg[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->reg[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->reg[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->reg[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->reg[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;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_txq_saved
|
||||
* @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 TXQ, size of the buffer, in & out
|
||||
* pointer values, size of stored data} into the 'circ' instance.
|
||||
*
|
||||
* Returns an error code.
|
||||
*/
|
||||
static inline int get_txq_saved(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 saved;
|
||||
|
||||
while (1) {
|
||||
qsize = get_txq_buff_size(shmd);
|
||||
head = get_txq_head(shmd);
|
||||
tail = get_txq_tail(shmd);
|
||||
saved = circ_get_usage(qsize, head, tail);
|
||||
|
||||
gif_debug("%s: %s_TXQ{qsize:%u in:%u out:%u saved:%u}\n",
|
||||
ld->name, "FMT", qsize, head, tail, saved);
|
||||
|
||||
if (circ_valid(qsize, head, tail))
|
||||
break;
|
||||
|
||||
cnt++;
|
||||
gif_err("%s: ERR! invalid %s_TXQ{qsize:%d in:%d out:%d saved:%d}, count %d\n",
|
||||
ld->name, "FMT", qsize, head, tail,
|
||||
saved, cnt);
|
||||
if (cnt >= MAX_RETRY_CNT) {
|
||||
saved = -EIO;
|
||||
break;
|
||||
}
|
||||
|
||||
udelay(100);
|
||||
}
|
||||
|
||||
circ->buff = get_txq_buff(shmd);
|
||||
circ->qsize = qsize;
|
||||
circ->in = head;
|
||||
circ->out = tail;
|
||||
circ->size = saved;
|
||||
|
||||
return saved;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
memset(shmd->base, 0x0, shmd->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
|
||||
|
502
drivers/misc/gnss_if/gnss_main.c
Normal file
502
drivers/misc/gnss_if/gnss_main.c
Normal file
|
@ -0,0 +1,502 @@
|
|||
/* 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 <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/if_arp.h>
|
||||
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/wakelock.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/clk-private.h>
|
||||
#ifdef CONFIG_OF
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_platform.h>
|
||||
#endif
|
||||
|
||||
#include "gnss_prj.h"
|
||||
|
||||
extern struct shmem_conf shmem_conf;
|
||||
|
||||
static struct gnss_ctl *create_gnssctl_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;
|
||||
}
|
||||
|
||||
#ifdef USE_IOREMAP_NOPMU
|
||||
gnssctl->pmu_reg = devm_ioremap(dev, PMU_ADDR, PMU_SIZE);
|
||||
if (gnssctl->pmu_reg == NULL) {
|
||||
gif_err("%s: pmu ioremap failed.\n", pdata->name);
|
||||
return NULL;
|
||||
} else
|
||||
gif_err("pmu_reg : 0x%p\n", gnssctl->pmu_reg);
|
||||
#endif
|
||||
|
||||
/* 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
|
||||
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);
|
||||
|
||||
/* Shared Memory Configuration from reserved_mem */
|
||||
pdata->shmem_base = shmem_conf.shmem_base;
|
||||
pdata->shmem_size = shmem_conf.shmem_size;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
static int parse_single_dt_reg(struct device *dev, const char *propname,
|
||||
struct gnss_shared_reg **reg)
|
||||
{
|
||||
struct device_node *np = dev->of_node;
|
||||
u32 val[2];
|
||||
if (!of_property_read_u32_array(np, propname, val, 2)) {
|
||||
if (!alloc_gnss_reg(dev, reg, propname, val[0], val[1]))
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_dt_reg_mbox_pdata(struct device *dev, struct gnss_data *pdata)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (parse_single_dt_reg(dev, "reg_rx_ipc_msg",
|
||||
&pdata->reg[GNSS_REG_RX_IPC_MSG]) != 0) {
|
||||
goto parse_dt_reg_nomem;
|
||||
}
|
||||
|
||||
if (parse_single_dt_reg(dev, "reg_tx_ipc_msg",
|
||||
&pdata->reg[GNSS_REG_TX_IPC_MSG]) != 0) {
|
||||
goto parse_dt_reg_nomem;
|
||||
}
|
||||
|
||||
if (parse_single_dt_reg(dev, "reg_wake_lock",
|
||||
&pdata->reg[GNSS_REG_WAKE_LOCK]) != 0) {
|
||||
goto parse_dt_reg_nomem;
|
||||
}
|
||||
|
||||
if (parse_single_dt_reg(dev, "reg_rx_head",
|
||||
&pdata->reg[GNSS_REG_RX_HEAD]) != 0) {
|
||||
goto parse_dt_reg_nomem;
|
||||
}
|
||||
|
||||
if (parse_single_dt_reg(dev, "reg_rx_tail",
|
||||
&pdata->reg[GNSS_REG_RX_TAIL]) != 0) {
|
||||
goto parse_dt_reg_nomem;
|
||||
}
|
||||
|
||||
if (parse_single_dt_reg(dev, "reg_tx_head",
|
||||
&pdata->reg[GNSS_REG_TX_HEAD]) != 0) {
|
||||
goto parse_dt_reg_nomem;
|
||||
}
|
||||
|
||||
if (parse_single_dt_reg(dev, "reg_tx_tail",
|
||||
&pdata->reg[GNSS_REG_TX_TAIL]) != 0) {
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
dev->platform_data = pdata;
|
||||
gif_info("DT parse complete!\n");
|
||||
return pdata;
|
||||
|
||||
parse_dt_pdata_err:
|
||||
if (pdata)
|
||||
devm_kfree(dev, pdata);
|
||||
|
||||
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 *gnssctl;
|
||||
struct io_device *iod;
|
||||
struct link_device *ld;
|
||||
unsigned size;
|
||||
|
||||
gif_err("%s: +++\n", pdev->name);
|
||||
|
||||
if (dev->of_node) {
|
||||
pdata = gnss_if_parse_dt_pdata(dev);
|
||||
if (IS_ERR(pdata)) {
|
||||
gif_err("GIF 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) {
|
||||
if (pdata->iodev)
|
||||
devm_kfree(dev, pdata->iodev);
|
||||
gif_err("iodev: failed to alloc memory\n");
|
||||
return PTR_ERR(pdata);
|
||||
}
|
||||
|
||||
gnssctl = create_gnssctl_device(pdev);
|
||||
if (!gnssctl) {
|
||||
gif_err("%s: gnssctl == NULL\n", pdata->name);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* 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 link device */
|
||||
ld = gnss_shmem_create_link_device(pdev);
|
||||
if (!ld)
|
||||
goto free_gc;
|
||||
|
||||
ld->gc = gnssctl;
|
||||
|
||||
gif_err("%s: %s link created\n", pdata->name, ld->name);
|
||||
|
||||
/* create io device and connect to gnssctl device */
|
||||
size = sizeof(struct io_device *);
|
||||
iod = (struct io_device *)devm_kzalloc(dev, size, GFP_KERNEL);
|
||||
|
||||
iod = create_io_device(pdev, pdata->iodev, ld, gnssctl, pdata);
|
||||
if (!iod) {
|
||||
gif_err("%s: iod == NULL\n", pdata->name);
|
||||
goto free_iod;
|
||||
}
|
||||
|
||||
/* attach device */
|
||||
gif_debug("set %s->%s\n", iod->name, ld->name);
|
||||
set_current_link(iod, iod->ld);
|
||||
|
||||
platform_set_drvdata(pdev, gnssctl);
|
||||
|
||||
gif_err("%s: ---\n", pdata->name);
|
||||
|
||||
return 0;
|
||||
|
||||
free_iod:
|
||||
devm_kfree(dev, iod);
|
||||
|
||||
free_gc:
|
||||
devm_kfree(dev, gnssctl);
|
||||
|
||||
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");
|
440
drivers/misc/gnss_if/gnss_prj.h
Normal file
440
drivers/misc/gnss_if/gnss_prj.h
Normal file
|
@ -0,0 +1,440 @@
|
|||
/*
|
||||
* 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 <linux/wait.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/wakelock.h>
|
||||
#include <linux/rbtree.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/cdev.h>
|
||||
#include <linux/types.h>
|
||||
#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,
|
||||
};
|
||||
|
||||
#ifndef 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
|
||||
|
||||
#define USE_SIMPLE_WAKE_LOCK
|
||||
|
||||
#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)
|
||||
#endif
|
||||
#endif /* USE_IOREMAP_NOPMU */
|
||||
|
||||
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 PSD_DATA_CHID_BEGIN 0x2A
|
||||
#define PSD_DATA_CHID_END 0x38
|
||||
|
||||
#define PS_DATA_CH_LAST 24
|
||||
|
||||
#define IP6VERSION 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
|
||||
|
||||
#define NO_WAKEUP_LOCK
|
||||
|
||||
/* 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 meminfo {
|
||||
unsigned int base_addr;
|
||||
unsigned int size;
|
||||
};
|
||||
|
||||
|
||||
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);
|
||||
|
||||
/* inform the IO device that the gnss is now online or offline or
|
||||
* crashing or whatever...
|
||||
*/
|
||||
void (*gnss_state_changed)(struct io_device *iod, enum gnss_state);
|
||||
|
||||
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 KEP_IOCTL_BCMD
|
||||
{
|
||||
u16 bcmd_id;
|
||||
u16 flags;
|
||||
u32 param1;
|
||||
u32 param2;
|
||||
};
|
||||
|
||||
struct link_device {
|
||||
struct list_head list;
|
||||
char *name;
|
||||
|
||||
/* Modem data */
|
||||
struct gnss_data *mdm_data;
|
||||
|
||||
/* Modem control */
|
||||
struct gnss_ctl *gc;
|
||||
|
||||
/* link to io device */
|
||||
struct io_device *iod;
|
||||
|
||||
/* completion for bcmd messages */
|
||||
struct completion bcmd_cmpl;
|
||||
|
||||
/* completion for waiting for link initialization */
|
||||
struct completion init_cmpl;
|
||||
|
||||
struct io_device *fmt_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 for GNSS BCMD Request */
|
||||
int (*req_bcmd)(struct link_device *ld, u16 cmd_id, u16 flags, \
|
||||
u32 param1, u32 param2);
|
||||
};
|
||||
|
||||
/** 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_pmu_ops {
|
||||
int (*init_conf)(struct gnss_ctl *);
|
||||
int (*hold_reset)(struct gnss_ctl *);
|
||||
int (*release_reset)(struct gnss_ctl *);
|
||||
int (*power_on)(struct gnss_ctl *, enum gnss_mode);
|
||||
int (*clear_int)(struct gnss_ctl *, enum gnss_int_clear);
|
||||
int (*change_tcxo_mode)(struct gnss_ctl *, 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 *, u32 **);
|
||||
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 *, unsigned long);
|
||||
};
|
||||
|
||||
struct gnss_ctl {
|
||||
struct device *dev;
|
||||
char *name;
|
||||
struct gnss_data *gnss_data;
|
||||
enum gnss_state gnss_state;
|
||||
|
||||
struct clk *ccore_qch_lh_gnss;
|
||||
|
||||
#ifdef USE_IOREMAP_NOPMU
|
||||
void __iomem *pmu_reg;
|
||||
#endif
|
||||
|
||||
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;
|
||||
|
||||
int wake_lock_irq;
|
||||
struct completion fault_cmpl;
|
||||
|
||||
struct pinctrl *gnss_gpio;
|
||||
struct pinctrl_state *gnss_sensor_gpio;
|
||||
|
||||
struct regulator *vdd_sensor_reg;
|
||||
};
|
||||
|
||||
unsigned long shm_get_phys_base(void);
|
||||
unsigned long shm_get_phys_size(void);
|
||||
unsigned long shm_get_ipc_rgn_size(void);
|
||||
unsigned long shm_get_ipc_rgn_offset(void);
|
||||
|
||||
extern int exynos_init_gnss_io_device(struct io_device *iod);
|
||||
|
||||
#define STD_UDL_STEP_MASK 0x0000000F
|
||||
#define STD_UDL_SEND 0x1
|
||||
#define STD_UDL_CRC 0xC
|
||||
|
||||
struct std_dload_info {
|
||||
u32 size;
|
||||
u32 mtu;
|
||||
u32 num_frames;
|
||||
} __packed;
|
||||
|
||||
u32 std_udl_get_cmd(u8 *frm);
|
||||
bool std_udl_with_payload(u32 cmd);
|
||||
|
||||
int init_gnssctl_device(struct gnss_ctl *mc, struct gnss_data *pdata);
|
||||
struct link_device *gnss_shmem_create_link_device(struct platform_device *pdev);
|
||||
|
||||
#endif
|
128
drivers/misc/gnss_if/gnss_utils.c
Normal file
128
drivers/misc/gnss_if/gnss_utils.c
Normal file
|
@ -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 <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/ip.h>
|
||||
#include <net/ip.h>
|
||||
#include <linux/tcp.h>
|
||||
#include <linux/udp.h>
|
||||
#include <linux/rtc.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/wakelock.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
50
drivers/misc/gnss_if/gnss_utils.h
Normal file
50
drivers/misc/gnss_if/gnss_utils.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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"
|
||||
|
||||
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__*/
|
||||
|
155
drivers/misc/gnss_if/include/exynos_ipc.h
Normal file
155
drivers/misc/gnss_if/include/exynos_ipc.h
Normal file
|
@ -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 <linux/types.h>
|
||||
#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
|
215
drivers/misc/gnss_if/include/gnss.h
Normal file
215
drivers/misc/gnss_if/include/gnss.h
Normal file
|
@ -0,0 +1,215 @@
|
|||
/*
|
||||
* 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 <linux/platform_device.h>
|
||||
#include <linux/miscdevice.h>
|
||||
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
|
||||
#define STR_SHMEM_BASE "shmem_base"
|
||||
|
||||
#define SHMEM_SIZE_1MB (1 << 20) /* 1 MB */
|
||||
#define SHMEM_SIZE_2MB (2 << 20) /* 2 MB */
|
||||
#define SHMEM_SIZE_4MB (4 << 20) /* 4 MB */
|
||||
|
||||
|
||||
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];
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
struct gnss_pmu {
|
||||
int (*power)(int);
|
||||
int (*init)(void);
|
||||
int (*get_pwr_status)(void);
|
||||
int (*stop)(void);
|
||||
int (*start)(void);
|
||||
int (*clear_cp_fail)(void);
|
||||
int (*clear_cp_wdt)(void);
|
||||
};
|
||||
|
||||
/* 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
|
336
drivers/misc/gnss_if/pmu-gnss.c
Normal file
336
drivers/misc/gnss_if/pmu-gnss.c
Normal file
|
@ -0,0 +1,336 @@
|
|||
#include <linux/io.h>
|
||||
#include <linux/cpumask.h>
|
||||
#include <linux/suspend.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/bug.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/smc.h>
|
||||
#include <soc/samsung/exynos-pmu.h>
|
||||
#include "pmu-gnss.h"
|
||||
|
||||
static void __set_shdmem_size(struct gnss_ctl *gc, u32 reg_offset, u32 memsz)
|
||||
{
|
||||
u32 tmp;
|
||||
memsz = (memsz >> MEMSIZE_SHIFT);
|
||||
#ifdef USE_IOREMAP_NOPMU
|
||||
{
|
||||
u32 memcfg_val;
|
||||
memcfg_val = __raw_readl(gc->pmu_reg + reg_offset);
|
||||
memcfg_val &= ~(MEMSIZE_MASK << MEMSIZE_OFFSET);
|
||||
memcfg_val |= (memsz << MEMSIZE_OFFSET);
|
||||
__raw_writel(memcfg_val, gc->pmu_reg + reg_offset);
|
||||
tmp = __raw_readl(gc->pmu_reg + reg_offset);
|
||||
}
|
||||
#else
|
||||
exynos_pmu_update(reg_offset, MEMSIZE_MASK << MEMSIZE_OFFSET,
|
||||
memsz << MEMSIZE_OFFSET);
|
||||
exynos_pmu_read(reg_offset, &tmp);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void set_shdmem_size(struct gnss_ctl *gc, u32 memsz)
|
||||
{
|
||||
gif_err("[GNSS]Set shared mem size: %dB\n", memsz);
|
||||
|
||||
#if !defined(CONFIG_SOC_EXYNOS7870) && !defined(CONFIG_SOC_EXYNOS7880)
|
||||
__set_shdmem_size(gc, EXYNOS_PMU_GNSS2AP_MEM_CONFIG, memsz);
|
||||
__set_shdmem_size(gc, EXYNOS_PMU_GNSS2AP_MEM_CONFIG3, memsz);
|
||||
#else
|
||||
__set_shdmem_size(gc, EXYNOS_PMU_GNSS2AP_MEM_CONFIG, memsz);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void __set_shdmem_base(struct gnss_ctl *gc, u32 reg_offset, u32 shmem_base)
|
||||
{
|
||||
u32 tmp, base_addr;
|
||||
base_addr = (shmem_base >> MEMBASE_ADDR_SHIFT);
|
||||
|
||||
#ifdef USE_IOREMAP_NOPMU
|
||||
{
|
||||
u32 memcfg_val;
|
||||
gif_err("Access Reg : 0x%p\n", gc->pmu_reg + reg_offset);
|
||||
|
||||
memcfg_val = __raw_readl(gc->pmu_reg + reg_offset);
|
||||
memcfg_val &= ~(MEMBASE_ADDR_MASK << MEMBASE_ADDR_OFFSET);
|
||||
memcfg_val |= (base_addr << MEMBASE_ADDR_OFFSET);
|
||||
__raw_writel(memcfg_val, gc->pmu_reg + reg_offset);
|
||||
tmp = __raw_readl(gc->pmu_reg + reg_offset);
|
||||
}
|
||||
#else
|
||||
exynos_pmu_update(reg_offset, MEMBASE_ADDR_MASK << MEMBASE_ADDR_OFFSET,
|
||||
base_addr << MEMBASE_ADDR_OFFSET);
|
||||
exynos_pmu_read(reg_offset, &tmp);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void set_shdmem_base(struct gnss_ctl *gc, u32 shmem_base)
|
||||
{
|
||||
gif_err("[GNSS]Set shared mem baseaddr : 0x%x\n", shmem_base);
|
||||
|
||||
#if !defined(CONFIG_SOC_EXYNOS7870) && !defined(CONFIG_SOC_EXYNOS7880)
|
||||
__set_shdmem_base(gc, EXYNOS_PMU_GNSS2AP_MEM_CONFIG1, shmem_base);
|
||||
__set_shdmem_base(gc, EXYNOS_PMU_GNSS2AP_MEM_CONFIG2, shmem_base);
|
||||
#else
|
||||
__set_shdmem_base(gc, EXYNOS_PMU_GNSS2AP_MEM_CONFIG, shmem_base);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void exynos_sys_powerdown_conf_gnss(struct gnss_ctl *gc)
|
||||
{
|
||||
#ifdef USE_IOREMAP_NOPMU
|
||||
__raw_writel(0, gc->pmu_reg + EXYNOS_PMU_CENTRAL_SEQ_GNSS_CONFIGURATION);
|
||||
__raw_writel(0, gc->pmu_reg + EXYNOS_PMU_RESET_AHEAD_GNSS_SYS_PWR_REG);
|
||||
__raw_writel(0, gc->pmu_reg + EXYNOS_PMU_CLEANY_BUS_SYS_PWR_REG);
|
||||
__raw_writel(0, gc->pmu_reg + EXYNOS_PMU_LOGIC_RESET_GNSS_SYS_PWR_REG);
|
||||
__raw_writel(0, gc->pmu_reg + EXYNOS_PMU_TCXO_GATE_GNSS_SYS_PWR_REG);
|
||||
__raw_writel(0, gc->pmu_reg + EXYNOS_PMU_RESET_ASB_GNSS_SYS_PWR_REG);
|
||||
#else
|
||||
exynos_pmu_write(EXYNOS_PMU_CENTRAL_SEQ_GNSS_CONFIGURATION, 0);
|
||||
exynos_pmu_write(EXYNOS_PMU_RESET_AHEAD_GNSS_SYS_PWR_REG, 0);
|
||||
exynos_pmu_write(EXYNOS_PMU_CLEANY_BUS_SYS_PWR_REG, 0);
|
||||
exynos_pmu_write(EXYNOS_PMU_LOGIC_RESET_GNSS_SYS_PWR_REG, 0);
|
||||
exynos_pmu_write(EXYNOS_PMU_TCXO_GATE_GNSS_SYS_PWR_REG, 0);
|
||||
exynos_pmu_write(EXYNOS_PMU_RESET_ASB_GNSS_SYS_PWR_REG, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
int gnss_pmu_clear_interrupt(struct gnss_ctl *gc, enum gnss_int_clear gnss_int)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
gif_debug("%s\n", __func__);
|
||||
#ifdef USE_IOREMAP_NOPMU
|
||||
{
|
||||
u32 reg_val = 0;
|
||||
|
||||
reg_val = __raw_readl(gc->pmu_reg + EXYNOS_PMU_GNSS_CTRL_NS);
|
||||
|
||||
if (gnss_int == GNSS_INT_WAKEUP_CLEAR) {
|
||||
reg_val |= GNSS_WAKEUP_REQ_CLR;
|
||||
} else if (gnss_int == GNSS_INT_ACTIVE_CLEAR) {
|
||||
reg_val |= GNSS_ACTIVE_REQ_CLR;
|
||||
} else if (gnss_int == GNSS_INT_WDT_RESET_CLEAR) {
|
||||
reg_val |= GNSS_WAKEUP_REQ_CLR;
|
||||
} else {
|
||||
gif_err("Unexpected interrupt value!\n");
|
||||
return -EIO;
|
||||
}
|
||||
__raw_writel(reg_val, gc->pmu_reg + EXYNOS_PMU_GNSS_CTRL_NS);
|
||||
}
|
||||
#else
|
||||
if (gnss_int == GNSS_INT_WAKEUP_CLEAR) {
|
||||
ret = exynos_pmu_update(EXYNOS_PMU_GNSS_CTRL_NS,
|
||||
GNSS_WAKEUP_REQ_CLR, GNSS_WAKEUP_REQ_CLR);
|
||||
} else if (gnss_int == GNSS_INT_ACTIVE_CLEAR) {
|
||||
ret = exynos_pmu_update(EXYNOS_PMU_GNSS_CTRL_NS,
|
||||
GNSS_ACTIVE_REQ_CLR, GNSS_ACTIVE_REQ_CLR);
|
||||
} else if (gnss_int == GNSS_INT_WDT_RESET_CLEAR) {
|
||||
ret = exynos_pmu_update(EXYNOS_PMU_GNSS_CTRL_NS,
|
||||
GNSS_RESET_REQ_CLR, GNSS_RESET_REQ_CLR);
|
||||
} else {
|
||||
gif_err("Unexpected interrupt value!\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
gif_err("ERR! GNSS Reset Fail: %d\n", ret);
|
||||
return -EIO;
|
||||
}
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int gnss_pmu_release_reset(struct gnss_ctl *gc)
|
||||
{
|
||||
u32 gnss_ctrl = 0;
|
||||
int ret = 0;
|
||||
|
||||
#ifdef USE_IOREMAP_NOPMU
|
||||
gnss_ctrl = __raw_readl(gc->pmu_reg + EXYNOS_PMU_GNSS_CTRL_NS);
|
||||
{
|
||||
u32 tmp_reg_val;
|
||||
if (!(gnss_ctrl & GNSS_PWRON)) {
|
||||
gnss_ctrl |= GNSS_PWRON;
|
||||
__raw_writel(gnss_ctrl, gc->pmu_reg + EXYNOS_PMU_GNSS_CTRL_NS);
|
||||
}
|
||||
|
||||
tmp_reg_val = __raw_readl(gc->pmu_reg + EXYNOS_PMU_GNSS_CTRL_S);
|
||||
tmp_reg_val |= GNSS_START;
|
||||
__raw_writel(tmp_reg_val, gc->pmu_reg + EXYNOS_PMU_GNSS_CTRL_S);
|
||||
|
||||
gif_err("PMU_GNSS_CTRL_S : 0x%x\n",
|
||||
__raw_readl(gc->pmu_reg + EXYNOS_PMU_GNSS_CTRL_S));
|
||||
}
|
||||
#else
|
||||
exynos_pmu_read(EXYNOS_PMU_GNSS_CTRL_NS, &gnss_ctrl);
|
||||
if (!(gnss_ctrl & GNSS_PWRON)) {
|
||||
ret = exynos_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 = exynos_pmu_update(EXYNOS_PMU_GNSS_CTRL_S, GNSS_START, GNSS_START);
|
||||
if (ret < 0)
|
||||
gif_err("ERR! GNSS Release Fail: %d\n", ret);
|
||||
else {
|
||||
exynos_pmu_read(EXYNOS_PMU_GNSS_CTRL_NS, &gnss_ctrl);
|
||||
gif_info("PMU_GNSS_CTRL_S[0x%08x]\n", gnss_ctrl);
|
||||
ret = -EIO;
|
||||
}
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int gnss_pmu_hold_reset(struct gnss_ctl *gc)
|
||||
{
|
||||
int ret = 0;
|
||||
u32 __maybe_unused gnss_ctrl;
|
||||
|
||||
/* set sys_pwr_cfg registers */
|
||||
exynos_sys_powerdown_conf_gnss(gc);
|
||||
|
||||
#ifdef USE_IOREMAP_NOPMU
|
||||
{
|
||||
u32 reg_val;
|
||||
reg_val = __raw_readl(gc->pmu_reg + EXYNOS_PMU_GNSS_CTRL_NS);
|
||||
reg_val |= GNSS_RESET_SET;
|
||||
__raw_writel(reg_val, gc->pmu_reg + EXYNOS_PMU_GNSS_CTRL_NS);
|
||||
}
|
||||
#else
|
||||
ret = exynos_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);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* some delay */
|
||||
cpu_relax();
|
||||
usleep_range(80, 100);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int gnss_pmu_power_on(struct gnss_ctl *gc, enum gnss_mode mode)
|
||||
{
|
||||
u32 gnss_ctrl;
|
||||
int ret = 0;
|
||||
|
||||
gif_err("mode[%d]\n", mode);
|
||||
|
||||
#ifdef USE_IOREMAP_NOPMU
|
||||
gnss_ctrl = __raw_readl(gc->pmu_reg + EXYNOS_PMU_GNSS_CTRL_NS);
|
||||
if (mode == GNSS_POWER_ON) {
|
||||
u32 tmp_reg_val;
|
||||
if (!(gnss_ctrl & GNSS_PWRON)) {
|
||||
gnss_ctrl |= GNSS_PWRON;
|
||||
__raw_writel(gnss_ctrl, gc->pmu_reg + EXYNOS_PMU_GNSS_CTRL_NS);
|
||||
}
|
||||
|
||||
tmp_reg_val = __raw_readl(gc->pmu_reg + EXYNOS_PMU_GNSS_CTRL_S);
|
||||
tmp_reg_val |= GNSS_START;
|
||||
__raw_writel(tmp_reg_val, gc->pmu_reg + EXYNOS_PMU_GNSS_CTRL_S);
|
||||
} else {
|
||||
gif_err("Not supported!!!(%d)\n", mode);
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
exynos_pmu_read(EXYNOS_PMU_GNSS_CTRL_NS, &gnss_ctrl);
|
||||
if (mode == GNSS_POWER_ON) {
|
||||
if (!(gnss_ctrl & GNSS_PWRON)) {
|
||||
ret = exynos_pmu_update(EXYNOS_PMU_GNSS_CTRL_NS,
|
||||
GNSS_PWRON, GNSS_PWRON);
|
||||
if (ret < 0)
|
||||
gif_err("ERR! write Fail: %d\n", ret);
|
||||
}
|
||||
|
||||
ret = exynos_pmu_update(EXYNOS_PMU_GNSS_CTRL_S, GNSS_START,
|
||||
GNSS_START);
|
||||
if (ret < 0)
|
||||
gif_err("ERR! write Fail: %d\n", ret);
|
||||
} else {
|
||||
ret = exynos_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(gc);
|
||||
}
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int gnss_change_tcxo_mode(struct gnss_ctl *gc, enum gnss_tcxo_mode mode)
|
||||
{
|
||||
int ret = 0;
|
||||
#ifdef USE_IOREMAP_NOPMU
|
||||
{
|
||||
u32 regval, tmp;
|
||||
regval = __raw_readl(gc->pmu_reg + EXYNOS_PMU_GNSS_CTRL_NS);
|
||||
if (mode == TCXO_SHARED_MODE) {
|
||||
gif_err("Change TCXO mode to Shared Mode(%d)\n", mode);
|
||||
regval &= ~TCXO_26M_40M_SEL;
|
||||
__raw_writel(regval, gc->pmu_reg + EXYNOS_PMU_GNSS_CTRL_NS);
|
||||
} else if (mode == TCXO_NON_SHARED_MODE) {
|
||||
gif_err("Change TCXO mode to NON-sared Mode(%d)\n", mode);
|
||||
regval |= TCXO_26M_40M_SEL;
|
||||
__raw_writel(regval, gc->pmu_reg + EXYNOS_PMU_GNSS_CTRL_NS);
|
||||
} else
|
||||
gif_err("Unexpected modem(Mode:%d)\n", mode);
|
||||
|
||||
tmp = __raw_readl(gc->pmu_reg + EXYNOS_PMU_GNSS_CTRL_NS);
|
||||
if (tmp != regval) {
|
||||
gif_err("ERR! GNSS change tcxo: %d\n", ret);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (mode == TCXO_SHARED_MODE) {
|
||||
gif_err("Change TCXO mode to Shared Mode(%d)\n", mode);
|
||||
ret = exynos_pmu_update(EXYNOS_PMU_GNSS_CTRL_NS,
|
||||
TCXO_26M_40M_SEL, 0);
|
||||
} else if (mode == TCXO_NON_SHARED_MODE) {
|
||||
gif_err("Change TCXO mode to NON-sared Mode(%d)\n", mode);
|
||||
ret = exynos_pmu_update(EXYNOS_PMU_GNSS_CTRL_NS,
|
||||
TCXO_26M_40M_SEL, TCXO_26M_40M_SEL);
|
||||
} else
|
||||
gif_err("Unexpected modem(Mode:%d)\n", mode);
|
||||
|
||||
if (ret < 0) {
|
||||
gif_err("ERR! GNSS change tcxo: %d\n", ret);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gnss_pmu_init_conf(struct gnss_ctl *gc)
|
||||
{
|
||||
u32 shmem_size = gc->gnss_data->shmem_size;
|
||||
u32 shmem_base = gc->gnss_data->shmem_base;
|
||||
|
||||
set_shdmem_size(gc, shmem_size);
|
||||
set_shdmem_base(gc, shmem_base);
|
||||
|
||||
#ifndef USE_IOREMAP_NOPMU
|
||||
/* set access window for GNSS */
|
||||
exynos_pmu_write(EXYNOS_PMU_GNSS2AP_MIF0_PERI_ACCESS_CON, 0x0);
|
||||
exynos_pmu_write(EXYNOS_PMU_GNSS2AP_MIF1_PERI_ACCESS_CON, 0x0);
|
||||
#if !defined(CONFIG_SOC_EXYNOS7870)
|
||||
exynos_pmu_write(EXYNOS_PMU_GNSS2AP_MIF2_PERI_ACCESS_CON, 0x0);
|
||||
exynos_pmu_write(EXYNOS_PMU_GNSS2AP_MIF3_PERI_ACCESS_CON, 0x0);
|
||||
#endif
|
||||
exynos_pmu_write(EXYNOS_PMU_GNSS2AP_PERI_ACCESS_WIN, 0x0);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
110
drivers/misc/gnss_if/pmu-gnss.h
Normal file
110
drivers/misc/gnss_if/pmu-gnss.h
Normal file
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* 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__
|
||||
|
||||
#include "gnss_prj.h"
|
||||
|
||||
/* 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_CONFIG 0x0090
|
||||
#if !defined(CONFIG_SOC_EXYNOS7870) && !defined(CONFIG_SOC_EXYNOS7880)
|
||||
#define EXYNOS_PMU_GNSS2AP_MEM_CONFIG1 0x00A4
|
||||
#define EXYNOS_PMU_GNSS2AP_MEM_CONFIG2 0x00BC
|
||||
#define EXYNOS_PMU_GNSS2AP_MEM_CONFIG3 0x00C4
|
||||
#endif
|
||||
#define EXYNOS_PMU_GNSS2AP_MIF0_PERI_ACCESS_CON 0x0094
|
||||
#define EXYNOS_PMU_GNSS2AP_MIF1_PERI_ACCESS_CON 0x0098
|
||||
#if !defined(CONFIG_SOC_EXYNOS7870)
|
||||
#define EXYNOS_PMU_GNSS2AP_MIF2_PERI_ACCESS_CON 0x009C
|
||||
#define EXYNOS_PMU_GNSS2AP_MIF3_PERI_ACCESS_CON 0x00A0
|
||||
#endif
|
||||
#define EXYNOS_PMU_GNSS_BOOT_TEST_RST_CONFIG 0x00A8
|
||||
#define EXYNOS_PMU_GNSS2AP_PERI_ACCESS_WIN 0x00AC
|
||||
#define EXYNOS_PMU_GNSS_MODAPIF_CONFIG 0x00B0
|
||||
#define EXYNOS_PMU_GNSS_QOS 0x00B8
|
||||
#define EXYNOS_PMU_CENTRAL_SEQ_GNSS_CONFIGURATION 0x02C0
|
||||
#define EXYNOS_PMU_RESET_AHEAD_GNSS_SYS_PWR_REG 0x1174
|
||||
#define EXYNOS_PMU_CLEANY_BUS_SYS_PWR_REG 0x11E8
|
||||
#define EXYNOS_PMU_LOGIC_RESET_GNSS_SYS_PWR_REG 0x11EC
|
||||
#define EXYNOS_PMU_TCXO_GATE_GNSS_SYS_PWR_REG 0x11C4
|
||||
#define EXYNOS_PMU_RESET_ASB_GNSS_SYS_PWR_REG 0x11C8
|
||||
|
||||
/* 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 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 TCXO_26M_40M_SEL BIT(17)
|
||||
|
||||
#if !defined(CONFIG_SOC_EXYNOS7870) && !defined(CONFIG_SOC_EXYNOS7880)
|
||||
#define MEMSIZE_SHIFT 12
|
||||
#define MEMSIZE_OFFSET 0
|
||||
#define MEMSIZE_MASK 0xfffff
|
||||
#define MEMBASE_ADDR_SHIFT 12
|
||||
#define MEMBASE_ADDR_OFFSET 0
|
||||
#define MEMBASE_ADDR_MASK 0xffffff
|
||||
#else
|
||||
#define MEMSIZE_SHIFT 22
|
||||
#define MEMSIZE_OFFSET 16
|
||||
#define MEMSIZE_MASK 0x1ff
|
||||
#define MEMBASE_ADDR_SHIFT 22
|
||||
#define MEMBASE_ADDR_OFFSET 0
|
||||
#define MEMBASE_ADDR_MASK 0x3fff
|
||||
#endif
|
||||
|
||||
#define SMC_ID 0x82000700
|
||||
#define READ_CTRL 0x3
|
||||
#define WRITE_CTRL 0x4
|
||||
|
||||
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;
|
||||
extern int gnss_pmu_init_conf(struct gnss_ctl *);
|
||||
extern int gnss_pmu_hold_reset(struct gnss_ctl *);
|
||||
extern int gnss_pmu_release_reset(struct gnss_ctl *);
|
||||
extern int gnss_pmu_power_on(struct gnss_ctl *, enum gnss_mode mode);
|
||||
extern int gnss_pmu_clear_interrupt(struct gnss_ctl *,
|
||||
enum gnss_int_clear);
|
||||
extern int gnss_change_tcxo_mode(struct gnss_ctl *, enum gnss_tcxo_mode);
|
||||
|
||||
#endif /* __EXYNOS_PMU_GNSS_H */
|
Loading…
Add table
Add a link
Reference in a new issue