419 lines
12 KiB
C
419 lines
12 KiB
C
/*
|
|
* GSC3280 SoC SCI(7816) controller driver
|
|
*
|
|
* Copyright (C) 2013 BLX IC Design Corp.,Ltd.
|
|
* Author: Fei Lu, Lufei@china-cpu.com
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/sched.h>
|
|
#include <linux/io.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/poll.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/types.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include <asm/io.h>
|
|
#include <linux/gsc3280_sci.h>
|
|
|
|
#define DRIVER_NAME "gsc3280-sci"
|
|
#define SCI0_DEV_NAME "sci0"
|
|
#define SCI1_DEV_NAME "sci1"
|
|
static struct miscdevice gsc3280_sci_dev;
|
|
|
|
static void gsc3280_sci_clear_ports(gsc3280_sci_t *gsc3280_sci, unsigned int value)
|
|
{
|
|
unsigned int reg_data;
|
|
|
|
reg_data = readl(gsc3280_sci->ioaddr + SCI_CTRL0);
|
|
reg_data &= ~value;
|
|
writel(reg_data, gsc3280_sci->ioaddr + SCI_CTRL0);
|
|
}
|
|
|
|
static void gsc3280_sci_set_ports(gsc3280_sci_t *gsc3280_sci, unsigned int value)
|
|
{
|
|
unsigned int reg_data;
|
|
|
|
reg_data = readl(gsc3280_sci->ioaddr + SCI_CTRL0);
|
|
reg_data |= value;
|
|
writel(reg_data, gsc3280_sci->ioaddr + SCI_CTRL0);
|
|
}
|
|
|
|
static void gsc3280_sci_power_on(gsc3280_sci_t *gsc3280_sci)
|
|
{
|
|
if(gsc3280_sci->power == SIM_POWER_ON)
|
|
return;
|
|
else{
|
|
printk(KERN_DEBUG "%s entering.\n", __func__);
|
|
writeb(0x1, gsc3280_sci->ioaddr + SCI_CTRL1);
|
|
gsc3280_sci->power = SIM_POWER_ON;
|
|
gsc3280_sci->state = SIM_STATE_DETECTED_ATR;
|
|
}
|
|
|
|
}
|
|
|
|
static void gsc3280_sci_start(gsc3280_sci_t *gsc3280_sci)
|
|
{
|
|
printk(KERN_DEBUG "%s entering.\n", __func__);
|
|
writel(0x7b8, gsc3280_sci->ioaddr + SCI_CTRL0);
|
|
writel(0x7f, gsc3280_sci->ioaddr + SCI_IIR);
|
|
writel(0x7ff, gsc3280_sci->ioaddr + SCI_IMR);
|
|
writel(0x47, gsc3280_sci->ioaddr + SCI_FCR);
|
|
writel(0x6c, gsc3280_sci->ioaddr + SCI_BWTR);
|
|
writel(0x2574, gsc3280_sci->ioaddr + SCI_CWTR);
|
|
writel(0x0, gsc3280_sci->ioaddr + SCI_CGTR);
|
|
writel(0x4, gsc3280_sci->ioaddr + SCI_BGTR);
|
|
writel(0x0174, gsc3280_sci->ioaddr + SCI_BAUDR);
|
|
}
|
|
|
|
static void gsc3280_sci_stop(gsc3280_sci_t *gsc3280_sci)
|
|
{
|
|
unsigned int reg_data = 0;
|
|
|
|
/* gsc3280_sci_stop sequence */
|
|
writel(0x2, gsc3280_sci->ioaddr + SCI_CTRL1);
|
|
reg_data = readl(gsc3280_sci->ioaddr + SCI_CTRL0);
|
|
reg_data &=0xff7;/*rst*/
|
|
writel(reg_data, gsc3280_sci->ioaddr + SCI_CTRL0);
|
|
reg_data &=0xfd7;/*clk*/
|
|
writel(reg_data, gsc3280_sci->ioaddr + SCI_CTRL0);
|
|
reg_data &=0xfc7;/*vcc*/
|
|
writel(reg_data, gsc3280_sci->ioaddr + SCI_CTRL0);
|
|
gsc3280_sci->power = SIM_POWER_OFF;
|
|
gsc3280_sci->state = SIM_STATE_REMOVED;
|
|
}
|
|
|
|
static void gsc3280_sci_power_off(gsc3280_sci_t *gsc3280_sci)
|
|
{
|
|
if(gsc3280_sci->power == SIM_POWER_OFF)
|
|
return;
|
|
else
|
|
gsc3280_sci_stop(gsc3280_sci);
|
|
}
|
|
|
|
static void gsc3280_sci_cold_reset(gsc3280_sci_t *gsc3280_sci)
|
|
{
|
|
gsc3280_sci_start(gsc3280_sci);
|
|
gsc3280_sci_power_on(gsc3280_sci);
|
|
}
|
|
|
|
static void gsc3280_sci_warm_reset(gsc3280_sci_t *gsc3280_sci)
|
|
{
|
|
printk(KERN_DEBUG "%s entering.\n", __func__);
|
|
writel(0x4, gsc3280_sci->ioaddr + SCI_CTRL1);
|
|
}
|
|
|
|
static long gsc3280_sci_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|
{
|
|
int ret=0, value=-1;
|
|
unsigned long real_freq;
|
|
gsc3280_sci_t *gsc3280_sci = (gsc3280_sci_t *) file->private_data;
|
|
gsc3280_sci_reg s_reg = {0,0};
|
|
|
|
printk(KERN_DEBUG "%s ioctl cmd %d \n", __func__, cmd);
|
|
switch (cmd) {
|
|
case SIM_IOCTL_POWER_ON:
|
|
printk(KERN_DEBUG "ioctl cmd SIM_IOCTL_POWER_ON\n");
|
|
if (gsc3280_sci->power == SIM_POWER_ON) {
|
|
ret = -SIM_E_POWERED_ON;
|
|
break;
|
|
};
|
|
gsc3280_sci_power_on(gsc3280_sci);
|
|
break;
|
|
|
|
case SIM_IOCTL_POWER_OFF:
|
|
printk(KERN_DEBUG "ioctl cmd SIM_IOCTL_POWER_OFF\n");
|
|
if (gsc3280_sci->power == SIM_POWER_OFF) {
|
|
ret = -SIM_E_POWERED_OFF;
|
|
break;
|
|
};
|
|
gsc3280_sci_power_off(gsc3280_sci);
|
|
break;
|
|
case SIM_IOCTL_COLD_RESET:
|
|
printk(KERN_DEBUG "ioctl cmd SIM_IOCTL_COLD_RESET\n");
|
|
gsc3280_sci_cold_reset(gsc3280_sci);
|
|
break;
|
|
|
|
case SIM_IOCTL_WARM_RESET:
|
|
printk(KERN_DEBUG "ioctl cmd SIM_IOCTL_WARM_RESET\n");
|
|
if (gsc3280_sci->power == SIM_POWER_OFF) {
|
|
ret = -SIM_E_POWERED_OFF;
|
|
break;
|
|
};
|
|
gsc3280_sci_warm_reset(gsc3280_sci);
|
|
break;
|
|
|
|
case SIM_IOCTL_CLK_PORT:
|
|
printk(KERN_DEBUG "ioctl cmd SIM_IOCTL_CLK_PORT\n");
|
|
ret = copy_from_user(&value, (int *) arg, sizeof(int));
|
|
if (value == 0)
|
|
gsc3280_sci_set_ports(gsc3280_sci, CTRL0_CLK_EN);
|
|
else
|
|
gsc3280_sci_clear_ports(gsc3280_sci, CTRL0_CLK_EN);
|
|
break;
|
|
|
|
case SIM_IOCTL_RST_PORT:
|
|
printk(KERN_DEBUG "ioctl cmd SIM_IOCTL_RST_PORT\n");
|
|
ret = copy_from_user(&value, (int *) arg, sizeof(int));
|
|
if (value == 0)
|
|
gsc3280_sci_set_ports(gsc3280_sci, CTRL0_SIM_RST);
|
|
else
|
|
gsc3280_sci_clear_ports(gsc3280_sci, CTRL0_SIM_RST);
|
|
break;
|
|
|
|
case SIM_IOCTL_VCC_PORT:
|
|
printk(KERN_DEBUG "ioctl cmd SIM_IOCTL_VCC_PORT\n");
|
|
ret = copy_from_user(&value, (int *) arg, sizeof(int));
|
|
if (value == 0)
|
|
gsc3280_sci_set_ports(gsc3280_sci, CTRL0_POW_EN);
|
|
else
|
|
gsc3280_sci_clear_ports(gsc3280_sci, CTRL0_POW_EN);
|
|
break;
|
|
case SIM_IOCTL_SET_CLK_SIM:
|
|
printk(KERN_DEBUG "ioctl cmd SIM_IOCTL_SET_CLK_SIM\n");
|
|
ret = copy_from_user(&real_freq, (unsigned long *) arg, sizeof(unsigned long));
|
|
if (gsc3280_sci->clk_sim > 0)
|
|
{
|
|
gsc3280_sci->clk_sim = real_freq;
|
|
real_freq = clk_set_rate(gsc3280_sci->clk,gsc3280_sci->clk_sim);
|
|
}
|
|
else
|
|
{
|
|
printk(KERN_DEBUG "Invalid Freq. %lu Hz\n", real_freq);
|
|
real_freq = -1;
|
|
}
|
|
ret = copy_to_user((unsigned long *) arg, &real_freq, sizeof(unsigned long));
|
|
break;
|
|
/*debug register*/
|
|
case SIM_IOCTL_GET_REG:
|
|
printk(KERN_DEBUG "ioctl cmd SIM_IOCTL_GET_REG\n");
|
|
ret = copy_from_user(&s_reg, (gsc3280_sci_reg*) arg, sizeof(gsc3280_sci_reg));
|
|
s_reg.data =(unsigned long) readl(gsc3280_sci->ioaddr + s_reg.addr);
|
|
ret = copy_to_user((gsc3280_sci_reg *) arg, &s_reg, sizeof(gsc3280_sci_reg));
|
|
break;
|
|
|
|
case SIM_IOCTL_SET_REG:
|
|
printk(KERN_DEBUG "ioctl cmd SIM_IOCTL_SET_REG\n");
|
|
ret = copy_from_user(&s_reg, (gsc3280_sci_reg *) arg, sizeof(gsc3280_sci_reg));
|
|
writel((unsigned int) s_reg.data, gsc3280_sci->ioaddr + s_reg.addr);
|
|
break;
|
|
case SIM_IOCTL_RESET_MODULE:
|
|
printk(KERN_DEBUG "ioctl cmd SIM_IOCTL_RESET_MODULE\n");
|
|
writel(0x40, (void*)0xbc04a010);
|
|
msleep(10);
|
|
writel(0x00, (void*)0xbc04a010);
|
|
break;
|
|
};
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t gsc3280_sci_write(struct file *file, const char *user_buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
int ret=0,i=0;
|
|
gsc3280_sci_t *gsc3280_sci = (gsc3280_sci_t *) file->private_data;
|
|
unsigned char write_buffer[SIM_XMT_BUFFER_SIZE];
|
|
unsigned int status;
|
|
|
|
printk(KERN_DEBUG "%s entering.\n", __func__);
|
|
ret = copy_from_user(write_buffer, user_buf, count);
|
|
if (ret)
|
|
return -EFAULT;
|
|
while(i<count){
|
|
status = readl(gsc3280_sci->ioaddr + SCI_IIR);
|
|
if(status&SCI_IIR_TEMPT) {
|
|
/*transmit 1 Byte*/
|
|
writeb(write_buffer[i], gsc3280_sci->ioaddr + SCI_TXDATA);
|
|
i++;
|
|
}
|
|
}
|
|
mdelay(10);
|
|
gsc3280_sci->state=SIM_STATE_IDLE;
|
|
return i;
|
|
};
|
|
|
|
static ssize_t gsc3280_sci_read(struct file *file, char *user_buf, size_t count, loff_t *ppos)
|
|
{
|
|
int ret=0;
|
|
unsigned char read_buffer[SIM_RCV_BUFFER_SIZE];
|
|
gsc3280_sci_t *gsc3280_sci = (gsc3280_sci_t *) file->private_data;
|
|
int i=0;
|
|
unsigned char data;
|
|
unsigned int status,sr;
|
|
|
|
while(i<count){
|
|
status = readl(gsc3280_sci->ioaddr + SCI_IIR);
|
|
sr = readl(gsc3280_sci->ioaddr + SCI_FSR);
|
|
if(sr&0x1f) {
|
|
data = readb(gsc3280_sci->ioaddr + SCI_RXDATA);
|
|
/*detect parity ERROR or FRAME ERROR or CWT ERROR
|
|
add code here
|
|
*/
|
|
read_buffer[i] = (data&0xff);
|
|
i++;
|
|
}
|
|
else if(status&(SCI_IIR_CWTOUT|SCI_IIR_BWTOUT|SCI_IIR_FRAMERR|SCI_IIR_ATRDTOUT|SCI_IIR_ATRSTOUT)){
|
|
break;
|
|
}
|
|
}
|
|
writel(status, gsc3280_sci->ioaddr+SCI_IIR);
|
|
gsc3280_sci->state = SIM_STATE_IDLE;
|
|
ret = copy_to_user(user_buf, read_buffer, i);
|
|
if (ret)
|
|
return -EFAULT;
|
|
return i;
|
|
}
|
|
|
|
static int gsc3280_sci_open(struct inode *inode, struct file *file)
|
|
{
|
|
gsc3280_sci_t *gsc3280_sci = dev_get_drvdata(gsc3280_sci_dev.parent);
|
|
|
|
file->private_data = gsc3280_sci;
|
|
printk(KERN_DEBUG "%s entering.\n", __func__);
|
|
if (!gsc3280_sci->ioaddr)
|
|
return -ENOMEM;
|
|
if (!(gsc3280_sci->clk_flag)) {
|
|
if(!(clk_enable(gsc3280_sci->clk)))
|
|
gsc3280_sci->clk_flag = 1;
|
|
else
|
|
return -EFAULT;
|
|
}
|
|
gsc3280_sci_start(gsc3280_sci);
|
|
return 0;
|
|
}
|
|
|
|
static int gsc3280_sci_release(struct inode *inode, struct file *file)
|
|
{
|
|
|
|
gsc3280_sci_t *gsc3280_sci = (gsc3280_sci_t *) file->private_data;
|
|
|
|
if (gsc3280_sci->clk_flag) {
|
|
clk_disable(gsc3280_sci->clk);
|
|
gsc3280_sci->clk_flag = 0;
|
|
}
|
|
gsc3280_sci_power_off(gsc3280_sci);
|
|
return 0;
|
|
}
|
|
|
|
static const struct file_operations gsc3280_sci_fops = {
|
|
.owner = THIS_MODULE,
|
|
.read = gsc3280_sci_read,
|
|
.write = gsc3280_sci_write,
|
|
.open = gsc3280_sci_open,
|
|
.unlocked_ioctl = gsc3280_sci_ioctl,
|
|
.release = gsc3280_sci_release
|
|
};
|
|
|
|
static int gsc3280_sci_probe(struct platform_device *pdev)
|
|
{
|
|
int ret = 0;
|
|
struct gsc3280_sci_platform_data *gsc3280_sci_plat = pdev->dev.platform_data;
|
|
gsc3280_sci_t *gsc3280_sci = kzalloc(sizeof(gsc3280_sci_t), GFP_KERNEL);
|
|
|
|
if (gsc3280_sci == 0) {
|
|
ret = -ENOMEM;
|
|
printk(KERN_ERR "Can't get the MEMORY\n");
|
|
return ret;
|
|
}
|
|
BUG_ON(pdev == NULL);
|
|
gsc3280_sci->plat_data = gsc3280_sci_plat;
|
|
gsc3280_sci->clk_flag = 0;
|
|
gsc3280_sci->gsc3280_sci_number = pdev->id;
|
|
printk(KERN_INFO "GSC3280 : sci(7816) driver\n");
|
|
printk(KERN_INFO "Trying initialize sci(7816) %d...\n", gsc3280_sci->gsc3280_sci_number);
|
|
gsc3280_sci->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
if (!gsc3280_sci->res) {
|
|
ret = -ENOMEM;
|
|
printk(KERN_ERR "Can't get the MEMORY\n");
|
|
goto err_kfree;
|
|
}
|
|
/* request the sim clk */
|
|
gsc3280_sci->clk = clk_get(&pdev->dev,gsc3280_sci->plat_data->clock);
|
|
if (IS_ERR(gsc3280_sci->clk)) {
|
|
ret = PTR_ERR(gsc3280_sci->clk);
|
|
printk(KERN_ERR "Get CLK ERROR !\n");
|
|
goto err;
|
|
}
|
|
if (!request_mem_region(gsc3280_sci->res->start,
|
|
gsc3280_sci->res->end -
|
|
gsc3280_sci->res->start + 1, pdev->name)) {
|
|
printk(KERN_ERR "request_mem_region failed\n");
|
|
ret = -ENOMEM;
|
|
goto err;
|
|
}
|
|
gsc3280_sci->ioaddr = (void *)ioremap_nocache(gsc3280_sci->res->start, gsc3280_sci->res->end -
|
|
gsc3280_sci->res->start + 1);
|
|
platform_set_drvdata(pdev, gsc3280_sci);
|
|
gsc3280_sci_dev.minor = MISC_DYNAMIC_MINOR;
|
|
|
|
if(gsc3280_sci->gsc3280_sci_number == 0)
|
|
gsc3280_sci_dev.name = SCI0_DEV_NAME;
|
|
else if(gsc3280_sci->gsc3280_sci_number == 1)
|
|
gsc3280_sci_dev.name = SCI1_DEV_NAME;
|
|
|
|
gsc3280_sci_dev.fops = &gsc3280_sci_fops;
|
|
gsc3280_sci_dev.parent = &(pdev->dev);
|
|
misc_register(&gsc3280_sci_dev);
|
|
return ret;
|
|
err:
|
|
clk_put(gsc3280_sci->clk);
|
|
err_kfree:
|
|
kfree(gsc3280_sci);
|
|
return ret;
|
|
}
|
|
|
|
static int gsc3280_sci_remove(struct platform_device *pdev)
|
|
{
|
|
gsc3280_sci_t *gsc3280_sci = platform_get_drvdata(pdev);
|
|
|
|
if (gsc3280_sci->irq)
|
|
free_irq(gsc3280_sci->irq, gsc3280_sci);
|
|
iounmap(gsc3280_sci->ioaddr);
|
|
kfree(gsc3280_sci);
|
|
release_mem_region(gsc3280_sci->res->start,
|
|
gsc3280_sci->res->end - gsc3280_sci->res->start + 1);
|
|
misc_deregister(&gsc3280_sci_dev);
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver gsc3280_sci_driver = {
|
|
.driver = {
|
|
.name = DRIVER_NAME,
|
|
.owner= THIS_MODULE,
|
|
},
|
|
.probe = gsc3280_sci_probe,
|
|
.remove = gsc3280_sci_remove,
|
|
.suspend = NULL,
|
|
.resume = NULL,
|
|
};
|
|
|
|
static int __init gsc3280_sci_drv_init(void)
|
|
{
|
|
return platform_driver_register(&gsc3280_sci_driver);
|
|
}
|
|
|
|
static void __exit gsc3280_sci_drv_exit(void)
|
|
{
|
|
platform_driver_unregister(&gsc3280_sci_driver);
|
|
}
|
|
|
|
module_init(gsc3280_sci_drv_init);
|
|
module_exit(gsc3280_sci_drv_exit);
|
|
|
|
MODULE_AUTHOR("lufei<lufei@china-cpu.com>");
|
|
MODULE_DESCRIPTION("BLX GSC3280 SoC SCI(7816) Controller Driver");
|
|
MODULE_LICENSE("GPL");
|