ckfwq/linux-3.0.4/drivers/char/sci/gsc3280_sci.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");