/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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(iioaddr + 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(iioaddr + 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"); MODULE_DESCRIPTION("BLX GSC3280 SoC SCI(7816) Controller Driver"); MODULE_LICENSE("GPL");