398 lines
8.2 KiB
C
398 lines
8.2 KiB
C
/* Copyright (c) 2010-2012, The Linux Foundation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only 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.
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/io.h>
|
|
#include <linux/gpio.h>
|
|
#include <mach/msm_iomap.h>
|
|
|
|
#include <linux/fsm_rfic_ftr.h>
|
|
|
|
/*
|
|
* FTR8700 RFIC
|
|
*/
|
|
|
|
#define RFIC_FTR_DEVICE_NUM 2
|
|
#define RFIC_GRFC_REG_NUM 6
|
|
|
|
#define ANY_BUS 0x0
|
|
#define TX1_BUS 0x0
|
|
#define TX2_BUS 0x1
|
|
#define MISC_BUS 0x2
|
|
#define RX_BUS 0x3
|
|
#define BUS_BITS 0x3
|
|
|
|
/*
|
|
* Device private information per device node
|
|
*/
|
|
|
|
static struct ftr_dev_node_info {
|
|
void *grfcCtrlAddr;
|
|
void *grfcMaskAddr;
|
|
unsigned int busSelect[4];
|
|
struct i2c_adapter *ssbi_adap;
|
|
|
|
/* lock */
|
|
struct mutex lock;
|
|
} ftr_dev_info[RFIC_FTR_DEVICE_NUM];
|
|
|
|
/*
|
|
* Device private information per file
|
|
*/
|
|
|
|
struct ftr_dev_file_info {
|
|
int ftrId;
|
|
};
|
|
|
|
/*
|
|
* File interface
|
|
*/
|
|
|
|
static int ftr_find_id(int minor);
|
|
|
|
static int ftr_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct ftr_dev_file_info *pdfi;
|
|
|
|
/* private data allocation */
|
|
pdfi = kmalloc(sizeof(*pdfi), GFP_KERNEL);
|
|
if (pdfi == NULL)
|
|
return -ENOMEM;
|
|
file->private_data = pdfi;
|
|
|
|
/* FTR ID */
|
|
pdfi->ftrId = ftr_find_id(MINOR(inode->i_rdev));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ftr_release(struct inode *inode, struct file *file)
|
|
{
|
|
struct ftr_dev_file_info *pdfi;
|
|
|
|
pdfi = (struct ftr_dev_file_info *) file->private_data;
|
|
|
|
kfree(file->private_data);
|
|
file->private_data = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t ftr_read(struct file *filp, char __user *buf, size_t count,
|
|
loff_t *f_pos)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t ftr_write(struct file *file, const char __user *buffer,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int ftr_ssbi_read(
|
|
struct ftr_dev_node_info *pdev,
|
|
unsigned int addr,
|
|
u8 *buf,
|
|
size_t len)
|
|
{
|
|
int ret;
|
|
struct i2c_msg msg = {
|
|
.addr = addr,
|
|
.flags = I2C_M_RD,
|
|
.buf = buf,
|
|
.len = len,
|
|
};
|
|
|
|
ret = i2c_transfer(pdev->ssbi_adap, &msg, 1);
|
|
|
|
return (ret == 1) ? 0 : ret;
|
|
}
|
|
|
|
static int ftr_ssbi_write(
|
|
struct ftr_dev_node_info *pdev,
|
|
unsigned int addr,
|
|
u8 *buf,
|
|
size_t len)
|
|
{
|
|
int ret;
|
|
struct i2c_msg msg = {
|
|
.addr = addr,
|
|
.flags = 0x0,
|
|
.buf = (u8 *) buf,
|
|
.len = len,
|
|
};
|
|
|
|
ret = i2c_transfer(pdev->ssbi_adap, &msg, 1);
|
|
|
|
return (ret == 1) ? 0 : ret;
|
|
}
|
|
|
|
static long ftr_ioctl(struct file *file,
|
|
unsigned int cmd, unsigned long arg)
|
|
{
|
|
unsigned int __user *argp = (unsigned int __user *) arg;
|
|
struct ftr_dev_file_info *pdfi =
|
|
(struct ftr_dev_file_info *) file->private_data;
|
|
struct ftr_dev_node_info *pdev;
|
|
|
|
if (pdfi->ftrId < 0 || pdfi->ftrId >= RFIC_FTR_DEVICE_NUM)
|
|
return -EINVAL;
|
|
|
|
pdev = ftr_dev_info + pdfi->ftrId;
|
|
|
|
switch (cmd) {
|
|
case RFIC_IOCTL_READ_REGISTER:
|
|
{
|
|
int ret;
|
|
unsigned int rficAddr;
|
|
u8 value;
|
|
|
|
if (get_user(rficAddr, argp))
|
|
return -EFAULT;
|
|
|
|
mutex_lock(&pdev->lock);
|
|
mb();
|
|
/* Need to write twice due to bug in hardware */
|
|
__raw_writel(
|
|
pdev->busSelect[RFIC_FTR_GET_BUS(rficAddr)],
|
|
pdev->grfcCtrlAddr);
|
|
__raw_writel(
|
|
pdev->busSelect[RFIC_FTR_GET_BUS(rficAddr)],
|
|
pdev->grfcCtrlAddr);
|
|
mb();
|
|
ret = ftr_ssbi_read(pdev, RFIC_FTR_GET_ADDR(rficAddr),
|
|
&value, 1);
|
|
mutex_unlock(&pdev->lock);
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (put_user(value, argp))
|
|
return -EFAULT;
|
|
}
|
|
break;
|
|
|
|
case RFIC_IOCTL_WRITE_REGISTER:
|
|
{
|
|
int ret;
|
|
struct rfic_write_register_param param;
|
|
unsigned int rficAddr;
|
|
u8 value;
|
|
|
|
if (copy_from_user(¶m, argp, sizeof param))
|
|
return -EFAULT;
|
|
rficAddr = param.rficAddr;
|
|
value = (u8) param.value;
|
|
|
|
mutex_lock(&pdev->lock);
|
|
mb();
|
|
/* Need to write twice due to bug in hardware */
|
|
__raw_writel(
|
|
pdev->busSelect[RFIC_FTR_GET_BUS(rficAddr)],
|
|
pdev->grfcCtrlAddr);
|
|
__raw_writel(
|
|
pdev->busSelect[RFIC_FTR_GET_BUS(rficAddr)],
|
|
pdev->grfcCtrlAddr);
|
|
mb();
|
|
ret = ftr_ssbi_write(pdev, RFIC_FTR_GET_ADDR(rficAddr),
|
|
&value, 1);
|
|
mutex_unlock(&pdev->lock);
|
|
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
break;
|
|
|
|
case RFIC_IOCTL_WRITE_REGISTER_WITH_MASK:
|
|
{
|
|
int ret;
|
|
struct rfic_write_register_mask_param param;
|
|
unsigned int rficAddr;
|
|
u8 value;
|
|
|
|
if (copy_from_user(¶m, argp, sizeof param))
|
|
return -EFAULT;
|
|
rficAddr = param.rficAddr;
|
|
|
|
mutex_lock(&pdev->lock);
|
|
mb();
|
|
/* Need to write twice due to bug in hardware */
|
|
__raw_writel(
|
|
pdev->busSelect[RFIC_FTR_GET_BUS(rficAddr)],
|
|
pdev->grfcCtrlAddr);
|
|
__raw_writel(
|
|
pdev->busSelect[RFIC_FTR_GET_BUS(rficAddr)],
|
|
pdev->grfcCtrlAddr);
|
|
mb();
|
|
ret = ftr_ssbi_read(pdev, RFIC_FTR_GET_ADDR(rficAddr),
|
|
&value, 1);
|
|
value &= (u8) ~param.mask;
|
|
value |= (u8) (param.value & param.mask);
|
|
ret = ftr_ssbi_write(pdev, RFIC_FTR_GET_ADDR(rficAddr),
|
|
&value, 1);
|
|
mutex_unlock(&pdev->lock);
|
|
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
break;
|
|
|
|
case RFIC_IOCTL_GET_GRFC:
|
|
{
|
|
struct rfic_grfc_param param;
|
|
|
|
if (copy_from_user(¶m, argp, sizeof param))
|
|
return -EFAULT;
|
|
|
|
if (param.grfcId >= RFIC_GRFC_REG_NUM)
|
|
return -EINVAL;
|
|
|
|
param.maskValue = __raw_readl(
|
|
MSM_GRFC_BASE + 0x18 + param.grfcId * 4);
|
|
param.ctrlValue = __raw_readl(
|
|
MSM_GRFC_BASE + 0x00 + param.grfcId * 4);
|
|
|
|
if (copy_to_user(argp, ¶m, sizeof param))
|
|
return -EFAULT;
|
|
}
|
|
break;
|
|
|
|
case RFIC_IOCTL_SET_GRFC:
|
|
{
|
|
struct rfic_grfc_param param;
|
|
|
|
if (copy_from_user(¶m, argp, sizeof param))
|
|
return -EFAULT;
|
|
|
|
if (param.grfcId >= RFIC_GRFC_REG_NUM)
|
|
return -EINVAL;
|
|
|
|
__raw_writel(param.maskValue,
|
|
MSM_GRFC_BASE + 0x18 + param.grfcId * 4);
|
|
/* Need to write twice due to bug in hardware */
|
|
__raw_writel(param.ctrlValue,
|
|
MSM_GRFC_BASE + 0x00 + param.grfcId * 4);
|
|
__raw_writel(param.ctrlValue,
|
|
MSM_GRFC_BASE + 0x00 + param.grfcId * 4);
|
|
mb();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct file_operations ftr_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = ftr_open,
|
|
.release = ftr_release,
|
|
.read = ftr_read,
|
|
.write = ftr_write,
|
|
.unlocked_ioctl = ftr_ioctl,
|
|
};
|
|
|
|
/*
|
|
* Driver initialization & cleanup
|
|
*/
|
|
|
|
struct miscdevice ftr_misc_dev[RFIC_FTR_DEVICE_NUM] = {
|
|
{
|
|
.minor = MISC_DYNAMIC_MINOR,
|
|
.name = RFIC_FTR_DEVICE_NAME "0",
|
|
.fops = &ftr_fops,
|
|
},
|
|
{
|
|
.minor = MISC_DYNAMIC_MINOR,
|
|
.name = RFIC_FTR_DEVICE_NAME "1",
|
|
.fops = &ftr_fops,
|
|
},
|
|
};
|
|
|
|
int ftr_find_id(int minor)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < RFIC_FTR_DEVICE_NUM; ++i)
|
|
if (ftr_misc_dev[i].minor == minor)
|
|
break;
|
|
|
|
return i;
|
|
}
|
|
|
|
static int __init ftr_init(void)
|
|
{
|
|
int i, ret;
|
|
struct ftr_dev_node_info *pdev;
|
|
|
|
for (i = 0; i < RFIC_FTR_DEVICE_NUM; ++i) {
|
|
pdev = ftr_dev_info + i;
|
|
|
|
if (i == 0) {
|
|
pdev->grfcCtrlAddr = MSM_GRFC_BASE + 0x00;
|
|
pdev->grfcMaskAddr = MSM_GRFC_BASE + 0x18;
|
|
__raw_writel(0x300000, pdev->grfcMaskAddr);
|
|
pdev->busSelect[TX1_BUS] = 0x000000;
|
|
pdev->busSelect[TX2_BUS] = 0x100000;
|
|
pdev->busSelect[MISC_BUS] = 0x200000;
|
|
pdev->busSelect[RX_BUS] = 0x300000;
|
|
pdev->ssbi_adap = i2c_get_adapter(1);
|
|
} else {
|
|
pdev->grfcCtrlAddr = MSM_GRFC_BASE + 0x04;
|
|
pdev->grfcMaskAddr = MSM_GRFC_BASE + 0x1c;
|
|
__raw_writel(0x480000, pdev->grfcMaskAddr);
|
|
pdev->busSelect[TX1_BUS] = 0x000000;
|
|
pdev->busSelect[TX2_BUS] = 0x400000;
|
|
pdev->busSelect[MISC_BUS] = 0x080000;
|
|
pdev->busSelect[RX_BUS] = 0x480000;
|
|
pdev->ssbi_adap = i2c_get_adapter(2);
|
|
}
|
|
|
|
mutex_init(&pdev->lock);
|
|
ret = misc_register(ftr_misc_dev + i);
|
|
|
|
if (ret < 0) {
|
|
while (--i >= 0)
|
|
misc_deregister(ftr_misc_dev + i);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __exit ftr_exit(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < RFIC_FTR_DEVICE_NUM; ++i)
|
|
misc_deregister(ftr_misc_dev + i);
|
|
}
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_AUTHOR("Rohit Vaswani <rvaswani@codeaurora.org>");
|
|
MODULE_DESCRIPTION("Qualcomm FSM RFIC driver");
|
|
MODULE_VERSION("1.0");
|
|
|
|
module_init(ftr_init);
|
|
module_exit(ftr_exit);
|