/*
* Copyright (C) 2011-2014 MediaTek Inc.
*
* 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.
*
* 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.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#if WMT_CREATE_NODE_DYNAMIC || REMOVE_MK_NODE
#include
#endif
#include "osal_typedef.h"
#include "stp_exp.h"
#include "wmt_exp.h"
#if defined(CONFIG_ARCH_MT6580)
#include
#endif
MODULE_LICENSE("GPL");
#define GPS_DRIVER_NAME "mtk_stp_GPS_chrdev"
#define GPS_DEV_MAJOR 191 /* never used number */
#define GPS_DEBUG_TRACE_GPIO 0
#define GPS_DEBUG_DUMP 0
#define PFX "[GPS] "
#define GPS_LOG_DBG 3
#define GPS_LOG_INFO 2
#define GPS_LOG_WARN 1
#define GPS_LOG_ERR 0
#define COMBO_IOC_GPS_HWVER 6
#define COMBO_IOC_GPS_IC_HW_VERSION 7
#define COMBO_IOC_GPS_IC_FW_VERSION 8
#define COMBO_IOC_D1_EFUSE_GET 9
#define COMBO_IOC_RTC_FLAG 10
#define COMBO_IOC_CO_CLOCK_FLAG 11
static UINT32 gDbgLevel = GPS_LOG_DBG;
#define GPS_DBG_FUNC(fmt, arg...) \
do { if (gDbgLevel >= GPS_LOG_DBG) \
pr_debug(PFX "[D]%s: " fmt, __func__ , ##arg); \
} while (0)
#define GPS_INFO_FUNC(fmt, arg...) \
do { if (gDbgLevel >= GPS_LOG_INFO) \
pr_info(PFX "[I]%s: " fmt, __func__ , ##arg); \
} while (0)
#define GPS_WARN_FUNC(fmt, arg...) \
do { if (gDbgLevel >= GPS_LOG_WARN) \
pr_warn(PFX "[W]%s: " fmt, __func__ , ##arg); \
} while (0)
#define GPS_ERR_FUNC(fmt, arg...) \
do { if (gDbgLevel >= GPS_LOG_ERR) \
pr_err(PFX "[E]%s: " fmt, __func__ , ##arg); \
} while (0)
#define GPS_TRC_FUNC(f) \
do { if (gDbgLevel >= GPS_LOG_DBG) \
pr_info(PFX "<%s> <%d>\n", __func__, __LINE__); \
} while (0)
static int GPS_devs = 1; /* device count */
static int GPS_major = GPS_DEV_MAJOR; /* dynamic allocation */
module_param(GPS_major, uint, 0);
static struct cdev GPS_cdev;
#if (defined(CONFIG_MTK_GMO_RAM_OPTIMIZE) && !defined(CONFIG_MT_ENG_BUILD))
#define STP_GPS_BUFFER_SIZE 2048
#else
#define STP_GPS_BUFFER_SIZE MTKSTP_BUFFER_SIZE
#endif
static unsigned char i_buf[STP_GPS_BUFFER_SIZE]; /* input buffer of read() */
static unsigned char o_buf[STP_GPS_BUFFER_SIZE]; /* output buffer of write() */
static struct semaphore wr_mtx, rd_mtx;
static DECLARE_WAIT_QUEUE_HEAD(GPS_wq);
static int flag;
static volatile int retflag;
static void GPS_event_cb(void);
bool rtc_GPS_low_power_detected(void)
{
static bool first_query = true;
if (first_query) {
first_query = false;
/*return rtc_low_power_detected();*/
return 0;
} else {
return false;
}
}
ssize_t GPS_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
int retval = 0;
int written = 0;
down(&wr_mtx);
/* GPS_TRC_FUNC(); */
/*pr_warn("%s: count %d pos %lld\n", __func__, count, *f_pos); */
if (count > 0) {
int copy_size = (count < MTKSTP_BUFFER_SIZE) ? count : MTKSTP_BUFFER_SIZE;
if (copy_from_user(&o_buf[0], &buf[0], copy_size)) {
retval = -EFAULT;
goto out;
}
/* pr_warn("%02x ", val); */
#if GPS_DEBUG_TRACE_GPIO
mtk_wcn_stp_debug_gpio_assert(IDX_GPS_TX, DBG_TIE_LOW);
#endif
written = mtk_wcn_stp_send_data(&o_buf[0], copy_size, GPS_TASK_INDX);
#if GPS_DEBUG_TRACE_GPIO
mtk_wcn_stp_debug_gpio_assert(IDX_GPS_TX, DBG_TIE_HIGH);
#endif
#if GPS_DEBUG_DUMP
{
unsigned char *buf_ptr = &o_buf[0];
int k = 0;
pr_warn("--[GPS-WRITE]--");
for (k = 0; k < 10; k++) {
if (k % 16 == 0)
pr_warn("\n");
pr_warn("0x%02x ", o_buf[k]);
}
pr_warn("\n");
}
#endif
/*
If cannot send successfully, enqueue again
if (written != copy_size) {
// George: FIXME! Move GPS retry handling from app to driver
}
*/
if (0 == written) {
retval = -ENOSPC;
/*no windowspace in STP is available,
native process should not call GPS_write with no delay at all */
GPS_ERR_FUNC
("target packet length:%zd, write success length:%d, retval = %d.\n",
count, written, retval);
} else {
retval = written;
}
} else {
retval = -EFAULT;
GPS_ERR_FUNC("target packet length:%zd is not allowed, retval = %d.\n", count, retval);
}
out:
up(&wr_mtx);
return retval;
}
ssize_t GPS_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
long val = 0;
int retval;
down(&rd_mtx);
/* pr_warn("GPS_read(): count %d pos %lld\n", count, *f_pos);*/
if (count > MTKSTP_BUFFER_SIZE)
count = MTKSTP_BUFFER_SIZE;
#if GPS_DEBUG_TRACE_GPIO
mtk_wcn_stp_debug_gpio_assert(IDX_GPS_RX, DBG_TIE_LOW);
#endif
retval = mtk_wcn_stp_receive_data(i_buf, count, GPS_TASK_INDX);
#if GPS_DEBUG_TRACE_GPIO
mtk_wcn_stp_debug_gpio_assert(IDX_GPS_RX, DBG_TIE_HIGH);
#endif
while (retval == 0) {
/* got nothing, wait for STP's signal */
/*wait_event(GPS_wq, flag != 0); *//* George: let signal wake up */
val = wait_event_interruptible(GPS_wq, flag != 0);
flag = 0;
#if GPS_DEBUG_TRACE_GPIO
mtk_wcn_stp_debug_gpio_assert(IDX_GPS_RX, DBG_TIE_LOW);
#endif
retval = mtk_wcn_stp_receive_data(i_buf, count, GPS_TASK_INDX);
#if GPS_DEBUG_TRACE_GPIO
mtk_wcn_stp_debug_gpio_assert(IDX_GPS_RX, DBG_TIE_HIGH);
#endif
/* if we are signaled */
if (val) {
if (-ERESTARTSYS == val)
GPS_DBG_FUNC("signaled by -ERESTARTSYS(%ld)\n ", val);
else
GPS_DBG_FUNC("signaled by %ld\n ", val);
break;
}
}
#if GPS_DEBUG_DUMP
{
unsigned char *buf_ptr = &i_buf[0];
int k = 0;
pr_warn("--[GPS-READ]--");
for (k = 0; k < 10; k++) {
if (k % 16 == 0)
pr_warn("\n");
pr_warn("0x%02x ", i_buf[k]);
}
pr_warn("--\n");
}
#endif
if (retval) {
/* we got something from STP driver */
if (copy_to_user(buf, i_buf, retval)) {
retval = -EFAULT;
goto OUT;
} else {
/* success */
}
} else {
/* we got nothing from STP driver, being signaled */
retval = val;
}
OUT:
up(&rd_mtx);
/* pr_warn("GPS_read(): retval = %d\n", retval);*/
return retval;
}
/* int GPS_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) */
long GPS_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int retval = 0;
ENUM_WMTHWVER_TYPE_T hw_ver_sym = WMTHWVER_INVALID;
UINT32 hw_version = 0;
UINT32 fw_version = 0;
pr_warn("GPS_ioctl(): cmd (%d)\n", cmd);
switch (cmd) {
case 0: /* enable/disable STP */
GPS_DBG_FUNC("GPS_ioctl(): disable STP control from GPS dev\n");
retval = -EINVAL;
#if 1
#else
/* George: STP is controlled by WMT only */
mtk_wcn_stp_enable(arg);
#endif
break;
case 1: /* send raw data */
GPS_DBG_FUNC("GPS_ioctl(): disable raw data from GPS dev\n");
retval = -EINVAL;
break;
case COMBO_IOC_GPS_HWVER:
/*get combo hw version */
hw_ver_sym = mtk_wcn_wmt_hwver_get();
GPS_DBG_FUNC("GPS_ioctl(): get hw version = %d, sizeof(hw_ver_sym) = %zd\n",
hw_ver_sym, sizeof(hw_ver_sym));
if (copy_to_user((int __user *)arg, &hw_ver_sym, sizeof(hw_ver_sym)))
retval = -EFAULT;
break;
case COMBO_IOC_GPS_IC_HW_VERSION:
/*get combo hw version from ic, without wmt mapping */
hw_version = mtk_wcn_wmt_ic_info_get(WMTCHIN_HWVER);
GPS_DBG_FUNC("GPS_ioctl(): get hw version = 0x%x\n", hw_version);
if (copy_to_user((int __user *)arg, &hw_version, sizeof(hw_version)))
retval = -EFAULT;
break;
case COMBO_IOC_GPS_IC_FW_VERSION:
/*get combo fw version from ic, without wmt mapping */
fw_version = mtk_wcn_wmt_ic_info_get(WMTCHIN_FWVER);
GPS_DBG_FUNC("GPS_ioctl(): get fw version = 0x%x\n", fw_version);
if (copy_to_user((int __user *)arg, &fw_version, sizeof(fw_version)))
retval = -EFAULT;
break;
case COMBO_IOC_RTC_FLAG:
retval = rtc_GPS_low_power_detected();
GPS_DBG_FUNC("low power flag (%d)\n", retval);
break;
case COMBO_IOC_CO_CLOCK_FLAG:
#if SOC_CO_CLOCK_FLAG
retval = mtk_wcn_wmt_co_clock_flag_get();
#endif
GPS_DBG_FUNC("GPS co_clock_flag (%d)\n", retval);
break;
case COMBO_IOC_D1_EFUSE_GET:
#if defined(CONFIG_ARCH_MT6735)
do {
char *addr = ioremap(0x10206198, 0x4);
retval = *(volatile unsigned int *)addr;
GPS_DBG_FUNC("D1 efuse (0x%x)\n", retval);
iounmap(addr);
} while (0);
#else
GPS_ERR_FUNC("Read Efuse not supported in this platform\n");
#endif
break;
default:
retval = -EFAULT;
GPS_DBG_FUNC("GPS_ioctl(): unknown cmd (%d)\n", cmd);
break;
}
/*OUT:*/
return retval;
}
long GPS_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
long ret;
pr_warn("%s: cmd (%d)\n", __func__, cmd);
ret = GPS_unlocked_ioctl(filp, cmd, arg);
pr_warn("%s: cmd (%d)\n", __func__, cmd);
return ret;
}
static void gps_cdev_rst_cb(ENUM_WMTDRV_TYPE_T src,
ENUM_WMTDRV_TYPE_T dst, ENUM_WMTMSG_TYPE_T type, void *buf, unsigned int sz)
{
/*
To handle reset procedure please
*/
ENUM_WMTRSTMSG_TYPE_T rst_msg;
GPS_DBG_FUNC("sizeof(ENUM_WMTRSTMSG_TYPE_T) = %zd\n", sizeof(ENUM_WMTRSTMSG_TYPE_T));
if (sz <= sizeof(ENUM_WMTRSTMSG_TYPE_T)) {
memcpy((char *)&rst_msg, (char *)buf, sz);
GPS_DBG_FUNC("src = %d, dst = %d, type = %d, buf = 0x%x sz = %d, max = %d\n", src,
dst, type, rst_msg, sz, WMTRSTMSG_RESET_MAX);
if ((src == WMTDRV_TYPE_WMT) && (dst == WMTDRV_TYPE_GPS)
&& (type == WMTMSG_TYPE_RESET)) {
if (rst_msg == WMTRSTMSG_RESET_START) {
GPS_DBG_FUNC("gps restart start!\n");
/*reset_start message handling */
retflag = 1;
} else if ((rst_msg == WMTRSTMSG_RESET_END) || (rst_msg == WMTRSTMSG_RESET_END_FAIL)) {
GPS_DBG_FUNC("gps restart end!\n");
/*reset_end message handling */
retflag = 0;
}
}
} else {
/*message format invalid */
}
}
static int GPS_open(struct inode *inode, struct file *file)
{
pr_debug("%s: major %d minor %d (pid %d)\n", __func__, imajor(inode), iminor(inode), current->pid);
if (current->pid == 1)
return 0;
if (retflag == 1) {
GPS_WARN_FUNC("whole chip resetting...\n");
return -EPERM;
}
#if 1 /* GeorgeKuo: turn on function before check stp ready */
/* turn on BT */
if (MTK_WCN_BOOL_FALSE == mtk_wcn_wmt_func_on(WMTDRV_TYPE_GPS)) {
GPS_WARN_FUNC("WMT turn on GPS fail!\n");
return -ENODEV;
}
mtk_wcn_wmt_msgcb_reg(WMTDRV_TYPE_GPS, gps_cdev_rst_cb);
GPS_DBG_FUNC("WMT turn on GPS OK!\n");
#endif
if (mtk_wcn_stp_is_ready()) {
#if 0
if (MTK_WCN_BOOL_FALSE == mtk_wcn_wmt_func_on(WMTDRV_TYPE_GPS)) {
GPS_WARN_FUNC("WMT turn on GPS fail!\n");
return -ENODEV;
}
GPS_DBG_FUNC("WMT turn on GPS OK!\n");
#endif
mtk_wcn_stp_register_event_cb(GPS_TASK_INDX, GPS_event_cb);
} else {
GPS_ERR_FUNC("STP is not ready, Cannot open GPS Devices\n\r");
/*return error code */
return -ENODEV;
}
#if defined(CONFIG_ARCH_MT6580)
clk_buf_ctrl(CLK_BUF_AUDIO, 1);
#endif
/* init_MUTEX(&wr_mtx); */
sema_init(&wr_mtx, 1);
/* init_MUTEX(&rd_mtx); */
sema_init(&rd_mtx, 1);
return 0;
}
static int GPS_close(struct inode *inode, struct file *file)
{
pr_debug("%s: major %d minor %d (pid %d)\n", __func__, imajor(inode), iminor(inode), current->pid);
if (current->pid == 1)
return 0;
if (retflag == 1) {
GPS_WARN_FUNC("whole chip resetting...\n");
return -EPERM;
}
/*Flush Rx Queue */
mtk_wcn_stp_register_event_cb(GPS_TASK_INDX, 0x0); /* unregister event callback function */
mtk_wcn_wmt_msgcb_unreg(WMTDRV_TYPE_GPS);
if (MTK_WCN_BOOL_FALSE == mtk_wcn_wmt_func_off(WMTDRV_TYPE_GPS)) {
GPS_WARN_FUNC("WMT turn off GPS fail!\n");
return -EIO; /* mostly, native programer does not care this return vlaue,
but we still return error code. */
}
GPS_DBG_FUNC("WMT turn off GPS OK!\n");
#if defined(CONFIG_ARCH_MT6580)
clk_buf_ctrl(CLK_BUF_AUDIO, 0);
#endif
return 0;
}
const struct file_operations GPS_fops = {
.open = GPS_open,
.release = GPS_close,
.read = GPS_read,
.write = GPS_write,
/* .ioctl = GPS_ioctl */
.unlocked_ioctl = GPS_unlocked_ioctl,
.compat_ioctl = GPS_compat_ioctl,
};
void GPS_event_cb(void)
{
/* pr_debug("GPS_event_cb()\n");*/
flag = 1;
wake_up(&GPS_wq);
}
#if WMT_CREATE_NODE_DYNAMIC || REMOVE_MK_NODE
struct class *stpgps_class = NULL;
#endif
static int GPS_init(void)
{
dev_t dev = MKDEV(GPS_major, 0);
int alloc_ret = 0;
int cdev_err = 0;
#if WMT_CREATE_NODE_DYNAMIC || REMOVE_MK_NODE
struct device *stpgps_dev = NULL;
#endif
/*static allocate chrdev */
alloc_ret = register_chrdev_region(dev, 1, GPS_DRIVER_NAME);
if (alloc_ret) {
pr_warn("fail to register chrdev\n");
return alloc_ret;
}
cdev_init(&GPS_cdev, &GPS_fops);
GPS_cdev.owner = THIS_MODULE;
cdev_err = cdev_add(&GPS_cdev, dev, GPS_devs);
if (cdev_err)
goto error;
#if WMT_CREATE_NODE_DYNAMIC || REMOVE_MK_NODE
stpgps_class = class_create(THIS_MODULE, "stpgps");
if (IS_ERR(stpgps_class))
goto error;
stpgps_dev = device_create(stpgps_class, NULL, dev, NULL, "stpgps");
if (IS_ERR(stpgps_dev))
goto error;
#endif
pr_warn("%s driver(major %d) installed.\n", GPS_DRIVER_NAME, GPS_major);
return 0;
error:
#if WMT_CREATE_NODE_DYNAMIC || REMOVE_MK_NODE
if (!IS_ERR(stpgps_dev))
device_destroy(stpgps_class, dev);
if (!IS_ERR(stpgps_class)) {
class_destroy(stpgps_class);
stpgps_class = NULL;
}
#endif
if (cdev_err == 0)
cdev_del(&GPS_cdev);
if (alloc_ret == 0)
unregister_chrdev_region(dev, GPS_devs);
return -1;
}
static void GPS_exit(void)
{
dev_t dev = MKDEV(GPS_major, 0);
#if WMT_CREATE_NODE_DYNAMIC || REMOVE_MK_NODE
device_destroy(stpgps_class, dev);
class_destroy(stpgps_class);
stpgps_class = NULL;
#endif
cdev_del(&GPS_cdev);
unregister_chrdev_region(dev, GPS_devs);
pr_warn("%s driver removed.\n", GPS_DRIVER_NAME);
}
#ifdef MTK_WCN_REMOVE_KERNEL_MODULE
int mtk_wcn_stpgps_drv_init(void)
{
return GPS_init();
}
EXPORT_SYMBOL(mtk_wcn_stpgps_drv_init);
void mtk_wcn_stpgps_drv_exit(void)
{
return GPS_exit();
}
EXPORT_SYMBOL(mtk_wcn_stpgps_drv_exit);
#else
module_init(GPS_init);
module_exit(GPS_exit);
#endif