Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

View 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.

View 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

View 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;
}

View 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;
}

View 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;
}

View 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

View 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;
}

View 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

View 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");

View 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

View 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);
}

View 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__*/

View 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

View 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

View 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;
}

View 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 */