SmartAudio/lichee/linux-4.9/drivers/char/sunxi-scr/sunxi-scr.c

1481 lines
37 KiB
C
Executable File

/*
* drivers/char/sunxi-scr/sunxi-scr.c
*
* Copyright (C) 2016 Allwinner.
* fuzhaoke <fuzhaoke@allwinnertech.com>
*
* SUNXI SCR Controller Driver
*
* 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/kernel.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <linux/err.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/major.h>
#include <linux/poll.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <asm/irq.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include "sunxi-scr.h"
/* ==================== For debug =============================== */
#define SCR_ENTER() pr_info("%s()%d - %s\n", __func__, __LINE__, "Enter ...")
#define SCR_EXIT() pr_info("%s()%d - %s\n", __func__, __LINE__, "Exit")
#define SCR_DBG(fmt, arg...) pr_debug("%s()%d - "fmt, __func__, __LINE__, ##arg)
#define SCR_INFO(fmt, arg...) pr_info("%s()%d - "fmt, __func__, __LINE__, ##arg)
#define SCR_WARN(fmt, arg...) pr_warn("%s()%d - "fmt, __func__, __LINE__, ##arg)
#define SCR_ERR(fmt, arg...) pr_err("%s()%d - "fmt, __func__, __LINE__, ##arg)
static struct sunxi_scr *pscr;
static int sunxi_scr_major;
static struct class *scr_dev_class;
static struct device *scr_device;
struct scr_data {
uint8_t buf[SCR_BUF_SIZE];
uint16_t cnt; /* valid count of data */
};
static struct scr_data scr_buf_rx, scr_buf_tx;
static void sunxi_scr_do_atr(struct sunxi_scr *pscr);
/* ================= smart card reader basic interface =================== */
/* clear control and status register */
static inline void scr_clear_csr_reg(void __iomem *reg_base)
{
writel(0x0, reg_base + SCR_CSR_OFF);
}
/* get control and status register config */
static inline uint32_t scr_get_csr_config(void __iomem *reg_base)
{
return readl(reg_base + SCR_CSR_OFF);
}
/* get detect status 0:card remove, 1:card insert */
static inline uint8_t scr_get_det_status(void __iomem *reg_base)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_CSR_OFF);
return (reg_val >> 31) & 0x1;
}
/* set detect polarity, 0:low active, 1:high active */
static inline void scr_set_det_polar(void __iomem *reg_base, uint8_t config)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_CSR_OFF);
reg_val &= ~(0x1 << 24);
reg_val |= ((config & 0x1) << 24);
writel(reg_val, reg_base + SCR_CSR_OFF);
}
/* set protocol 0:T0, 1:T1, 2~3:reserved */
static inline void scr_set_t_protocol(void __iomem *reg_base, uint8_t config)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_CSR_OFF);
reg_val &= ~(0x3 << 22);
reg_val |= ((config & 0x3) << 22);
writel(reg_val, reg_base + SCR_CSR_OFF);
}
/* when enable(1), both RX&TX FIFO will be flush before ATR start */
static inline void scr_set_atr_flush(void __iomem *reg_base, uint8_t config)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_CSR_OFF);
reg_val &= ~(0x1 << 21);
reg_val |= ((config & 0x1) << 21);
writel(reg_val, reg_base + SCR_CSR_OFF);
}
/* when enable(1), TS character(first ATR character) will be stored in FIFO */
static inline void scr_set_ts_recv(void __iomem *reg_base, uint8_t config)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_CSR_OFF);
reg_val &= ~(0x1 << 20);
reg_val |= ((config & 0x1) << 20);
writel(reg_val, reg_base + SCR_CSR_OFF);
}
/* scclk output state during clock stop, 0:low level, 1:high level */
static inline void scr_set_clk_polar(void __iomem *reg_base, uint8_t config)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_CSR_OFF);
reg_val &= ~(0x1 << 19);
reg_val |= ((config & 0x1) << 19);
writel(reg_val, reg_base + SCR_CSR_OFF);
}
/* parity error character receive enable,
* 0:disable, 1:enable store error parity in RX FIFO
*/
static inline void scr_set_recv_parity(void __iomem *reg_base, uint8_t config)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_CSR_OFF);
reg_val &= ~(0x1 << 18);
reg_val |= ((config & 0x1) << 18);
writel(reg_val, reg_base + SCR_CSR_OFF);
}
static inline uint8_t scr_get_recv_parity(void __iomem *reg_base)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_CSR_OFF);
return (reg_val >> 18) & 0x1;
}
/* 0:lsb first, 1:msb first */
static inline void scr_set_data_order(void __iomem *reg_base, uint8_t config)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_CSR_OFF);
reg_val &= ~(0x1 << 17);
reg_val |= ((config & 0x1) << 17);
writel(reg_val, reg_base + SCR_CSR_OFF);
}
/* when enable(1), invert data level */
static inline void scr_set_data_invert(void __iomem *reg_base, uint8_t config)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_CSR_OFF);
reg_val &= ~(0x1 << 16);
reg_val |= ((config & 0x1) << 16);
writel(reg_val, reg_base + SCR_CSR_OFF);
}
/* deinit card after stop use and auto clear when finish */
static inline void scr_set_deactivation(void __iomem *reg_base)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_CSR_OFF);
reg_val |= (0x1 << 11);
writel(reg_val, reg_base + SCR_CSR_OFF);
}
/* init card before start use and auto clear when finish */
static inline void scr_set_activation(void __iomem *reg_base)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_CSR_OFF);
reg_val |= (0x1 << 10);
writel(reg_val, reg_base + SCR_CSR_OFF);
}
/* reset card */
static inline void scr_set_warmreset(void __iomem *reg_base)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_CSR_OFF);
reg_val |= (0x1 << 9);
writel(reg_val, reg_base + SCR_CSR_OFF);
}
static inline void scr_set_clk_stop(void __iomem *reg_base)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_CSR_OFF);
reg_val |= (0x1 << 8);
writel(reg_val, reg_base + SCR_CSR_OFF);
}
static inline void scr_set_clk_restart(void __iomem *reg_base)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_CSR_OFF);
reg_val &= ~(0x1 << 8);
writel(reg_val, reg_base + SCR_CSR_OFF);
}
static inline void scr_global_interrupt_enable(void __iomem *reg_base)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_CSR_OFF);
reg_val |= (0x1 << 2);
writel(reg_val, reg_base + SCR_CSR_OFF);
}
static inline void scr_global_interrupt_disable(void __iomem *reg_base)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_CSR_OFF);
reg_val &= ~(0x1 << 2);
writel(reg_val, reg_base + SCR_CSR_OFF);
}
/* enable receive data stored in RX FIFO */
static inline void scr_receive_enable(void __iomem *reg_base)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_CSR_OFF);
reg_val |= (0x1 << 1);
writel(reg_val, reg_base + SCR_CSR_OFF);
}
/* disable receive data stored in RX FIFO */
static inline void scr_receive_disable(void __iomem *reg_base)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_CSR_OFF);
reg_val &= ~(0x1 << 1);
writel(reg_val, reg_base + SCR_CSR_OFF);
}
/* enable transmit data stored in TX FIFO */
static inline void scr_transmit_enable(void __iomem *reg_base)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_CSR_OFF);
reg_val |= (0x1 << 0);
writel(reg_val, reg_base + SCR_CSR_OFF);
}
/* disable transmit data stored in TX FIFO */
static inline void scr_transmit_disable(void __iomem *reg_base)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_CSR_OFF);
reg_val &= ~(0x1 << 0);
writel(reg_val, reg_base + SCR_CSR_OFF);
}
/* set each interrupt bit mask */
static inline void scr_set_interrupt_enable(void __iomem *reg_base, uint32_t bm)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_INTEN_OFF);
reg_val |= bm;
writel(reg_val, reg_base + SCR_INTEN_OFF);
}
/* disable interrupt bit mask */
static inline void scr_set_interrupt_disable(void __iomem *reg_base,
uint32_t bm)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_INTEN_OFF);
reg_val &= ~bm;
writel(reg_val, reg_base + SCR_INTEN_OFF);
}
/* get all interrupt status */
static inline uint32_t scr_get_interrupt_status(void __iomem *reg_base)
{
return readl(reg_base + SCR_INTST_OFF);
}
/* write 1 to clear interrupt flag */
static inline void scr_clear_interrupt_status(void __iomem *reg_base,
uint32_t bm)
{
writel(bm, reg_base + SCR_INTST_OFF);
}
/* flush RX FIFO and auto clear when finish */
static inline void scr_flush_rxfifo(void __iomem *reg_base)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_FCSR_OFF);
reg_val |= (0x1 << 10);
writel(reg_val, reg_base + SCR_FCSR_OFF);
}
static inline bool scr_rxfifo_is_full(void __iomem *reg_base)
{
return (readl(reg_base + SCR_FCSR_OFF) >> 9) & 0x1;
}
static inline bool scr_rxfifo_is_empty(void __iomem *reg_base)
{
return (readl(reg_base + SCR_FCSR_OFF) >> 8) & 0x1;
}
/* flush TX FIFO and auto clear when finish */
static inline void scr_flush_txfifo(void __iomem *reg_base)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_FCSR_OFF);
reg_val |= (0x1 << 2);
writel(reg_val, reg_base + SCR_FCSR_OFF);
}
static inline bool scr_txfifo_is_full(void __iomem *reg_base)
{
return (readl(reg_base + SCR_FCSR_OFF) >> 1) & 0x1;
}
static inline bool scr_txfifo_is_empty(void __iomem *reg_base)
{
return readl(reg_base + SCR_FCSR_OFF) & 0x1;
}
static inline void scr_set_rxfifo_threshold(void __iomem *reg_base, uint8_t thh)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_FCNT_OFF);
reg_val &= ~(0xffU << 24);
reg_val |= thh << 24;
writel(reg_val, reg_base + SCR_FCNT_OFF);
}
static inline void scr_set_txfifo_threshold(void __iomem *reg_base, uint8_t thh)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_FCNT_OFF);
reg_val &= ~(0xffU << 16);
reg_val |= thh << 16;
writel(reg_val, reg_base + SCR_FCNT_OFF);
}
static inline uint8_t scr_get_rxfifo_count(void __iomem *reg_base)
{
return (readl(reg_base + SCR_FCNT_OFF) >> 8) & 0xff;
}
static inline uint8_t scr_get_txfifo_count(void __iomem *reg_base)
{
return readl(reg_base + SCR_FCNT_OFF) & 0xff;
}
static inline void scr_set_rx_repeat(void __iomem *reg_base, uint8_t repeat)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_RPT_OFF);
reg_val &= ~(0xf << 4);
reg_val |= (repeat & 0xf) << 4;
writel(reg_val, reg_base + SCR_RPT_OFF);
}
static inline void scr_set_tx_repeat(void __iomem *reg_base, uint8_t repeat)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_RPT_OFF);
reg_val &= ~(0xf << 0);
reg_val |= (repeat & 0xf) << 0;
writel(reg_val, reg_base + SCR_RPT_OFF);
}
/* baud = F_sysclk/(2*(BAUDDIV+1)), BAUDDIV=bit[31:16] */
static inline void scr_set_baud_divisor(void __iomem *reg_base,
uint16_t divisor)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_DIV_OFF);
reg_val &= ~(0xffffU << 16);
reg_val |= divisor << 16;
writel(reg_val, reg_base + SCR_DIV_OFF);
}
static inline uint16_t scr_get_baud_divisor(void __iomem *reg_base)
{
return readl(reg_base + SCR_DIV_OFF) >> 16 & 0xffff;
}
/* F_scclk = F_sysclk/(2*(SCCLK+1)), SCCLK=bit[15:0] */
static inline void scr_set_scclk_divisor(void __iomem *reg_base,
uint16_t divisor)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_DIV_OFF);
reg_val &= ~0xffffU;
reg_val |= divisor;
writel(reg_val, reg_base + SCR_DIV_OFF);
}
static inline uint16_t scr_get_scclk_divisor(void __iomem *reg_base)
{
return readl(reg_base + SCR_DIV_OFF) & 0xffff;
}
/* ATR start time limit, it define the maximum time of ATR response
* limit_time = 128*ATR*T_scclk, ATR=bit[23:16], T_scclk=1/F_scclk
*/
static inline void scr_set_atr_time(void __iomem *reg_base, uint8_t scclk)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_LTIM_OFF);
reg_val &= ~(0xff << 16);
reg_val |= scclk << 16;
writel(reg_val, reg_base + SCR_LTIM_OFF);
}
/* reset duration, dura = 128*RST*T_scclk, RST=bit[15:8], T_scclk=1/F_scclk */
static inline void scr_set_reset_time(void __iomem *reg_base, uint8_t scclk)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_LTIM_OFF);
reg_val &= ~(0xff << 8);
reg_val |= scclk << 8;
writel(reg_val, reg_base + SCR_LTIM_OFF);
}
/* reset duration, dura = 128*ACT*T_scclk, ACT=bit[7:0], T_scclk=1/F_scclk */
static inline void scr_set_activation_time(void __iomem *reg_base,
uint8_t scclk)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_LTIM_OFF);
reg_val &= ~0xff;
reg_val |= scclk;
writel(reg_val, reg_base + SCR_LTIM_OFF);
}
static inline uint32_t scr_get_line_time(void __iomem *reg_base)
{
return readl(reg_base + SCR_LTIM_OFF);
}
/* character limit, maximum time of two consecutive character, ETU as unit */
static inline void scr_set_chlimit_time(void __iomem *reg_base, uint16_t etu)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_CTIM_OFF);
reg_val &= ~(0xffffU << 16);
reg_val |= etu << 16;
writel(reg_val, reg_base + SCR_CTIM_OFF);
}
/* character guard time, delay time of each character, ETU as unit */
static inline void scr_set_guard_time(void __iomem *reg_base, uint8_t etu)
{
uint32_t reg_val;
reg_val = readl(reg_base + SCR_CTIM_OFF);
reg_val &= ~0xff;
reg_val |= etu;
writel(reg_val, reg_base + SCR_CTIM_OFF);
}
static inline uint32_t scr_get_character_time(void __iomem *reg_base)
{
return readl(reg_base + SCR_CTIM_OFF);
}
static inline uint32_t scr_get_fsm(void __iomem *reg_base)
{
return readl(reg_base + SCR_FSM_OFF);
}
static inline void scr_write_fifo(void __iomem *reg_base, uint8_t data)
{
writel(data, reg_base + SCR_FIFO_OFF);
}
static inline uint8_t scr_read_fifo(void __iomem *reg_base)
{
return readl(reg_base + SCR_FIFO_OFF) & 0xff;
}
/* ========================= end =================================== */
/* IRQ interrupt handler */
static irqreturn_t sunxi_scr_interrupt(int irqno, void *dev_id)
{
struct sunxi_scr *pscr = (struct sunxi_scr *)dev_id;
uint32_t rx_cnt = 0, i = 0;
u32 irq_status = 0;
irq_status = scr_get_interrupt_status(pscr->reg_base);
scr_clear_interrupt_status(pscr->reg_base, irq_status);
SCR_DBG("irq_status = 0x%08x\n", irq_status);
if (irq_status & SCR_INTSTA_INS) {
SCR_DBG("SmartCard Inserted!!\n");
scr_set_activation(pscr->reg_base);
pscr->card_in = true;
/* avoid error multi trigger */
mod_timer(&pscr->poll_timer, jiffies + HZ/100); /* 10ms */
}
if (irq_status & SCR_INTSTA_REM) {
SCR_DBG("SmartCard Removed!!\n\n");
scr_set_deactivation(pscr->reg_base);
pscr->card_in = false;
/* avoid error multi trigger */
mod_timer(&pscr->poll_timer, jiffies + HZ/100); /* 10ms */
}
if (irq_status & SCR_INTSTA_ACT) {
SCR_DBG("SmartCard Activated!!\n");
memset(&scr_buf_rx, 0, sizeof(struct scr_data));
pscr->card_in = true;
}
if (irq_status & SCR_INTSTA_DEACT) {
SCR_DBG("SmartCard Deactivated!!\n");
pscr->card_in = false;
}
if ((irq_status & SCR_INTSTA_RXDONE) ||
(irq_status & SCR_INTSTA_RXFTH) ||
(irq_status & SCR_INTSTA_RXFFULL)) {
SCR_DBG("SmartCard Rx interrupt!!\n");
rx_cnt = scr_get_rxfifo_count(pscr->reg_base);
SCR_DBG("rx_cnt=%d\n", rx_cnt);
if (rx_cnt > (SCR_BUF_SIZE - scr_buf_rx.cnt)) {
SCR_ERR("There are not more space filled in RX buffer");
} else {
spin_lock(&pscr->rx_lock);
for (i = 0; i < rx_cnt; i++) {
scr_buf_rx.buf[scr_buf_rx.cnt] =
scr_read_fifo(pscr->reg_base);
scr_buf_rx.cnt++;
}
spin_unlock(&pscr->rx_lock);
}
}
if (irq_status & SCR_INTSTA_RXPERR) {
SCR_DBG("SmartCard Rx Parity Error!!\n");
}
if (irq_status & SCR_INTSTA_ATRFAIL) {
SCR_DBG("SmartCard ATR Fail!!\n");
pscr->atr_resp = SCR_ATR_RESP_FAIL;
/* set activation again */
scr_set_activation(pscr->reg_base);
}
if (irq_status & SCR_INTSTA_ATRDONE) {
SCR_DBG("SmartCard ATR Done!!\n");
memcpy(pscr->scr_atr_des.atr_data, scr_buf_rx.buf,
scr_buf_rx.cnt);
pscr->scr_atr_des.atr_len = scr_buf_rx.cnt;
pscr->atr_resp = SCR_ATR_RESP_OK;
/* parse ATR data to reconfig smart card */
sunxi_scr_do_atr(pscr);
}
if (irq_status & SCR_INTSTA_CHTO) {
SCR_DBG("character timeout!!\n");
pscr->rx_transmit_status = SCR_RX_TRANSMIT_TMOUT;
}
if (irq_status & SCR_INTSTA_TXFEMPTY)
SCR_DBG("SmartCard TX Empty!!\n");
if (irq_status & SCR_INTSTA_TXDONE)
SCR_DBG("SmartCard TX Done!!\n");
if (irq_status & SCR_INTSTA_TXPERR)
SCR_DBG("SmartCard TX Error!!\n");
if (irq_status & SCR_INTSTA_TXFDONE)
SCR_DBG("SmartCard TX FIFO Done!!\n");
return IRQ_HANDLED;
}
static int scr_request_gpio(struct sunxi_scr *pscr)
{
int ret = 0;
struct pinctrl_state *pctrl_state = NULL;
pscr->scr_pinctrl = devm_pinctrl_get(&(pscr->scr_device->dev));
if (IS_ERR_OR_NULL(pscr->scr_pinctrl)) {
SCR_ERR("request pinctrl handle fail!\n");
return -EINVAL;
}
pctrl_state = pinctrl_lookup_state(pscr->scr_pinctrl,
PINCTRL_STATE_DEFAULT);
if (IS_ERR(pctrl_state)) {
SCR_ERR("pinctrl_lookup_state fail! return %p\n", pctrl_state);
return -EINVAL;
}
ret = pinctrl_select_state(pscr->scr_pinctrl, pctrl_state);
if (ret < 0)
SCR_ERR("pinctrl_select_state fail! return %d\n", ret);
return ret;
}
static void scr_release_gpio(struct sunxi_scr *pscr)
{
if (!IS_ERR_OR_NULL(pscr->scr_pinctrl))
devm_pinctrl_put(pscr->scr_pinctrl);
pscr->scr_pinctrl = NULL;
}
static uint32_t scr_init_reg(struct sunxi_scr *pscr)
{
scr_global_interrupt_disable(pscr->reg_base);
scr_set_interrupt_disable(pscr->reg_base, 0xffffffff);
scr_clear_interrupt_status(pscr->reg_base, 0xffffffff);
scr_flush_txfifo(pscr->reg_base);
scr_flush_rxfifo(pscr->reg_base);
scr_set_txfifo_threshold(pscr->reg_base, pscr->txfifo_thh);
scr_set_rxfifo_threshold(pscr->reg_base, pscr->rxfifo_thh);
scr_set_tx_repeat(pscr->reg_base, pscr->tx_repeat);
scr_set_rx_repeat(pscr->reg_base, pscr->rx_repeat);
scr_set_scclk_divisor(pscr->reg_base, pscr->scclk_div);
scr_set_baud_divisor(pscr->reg_base, pscr->baud_div);
scr_set_activation_time(pscr->reg_base, pscr->act_time);
scr_set_reset_time(pscr->reg_base, pscr->rst_time);
scr_set_atr_time(pscr->reg_base, pscr->atr_time);
scr_set_guard_time(pscr->reg_base, pscr->guard_time);
scr_set_chlimit_time(pscr->reg_base, pscr->chlimit_time);
scr_set_atr_flush(pscr->reg_base, 1);
scr_set_ts_recv(pscr->reg_base, 1);
scr_set_t_protocol(pscr->reg_base, pscr->card_para.protocol_type);
scr_receive_enable(pscr->reg_base);
scr_transmit_enable(pscr->reg_base);
scr_set_interrupt_enable(pscr->reg_base, pscr->inten_bm);
scr_global_interrupt_enable(pscr->reg_base);
scr_set_recv_parity(pscr->reg_base,
pscr->card_para.recv_no_parity);
return 0;
}
static void sunxi_scr_param_init(struct sunxi_scr *pscr)
{
/* init register parameters */
pscr->inten_bm = 0xfffffff0;
pscr->txfifo_thh = SCR_FIFO_DEPTH;
pscr->rxfifo_thh = SCR_FIFO_DEPTH;
pscr->tx_repeat = 0x3;
pscr->rx_repeat = 0x3;
/* (APB1CLK/4000000) PCLK/14, <175, && SCCLK >= 1M && =<4M */
pscr->scclk_div = 0;
pscr->baud_div = 0; /* ETU = 372*SCCLK */
pscr->act_time = 2; /* =1*256, 100 */
pscr->rst_time = 0xff; /* 2*256, >=400 */
/* scr.atr_time = (40000>>8)+1; //=256*256, 400~40000 */
pscr->atr_time = 0xff;
pscr->guard_time = 2; /* =2*ETUs */
/* interval time (400-1) characters */
pscr->chlimit_time = 100 * (10 + pscr->guard_time);
pscr->atr_resp = SCR_ATR_RESP_INVALID;
pscr->rx_transmit_status = SCR_RX_TRANSMIT_NOYET;
/* init card parameters */
pscr->card_para.f = 372;
pscr->card_para.d = 1;
/* 3.579MHz, unit is KHz, expect baud=9600bps*/
pscr->card_para.freq = 3579;
pscr->card_para.recv_no_parity = 1;
pscr->card_para.protocol_type = 0;
/* init atr data */
pscr->smc_atr_para.TS = 0x3B;
pscr->smc_atr_para.TK_NUM = 0x00;
pscr->smc_atr_para.T = 0; /* T=0 Protocol */
pscr->smc_atr_para.FMAX = 4; /* 4MHz, unit is MHz */
pscr->smc_atr_para.F = 372;
pscr->smc_atr_para.D = 1;
pscr->smc_atr_para.I = 50; /* 50mA */
pscr->smc_atr_para.P = 5; /* 5V */
pscr->smc_atr_para.N = 2;
/* pscr->clk_freq'unit is hz but pscr->card_para.freq'unit is khz */
pscr->scclk_div = pscr->clk_freq / pscr->card_para.freq / 2000 - 1;
pscr->baud_div = (pscr->scclk_div + 1) *
(pscr->card_para.f / pscr->card_para.d) - 1;
SCR_DBG("clk_freq=%d, scclk_div=%d, baud_div=%d\n",
pscr->clk_freq, pscr->scclk_div, pscr->baud_div);
/* init registers */
scr_init_reg(pscr);
}
/* use ATR data to reconfig smart card control register */
static void sunxi_scr_do_atr(struct sunxi_scr *pscr)
{
struct scr_atr *pscr_atr_des = &pscr->scr_atr_des;
struct smc_atr_para *psmc_atr_para = &pscr->smc_atr_para;
struct smc_pps_para *psmc_pps_para = &pscr->smc_pps_para;
SCR_DBG("\nBefore Decode:\n"
"psmc_atr_para->TS = 0x%x\n"
"psmc_atr_para->T = %d\n"
"psmc_atr_para->FMAX = %d(MHz), Current: %d(KHz)\n"
"psmc_atr_para->F = %d\n"
"psmc_atr_para->D = %d\n",
psmc_atr_para->TS,
psmc_atr_para->T,
psmc_atr_para->FMAX,
pscr->card_para.freq, psmc_atr_para->F, psmc_atr_para->D);
smartcard_atr_decode(psmc_atr_para, psmc_pps_para,
(uint8_t *)&pscr_atr_des->atr_data, 1);
SCR_DBG("\nAfter Decode:\n"
"psmc_atr_para->TS = 0x%x\n"
"psmc_atr_para->T = %d\n"
"psmc_atr_para->FMAX = %d(MHz), Current: %d(KHz)\n"
"psmc_atr_para->F = %d\n"
"psmc_atr_para->D = %d\n",
psmc_atr_para->TS,
psmc_atr_para->T,
psmc_atr_para->FMAX,
pscr->card_para.freq, psmc_atr_para->F, psmc_atr_para->D);
/* use default F&D or set by up layer
pscr->card_para.f = pscr->smc_atr_para.F;
pscr->card_para.d = pscr->smc_atr_para.D;
pscr->card_para.freq = psmc_atr_para->FMAX * 1000;
pscr->scclk_div = pscr->clk_freq/pscr->card_para.freq/2000-1;
pscr->baud_div = (pscr->scclk_div + 1) *
(pscr->card_para.f/pscr->card_para.d)-1;
scr_set_scclk_divisor(pscr->reg_base, pscr->scclk_div);
scr_set_baud_divisor(pscr->reg_base, pscr->baud_div);
*/
pscr->card_para.protocol_type = psmc_atr_para->T;
scr_set_t_protocol(pscr->reg_base, pscr->card_para.protocol_type);
if (0x3f == psmc_atr_para->TS) {
/* 0x3b:direct convention, 0x3f:inverse convention */
scr_set_data_order(pscr->reg_base, 1);
scr_set_data_invert(pscr->reg_base, 1);
}
}
static uint32_t sunxi_scr_clk_init(struct sunxi_scr *pscr)
{
struct platform_device *pdev = pscr->scr_device;
struct device_node *node = pdev->dev.of_node;
if (NULL == pdev || !of_device_is_available(node)) {
SCR_ERR("platform_device invalid!\n");
return -EINVAL;
}
pscr->scr_clk = of_clk_get(node, 0);
if (!pscr->scr_clk || IS_ERR(pscr->scr_clk)) {
SCR_ERR("try to get scr clock fail!\n");
return -EINVAL;
}
pscr->scr_clk_source = of_clk_get(node, 1);
if (!pscr->scr_clk_source || IS_ERR(pscr->scr_clk_source)) {
SCR_ERR("err: try to get scr_clk_source clock fail!\n");
return -EINVAL;
}
if (clk_set_parent(pscr->scr_clk, pscr->scr_clk_source)) {
SCR_ERR("set scr_clk parent to scr_clk_source fail!\n");
return -EINVAL;
}
if (of_property_read_u32(node, "clock-frequency", &pscr->clk_freq)) {
SCR_INFO("get clock-frequency fail! use default 24Mhz\n");
pscr->clk_freq = 24000000;
}
if (clk_set_rate(pscr->scr_clk, pscr->clk_freq)) {
SCR_ERR("set ir scr_clk freq failed!\n");
return -EINVAL;
}
if (clk_prepare_enable(pscr->scr_clk)) {
SCR_ERR("try to enable scr_clk failed!\n");
return -EINVAL;
}
return 0;
}
static uint32_t sunxi_scr_clk_exit(struct sunxi_scr *pscr)
{
if (NULL == pscr->scr_clk || IS_ERR(pscr->scr_clk)) {
SCR_ERR("scr_clk handle is invalid, just return!\n");
return -EINVAL;
} else {
clk_disable_unprepare(pscr->scr_clk);
clk_put(pscr->scr_clk);
pscr->scr_clk = NULL;
}
if (NULL == pscr->scr_clk_source || IS_ERR(pscr->scr_clk_source)) {
SCR_ERR("scr_clk_source handle is invalid, just return!\n");
return -EINVAL;
} else {
clk_put(pscr->scr_clk_source);
pscr->scr_clk_source = NULL;
}
return 0;
}
static int sunxi_scr_open(struct inode *inode, struct file *file)
{
file->private_data = pscr;
if (pscr->open_cnt > 0) {
SCR_DBG("smart card opened already\n");
pscr->open_cnt++;
return 0;
}
sunxi_scr_param_init(pscr);
pscr->card_in = scr_get_det_status(pscr->reg_base) ? true : false;
pscr->card_last = pscr->card_in;
if (true == pscr->card_in)
scr_set_activation(pscr->reg_base);
pscr->open_cnt++;
return 0;
}
static int sunxi_scr_release(struct inode *inode, struct file *file)
{
struct sunxi_scr *pscr = file->private_data;
if (--pscr->open_cnt) {
SCR_DBG("There is not really close, just return!\n");
return 0;
}
scr_set_deactivation(pscr->reg_base);
scr_clear_csr_reg(pscr->reg_base);
return 0;
}
static ssize_t
sunxi_scr_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
struct sunxi_scr *pscr = file->private_data;
uint32_t rx_size = 0;
int try_num = 100;
while ((SCR_RX_TRANSMIT_NOYET == pscr->rx_transmit_status) && try_num--)
msleep(50);
if (try_num < 0) {
SCR_ERR("read timeout\n");
return -EAGAIN;
}
rx_size = scr_buf_rx.cnt;
if (rx_size > size)
rx_size = size;
if (copy_to_user(buf, scr_buf_rx.buf, rx_size))
return -EFAULT;
scr_flush_rxfifo(pscr->reg_base);
return rx_size;
}
static int scr_write(struct sunxi_scr *pscr, char *buf, int size)
{
int try_num = 100;
int i;
for (i = 0; i < size; i++) {
while (scr_txfifo_is_full(pscr->reg_base) && try_num--)
msleep(50);
if (try_num < 0) {
SCR_ERR("TX FIFO full, write timeout\n");
break;
}
scr_write_fifo(pscr->reg_base, buf[i]);
try_num = 100;
}
SCR_DBG("writed %d byte\n", i);
return i;
}
static ssize_t sunxi_scr_write(struct file *file, const char __user *buf,
size_t size, loff_t *ppos)
{
struct sunxi_scr *pscr = file->private_data;
if (copy_from_user(scr_buf_tx.buf, buf, size))
return -EFAULT;
scr_flush_txfifo(pscr->reg_base);
memset(&scr_buf_rx, 0, sizeof(struct scr_data));
pscr->rx_transmit_status = SCR_RX_TRANSMIT_NOYET;
return scr_write(pscr, scr_buf_tx.buf, size);
}
static void scr_timer_handler(unsigned long data)
{
(void)data;
wake_up(&pscr->scr_poll);
}
unsigned int sunxi_scr_poll(struct file *file, struct poll_table_struct *wait)
{
struct sunxi_scr *pscr = file->private_data;
unsigned int mask = 0;
/* add wait_queue to poll_table */
poll_wait(file, &pscr->scr_poll, wait);
/* using Edge Triggered instand of Level Triggered */
if (pscr->card_last^pscr->card_in) {
if (pscr->card_in)
mask |= POLLIN;
else
mask |= POLLOUT;
pscr->card_last = pscr->card_in;
}
return mask;
}
static long sunxi_scr_ioctl(struct file *file, uint32_t cmd, unsigned long arg)
{
struct sunxi_scr *pscr = file->private_data;
uint32_t tmp = 0, ret = 0;
int try_num = 300;
SCR_ENTER();
switch (cmd) {
/* get smart card status 0:SCR_CARD_OUT, 1:SCR_CARD_IN */
case SCR_IOCGSTATUS:
tmp = scr_get_det_status(pscr->reg_base);
ret = put_user(tmp, (int __user *)arg);
break;
/* reset card and store ATR data immediately */
case SCR_IOCRESET:
scr_set_activation(pscr->reg_base);
mdelay(10);
break;
/* get ATR data, the arg type is struct scr_atr */
case SCR_IOCGATR:
while (SCR_ATR_RESP_OK != pscr->atr_resp && try_num--) {
msleep(10);
};
if (try_num < 0) {
SCR_ERR("SCR_IOCGATR timeout!\n");
ret = -EAGAIN;
break;
}
ret = copy_to_user((void __user *)arg, &pscr->scr_atr_des,
sizeof(struct scr_atr)) ? -EFAULT : 0;
break;
/* get current card parameters & status */
case SCR_IOCGPARA:
ret = copy_to_user((void __user *)arg, &pscr->card_para,
sizeof(struct scr_card_para)) ? -EFAULT : 0;
break;
/* set current card parameters & status */
case SCR_IOCSPARA:
if (copy_from_user(&pscr->card_para, (void __user *)arg,
sizeof(struct scr_card_para))) {
SCR_ERR("get card para from user error!\n");
ret = -EFAULT;
break;
}
pscr->scclk_div = pscr->clk_freq / pscr->card_para.freq / 2000
- 1;
pscr->baud_div = (pscr->scclk_div + 1) *
(pscr->card_para.f / pscr->card_para.d) - 1;
scr_set_scclk_divisor(pscr->reg_base, pscr->scclk_div);
scr_set_baud_divisor(pscr->reg_base, pscr->baud_div);
scr_set_recv_parity(pscr->reg_base,
pscr->card_para.recv_no_parity);
scr_set_t_protocol(pscr->reg_base,
pscr->card_para.protocol_type);
break;
/* get the parse parameters come from ATR data */
case SCR_IOCGATRPARA:
ret = copy_to_user((void __user *)arg, &pscr->smc_atr_para,
sizeof(struct smc_atr_para)) ? -EFAULT : 0;
break;
/* get the pps parse parameters come from ATR data */
case SCR_IOCGPPSPARA:
ret = copy_to_user((void __user *)arg, &pscr->smc_pps_para,
sizeof(struct smc_pps_para)) ? -EFAULT : 0;
break;
/* write cmd and read data immediately */
case SCR_IOCWRDATA: {
int rtn_data_len;
struct scr_wr_data wr_data;
if (copy_from_user(&wr_data, (void __user *)arg,
sizeof(struct scr_wr_data))) {
SCR_ERR("get wr_data from user error!\n");
ret = -EFAULT;
break;
}
if (copy_from_user(scr_buf_tx.buf,
(void __user *)wr_data.cmd_buf,
wr_data.cmd_len)) {
SCR_ERR("get wr_data cmd_buf from user error!\n");
ret = -EFAULT;
break;
}
scr_buf_tx.cnt = wr_data.cmd_len;
scr_flush_txfifo(pscr->reg_base);
scr_flush_rxfifo(pscr->reg_base);
memset(&scr_buf_rx, 0, sizeof(struct scr_data));
/* APDU, smart card cammand format */
/* type1: CLS + INS + P1 + P2 + le -> only read, le=read size*/
if (5 == scr_buf_tx.cnt) {
scr_write(pscr, scr_buf_tx.buf, 5);
/* respond:
* INS(=buf[1]) + valid_data(=buf[4]) + SW1 + SW2
*/
rtn_data_len = scr_buf_tx.buf[4] + 3;
while ((rtn_data_len > scr_buf_rx.cnt) && try_num--) {
msleep(10);
};
if (try_num < 0) {
SCR_ERR("read timeout\n");
ret = -EFAULT;
break;
}
ret = copy_to_user((void __user *)wr_data.rtn_data,
&scr_buf_rx.buf[1],
scr_buf_tx.buf[4]) ? -EFAULT : 0;
put_user(scr_buf_tx.buf[4], wr_data.rtn_len);
put_user(scr_buf_rx.buf[rtn_data_len - 2],
wr_data.psw1);
put_user(scr_buf_rx.buf[rtn_data_len - 1],
wr_data.psw2);
/* type2: CLS + INS + P1 + P2 + lc + data
* only lc, write data, lc=data size
*/
} else if (scr_buf_tx.buf[4]+5 == scr_buf_tx.cnt) {
scr_write(pscr, scr_buf_tx.buf, 5);
while ((0 == scr_buf_rx.cnt) && try_num--) {
msleep(10);
};
if (try_num < 0) {
SCR_ERR("timeout: there is not any data\n");
ret = -EFAULT;
break;
}
if (scr_buf_rx.buf[0] != scr_buf_tx.buf[1]) {
SCR_ERR("do not support this instruction\n");
ret = -EFAULT;
break;
}
scr_write(pscr, &scr_buf_tx.buf[5], scr_buf_tx.buf[4]);
try_num = 300;
while ((scr_buf_rx.cnt < 3) && try_num--) {
msleep(10);
};
if (try_num < 0) {
SCR_ERR("timeout: get sw1,sw2 fail\n");
ret = -EFAULT;
break;
}
put_user(scr_buf_rx.buf[1], wr_data.psw1);
put_user(scr_buf_rx.buf[2], wr_data.psw2);
/* type3: CLS + INS + P1 + P2 + lc + data +le -> le+lc */
} else if (scr_buf_tx.buf[4]+6 == scr_buf_tx.cnt) {
scr_write(pscr, scr_buf_tx.buf, 5);
while ((0 == scr_buf_rx.cnt) && try_num--) {
msleep(10);
};
if (try_num < 0) {
SCR_ERR("timeout: there is not any data\n");
ret = -EFAULT;
break;
}
if (scr_buf_rx.buf[0] != scr_buf_tx.buf[1]) {
SCR_ERR("do not support this instruction\n");
ret = -EFAULT;
break;
}
scr_write(pscr, &scr_buf_tx.buf[5],
scr_buf_tx.buf[4]+1);
try_num = 300;
/* respond: INS + valid_data + SW1 + SW2 */
rtn_data_len = scr_buf_tx.buf[scr_buf_tx.cnt-1] + 3;
while ((rtn_data_len > scr_buf_rx.cnt) && try_num--) {
msleep(10);
};
if (try_num < 0) {
SCR_ERR("read timeout\n");
ret = -EFAULT;
break;
}
ret = copy_to_user((void __user *)wr_data.rtn_data,
&scr_buf_rx.buf[1],
scr_buf_tx.buf[scr_buf_tx.cnt-1]) ? -EFAULT : 0;
put_user(scr_buf_tx.buf[scr_buf_tx.cnt-1],
wr_data.rtn_len);
put_user(scr_buf_rx.buf[rtn_data_len - 2],
wr_data.psw1);
put_user(scr_buf_rx.buf[rtn_data_len - 1],
wr_data.psw2);
} else {
SCR_ERR("invalid command format\n");
ret = -EFAULT;
break;
}
break;
}
default:
SCR_ERR("Invalid iocontrol command!\n");
break;
}
return ret;
}
static const struct file_operations sunxi_scr_fops = {
.owner = THIS_MODULE,
.llseek = noop_llseek,
.read = sunxi_scr_read,
.write = sunxi_scr_write,
.unlocked_ioctl = sunxi_scr_ioctl,
.open = sunxi_scr_open,
.release = sunxi_scr_release,
.poll = sunxi_scr_poll,
};
static int sunxi_scr_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
struct resource *mem_res = NULL;
int ret = 0;
SCR_ENTER();
pscr = kzalloc(sizeof(struct sunxi_scr), GFP_KERNEL);
if (!pscr) {
SCR_ERR("kzalloc struct sunxi_scr fail!\n");
return -ENOMEM;
}
pscr->scr_device = pdev;
if (!of_device_is_available(node)) {
SCR_ERR("invalid node!\n");
ret = -EINVAL;
goto emloc;
}
if (sunxi_scr_clk_init(pscr)) {
SCR_ERR("sunxi_scr_clk_init fail!\n");
ret = -EINVAL;
goto eclk;
}
pscr->irq_no = platform_get_irq(pdev, 0);
if (pscr->irq_no < 0) {
SCR_ERR("get irq number fail!\n");
ret = -EINVAL;
goto eclk;
}
mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (mem_res == NULL) {
SCR_ERR("failed to get MEM res\n");
ret = -ENXIO;
goto eclk;
}
if (!request_mem_region(mem_res->start,
resource_size(mem_res), mem_res->name)) {
SCR_ERR("failed to request mem region\n");
ret = -EINVAL;
goto eclk;
}
pscr->reg_base = ioremap(mem_res->start, resource_size(mem_res));
if (!pscr->reg_base) {
SCR_ERR("failed to io remap\n");
ret = -EIO;
goto eiomem;
}
pscr->mem_res = mem_res;
if (request_irq(pscr->irq_no, sunxi_scr_interrupt,
IRQF_TRIGGER_NONE, "scr", pscr)) {
SCR_ERR("request irq fail!\n");
ret = -EINVAL;
goto eiomap;
}
if (scr_request_gpio(pscr)) {
SCR_ERR("failed to request gpio\n");
ret = -EINVAL;
goto eirq;
}
spin_lock_init(&pscr->rx_lock);
sunxi_scr_param_init(pscr);
init_waitqueue_head(&pscr->scr_poll);
pscr->poll_timer.expires = jiffies + HZ/100; /* 10ms */
pscr->poll_timer.function = scr_timer_handler;
init_timer(&pscr->poll_timer);
add_timer(&pscr->poll_timer);
/* creat character device */
sunxi_scr_major = register_chrdev(0, SCR_MODULE_NAME, &sunxi_scr_fops);
if (sunxi_scr_major < 0) {
SCR_ERR("register_chrdev fail!\n");
ret = -ENODEV;
goto eirq;
}
scr_dev_class = class_create(THIS_MODULE, SCR_MODULE_NAME);
if (IS_ERR(scr_dev_class)) {
SCR_ERR("class_create fail!\n");
ret = -ENODEV;
goto edev;
}
scr_device = device_create(scr_dev_class, NULL,
MKDEV(sunxi_scr_major, 0),
NULL, SCR_MODULE_NAME);
if (IS_ERR(scr_device)) {
SCR_ERR("device_create fail!\n");
ret = -ENODEV;
goto ecla;
}
platform_set_drvdata(pdev, pscr);
return 0;
ecla:
class_destroy(scr_dev_class);
edev:
unregister_chrdev(sunxi_scr_major, SCR_MODULE_NAME);
eirq:
free_irq(pscr->irq_no, pscr);
eiomap:
iounmap(pscr->reg_base);
eiomem:
release_mem_region(mem_res->start, resource_size(mem_res));
eclk:
sunxi_scr_clk_exit(pscr);
emloc:
kfree(pscr);
return ret;
}
static int sunxi_scr_remove(struct platform_device *pdev)
{
struct sunxi_scr *pscr = platform_get_drvdata(pdev);
device_destroy(scr_dev_class, MKDEV(sunxi_scr_major, 0));
class_destroy(scr_dev_class);
unregister_chrdev(sunxi_scr_major, SCR_MODULE_NAME);
free_irq(pscr->irq_no, pscr);
iounmap(pscr->reg_base);
release_mem_region(pscr->mem_res->start,
resource_size(pscr->mem_res));
scr_release_gpio(pscr);
sunxi_scr_clk_exit(pscr);
del_timer(&pscr->poll_timer);
SCR_EXIT();
return 0;
}
#ifdef CONFIG_PM
static int sunxi_scr_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct sunxi_scr *pscr = platform_get_drvdata(pdev);
struct pinctrl_state *pctrl_state = NULL;
pscr->suspended = true;
if (sunxi_scr_clk_exit(pscr)) {
SCR_ERR("SCR suspend failed !\n");
pscr->suspended = false;
return -1;
}
if (!IS_ERR_OR_NULL(pscr->scr_pinctrl)) {
pctrl_state = pinctrl_lookup_state(pscr->scr_pinctrl,
PINCTRL_STATE_SLEEP);
if (IS_ERR(pctrl_state)) {
SCR_ERR("SCR pinctrl lookup sleep fail\n");
return -1;
}
if (pinctrl_select_state(pscr->scr_pinctrl, pctrl_state) < 0) {
SCR_ERR("SCR pinctrl select sleep fail\n");
return -1;
}
}
disable_irq_nosync(pscr->irq_no);
SCR_DBG("SCR suspend okay\n");
return 0;
}
static int sunxi_scr_resume(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct sunxi_scr *pscr = platform_get_drvdata(pdev);
struct pinctrl_state *pctrl_state = NULL;
pscr->suspended = false;
if (sunxi_scr_clk_init(pscr)) {
SCR_ERR("SCR resume failed !\n");
return -1;
}
if (!IS_ERR_OR_NULL(pscr->scr_pinctrl)) {
pctrl_state = pinctrl_lookup_state(pscr->scr_pinctrl,
PINCTRL_STATE_DEFAULT);
if (IS_ERR(pctrl_state)) {
SCR_ERR("SCR pinctrl lookup default fail\n");
return -1;
}
if (pinctrl_select_state(pscr->scr_pinctrl, pctrl_state) < 0) {
SCR_ERR("SCR pinctrl select default fail\n");
return -1;
}
}
enable_irq(pscr->irq_no);
SCR_DBG("SCR resume okay\n");
return 0;
}
static const struct dev_pm_ops sunxi_scr_dev_pm_ops = {
.suspend = sunxi_scr_suspend,
.resume = sunxi_scr_resume,
};
#define SUNXI_SCR_DEV_PM_OPS (&sunxi_scr_dev_pm_ops)
#else
#define SUNXI_SCR_DEV_PM_OPS NULL
#endif
static const struct of_device_id sunxi_scr_match[] = {
{.compatible = "allwinner,sunxi-scr",},
{},
};
MODULE_DEVICE_TABLE(of, sunxi_scr_match);
static struct platform_driver scr_platform_driver = {
.probe = sunxi_scr_probe,
.remove = sunxi_scr_remove,
.driver = {
.name = SCR_MODULE_NAME,
.owner = THIS_MODULE,
.pm = SUNXI_SCR_DEV_PM_OPS,
.of_match_table = sunxi_scr_match,
},
};
static int __init sunxi_scr_init(void)
{
return platform_driver_register(&scr_platform_driver);
}
static void __exit sunxi_scr_exit(void)
{
platform_driver_unregister(&scr_platform_driver);
}
module_init(sunxi_scr_init);
module_exit(sunxi_scr_exit);
MODULE_DESCRIPTION("Smart Card Driver");
MODULE_AUTHOR("fuzhaoke");
MODULE_LICENSE("GPL");