On Wed, May 28, 2014 at 3:57 AM, Satish Patel <satish.patel@xxxxxx> wrote:This for ePOS -Point of Sale domain. Where we swipe out pin based EMV(Europay/Master/Visa) smartcard. EMVCo defines specification for this.
TI-USIM driver is a platform driver that provides a character
driver interface to user applications.
It allows user applications to call IOCTL's to
perform smart card operations.
What's the usecase? For cellular, isn't the SC typically attached to the modem?
One which present inside kernel is USB based smartcard, which communicates over USB serial line.
Driver currently supports
- Cold & Warm Reset
- T=0 & T=1 protocol
- clock stop mode
- smart card clock configuration
- Tx/Rx application data units (APDU) to smart card
- Interface to PHY using DT & phy interface
Validation is done with ACOS3 smart cards
Signed-off-by: Satish Patel <satish.patel@xxxxxx>
---
.../devicetree/bindings/ti-usim/ti-usim.txt | 32 +
drivers/char/Kconfig | 7 +
drivers/char/Makefile | 1 +
drivers/char/ti-usim-hw.h | 864 ++++++++
drivers/char/ti-usim.c | 2213 ++++++++++++++++++++
Perhaps drivers/char/smartcard or drivers/smartcard would be a better
location. This should be designed assuming we get more than 1
SmartCard controller. Perhaps there already is one in the kernel.
IP can be introduced to future SoCs by TI.
include/linux/ti-usim.h | 111 +
6 files changed, 3228 insertions(+)
create mode 100644 Documentation/devicetree/bindings/ti-usim/ti-usim.txt
create mode 100644 drivers/char/ti-usim-hw.h
create mode 100644 drivers/char/ti-usim.c
create mode 100644 include/linux/ti-usim.h
diff --git a/Documentation/devicetree/bindings/ti-usim/ti-usim.txt b/Documentation/devicetree/bindings/ti-usim/ti-usim.txt
new file mode 100644
index 0000000..4e599e2
--- /dev/null
+++ b/Documentation/devicetree/bindings/ti-usim/ti-usim.txt
@@ -0,0 +1,32 @@
+ti-usim: USIM - Smart Card Controller
+
+Required Properties:
+- compatible: Should be "ti,usim"
This should be more specific like "ti,am43xx-usim".
Depends on IP integration inside SoC. In am43xx, we have 2 options to use - 26Mhz/40Mhz
+- reg: Specifies base physical address and size of the USIM registers
+- interrupts: Interrupt number for the USIM controller
+- ti,hwmods: Name of the hwmod associated to the USIM controller
+
+- clocks : list of clock specifiers, corresponding to entries in the
+ clock-names property
How many clocks and what is their use and order?
Built against "v3.15-rc7"
+- clock-names : should contain "opt_fck" and "opt_fck32" entries, matching
+ entries in the clocks property
+
+Optional properties:
+- pinctrl-0: Should specify pin control group used for this controller.
+- pinctrl-names: Should contain only one value - "default", for more details
+ please refer to pinctrl-bindings.txt
+- phy : Should specify <smart card phy> reference connected to controller
+- phy-slots : No of slots to which controller will communicate
+
+Example:
+
+usim0: usim@48034000 {
+ compatible = "ti,usim";
+ reg = <0x48034000 0x1000>;
+ interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
+ ti,hwmods = "usim0";
+ clocks = <&usim0_opt_fck>, <&usim0_opt_fck32>,
+ <&dpll_per_m2_div4_ck>;
+ clock-names = "opt_fck", "opt_fck32", "fck";
+ status = "disabled";
+ };
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig
index 6e9f74a..c7c5fae 100644
--- a/drivers/char/Kconfig
+++ b/drivers/char/Kconfig
@@ -600,5 +600,12 @@ config TILE_SROM
device appear much like a simple EEPROM, and knows
how to partition a single ROM for multiple purposes.
+config TI_USIM
+ tristate "Character device access to TI's USIM module on AM43X"
+ depends on SOC_AM43XX
|| COMPILE_TEST
To avoid mistake, usually I preferred to used file generated by design team.
+ help
+ This device creates a character device interface that enables
+ user applications to exchange data with TI's USIM module.
+
endmenu
diff --git a/drivers/char/Makefile b/drivers/char/Makefile
index a324f93..f7ee777 100644
--- a/drivers/char/Makefile
+++ b/drivers/char/Makefile
@@ -61,3 +61,4 @@ obj-$(CONFIG_JS_RTC) += js-rtc.o
js-rtc-y = rtc.o
obj-$(CONFIG_TILE_SROM) += tile-srom.o
+obj-$(CONFIG_TI_USIM) += ti-usim.o
diff --git a/drivers/char/ti-usim-hw.h b/drivers/char/ti-usim-hw.h
new file mode 100644
index 0000000..1d3dd6e
--- /dev/null
+++ b/drivers/char/ti-usim-hw.h
@@ -0,0 +1,864 @@
+/*
+ * ti-usim-hw.h - Header file for USIM smart card interface
This can go into the .c file.
+ *
+ * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __TI_USIM_HW_H__
+#define __TI_USIM_HW_H__
+
+#include <linux/types.h>
+#include <linux/ioctl.h>
+#include <linux/sc_phy.h>
+#include <linux/ti-usim.h>
+
+
+#define USIM_MAX_SLOTS 0x2
+
+/* WWT Work Wait Time */
+#define USIM_EMV_WI (10)
+#define USIM_EMV_WWT ((960 * USIM_EMV_WI) + (480))
+/* CGT Character Guard Time */
+#define USIM_EMV_CGT (12)
+
+#define USIM_ATR_TIMEOUT_EMV (20160)
+#define USIM_EMV_ATR_EARLY_TO (370)
+#define USIM_EMV_ATR_MUTE_TO (42000)
+
+#define USIM_MAX_RX_FIFO_SIZE (260)
+#define USIM_MAX_TX_FIFO_SIZE (260)
+#define USIM_MAX_PARITY_RETRIES (7)
+
+#define USIM_IRQ_NATR (0x00000001)
+#define USIM_IRQ_WT (0x00000002)
+#define USIM_IRQ_RXFULL (0x00000004)
+#define USIM_IRQ_TX (0x00000008)
+#define USIM_IRQ_RX (0x00000010)
+#define USIM_IRQ_CD (0x00000020)
+#define USIM_IRQ_EOB (0x00000040)
+#define USIM_IRQ_TOC (0x00000080)
+#define USIM_IRQ_TOB (0x00000100)
+#define USIM_IRQ_RESENT (0x00000200)
+#define USIM_IRQ_TS_ERR (0x00000400)
+#define USIM_IRQ_EMV_ATR_LENGTH_TIME_OUT (0x00000800)
+#define USIM_IRQ_STOP (0x00001000)
+#define USIM_IRQ_PAR_ERR_LEVEL_REACHED (0x00002000)
+#define USIM_IRQ_FRAME_ERR (0x00004000)
+#define USIM_IRQ_RXDMA_RDY (0x00008000)
+#define USIM_IRQ_ATR_START (0x00010000)
+#define USIM_IRQ_ACT_DONE (0x00020000)
+#define USIM_IRQ_DEACT_DONE (0x00040000)
+#define USIM_IRQ_TX_BLOCK_DONE (0x00080000)
+#define USIM_IRQ_TX_BLOCK_REQ (0x00100000)
+
+#define USIM_CONFSCLKMODE_LEGACY 0x0
+#define USIM_CONFSCLKMODE_HF 0x1
+
+/*
+ * Different operating modes supported in USIM.
+ * Programming USIM to a different mode from current mode would
+ * endup in state machine state change within the IPs FSM
+ */
+enum usim_mode {
+ USIM_MODE_LEGACY = 0x0,
+ USIM_MODE_FREEZE = 0x1,
+ USIM_MODE_TXRX = 0x2,
+ USIM_MODE_ATR = 0x3,
+ USIM_MODE_ACT = 0x4,
+ USIM_MODE_DEACT = 0x5,
+ USIM_MODE_IDLE = 0x6,
+};
+
+/*
+ * structure to store slot specific information
+ */
+struct usim_slotcontext {
+ char atr[USIM_MAX_ATRLENGTH];
+ char rxbuf[USIM_MAX_APDU_LENGTH];
+ bool emv;
+ enum usim_mode state;
+ int event;
+ int protocol;
+ enum usim_card_voltage supply;
+ int rx_explen;
+ int rx_counter;
+ int atr_length;
+ enum usim_smartcard_clock clock;
+ enum usim_card_mode card_mode;
+};
+
+struct usim {
+ struct device *dev;
+
+ /* to protect interrput handling */
+ spinlock_t lock;
+ int irq;
+ void __iomem *base;
+ int slot;
+ int max_slots;
+ int phy_present;
+ int txdone;
+ int rxdone;
+ int atrdone;
+ int user_pid;
+ int enable;
+ struct sc_phy *phy;
+ struct usim_slotcontext *slot_ctx;
+
+ struct clk *opt_fclk;
+ struct clk *opt_fclk32;
+ struct clk *usim_dbclk;
+ struct clk *clkdiv32k_ick;
+ struct clk *usim0_fck;
+ struct clk *dpll_core_m4_ck;
+
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *debugfs_root;
+#endif
+};
+
+/*
+ * Register Definitions: Taken from auto generated file
+ */
+#define USIM_REVISION (0x0U)
Parenthesis and U are generally not needed throughout.
All three register's definition/usage are different. It make ease while handling interrupt and checking the status.
+#define USIM_IDENT (0x4U)
+#define USIM_SYSCONFIG (0x10U)
+#define USIM_SYSSTATUS (0x14U)
+#define USIM_IRQSTATUS (0x18U)
+#define USIM_IRQENABLE (0x1cU)
+#define USIM_WAKEUPEN (0x20U)
+#define USIM_CMD (0x24U)
+#define USIM_STAT (0x28U)
+#define USIM_CONF1 (0x2cU)
+#define USIM_CONF2 (0x30U)
+#define USIM_CONF3 (0x34U)
+#define USIM_DRX (0x38U)
+#define USIM_DTX (0x3cU)
+#define USIM_FIFOS (0x40U)
+#define USIM_CGT (0x44U)
+#define USIM_CWT (0x48U)
+#define USIM_BWT (0x4cU)
+#define USIM_DEBUG (0x50U)
+#define USIM_CONF_SAM1_DIV (0x54U)
+#define USIM_CONF4 (0x58U)
+#define USIM_ATR_CLK_PRD_NBS (0x5cU)
+#define USIM_CONF_ETU_DIV (0x60U)
+#define USIM_CONF5 (0x64U)
+#define USIM_TC_GUARD_TIME_ADD (0x68U)
+#define USIM_RXFIFO_LEVEL (0x6cU)
+#define USIM_RXFIFO_BYTECNT (0x70U)
+#define USIM_WWT (0x74U)
+#define USIM_CONF6 (0x78U)
+#define USIM_IO_DIRECT (0x7cU)
+#define USIM_TX_BLOCK (0x84U)
+
+/*
+ * Field Definition Macros
+ */
+#define USIM_REVISION_REV_SHIFT (0U)
+#define USIM_REVISION_REV_MASK (0x000000ffU)
+
+#define USIM_REVISION_RESERVED_24_SHIFT (8U)
+#define USIM_REVISION_RESERVED_24_MASK (0xffffff00U)
+
+#define USIM_IDENT_VC_SHIFT (0U)
+#define USIM_IDENT_VC_MASK (0x0000ffffU)
+
+#define USIM_IDENT_RESERVED_16_31_SHIFT (16U)
+#define USIM_IDENT_RESERVED_16_31_MASK (0xffff0000U)
+
+#define USIM_SYSCONFIG_AUTOIDLE_SHIFT (0U)
+#define USIM_SYSCONFIG_AUTOIDLE_MASK (0x00000001U)
+#define USIM_SYSCONFIG_AUTOIDLE_AUTOIDLE_VALUE_1 (1U)
+#define USIM_SYSCONFIG_AUTOIDLE_AUTOIDLE_VALUE_0 (0U)
+
+#define USIM_SYSCONFIG_SOFTRESET_SHIFT (1U)
+#define USIM_SYSCONFIG_SOFTRESET_MASK (0x00000002U)
+#define USIM_SYSCONFIG_SOFTRESET_SOFTRESET_VALUE_1 (1U)
+#define USIM_SYSCONFIG_SOFTRESET_SOFTRESET_VALUE_0 (0U)
+
+#define USIM_SYSCONFIG_ENAWAKEUP_SHIFT (2U)
+#define USIM_SYSCONFIG_ENAWAKEUP_MASK (0x00000004U)
+#define USIM_SYSCONFIG_ENAWAKEUP_ENAWAKEUP_VALUE_1 (1U)
+#define USIM_SYSCONFIG_ENAWAKEUP_ENAWAKEUP_VALUE_0 (0U)
+
+#define USIM_SYSCONFIG_IDLEMODE_SHIFT (3U)
+#define USIM_SYSCONFIG_IDLEMODE_MASK (0x00000018U)
+#define USIM_SYSCONFIG_IDLEMODE_IDLEMODE_VALUE_3 (3U)
+#define USIM_SYSCONFIG_IDLEMODE_IDLEMODE_VALUE_2 (2U)
+#define USIM_SYSCONFIG_IDLEMODE_IDLEMODE_VALUE_1 (1U)
+#define USIM_SYSCONFIG_IDLEMODE_IDLEMODE_VALUE_0 (0U)
+
+#define USIM_SYSCONFIG_EMUFREE_SHIFT (5U)
+#define USIM_SYSCONFIG_EMUFREE_MASK (0x00000020U)
+#define USIM_SYSCONFIG_EMUFREE_EMUFREE_VALUE_0 (0U)
+#define USIM_SYSCONFIG_EMUFREE_EMUFREE_VALUE_1 (1U)
+
+#define USIM_SYSCONFIG_RESERVED_6_7_SHIFT (6U)
+#define USIM_SYSCONFIG_RESERVED_6_7_MASK (0x000000c0U)
+
+#define USIM_SYSCONFIG_CLOCKACTIVITY_SHIFT (8U)
+#define USIM_SYSCONFIG_CLOCKACTIVITY_MASK (0x00000300U)
+
+#define USIM_SYSCONFIG_RESERVED_22_SHIFT (10U)
+#define USIM_SYSCONFIG_RESERVED_22_MASK (0xfffffc00U)
+
+#define USIM_SYSSTATUS_RESETDONE_SHIFT (0U)
+#define USIM_SYSSTATUS_RESETDONE_MASK (0x00000001U)
+#define USIM_SYSSTATUS_RESETDONE_RESETDONE_VALUE_1 (1U)
+#define USIM_SYSSTATUS_RESETDONE_RESETDONE_VALUE_0 (0U)
+
+#define USIM_SYSSTATUS_RESERVED_31_SHIFT (1U)
+#define USIM_SYSSTATUS_RESERVED_31_MASK (0xfffffffeU)
+
+#define USIM_IRQSTATUS_USIM_NATR_SHIFT (0U)
+#define USIM_IRQSTATUS_USIM_NATR_MASK (0x00000001U)
+
+#define USIM_IRQSTATUS_USIM_WT_SHIFT (1U)
+#define USIM_IRQSTATUS_USIM_WT_MASK (0x00000002U)
+
+#define USIM_IRQSTATUS_USIM_RXFULL_SHIFT (2U)
+#define USIM_IRQSTATUS_USIM_RXFULL_MASK (0x00000004U)
+
+#define USIM_IRQSTATUS_USIM_TX_SHIFT (3U)
+#define USIM_IRQSTATUS_USIM_TX_MASK (0x00000008U)
+
+#define USIM_IRQSTATUS_USIM_RX_SHIFT (4U)
+#define USIM_IRQSTATUS_USIM_RX_MASK (0x00000010U)
+
+#define USIM_IRQSTATUS_USIM_CD_SHIFT (5U)
+#define USIM_IRQSTATUS_USIM_CD_MASK (0x00000020U)
+
+#define USIM_IRQSTATUS_USIM_EOB_SHIFT (6U)
+#define USIM_IRQSTATUS_USIM_EOB_MASK (0x00000040U)
+
+#define USIM_IRQSTATUS_USIM_TOC_SHIFT (7U)
+#define USIM_IRQSTATUS_USIM_TOC_MASK (0x00000080U)
+
+#define USIM_IRQSTATUS_USIM_TOB_SHIFT (8U)
+#define USIM_IRQSTATUS_USIM_TOB_MASK (0x00000100U)
+
+#define USIM_IRQSTATUS_USIM_RESENT_SHIFT (9U)
+#define USIM_IRQSTATUS_USIM_RESENT_MASK (0x00000200U)
+
+#define USIM_IRQSTATUS_TS_ERROR_SHIFT (10U)
+#define USIM_IRQSTATUS_TS_ERROR_MASK (0x00000400U)
+
+#define USIM_IRQSTATUS_IT_EMV_ATR_LENGTH_TIME_OUT_SHIFT (11U)
+#define USIM_IRQSTATUS_IT_EMV_ATR_LENGTH_TIME_OUT_MASK (0x00000800U)
+
+#define USIM_IRQSTATUS_RESERVED_SHIFT (21U)
+#define USIM_IRQSTATUS_RESERVED_MASK (0xffe00000U)
+
+#define USIM_IRQSTATUS_USIM_STOP_CLK_SHIFT (12U)
+#define USIM_IRQSTATUS_USIM_STOP_CLK_MASK (0x00001000U)
+
+#define USIM_IRQSTATUS_PAR_ERR_LEVEL_REACHED_SHIFT (13U)
+#define USIM_IRQSTATUS_PAR_ERR_LEVEL_REACHED_MASK (0x00002000U)
+
+#define USIM_IRQSTATUS_FRAME_ERR_SHIFT (14U)
+#define USIM_IRQSTATUS_FRAME_ERR_MASK (0x00004000U)
+
+#define USIM_IRQSTATUS_RXDMA_RDY_SHIFT (15U)
+#define USIM_IRQSTATUS_RXDMA_RDY_MASK (0x00008000U)
+
+#define USIM_IRQSTATUS_ATR_START_SHIFT (16U)
+#define USIM_IRQSTATUS_ATR_START_MASK (0x00010000U)
+
+#define USIM_IRQSTATUS_ACT_DONE_SHIFT (17U)
+#define USIM_IRQSTATUS_ACT_DONE_MASK (0x00020000U)
+
+#define USIM_IRQSTATUS_DEACT_DONE_SHIFT (18U)
+#define USIM_IRQSTATUS_DEACT_DONE_MASK (0x00040000U)
+
+#define USIM_IRQSTATUS_TX_BLOCK_DONE_SHIFT (19U)
+#define USIM_IRQSTATUS_TX_BLOCK_DONE_MASK (0x00080000U)
+
+#define USIM_IRQSTATUS_TX_BLOCK_REQ_SHIFT (20U)
+#define USIM_IRQSTATUS_TX_BLOCK_REQ_MASK (0x00100000U)
+
+#define USIM_IRQENABLE_RESERVED_SHIFT (21U)
+#define USIM_IRQENABLE_RESERVED_MASK (0xffe00000U)
+
+#define USIM_IRQENABLE_EMV_ATR_LENGTH_TIME_OUT_EN_SHIFT (11U)
+#define USIM_IRQENABLE_EMV_ATR_LENGTH_TIME_OUT_EN_MASK (0x00000800U)
+
+#define USIM_IRQENABLE_TS_ERR_EN_SHIFT (10U)
+#define USIM_IRQENABLE_TS_ERR_EN_MASK (0x00000400U)
+
+#define USIM_IRQENABLE_RESENT_EN_SHIFT (9U)
+#define USIM_IRQENABLE_RESENT_EN_MASK (0x00000200U)
+
+#define USIM_IRQENABLE_TOB_EN_SHIFT (8U)
+#define USIM_IRQENABLE_TOB_EN_MASK (0x00000100U)
+
+#define USIM_IRQENABLE_TOC_EN_SHIFT (7U)
+#define USIM_IRQENABLE_TOC_EN_MASK (0x00000080U)
+
+#define USIM_IRQENABLE_EOB_EN_SHIFT (6U)
+#define USIM_IRQENABLE_EOB_EN_MASK (0x00000040U)
+
+#define USIM_IRQENABLE_CD_EN_SHIFT (5U)
+#define USIM_IRQENABLE_CD_EN_MASK (0x00000020U)
+
+#define USIM_IRQENABLE_RX_EN_SHIFT (4U)
+#define USIM_IRQENABLE_RX_EN_MASK (0x00000010U)
+
+#define USIM_IRQENABLE_TX_EN_SHIFT (3U)
+#define USIM_IRQENABLE_TX_EN_MASK (0x00000008U)
+
+#define USIM_IRQENABLE_RXFULL_EN_SHIFT (2U)
+#define USIM_IRQENABLE_RXFULL_EN_MASK (0x00000004U)
+
+#define USIM_IRQENABLE_WT_EN_SHIFT (1U)
+#define USIM_IRQENABLE_WT_EN_MASK (0x00000002U)
+
+#define USIM_IRQENABLE_NATR_EN_SHIFT (0U)
+#define USIM_IRQENABLE_NATR_EN_MASK (0x00000001U)
+
+#define USIM_IRQENABLE_STOP_CLK_SHIFT (12U)
+#define USIM_IRQENABLE_STOP_CLK_MASK (0x00001000U)
+
+#define USIM_IRQENABLE_PAR_ERR_LEVEL_REACHED_EN_SHIFT (13U)
+#define USIM_IRQENABLE_PAR_ERR_LEVEL_REACHED_EN_MASK (0x00002000U)
+
+#define USIM_IRQENABLE_FRAME_ERR_EN_SHIFT (14U)
+#define USIM_IRQENABLE_FRAME_ERR_EN_MASK (0x00004000U)
+
+#define USIM_IRQENABLE_RXDMA_RDY_EN_SHIFT (15U)
+#define USIM_IRQENABLE_RXDMA_RDY_EN_MASK (0x00008000U)
+
+#define USIM_IRQENABLE_ATR_START_EN_SHIFT (16U)
+#define USIM_IRQENABLE_ATR_START_EN_MASK (0x00010000U)
+
+#define USIM_IRQENABLE_ACT_DONE_EN_SHIFT (17U)
+#define USIM_IRQENABLE_ACT_DONE_EN_MASK (0x00020000U)
+
+#define USIM_IRQENABLE_DEACT_DONE_EN_SHIFT (18U)
+#define USIM_IRQENABLE_DEACT_DONE_EN_MASK (0x00040000U)
+
+#define USIM_IRQENABLE_TX_BLOCK_DONE_EN_SHIFT (19U)
+#define USIM_IRQENABLE_TX_BLOCK_DONE_EN_MASK (0x00080000U)
+
+#define USIM_IRQENABLE_TX_BLOCK_REQ_EN_SHIFT (20U)
+#define USIM_IRQENABLE_TX_BLOCK_REQ_EN_MASK (0x00100000U)
+
+#define USIM_WAKEUPEN_STOP_CLK_SHIFT (12U)
+#define USIM_WAKEUPEN_STOP_CLK_MASK (0x00001000U)
Can't you have common defines for IRQSTATUS, IRQENABLE and WAKEUPEN?
They seem to all be the same bit positions. And you already have IRQ
bit defines above.
[lots more register defines...]
Again, used file generated by Tool to avoid minor mistakes.+
+#define USIM_TX_BLOCK_RESERVED_SHIFT (16U)
+#define USIM_TX_BLOCK_RESERVED_MASK (0xffff0000U)
In general, trim these defines down to what you actually use. Single
bit fields rarely need both a shift and mask.
Any specific reason ?
+
+#endif /* __TI_USIM_HW_H__ */
diff --git a/drivers/char/ti-usim.c b/drivers/char/ti-usim.c
new file mode 100644
index 0000000..ffabf87
--- /dev/null
+++ b/drivers/char/ti-usim.c
@@ -0,0 +1,2213 @@
+/*
+ * usim.c - USIM driver for Smart Card module
+ *
+ *
+ * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/io.h>
+#include <linux/fs.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/i2c.h>
+#include <linux/miscdevice.h>
+#include <linux/uaccess.h>
+#include <linux/ctype.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <linux/debugfs.h>
+#include <linux/notifier.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+/* for send_sig_info */
+#include <linux/rcupdate.h>
+#include <asm/siginfo.h>
+
+#include "ti-usim-hw.h"
+
+#define USIM_WRITEREG(base, offset, field, value) \
+ usim_writereg(base+offset, offset##_##field##_MASK, \
+ offset##_##field##_SHIFT, value)
+
+#define USIM_READREG(base, offset, field) \
+ usim_readreg(base+offset, offset##_##field##_MASK, \
+ offset##_##field##_SHIFT)
+
+#define USIM_SETFIELD(reg, offset, field, value) \
+ usim_setfield(reg, offset##_##field##_MASK, \
+ offset##_##field##_SHIFT, value)
Get rid of this.
yes, using locks inside irq handling.
+
+/* calculation of max ATR waiting time
+ * 372 is default FI value, so etu for 1Mhz SC clock cycle would be
+ * etu = FI/f sec = 372/1Mhz = 372 micro second
+ * Max ATR waiting is - USIM_ATR_TIMEOUT_EMV etu
+ */
+#define MAX_ATR_WAITTIME_US (372 * USIM_ATR_TIMEOUT_EMV)
+
+/*
+ * phy states
+ */
+enum usim_phy_state {
+ USIM_PHY_NOT_PRESENT = 0x0,
+ USIM_PHY_PRESENT,
+ USIM_PHY_NOT_ATTACHED,
+};
+
+static struct miscdevice usim_dev;
+
+static DECLARE_WAIT_QUEUE_HEAD(rx_wait);
+static DECLARE_WAIT_QUEUE_HEAD(tx_wait);
+static DECLARE_WAIT_QUEUE_HEAD(atr_wait);
+
+static int usim_set_smartcardclock(struct usim *usim, u32 clock);
+static int usim_deactivate_card(struct usim *usim);
+
+static void usim_writereg(void __iomem *base, u32 mask, u32 shift, u32 value)
+{
+ u32 v = readl(base);
+
+ v &= ~mask;
+ v |= (value << shift) & mask;
+ writel(v, base);
+ v = readl(base);
+ return;
+}
+
+static u32 usim_readreg(void __iomem *base, u32 mask, u32 shift)
+{
+ u32 v = readl(base);
+
+ v &= mask;
+ v = (v >> shift);
+ return v;
+}
+
+static u32 usim_setfield(u32 reg, u32 mask, u32 shift, u32 value)
+{
+ reg &= ~mask;
+ reg |= (value << shift) & mask;
+ return reg;
+}
Use readl/writel directly. If we wanted drivers written with accessors
like these, then there would be common ones to use.
+
+
+static inline void usim_irq_enable(void __iomem *base, u32 irqs)
+{
+ u32 v = readl(base + USIM_IRQENABLE);
+
+ v |= irqs;
+ writel(v, base + USIM_IRQENABLE);
+}
+
+static inline void usim_irq_disable(void __iomem *base, u32 irqs)
+{
+ u32 v = readl(base + USIM_IRQENABLE);
+
+ v &= ~irqs;
+ writel(v, base + USIM_IRQENABLE);
I assume these are called with appropriate locking?
There is a use case for this. We need to send immediate signal to application for card insert/remove. So used this approach after internal discussion.
+}
+
+static inline void usim_irq_get(void __iomem *base, u32 *irqs)
+{
+ *irqs = readl(base + USIM_IRQENABLE);
+}
+
+static inline u32 usim_irqstatus(void __iomem *base)
+{
+ return readl(base + USIM_IRQSTATUS);
+}
+
+static inline void usim_irqstatus_clear(void __iomem *base, u32 irqs)
+{
+ writel(irqs, base + USIM_IRQSTATUS);
+}
Use readl/writel directly.
+
+static inline struct usim *dev_to_usim(struct device *dev)
+{
+ return dev_get_drvdata(dev);
+}
+
+static int usim_send_signal(struct usim *usim, int event)
+{
Using signals is not a typical driver interface.
handling interrput in order of priority.+ struct siginfo info;
+ struct task_struct *tid;
+ int ret = 0;
+ int pid = usim->user_pid;
+
+ if (pid == 0)
+ return -EINVAL;
+ info.si_signo = USIM_SIGID;
+ info.si_code = SI_QUEUE;
+
+ info.si_int = event;
+ rcu_read_lock();
+
+ /* find task structure associated with this pid */
+ tid = pid_task(find_vpid(pid), PIDTYPE_PID);
+ if (tid == NULL) {
+ dev_err(usim->dev, "usim-err:no such pid :%d\n", pid);
+ rcu_read_unlock();
+ return -ENODEV;
+ }
+
+ rcu_read_unlock();
+
+ /* send the signal */
+ ret = send_sig_info(USIM_SIGID, &info, tid);
+ if (ret < 0) {
+ dev_err(usim->dev, "error sending signal:%d\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+static void usim_getrx(struct usim *usim)
+{
+ u32 rxlen = 0;
+ u32 cnt = 0;
+
+ /* Check if FIFO contains some data */
+ rxlen = USIM_READREG(usim->base, USIM_RXFIFO_LEVEL,
+ USIM_RXFIFO_LEVEL);
More register accessors?
+
+ usim->slot_ctx[usim->slot].rx_counter += rxlen;
+ if (rxlen > 0) {
+ for (cnt = 0; cnt < rxlen; cnt++) {
+ usim->slot_ctx[usim->slot].rxbuf[cnt] =
+ USIM_READREG(usim->base, USIM_DRX, USIMDRX);
+ }
+ }
+}
+
+static void usim_irq_atrhandler(struct usim *usim, u32 reg)
+{
+ u32 event = 0;
+ u32 val = 0;
+ u32 cnt = 0;
+ u32 rxval = 0;
+ if (usim->atrdone)
+ return;
+ do {
+ /* WWT would be used to identify end of ATR */
+ if (reg & (USIM_IRQ_WT | USIM_IRQ_EMV_ATR_LENGTH_TIME_OUT)) {
+ event |= USIM_EVENT_TIMEOUT;
+ val = USIM_READREG(usim->base, USIM_STAT,
+ ATRRX_AFTER_TIMEOUT);
+ if (val) {
+ /* do not store rx character if it comes after
+ * ATR timeout
+ */
+ dev_dbg(usim->dev, "Error: Rx after ATR Timeout");
+ break;
+ }
+ }
+ if (reg & USIM_IRQ_TS_ERR) {
+ event |= USIM_EVENT_ERR_FRAME;
+ break;
+ }
+
+ /* check the rx fifo and store available bytes in atrbuf */
+ val = USIM_READREG(usim->base, USIM_RXFIFO_LEVEL,
+ USIM_RXFIFO_LEVEL);
+ cnt = usim->slot_ctx[usim->slot].atr_length;
+
+ while (val > 0) {
+ if (cnt < USIM_MAX_ATRLENGTH) {
+ rxval = readl(usim->base + USIM_DRX);
+ usim->slot_ctx[usim->slot].atr[cnt++] = rxval &
+ USIM_DRX_USIMDRX_MASK;
+ /* check of parity */
+ if (!(rxval & USIM_DRX_STATRXPAR_MASK)) {
+ dev_dbg(usim->dev,
+ "Error : incorrect parity:%0x"
+ , rxval);
+ event |= USIM_EVENT_ERR_PARITY;
+ }
+ }
+ val--;
+ }
+
+ usim->slot_ctx[usim->slot].atr_length = cnt;
+ } while (0);
It would be nicer to see these written w/o all these while (0) loops.
+
+ if (event != 0) {
+ USIM_WRITEREG(usim->base, USIM_CONF6, MODE, USIM_MODE_IDLE);
+ usim->slot_ctx[usim->slot].state = USIM_MODE_IDLE;
+ usim->slot_ctx[usim->slot].event = event;
+ usim->atrdone = 1;
+ }
+
+ if (usim->atrdone)
+ wake_up(&atr_wait);
+
+ return;
+}
+
+static void usim_irq_txhandler(struct usim *usim, u32 reg)
+{
+ u32 protocol = 0;
This does not need to be initialized.
+ u32 event = 0;
+
+ if (usim->txdone)
+ return;
+
+ protocol = usim->slot_ctx[usim->slot].protocol;
+ do {
+ if (reg & USIM_IRQ_FRAME_ERR) {
+ event |= USIM_EVENT_ERR_FRAME;
+ break;
+ }
+ if (!protocol && (reg & USIM_IRQ_RESENT)) {
+ event |= USIM_EVENT_ERR_TXRETRY;
+ break;
+ }
+ if (reg & USIM_IRQ_TX_BLOCK_REQ) {
+ /* TODO : As per EMV max tx block will be of 256 bytes
+ * and USIM controller has sufficient place for this.
+ * Need to implement this case when it is practially
+ * required
+ */
+ dev_dbg(usim->dev, "Error: TX_BLOCK_REQ - Not Implemented");
+ }
+ if (reg & USIM_IRQ_TX_BLOCK_DONE) {
+ usim_irq_disable(usim->base, USIM_IRQ_TX_BLOCK_REQ
+ | USIM_IRQ_TX_BLOCK_DONE
+ | USIM_IRQ_TX);
+ usim->txdone = 1;
+ usim_irq_enable(usim->base, USIM_IRQ_RX | USIM_IRQ_EOB
+ | USIM_IRQ_RXDMA_RDY);
+ break;
+ }
+ } while (0);
+
+ if (event != 0) {
+ USIM_WRITEREG(usim->base, USIM_CONF6, MODE, USIM_MODE_IDLE);
+ usim->slot_ctx[usim->slot].state = USIM_MODE_IDLE;
+ usim->slot_ctx[usim->slot].event = event;
+ usim->txdone = 1;
+ usim->rxdone = 1;
+ }
+ if (usim->txdone)
+ wake_up(&tx_wait);
+ return;
+}
+
+static void usim_irq_rxhandler(struct usim *usim, u32 reg)
+{
+ u32 event = 0;
+ u32 val = 0;
+
+ u32 protocol = usim->slot_ctx[usim->slot].protocol;
+
+ /* if tx not done then do not check of any rx */
+ if (usim->rxdone || !usim->txdone)
+ return;
+
+ /* For T=0 protocol */
+ if (protocol == 0) {
+ do {
+ /* ignore interrupts if expected bytes recevied */
+ if (usim->slot_ctx[usim->slot].rx_counter >=
+ usim->slot_ctx[usim->slot].rx_explen) {
+ dev_dbg(usim->dev, "All bytes recvd,ignore this timeout\n");
+ usim->rxdone = 1;
+ break;
How about using "else if" instead of all these breaks. Can't you have
multiple interrupt bits set?
As for POS application, opening device is high priority, so used delay.
+ }
+
+ if (reg & USIM_IRQ_WT) {
+ dev_dbg(usim->dev, "Expected bytes not recvd counter = %d\n",
+ usim->slot_ctx[usim->slot].rx_counter);
+ usim_getrx(usim);
+ event |= USIM_EVENT_TIMEOUT;
+ break;
+ }
+
+ if (reg & USIM_IRQ_PAR_ERR_LEVEL_REACHED) {
+ dev_err(usim->dev,
+ "Rx parity level reached:%x\n"
+ , reg);
+ usim_getrx(usim);
+ event |= USIM_EVENT_ERR_PARITY;
+ break;
+ }
+
+ if (reg & (USIM_IRQ_RX | USIM_IRQ_RXDMA_RDY)) {
+ /* Read number of bytes present in the FIFO */
+ usim_getrx(usim);
+ usim->rxdone = 1;
+ break;
+ }
+ } while (0);
+ } else {
+ /* T=1 protocol */
+ do {
+ if (reg & (USIM_IRQ_TOB | USIM_IRQ_TOC)) {
+ usim_getrx(usim);
+ event |= USIM_EVENT_TIMEOUT;
+ break;
+ }
+ if (reg & USIM_IRQ_EOB) {
+ usim_getrx(usim);
+ usim->rxdone = 1;
+ val = USIM_READREG(usim->base, USIM_STAT,
+ STATLRC);
+ if (val != 0)
+ event |= USIM_EVENT_ERR_LRC;
+ break;
+ }
+ } while (0);
+ }
+
+ if (event != 0 || usim->rxdone == 1) {
+ USIM_WRITEREG(usim->base, USIM_CONF6, MODE, USIM_MODE_IDLE);
+ usim->slot_ctx[usim->slot].state = USIM_MODE_IDLE;
+ usim->slot_ctx[usim->slot].event = event;
+ usim->rxdone = 1;
+ }
+
+ if (usim->rxdone)
+ wake_up(&rx_wait);
+
+ return;
+}
+
+static irqreturn_t usim_interrupt(int irq, void *_usim)
+{
+ u32 reg = 0;
+ u32 state = 0;
+ struct usim *usim = (struct usim *)_usim;
+
+ state = usim->slot_ctx[usim->slot].state;
+
+ spin_lock(&usim->lock);
+
+ reg = readl(usim->base + USIM_IRQSTATUS);
+
+ if (state == USIM_MODE_ATR)
+ usim_irq_atrhandler(usim, reg);
+
+ if (state == USIM_MODE_TXRX) {
+ usim_irq_txhandler(usim, reg);
+ usim_irq_rxhandler(usim, reg);
+ }
+
+ if (reg & USIM_IRQSTATUS_USIM_NATR_MASK)
+ dev_dbg(usim->dev, "NO ATR\n");
+
+ if (reg & USIM_IRQSTATUS_USIM_CD_MASK)
+ dev_dbg(usim->dev, "CARD Insert/Removed\n");
+
+ if (reg & USIM_IRQSTATUS_USIM_STOP_CLK_MASK)
+ dev_dbg(usim->dev, "SIM CLK STOPPED\n");
+
+ if (reg & USIM_IRQSTATUS_ACT_DONE_MASK)
+ dev_dbg(usim->dev, "Activation Sequence completed\n");
+
+ if (reg & USIM_IRQSTATUS_DEACT_DONE_MASK)
+ dev_dbg(usim->dev, "Deactivation Sequence complteted\n");
+
+ /* Clear the interrupt by writing the corresponding bit
+ * in IRQ_STATUS register
+ */
+ usim_irqstatus_clear(usim->base, reg);
+
+ spin_unlock(&usim->lock);
+
+ return IRQ_HANDLED;
+}
+
+static int usim_configure(struct usim *usim)
+{
+ int reg = 0;
+ int count = 3;
+
+ /* perform softreset of IP */
+ USIM_WRITEREG(usim->base, USIM_SYSCONFIG, SOFTRESET, 1);
+
+ /* wait until reset get completed */
+ while (count > 0) {
+ reg = USIM_READREG(usim->base, USIM_SYSCONFIG, SOFTRESET);
+ if (reg == 0x0)
+ break;
+ mdelay(10);
Does this really need to be a delay loop rather than a sleep? If not,
use msleep and time_after or time_before.
Agree
+ count--;
+ }
+ if (reg != 0x0)
+ return -EIO;
+
+ /* activate phy */
+ if (usim->phy_present)
+ usim->phy->set_config(usim->phy, usim->slot, SC_PHY_MODE,
+ SC_PHY_ACTIVE);
+
+ /* Disable Auto Idle and set NO IDLE config */
+ reg = readl(usim->base + USIM_SYSCONFIG);
+ reg = USIM_SETFIELD(reg, USIM_SYSCONFIG, AUTOIDLE, 0);
+ reg = USIM_SETFIELD(reg, USIM_SYSCONFIG, IDLEMODE, 1);
+ writel(reg, usim->base + USIM_SYSCONFIG);
+
+ if (usim->phy_present) {
+ USIM_WRITEREG(usim->base, USIM_STAT, STATNOCARD, 1);
+ USIM_WRITEREG(usim->base, USIM_CONF1, BYPASS_HW_AUTO, 0);
+ } else {
+ USIM_WRITEREG(usim->base, USIM_STAT, STATNOCARD, 0);
+ USIM_WRITEREG(usim->base, USIM_CONF1, BYPASS_HW_AUTO, 1);
+ }
+
+ /* Set default card type as EMV, Force SIO to low level */
+ reg = readl(usim->base + USIM_CONF1);
+ reg = USIM_SETFIELD(reg, USIM_CONF1, EMV_CONF, 1);
+ reg = USIM_SETFIELD(reg, USIM_CONF1, CONFSIOLOW, 1);
+ writel(reg, usim->base + USIM_CONF1);
+
+ /* Set parity level to 1, auto resent to 2 on parity error, */
+ reg = readl(usim->base + USIM_CONF2);
+ reg = USIM_SETFIELD(reg, USIM_CONF2, NACKING_EN, 0);
+ reg = USIM_SETFIELD(reg, USIM_CONF2, CARD_POLARITY, 0);
+ reg = USIM_SETFIELD(reg, USIM_CONF2, CONFEDC, 0);
+ reg = USIM_SETFIELD(reg, USIM_CONF2, CONFPROTOCOL, 0);
+ reg = USIM_SETFIELD(reg, USIM_CONF2, ATR_ASYN_BYPASS, 0);
+ reg = USIM_SETFIELD(reg, USIM_CONF2, CONFSCLKDIV, 0);
+ reg = USIM_SETFIELD(reg, USIM_CONF2, CONFSCLKMODE, 0);
+ reg = USIM_SETFIELD(reg, USIM_CONF2, PAR_ERR_LEVEL, 1);
+ reg = USIM_SETFIELD(reg, USIM_CONF2, CONFRESENT, 2);
+ reg = USIM_SETFIELD(reg, USIM_CONF2, PUT_ERR_IN_FIFO, 1);
+ reg = USIM_SETFIELD(reg, USIM_CONF2, CONFLRCCHECK, 2);
+ reg = USIM_SETFIELD(reg, USIM_CONF2, CONFSCLKDIV, 0);
+ reg = USIM_SETFIELD(reg, USIM_CONF2, CONFCHKPAR, 1);
+
+ if (usim->phy_present) {
+ reg = USIM_SETFIELD(reg, USIM_CONF2, HW_DEACTIV_EN, 0);
+ reg = USIM_SETFIELD(reg, USIM_CONF2, DEBOUNCE_EN, 0);
+ } else {
+ reg = USIM_SETFIELD(reg, USIM_CONF2, HW_DEACTIV_EN, 1);
+ }
+
+ writel(reg, usim->base + USIM_CONF2);
+
+ /* Reset Tx FIFO Pointer */
+ USIM_WRITEREG(usim->base, USIM_FIFOS, FIFOTX_RESET, 1);
+ USIM_WRITEREG(usim->base, USIM_FIFOS, FIFOTX_RESET, 0);
+
+ /* Reset Rx FIFO Pointer */
+ USIM_WRITEREG(usim->base, USIM_FIFOS, FIFORX_RESET, 1);
+ USIM_WRITEREG(usim->base, USIM_FIFOS, FIFORX_RESET, 0);
+
+ /* Configure FIFO settings */
+ /* Set Tx and Rx trigger to 1 byte */
+ reg = readl(usim->base + USIM_FIFOS);
+ reg = USIM_SETFIELD(reg, USIM_FIFOS, FIFO_TX_TRIGGER, 0);
+ reg = USIM_SETFIELD(reg, USIM_FIFOS, FIFO_RX_TRIGGER, 0);
+ reg = USIM_SETFIELD(reg, USIM_FIFOS, RXDMA_TYPE, 0x3);
+ reg = USIM_SETFIELD(reg, USIM_FIFOS, DMA_MODE, 0x0);
+ writel(reg, usim->base + USIM_FIFOS);
+
+ /* Enable FIFO access */
+ USIM_WRITEREG(usim->base, USIM_FIFOS, FIFO_ENABLE, 1);
+
+ /* Use HW mode for ETU calculation and set FI = 372 and DI = 1 */
+ reg = readl(usim->base + USIM_CONF5);
+ reg = USIM_SETFIELD(reg, USIM_CONF5, FI, 0);
+ reg = USIM_SETFIELD(reg, USIM_CONF5, DI, 0);
+ reg = USIM_SETFIELD(reg, USIM_CONF5, SOFT_NHARD_FIDI_PROG, 0);
+ writel(reg, usim->base + USIM_CONF5);
+
+ /* Configure CONF6 settings */
+ reg = readl(usim->base + USIM_CONF6);
+ reg = USIM_SETFIELD(reg, USIM_CONF6, VCC_BYPASS, 0);
+ reg = USIM_SETFIELD(reg, USIM_CONF6, RST_BYPASS, 0);
+ reg = USIM_SETFIELD(reg, USIM_CONF6, SCLK0_BYPASS, 0);
+ reg = USIM_SETFIELD(reg, USIM_CONF6, RST_POLARITY, 0);
+ reg = USIM_SETFIELD(reg, USIM_CONF6, ATR_TIMER_BYPASS, 1);
+ reg = USIM_SETFIELD(reg, USIM_CONF6, MODE, USIM_CONF6_MODE_FREEZE);
+ writel(reg, usim->base + USIM_CONF6);
+
+ /* Clear all bits in IO_DIRECT register */
+ writel(0, usim->base + USIM_IO_DIRECT);
+
+ /* Disable legacy bypass mode */
+ USIM_WRITEREG(usim->base, USIM_CONF1, CONFBYPASS, 0);
+
+ /* Enable required interrupts */
+ reg = readl(usim->base + USIM_IRQENABLE);
+ writel(reg, usim->base + USIM_IRQENABLE);
+
+ /* Toggling ATR length to ensure 'USIM_STAT_ATRRX_AFTER_TIMEOUT'
+ * gets disable
+ */
+ USIM_WRITEREG(usim->base, USIM_CONF6, ATR_TIMEOUT, 1);
+ USIM_WRITEREG(usim->base, USIM_CONF6, ATR_TIMEOUT,
+ USIM_ATR_TIMEOUT_EMV);
+
+ /* Set STOP_RX_TIMEOUT */
+ /* Set STOP_RESEND_FAILURE */
+ USIM_WRITEREG(usim->base, USIM_CONF2, STOP_RX_TIMEOUT, 1);
+ USIM_WRITEREG(usim->base, USIM_CONF2, STOP_RESEND_FAILURE, 1);
+
+ /* set smartcard clock */
+ usim_set_smartcardclock(usim, usim->slot_ctx[usim->slot].clock);
+
+ return 0;
+}
+
+static int usim_set_voltage(struct usim *usim, u32 voltage)
+{
+ int ret = 0;
+ struct sc_phy *phy = usim->phy;
+ /*
+ * voltage = 0 for 5V,
+ * voltage = 1 for 3V,
+ * voltage = 2 for 1.8V,
+ */
+ if (voltage > 3)
+ return -EINVAL;
+ if (usim->phy_present) {
+ ret = phy->set_config(phy, usim->slot,
+ SC_PHY_CARD_SUPPLY_VOLTAGE, voltage);
+ }
+ usim->slot_ctx[usim->slot].supply = voltage;
+ return ret;
+}
+
+static int usim_set_smartcardclock(struct usim *usim, u32 clock)
+{
+ int clkdiv;
+ int clkmode;
+ int reg = 0;
+ struct sc_phy *phy = usim->phy;
+
+ switch (clock) {
+ case USIM_SMARTCART_CLOCK_3_3MHZ:
+ clkmode = USIM_CONFSCLKMODE_HF;
+ clkdiv = 3;
+ break;
+
+ case USIM_SMARTCART_CLOCK_4MHZ:
+ clkmode = USIM_CONFSCLKMODE_HF;
+ clkdiv = 2;
+ break;
+
+ case USIM_SMARTCART_CLOCK_5MHZ:
+ clkmode = USIM_CONFSCLKMODE_LEGACY;
+ clkdiv = 3;
+ break;
+
+ case USIM_SMARTCART_CLOCK_6_6MHZ:
+ clkmode = USIM_CONFSCLKMODE_LEGACY;
+ clkdiv = 2;
+ break;
+
+ case USIM_SMARTCART_CLOCK_10MHZ:
+ clkmode = USIM_CONFSCLKMODE_LEGACY;
+ clkdiv = 1;
+ break;
+
+ case USIM_SMARTCART_CLOCK_20MHZ:
+ clkmode = USIM_CONFSCLKMODE_LEGACY;
+ clkdiv = 0;
+ break;
+
+ default:
+ dev_err(usim->dev, "Unsupported Clock configuration for smartcard\n");
+ return -EINVAL;
+ break;
+ }
+
+ /* Set default card type as EMV, Force SIO to low level */
+ reg = readl(usim->base + USIM_CONF2);
+ reg = USIM_SETFIELD(reg, USIM_CONF2, CONFSCLKMODE, clkmode);
+ reg = USIM_SETFIELD(reg, USIM_CONF2, CONFSCLKDIV, clkdiv);
+ writel(reg, usim->base + USIM_CONF2);
+
+ /* setting phy division to zero, as USIM samples smartcard clk line and
+ * put the data in USIM fifo. Phy supply the clock to smartcard wihtout
+ * furhter division
+ */
+ if (usim->phy_present)
+ phy->set_config(phy, usim->slot, SC_PHY_CLKDIV, 0);
+
+ usim->slot_ctx[usim->slot].clock = clock;
+ return 0;
+}
+
+static int usim_set_etu(struct usim *usim, u32 fi, u32 di)
+{
+ USIM_WRITEREG(usim->base, USIM_CONF5, SOFT_NHARD_FIDI_PROG, 0);
+ USIM_WRITEREG(usim->base, USIM_CONF5, FI, fi);
+ USIM_WRITEREG(usim->base, USIM_CONF5, DI, di);
+ return 0;
+}
+
+static int usim_set_rxparitycount(struct usim *usim, u32 rxcount)
+{
+ if (rxcount > USIM_MAX_PARITY_RETRIES)
+ return -EINVAL;
+
+ /* Program fields required for RX retry in USIM IP */
+ USIM_WRITEREG(usim->base, USIM_CONF2, PAR_ERR_LEVEL, rxcount);
+
+ /* Enable rx parity check */
+ if (rxcount > 0) {
+ USIM_WRITEREG(usim->base, USIM_CONF2, CONFCHKPAR, 1);
+ usim_irq_enable(usim->base, USIM_IRQ_PAR_ERR_LEVEL_REACHED);
+ } else {
+ usim_irq_disable(usim->base, USIM_IRQ_PAR_ERR_LEVEL_REACHED);
+ }
+ return 0;
+}
+
+static int usim_set_txretrycount(struct usim *usim, u32 txcount)
+{
+ if (txcount > USIM_MAX_PARITY_RETRIES)
+ return -EINVAL;
+
+ USIM_WRITEREG(usim->base, USIM_CONF2, CONFRESENT, txcount);
+ if (txcount > 0)
+ usim_irq_enable(usim->base, USIM_IRQ_RESENT);
+ else
+ usim_irq_disable(usim->base, USIM_IRQ_RESENT);
+
+ return 0;
+}
+
+static int usim_set_c4(struct usim *usim, int state)
+{
+ int ret = 0;
+ struct sc_phy *phy = usim->phy;
+ if (usim->phy_present)
+ ret = phy->set_config(phy, usim->slot, SC_PHY_PIN_C4, state);
+ else
+ USIM_WRITEREG(usim->base, USIM_IO_DIRECT, C4, state);
+ return ret;
+}
+
+static int usim_set_c8(struct usim *usim, int state)
+{
+ int ret = 0;
+ struct sc_phy *phy = usim->phy;
+
+ if (usim->phy_present)
+ ret = phy->set_config(phy, usim->slot, SC_PHY_PIN_C8, state);
+ return ret;
+}
+static int usim_get_version(struct usim *usim)
+{
+ int version = 0x0;
+
+ /* last 16 bytes represents controller version
+ * and first 16 bytes represents phy version (if connected)
+ */
+ version = USIM_READREG(usim->base, USIM_REVISION, REV);
+ if (usim->phy_present)
+ version |= ((usim->phy->get_config(usim->phy, 0,
+ SC_PHY_VERSION)) << 0x10);
+ return version;
+}
+static int usim_init_emvusercard(struct usim *usim)
+{
+ int ret = 0;
+ struct sc_phy *phy = usim->phy;
+
+ USIM_WRITEREG(usim->base, USIM_CONF1, EMV_CONF, 1);
+ USIM_WRITEREG(usim->base, USIM_FIFOS, DMA_MODE, 0);
+
+ usim_set_etu(usim, 0, 0);
+
+ if (usim_set_txretrycount(usim, 5) != 0)
+ return -EINVAL;
+
+ if (usim_set_rxparitycount(usim, 5) != 0)
+ return -EINVAL;
+
+ usim_set_c4(usim, 0);
+ usim_set_c8(usim, 0);
+
+ if (usim->phy_present) {
+ /* Set early ATR and mute ATR in case of phy */
+ ret = phy->set_config(phy, usim->slot, SC_PHY_ATR_EARLY_TIME,
+ USIM_EMV_ATR_EARLY_TO);
+ if (ret != 0)
+ return ret;
+
+ ret = phy->set_config(phy, usim->slot, SC_PHY_ATR_MUTE_TIME,
+ USIM_EMV_ATR_MUTE_TO);
+ if (ret != 0)
+ return ret;
+
+ /* enable user slot */
+ ret = phy->set_config(phy, usim->slot, SC_PHY_IO, 1);
+ if (ret != 0)
+ return ret;
+ }
+ /* set cwt,wwt,cgt */
+ USIM_WRITEREG(usim->base, USIM_WWT, WWT, USIM_EMV_WWT);
+ USIM_WRITEREG(usim->base, USIM_CWT, CWT, USIM_EMV_WWT - 22);
+ USIM_WRITEREG(usim->base, USIM_CGT, CGT, USIM_EMV_CGT);
+
+ return 0;
+}
+
+static int usim_warmreset(struct usim *usim)
+{
+ int ret = 0;
+ struct sc_phy *phy = usim->phy;
+
+ /* reset ATR wait flag */
+ usim->atrdone = 0;
+
+ usim->slot_ctx[usim->slot].atr_length = 0;
+ USIM_WRITEREG(usim->base, USIM_CONF6, MODE, USIM_MODE_IDLE);
+ usim->slot_ctx[usim->slot].state = USIM_MODE_IDLE;
+
+ /* reset FIFO pointer */
+ USIM_WRITEREG(usim->base, USIM_FIFOS, FIFOTX_RESET, 1);
+ USIM_WRITEREG(usim->base, USIM_FIFOS, FIFOTX_RESET, 0);
+ USIM_WRITEREG(usim->base, USIM_FIFOS, FIFORX_RESET, 1);
+ USIM_WRITEREG(usim->base, USIM_FIFOS, FIFORX_RESET, 0);
+
+ USIM_WRITEREG(usim->base, USIM_CMD, STOP_EMV_ATR_LENGTH_TIMER, 0x1);
+ USIM_WRITEREG(usim->base, USIM_CMD, STOP_EMV_ATR_LENGTH_TIMER, 0x0);
+
+ /* Do store bytes with parity error in Rx FIFO */
+ USIM_WRITEREG(usim->base, USIM_CONF2, PUT_ERR_IN_FIFO, 0x1);
+
+ usim_irq_disable(usim->base, (USIM_IRQ_TX | USIM_IRQ_EOB));
+
+ usim->slot_ctx[usim->slot].state = USIM_MODE_ATR;
+
+ /* warm reset the card */
+ if (usim->phy_present) {
+ ret = phy->warm_reset(phy, usim->slot);
+ if (ret != 0)
+ return ret;
+ } else {
+ /* warm reset using USIM */
+ USIM_WRITEREG(usim->base, USIM_CMD, CMD_WARM_RST, 0x1);
+ }
+
+ USIM_WRITEREG(usim->base, USIM_CONF6, MODE, USIM_MODE_ATR);
+
+ return 0;
+}
+static int usim_set_cardmode(struct usim *usim, int slot, int card_mode)
+{
+ int val = 0;
+ struct sc_phy *phy = usim->phy;
+
+ if (card_mode != usim->slot_ctx[slot].card_mode) {
+ /* deactivate current card before changing the
+ * mode of smart card
+ */
+ usim_deactivate_card(usim);
+ } else {
+ dev_dbg(usim->dev, "mode is same as previous, no action!!");
+ return 0;
+ }
+
+ /* set card mode */
+ switch (card_mode) {
+ case USIM_CARD_MODE_ASYNC:
+ val = SC_PHY_ASYNC;
+ break;
+
+ case USIM_CARD_MODE_SYNC_TYPE1:
+ val = SC_PHY_SYNC_TYPE1;
+ break;
+
+ case USIM_CARD_MODE_SYNC_TYPE2:
+ val = SC_PHY_SYNC_TYPE2;
+ break;
+
+ case USIM_CARD_MODE_SYNC_OTHER:
+ val = SC_PHY_SYNC;
+ break;
+
+ default:
+ dev_err(usim->dev, "Invalid card mode");
+ return -EINVAL;
+ break;
+ }
+ if (usim->phy_present == USIM_PHY_PRESENT)
+ phy->set_config(usim->phy, slot, SC_PHY_CARD_MODE, val);
+ usim->slot_ctx[slot].card_mode = card_mode;
+ return 0;
+}
+
+static int usim_activate_synccard(struct usim *usim)
+{
+ int ret = 0;
+ int reg = 0;
These don't need to be initialized and can be 1 line.
will do
+ struct sc_phy *phy = usim->phy;
+ if (usim->phy_present != USIM_PHY_PRESENT) {
+ dev_err(usim->dev, "Sync card w/o phy is not supported");
+ return -EPERM;
+ }
+
+ /* Enable legacy bypass mode */
+ USIM_WRITEREG(usim->base, USIM_CONF1, CONFBYPASS, 1);
+
+ /* set all lines to state L */
+ writel(0x0, usim->base + USIM_IO_DIRECT);
+
+ /* configure h/w control mode for select slot */
+ reg = readl(usim->base + USIM_CONF6);
+ reg = USIM_SETFIELD(reg, USIM_CONF6, SCLK0_BYPASS, 1);
+ reg = USIM_SETFIELD(reg, USIM_CONF6, ATR_TIMER_BYPASS, 1);
+ /* lets put i/o line in SW ctrl */
+ reg = USIM_SETFIELD(reg, USIM_CONF6, IO_BYPASS, 0x3);
+ writel(reg, usim->base + USIM_CONF6);
+
+ /* activate the card */
+ ret = phy->activate_card(phy, usim->slot);
+ return ret;
+}
+
+static int usim_activate_card(struct usim *usim)
+{
+ int ret = 0;
+ int reg = 0;
+ struct sc_phy *phy = usim->phy;
+ int mode = usim->slot_ctx[usim->slot].card_mode;
+
+ usim->atrdone = 0;
+ usim->slot_ctx[usim->slot].atr_length = 0;
+
+ /* set card mode */
+ usim_set_cardmode(usim, usim->slot, mode);
+
+ if (usim->slot_ctx[usim->slot].card_mode != USIM_CARD_MODE_ASYNC) {
+ /* synchronous card activation */
+ ret = usim_activate_synccard(usim);
+ return ret;
+ }
+
+ if (usim->slot_ctx[usim->slot].emv)
+ usim_init_emvusercard(usim);
+
+ /* disable legacy bypass mode */
+ USIM_WRITEREG(usim->base, USIM_CONF1, CONFBYPASS, 0);
+
+ /* configure h/w control mode for select slot */
+ reg = readl(usim->base + USIM_CONF6);
+ reg = USIM_SETFIELD(reg, USIM_CONF6, SCLK0_BYPASS, 0);
+ reg = USIM_SETFIELD(reg, USIM_CONF6, ATR_TIMER_BYPASS, 0);
+ /* lets put i/o line in h/w ctrl */
+ reg = USIM_SETFIELD(reg, USIM_CONF6, IO_BYPASS, 0x2);
+ writel(reg, usim->base + USIM_CONF6);
+
+ USIM_WRITEREG(usim->base, USIM_CONF1, EMV_CONF, 1);
+ USIM_WRITEREG(usim->base, USIM_CONF6, MODE, USIM_MODE_IDLE);
+ USIM_WRITEREG(usim->base, USIM_FIFOS, FIFOTX_RESET, 1);
+ USIM_WRITEREG(usim->base, USIM_FIFOS, FIFOTX_RESET, 0);
+ USIM_WRITEREG(usim->base, USIM_FIFOS, FIFORX_RESET, 1);
+ USIM_WRITEREG(usim->base, USIM_FIFOS, FIFORX_RESET, 0);
+ USIM_WRITEREG(usim->base, USIM_FIFOS, FIFO_RX_TRIGGER, 0x103);
+
+ /* RXDMA_TYPE = 0x1 - USIM_RXFIFO_BYTECNT value is ignored */
+ USIM_WRITEREG(usim->base, USIM_FIFOS, RXDMA_TYPE, 0x1);
+ USIM_WRITEREG(usim->base, USIM_FIFOS, FIFO_ENABLE, 0x1);
+
+ /* Do store bytes with parity error in Rx FIFO */
+ USIM_WRITEREG(usim->base, USIM_CONF2, PUT_ERR_IN_FIFO, 0x1);
+ usim_irq_disable(usim->base, (USIM_IRQ_TX | USIM_IRQ_EOB));
+
+ USIM_WRITEREG(usim->base, USIM_CMD, STOP_EMV_ATR_LENGTH_TIMER, 0x1);
+ USIM_WRITEREG(usim->base, USIM_CMD, STOP_EMV_ATR_LENGTH_TIMER, 0x0);
+
+ /*
+ * Toggling ATR length to ensure 'USIM_STAT_ATRRX_AFTER_TIMEOUT'
+ * gets disable. EMVCo Test case ref#1703_21/22
+ */
+ USIM_WRITEREG(usim->base, USIM_CONF6, ATR_TIMEOUT, 0x1);
+ USIM_WRITEREG(usim->base, USIM_CONF6, ATR_TIMEOUT,
+ USIM_ATR_TIMEOUT_EMV);
+ USIM_WRITEREG(usim->base, USIM_CMD, MODULE_CLK_EN, 0x1);
+
+ usim->slot_ctx[usim->slot].state = USIM_MODE_ATR;
+
+ /* set smartcard clock */
+ usim_set_smartcardclock(usim, usim->slot_ctx[usim->slot].clock);
+
+ /* Activate card */
+ if (usim->phy_present) {
+ usim_irq_disable(usim->base, USIM_IRQ_TX|USIM_IRQ_ATR_START);
+ usim_irq_enable(usim->base, 0xFFFFFFF7);
+ usim_irq_disable(usim->base, USIM_IRQ_NATR);
+ usim_irq_enable(usim->base, USIM_IRQ_EMV_ATR_LENGTH_TIME_OUT);
+ usim_irq_disable(usim->base, USIM_IRQ_TX|USIM_IRQ_ATR_START);
+
+ /* do no bypass ATR length timer, also do not
+ * disturb the bypass setting of other param
+ */
+ USIM_WRITEREG(usim->base, USIM_CONF6, ATR_TIMER_BYPASS, 0x1);
+
+ usim_irqstatus_clear(usim->base, usim_irqstatus(usim->base));
+
+ ret = phy->activate_card(phy, usim->slot);
+ if (ret != 0)
+ return ret;
+ } else {
+ /* Activate using USIM */
+ USIM_WRITEREG(usim->base, USIM_CMD, CMDSTOP, 0x0);
+ USIM_WRITEREG(usim->base, USIM_CMD, CMDSTOP, 0x1);
+ }
+ USIM_WRITEREG(usim->base, USIM_CONF6, MODE, USIM_MODE_ATR);
+ return 0;
+}
+
+static int usim_deactivate_card(struct usim *usim)
+{
+ int ret = 0;
+ int cnt = 0;
+ struct sc_phy *phy = usim->phy;
+
+ /* clear atr buffer */
+ for (cnt = 0; cnt < USIM_MAX_ATRLENGTH; cnt++)
+ usim->slot_ctx[usim->slot].atr[cnt] = 0x0;
+ usim->slot_ctx[usim->slot].atr_length = 0x0;
+
+ /* Use USIM IP for deactivation if there is no phy */
+ if (usim->phy_present == USIM_PHY_PRESENT) {
+ ret = phy->deactivate_card(phy, usim->slot);
+ if (ret != 0)
+ return ret;
+ } else {
+ USIM_WRITEREG(usim->base, USIM_CMD, CMDSTART, 0x0);
+ USIM_WRITEREG(usim->base, USIM_CMD, CMDSTOP, 1);
+ }
+
+ USIM_WRITEREG(usim->base, USIM_CONF6, MODE, USIM_MODE_IDLE);
+ USIM_WRITEREG(usim->base, USIM_TX_BLOCK, BLOCK_LENGTH, 0);
+
+ /* Toggling ATR length to ensure 'USIM_STAT_ATRRX_AFTER_TIMEOUT'
+ * gets disable TC Ref: 1703_21/22
+ */
+ USIM_WRITEREG(usim->base, USIM_CONF6, ATR_TIMEOUT, 1);
+ USIM_WRITEREG(usim->base, USIM_CONF6, ATR_TIMEOUT,
+ USIM_ATR_TIMEOUT_EMV);
+
+ /* stop ATR length timeout */
+ USIM_WRITEREG(usim->base, USIM_CMD, STOP_EMV_ATR_LENGTH_TIMER, 1);
+ usim->slot_ctx[usim->slot].state = USIM_MODE_DEACT;
+ usim->atrdone = 0;
+
+ return 0;
+}
+
+static void usim_set_protocol(struct usim *usim, int protocol)
+{
+ u32 irq;
+
+ /* As per spec, mask all interrupts before switching
+ * from one protocol to other.
+ */
+ usim_irq_get(usim->base, &irq);
+
+ /* disable all interrupts */
+ usim_irq_disable(usim->base, 0xFFFFFFFF);
+
+ /* 0 for T=0 and 1 for T=1 protocol */
+ USIM_WRITEREG(usim->base, USIM_CONF2, CONFPROTOCOL, protocol);
+ usim->slot_ctx[usim->slot].protocol = protocol;
+
+ /* read and clear status */
+ usim_irqstatus_clear(usim->base, usim_irqstatus(usim->base));
+
+ /* now renable interrupts */
+ usim_irq_enable(usim->base, irq);
+ return;
+}
+
+static int usim_get_cardpinlevel(struct usim *usim, int pin, int *level)
+{
+ struct sc_phy *phy = usim->phy;
+ int param = 0;
+
+ if (usim->slot_ctx[usim->slot].card_mode == USIM_CARD_MODE_ASYNC) {
+ dev_err(usim->dev, "Operation not permitted for async mode");
+ return -EPERM;
+ }
+ if (pin == USIM_PARAM_CARD_PIN_IO) {
+ /* For Rx, RNW:1, OEN:1 */
+ USIM_WRITEREG(usim->base, USIM_IO_DIRECT, RNW0, 1);
+ USIM_WRITEREG(usim->base, USIM_IO_DIRECT, SIOEN0, 1);
+ *level = USIM_READREG(usim->base, USIM_IO_DIRECT, SIORX0);
+ return 0;
+ }
+
+ if (usim->phy_present == USIM_PHY_PRESENT) {
+ switch (pin) {
+ case USIM_PARAM_CARD_PIN_VCC:
+ break;
+
+ case USIM_PARAM_CARD_PIN_RST:
+ param = SC_PHY_PIN_RST;
+ break;
+
+ case USIM_PARAM_CARD_PIN_CLK:
+ param = SC_PHY_PIN_CLK;
+ break;
+
+ default:
+ dev_err(usim->dev, "Invalid pin");
+ return -EINVAL;
+ }
+ *level = phy->get_config(phy, usim->slot, param);
+ return 0;
+ } else {
+ switch (pin) {
+ case USIM_PARAM_CARD_PIN_VCC:
+ *level = USIM_READREG(usim->base,
+ USIM_IO_DIRECT, SVCC);
+ break;
+
+ case USIM_PARAM_CARD_PIN_RST:
+ *level = USIM_READREG(usim->base,
+ USIM_IO_DIRECT, RST);
+ break;
+
+ case USIM_PARAM_CARD_PIN_CLK:
+ *level = USIM_READREG(usim->base,
+ USIM_IO_DIRECT, SCLK0);
+ break;
+
+ default:
+ dev_err(usim->dev, "Invalid pin");
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+static int usim_set_cardpinlevel(struct usim *usim, int pin, int level)
+{
+ int ret = 0;
+ int param = 0;
+ int value = level > 0 ? 1 : 0;
+ struct sc_phy *phy = usim->phy;
+
+ if (usim->slot_ctx[usim->slot].card_mode == USIM_CARD_MODE_ASYNC) {
+ dev_err(usim->dev, "Operation not permitted for async mode");
+ return -EPERM;
+ }
+ if (pin == USIM_PARAM_CARD_PIN_IO) {
+ /* For Tx: RNW=0; OEN=Tx */
+ /* Tx line will be followed by OEN line, so setting OEN bit*/
+ USIM_WRITEREG(usim->base, USIM_IO_DIRECT, RNW0, 0);
+ USIM_WRITEREG(usim->base, USIM_IO_DIRECT, SIOEN0, value);
+ return 0;
+ }
+
+ if (usim->phy_present == USIM_PHY_PRESENT) {
+ switch (pin) {
+ case USIM_PARAM_CARD_PIN_VCC:
+ /* will be set by activation or deactivation */
+ break;
+
+ case USIM_PARAM_CARD_PIN_RST:
+ param = SC_PHY_PIN_RST;
+ break;
+
+ case USIM_PARAM_CARD_PIN_CLK:
+ param = SC_PHY_PIN_CLK;
+ break;
+
+ default:
+ dev_err(usim->dev, "Invalid pin");
+ return -EINVAL;
+ }
Make this switch a helper function. Same code exists above.
Actually, there are mutliple combination possible.+ ret = phy->set_config(phy, usim->slot, param, value);
+ return ret;
+ } else {
+ switch (pin) {
+ case USIM_PARAM_CARD_PIN_VCC:
+ USIM_WRITEREG(usim->base,
+ USIM_IO_DIRECT, SVCC, value);
+ break;
+
+ case USIM_PARAM_CARD_PIN_RST:
+ USIM_WRITEREG(usim->base,
+ USIM_IO_DIRECT, RST, value);
+ break;
+
+ case USIM_PARAM_CARD_PIN_CLK:
+ USIM_WRITEREG(usim->base,
+ USIM_IO_DIRECT, SCLK0, value);
+ break;
+
+ default:
+ dev_err(usim->dev, "Invalid pin");
+ return -EINVAL;
+ }
Probably something similar can be done here. Perhaps you should have
default phy ops which is built-in. Then you can always just call
phy->set_config.
This is to give complete register dump using sysfs. It helped me a lot while doing IP validation against EMV compliance. Easy to share reg dump to various team (IP design team, customer etc.)
+ }
+ return 0;
+}
+static void usim_configure_rx_pio(struct usim *usim)
+{
+ /* Reset RX FIFO pointers */
+ USIM_WRITEREG(usim->base, USIM_FIFOS, FIFORX_RESET, 1);
+ USIM_WRITEREG(usim->base, USIM_FIFOS, FIFORX_RESET, 0);
+
+ /* read and clear any pending interrupt status */
+ usim_irqstatus_clear(usim->base, usim_irqstatus(usim->base));
+
+ /* Enable WWT underflow interupt,
+ * RX FIFO full interrupt,
+ * BWT, CWT and parity error level interrupts.
+ */
+ usim_irq_enable(usim->base, USIM_IRQ_WT | USIM_IRQ_RXFULL |
+ USIM_IRQ_TOB |
+ USIM_IRQ_TOC |
+ USIM_IRQ_PAR_ERR_LEVEL_REACHED);
+
+ /* Lets disable key RX interrupts. We will enable them later
+ * when we want to start RX
+ */
+ usim_irq_disable(usim->base, USIM_IRQ_RX |
+ USIM_IRQ_RXDMA_RDY | USIM_IRQ_EOB);
+
+ /* We will use only RX FIFO threshold in RX */
+ USIM_WRITEREG(usim->base, USIM_FIFOS, RXDMA_TYPE, 0x1);
+
+ if (usim->slot_ctx[usim->slot].protocol == 0) {
+ /* Set Rx FIFO Threshold to expected recv length
+ * Subtract 1 from length as HW adds 1 to the trigger
+ */
+ USIM_WRITEREG(usim->base, USIM_FIFOS, FIFO_RX_TRIGGER,
+ usim->slot_ctx[usim->slot].rx_explen - 1);
+ } else {
+ /* T=1 protocol */
+ /* for T1 we should not use parity error level interrupt */
+ usim_irq_disable(usim->base, USIM_IRQ_PAR_ERR_LEVEL_REACHED);
+
+ /* set RX FIFO threshold to MAX_RX_FIFO size.
+ * We will rely on End-Of-Block interrupt to
+ * terminate reception in T1
+ */
+ USIM_WRITEREG(usim->base, USIM_FIFOS, FIFO_RX_TRIGGER,
+ USIM_MAX_RX_FIFO_SIZE - 1);
+ }
+ return;
+}
+
+static void usim_configure_tx_pio(struct usim *usim)
+{
+ /* Make sure TX is stopped first by programming
+ * TX_BLOCK to zero and disabling TX_BLOCK_DONE
+ * and USIM_IRQ_TX_BLOCK_REQ interrupts
+ */
+ USIM_WRITEREG(usim->base, USIM_TX_BLOCK, BLOCK_LENGTH, 0);
+ usim_irq_disable(usim->base, USIM_IRQ_TX_BLOCK_DONE |
+ USIM_IRQ_TX_BLOCK_REQ);
+
+ /* We will use Tx Block length feature so clear TX_EN bit */
+ USIM_WRITEREG(usim->base, USIM_CONF2, TX_EN, 0);
+ /* We will not use USIM_TX interrupt for transmit operation */
+ usim_irq_disable(usim->base, USIM_IRQ_TX);
+ /* Reset TX FIFO pointers */
+ USIM_WRITEREG(usim->base, USIM_FIFOS, FIFOTX_RESET, 1);
+ USIM_WRITEREG(usim->base, USIM_FIFOS, FIFOTX_RESET, 0);
+
+ /* Ensure PIO mode is programmed */
+ USIM_WRITEREG(usim->base, USIM_FIFOS, DMA_MODE, 0);
+}
+
+static int usim_send_data(struct usim *usim, char *txbuf, int len)
+{
+ u32 val;
+ int i;
+ int ret = 0;
+
+ usim->txdone = 0;
+ usim->rxdone = 0;
+
+ if (len == 0) {
+ dev_dbg(usim->dev, "Error: Invalid Tx length:%d", len);
+ return -EINVAL;
+ }
+
+ usim->slot_ctx[usim->slot].event = 0;
+
+ /* Configure Tx PIO mode patams */
+ usim_configure_tx_pio(usim);
+
+ /* Tx FIFO must be empty after reset */
+ val = USIM_READREG(usim->base, USIM_FIFOS, FIFOTX_EMPTY);
+ if (val == 0) {
+ dev_dbg(usim->dev, "Error: Tx FIFO is not empty");
+ return -EFAULT;
+ }
+
+ /* write data in Tx FIFO */
+ for (i = 0; i < len; i++) {
+ USIM_WRITEREG(usim->base, USIM_DTX, DTX, txbuf[i]);
+ dev_dbg(usim->dev, "txbyte %d = %x\n", i, txbuf[i]);
+ }
+
+ /* Finally re-enable TX_BLOCK_xxx interrupts and clear RX interrupts */
+ usim_irq_enable(usim->base, USIM_IRQ_TX_BLOCK_DONE |
+ USIM_IRQ_TX_BLOCK_REQ);
+
+ /* For T=0, stop re-tranmission after resend failure */
+ if (usim->slot_ctx[usim->slot].protocol == 0) {
+ USIM_WRITEREG(usim->base, USIM_CONF2, STOP_RESEND_FAILURE, 0);
+ USIM_WRITEREG(usim->base, USIM_CONF2, STOP_RESEND_FAILURE, 1);
+ }
+
+ /* Do not store bytes with parity error in Rx FIFO */
+ USIM_WRITEREG(usim->base, USIM_CONF2, PUT_ERR_IN_FIFO, 0);
+
+ usim_irq_enable(usim->base, USIM_IRQ_TOC);
+
+ if (usim->phy_present)
+ USIM_WRITEREG(usim->base, USIM_CONF6, MODE, USIM_MODE_TXRX);
+ else
+ USIM_WRITEREG(usim->base, USIM_CONF6, MODE, USIM_MODE_LEGACY);
+
+ usim->slot_ctx[usim->slot].state = USIM_MODE_TXRX;
+
+ /* Configure Rx settings before performing a Tx
+ * As soon as we are done with Tx, card will send
+ * data, which we should be ready to capture
+ */
+ usim_configure_rx_pio(usim);
+ /* Start TX operation - program TX_BLOCK register to length
+ * of the TX buffer to start the TX operation.
+ */
+ USIM_WRITEREG(usim->base, USIM_TX_BLOCK, BLOCK_LENGTH, len);
+
+ /* We need to block the caller here */
+ ret = wait_event_interruptible(tx_wait, (usim->txdone == 1));
+ dev_dbg(usim->dev, "Tx WAIT OVER\n");
+ if (usim->slot_ctx[usim->slot].event == USIM_EVENT_TIMEOUT)
+ usim_send_signal(usim, USIM_EVENT_TIMEOUT);
+
+ return ret;
+}
+
+static int usim_set_config(struct usim *usim, struct usim_config *param)
+{
+ u32 ret = 0;
+ dev_dbg(usim->dev, "param:%d, value:%d\n", param->attr, param->value);
+
+ switch (param->attr) {
+ case USIM_PARAM_CWT:
+ USIM_WRITEREG(usim->base, USIM_CWT, CWT, param->value);
+ break;
+
+ case USIM_PARAM_WWT:
+ USIM_WRITEREG(usim->base, USIM_WWT, WWT, param->value);
+ break;
+
+ case USIM_PARAM_CGT:
+ USIM_WRITEREG(usim->base, USIM_CGT, CGT, param->value);
+ break;
+
+ case USIM_PARAM_BWT:
+ USIM_WRITEREG(usim->base, USIM_BWT, BWT, param->value);
+ break;
+
+ case USIM_PARAM_EDCTYPE:
+ /* 0 = LRC check, 1 = CRC check */
+ USIM_WRITEREG(usim->base, USIM_CONF2, CONFEDC, param->value);
+ break;
+
+ case USIM_PARAM_LRCCHECK:
+ /* 0 = No LRC check, 1 = LRC check */
+ USIM_WRITEREG(usim->base, USIM_CONF2, CONFLRCCHECK,
+ param->value);
+ break;
+
+ case USIM_PARAM_C4:
+ usim_set_c4(usim, param->value);
+ break;
+
+ case USIM_PARAM_C8:
+ usim_set_c8(usim, param->value);
+ break;
+
+ case USIM_PARAM_PROTOCOL:
+ /* 0 for T=0 and 1 for T=1 */
+ usim_set_protocol(usim, param->value);
+ break;
+
+ case USIM_PARAM_VOLTAGE:
+ ret = usim_set_voltage(usim, param->value);
+ break;
+
+ case USIM_PARAM_EMV:
+ USIM_WRITEREG(usim->base, USIM_CONF1, EMV_CONF, param->value);
+ if (param->value)
+ usim->slot_ctx[usim->slot].emv = true;
+ else
+ usim->slot_ctx[usim->slot].emv = false;
+ break;
+
+ case USIM_PARAM_FI:
+ USIM_WRITEREG(usim->base, USIM_CONF5, SOFT_NHARD_FIDI_PROG, 0);
+ USIM_WRITEREG(usim->base, USIM_CONF5, FI, param->value);
+ break;
+
+ case USIM_PARAM_DI:
+ USIM_WRITEREG(usim->base, USIM_CONF5, SOFT_NHARD_FIDI_PROG, 0);
+ USIM_WRITEREG(usim->base, USIM_CONF5, DI, param->value);
+ break;
+
+ case USIM_PARAM_CODING_CONV:
+ USIM_WRITEREG(usim->base, USIM_STAT, CONFCODCONV, param->value);
+ break;
+
+ case USIM_PARAM_CLOCK_STOP:
+ USIM_WRITEREG(usim->base, USIM_CMD, CMD_CLOCK_STOP,
+ param->value);
+ break;
+
+ case USIM_PARAM_SMARTCARD_CLOCK:
+ ret = usim_set_smartcardclock(usim, param->value);
+ break;
+
+ case USIM_PARAM_SMARTCARD_MODE:
+ ret = usim_set_cardmode(usim, usim->slot, param->value);
+ break;
+
+ case USIM_PARAM_CARD_PIN_VCC:
+ case USIM_PARAM_CARD_PIN_RST:
+ case USIM_PARAM_CARD_PIN_CLK:
+ case USIM_PARAM_CARD_PIN_IO:
+ ret = usim_set_cardpinlevel(usim, param->attr, param->value);
+ break;
+
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+static void usim_get_syncatr(struct usim *usim)
+{
+ int ret = 0;
+ struct sc_phy *phy = usim->phy;
+
+ /* Max ATR bytes for sync card is 4 */
+ usim->slot_ctx[usim->slot].atr_length = 0x4;
+ if (usim->phy_present != USIM_PHY_PRESENT) {
+ dev_err(usim->dev, "Sync card w/o phy is not supported");
+ return;
+ }
+ /* get sync ATR */
+ if (phy->get_syncatr == NULL)
+ return;
+ ret = phy->get_syncatr(phy,
+ usim->slot,
+ usim->slot_ctx[usim->slot].atr_length,
+ usim->slot_ctx[usim->slot].atr);
+ return;
+}
+
+static int usim_get_config(struct usim *usim, struct usim_config *param)
+{
+ u32 ret = 0;
+ dev_dbg(usim->dev, "param:%d, value:%d\n", param->attr, param->value);
+
+ switch (param->attr) {
+ case USIM_PARAM_CWT:
+ param->value = USIM_READREG(usim->base, USIM_CWT, CWT);
+ break;
+
+ case USIM_PARAM_WWT:
+ param->value = USIM_READREG(usim->base, USIM_WWT, WWT);
+ break;
+
+ case USIM_PARAM_CGT:
+ param->value = USIM_READREG(usim->base, USIM_CGT, CGT);
+ break;
+
+ case USIM_PARAM_BWT:
+ param->value = USIM_READREG(usim->base, USIM_BWT, BWT);
+ break;
+
+ case USIM_PARAM_EDCTYPE:
+ param->value = USIM_READREG(usim->base, USIM_CONF2, CONFEDC);
+ break;
+
+ case USIM_PARAM_LRCCHECK:
+ param->value = USIM_READREG(usim->base, USIM_CONF2,
+ CONFLRCCHECK);
+ break;
+
+ case USIM_PARAM_PROTOCOL:
+ /* 0 for T=0 and 1 for T=1 */
+ param->value = USIM_READREG(usim->base, USIM_CONF2,
+ CONFPROTOCOL);
+ break;
+
+ case USIM_PARAM_VOLTAGE:
+ param->value = usim->slot_ctx[usim->slot].supply;
+ break;
+
+ case USIM_PARAM_EMV:
+ param->value = USIM_READREG(usim->base, USIM_CONF1, EMV_CONF);
+ break;
+
+ case USIM_PARAM_FI:
+ param->value = USIM_READREG(usim->base, USIM_CONF5, FI);
+ break;
+
+ case USIM_PARAM_DI:
+ param->value = USIM_READREG(usim->base, USIM_CONF5, DI);
+ break;
+
+ case USIM_PARAM_CODING_CONV:
+ param->value = USIM_READREG(usim->base, USIM_STAT, CONFCODCONV);
+ break;
+
+ case USIM_PARAM_CLOCK_STOP:
+ param->value = USIM_READREG(usim->base, USIM_CMD,
+ CMD_CLOCK_STOP);
+ break;
+
+ case USIM_PARAM_SMARTCARD_CLOCK:
+ param->value = usim->slot_ctx[usim->slot].clock;
+ break;
+
+ case USIM_PARAM_SMARTCARD_MODE:
+ param->value = usim->slot_ctx[usim->slot].card_mode;
+ break;
+
+ case USIM_PARAM_CARD_PIN_VCC:
+ case USIM_PARAM_CARD_PIN_RST:
+ case USIM_PARAM_CARD_PIN_CLK:
+ case USIM_PARAM_CARD_PIN_IO:
+ ret = usim_get_cardpinlevel(usim, param->attr, ¶m->value);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static long usim_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct usim *usim = file->private_data;
+ struct usim_data data;
+ struct usim_config param;
+
+ int ret = 0;
+ int cnt = 0;
+ int version = 0;
+ int u_pid = 0;
+ int present = 0;
+ unsigned long atr_timeout = 0;
+
+ if (usim->phy_present == USIM_PHY_NOT_ATTACHED)
+ return -ENXIO;
+
+ switch (cmd) {
+ case USIM_IOCTL_GET_PROVIDER_VERSION:
+ dev_dbg(usim->dev, "IOCTL: GET PROVIDER VERSION\n");
+ version = usim_get_version(usim);
+ ret = copy_to_user((unsigned int *)arg, &version,
+ sizeof(unsigned int));
+ if (ret != 0)
+ ret = -EFAULT;
+ break;
+
+ case USIM_IOCTL_ACTIVATE_CARD:
+ dev_dbg(usim->dev, "IOCTL: ACTIVATE CARD\n");
+ if (usim->phy_present) {
+ present = usim->phy->get_config(usim->phy, usim->slot,
+ SC_PHY_CARD_PRESENCE);
+ if (present)
+ ret = usim_activate_card(usim);
+ else
+ ret = -EFAULT;
+ }
+ break;
+
+ case USIM_IOCTL_DEACTIVATE_CARD:
+ dev_dbg(usim->dev, "IOCTL: DEACTIVATE CARD\n");
+ ret = usim_deactivate_card(usim);
+ break;
+
+ case USIM_IOCTL_WARM_RESET:
+ dev_dbg(usim->dev, "IOCTL: WARM RESET\n");
+ ret = usim_warmreset(usim);
+ break;
+
+ case USIM_IOCTL_GET_ATR:
+ dev_dbg(usim->dev, "IOCTL: GET ATR\n");
+ /* waiting for max ATR response timeout */
+ atr_timeout = usecs_to_jiffies(MAX_ATR_WAITTIME_US);
+ dev_dbg(usim->dev,
+ "GET_ATR:atr timeout, us:%d, jiffies:%ld",
+ MAX_ATR_WAITTIME_US, atr_timeout);
+ wait_event_timeout(atr_wait, (usim->atrdone == 1), atr_timeout);
+ if (usim->slot_ctx[usim->slot].card_mode !=
+ USIM_CARD_MODE_ASYNC) {
+ usim_get_syncatr(usim);
+ }
+ ret = copy_to_user((char __user *)arg,
+ usim->slot_ctx[usim->slot].atr,
+ usim->slot_ctx[usim->slot].atr_length);
+ if (ret != 0)
+ ret = -EFAULT;
+ else
+ ret = usim->slot_ctx[usim->slot].atr_length;
+ break;
+
+ case USIM_IOCTL_SEND_DATA:
+ dev_dbg(usim->dev, "IOCTL: SEND DATA\n");
+ ret = copy_from_user(&data, (struct usim_data *)arg,
+ sizeof(struct usim_data));
+ if (ret != 0)
+ return -EFAULT;
+
+ usim->slot = data.slot;
+ usim->slot_ctx[usim->slot].rx_explen = data.rxexplen;
+ usim->slot_ctx[usim->slot].rx_counter = 0;
+ for (cnt = 0; cnt < data.txlen; cnt++)
+ dev_dbg(usim->dev, "apdu[%d] = %x\n", cnt,
+ data.apdu[cnt]);
+ ret = usim_send_data(usim, &data.apdu[0], data.txlen);
+ break;
+
+ case USIM_IOCTL_SET_CONFIG:
+ dev_dbg(usim->dev, "IOCTL: SET CONFIG\n");
+ ret = copy_from_user(¶m, (struct usim_config *)arg,
+ sizeof(struct usim_config));
+ if (ret != 0)
+ return -EFAULT;
+
+ usim_set_config(usim, ¶m);
+ break;
+
+ case USIM_IOCTL_GET_CONFIG:
+ dev_dbg(usim->dev, "IOCTL: GET CONFIG\n");
+ ret = copy_from_user(¶m, (struct usim_config *)arg,
+ sizeof(struct usim_config));
+ if (ret != 0)
+ return -EFAULT;
+
+ usim_get_config(usim, ¶m);
+ ret = copy_to_user((struct usim_config *)arg, ¶m,
+ sizeof(struct usim_config));
+ if (ret != 0)
+ ret = -EFAULT;
+ break;
+
+ case USIM_IOCTL_GET_CARD_PRESENCE:
+ dev_dbg(usim->dev, "IOCTL: CARD PRESENCE\n");
+ if (usim->phy_present) {
+ present = usim->phy->get_config(usim->phy, usim->slot,
+ SC_PHY_CARD_PRESENCE);
+ ret = copy_to_user((unsigned int *)arg, &present,
+ sizeof(unsigned int));
+ if (ret != 0)
+ ret = -EFAULT;
+ }
+ break;
+
+ case USIM_IOCTL_REGISTER_PID:
+ dev_dbg(usim->dev, "IOCTL: USIM_IOCTL_REGISTER_PID");
+ ret = copy_from_user(&u_pid, (int *)arg, sizeof(int));
+ if (ret != 0)
+ return -EFAULT;
+ usim->user_pid = u_pid;
+ break;
+ }
+ return ret;
+}
+
+static ssize_t usim_read(struct file *file, char *user_buf,
+ size_t count, loff_t *ppos)
+{
+ struct usim *usim = file->private_data;
+ if (usim->phy_present == USIM_PHY_NOT_ATTACHED)
+ return -ENXIO;
+
+ wait_event_interruptible(rx_wait, (usim->rxdone == 1));
+ dev_dbg(usim->dev, "RX WAIT over\n");
+
+ /* check for timeout and send signal if any */
+ if (usim->slot_ctx[usim->slot].event == USIM_EVENT_TIMEOUT)
+ usim_send_signal(usim, USIM_EVENT_TIMEOUT);
+
+ if (copy_to_user(user_buf, usim->slot_ctx[usim->slot].rxbuf,
+ usim->slot_ctx[usim->slot].rx_counter)) {
+ dev_err(usim->dev, "Copy failed\n");
+ return -EFAULT;
+ }
+ *ppos = usim->slot_ctx[usim->slot].rx_counter;
+ dev_dbg(usim->dev, "Card response returning %d bytes\n",
+ usim->slot_ctx[usim->slot].rx_counter);
+
+ return usim->slot_ctx[usim->slot].rx_counter;
+}
+
+#ifdef CONFIG_DEBUG_FS
+
+#define DUMP_REG(r) seq_printf(s, "%-25s: %08x\n", #r, readl(usim->base + r));
+
+static int usim_regdump_show(struct seq_file *s, void *unused)
+{
devmem2 will not work for this purpose?
PHY is indepent entiry and can be build & inserted as module. So handling the detachment senario over here.
+ struct usim *usim = s->private;
+
+ seq_puts(s, "USIM Register Dump\n");
+
+ DUMP_REG(USIM_REVISION);
+ DUMP_REG(USIM_IDENT);
+ DUMP_REG(USIM_SYSCONFIG);
+ DUMP_REG(USIM_SYSSTATUS);
+ DUMP_REG(USIM_IRQSTATUS);
+ DUMP_REG(USIM_IRQENABLE);
+ DUMP_REG(USIM_WAKEUPEN);
+ DUMP_REG(USIM_CMD);
+ DUMP_REG(USIM_STAT);
+ DUMP_REG(USIM_CONF1);
+ DUMP_REG(USIM_CONF2);
+ DUMP_REG(USIM_CONF3);
+ DUMP_REG(USIM_DRX);
+ DUMP_REG(USIM_DTX);
+ DUMP_REG(USIM_FIFOS);
+ DUMP_REG(USIM_CGT);
+ DUMP_REG(USIM_BWT);
+ DUMP_REG(USIM_DEBUG);
+ DUMP_REG(USIM_CONF_SAM1_DIV);
+ DUMP_REG(USIM_CONF4);
+ DUMP_REG(USIM_ATR_CLK_PRD_NBS);
+ DUMP_REG(USIM_CONF_ETU_DIV);
+ DUMP_REG(USIM_CONF5);
+ DUMP_REG(USIM_TC_GUARD_TIME_ADD);
+ DUMP_REG(USIM_RXFIFO_LEVEL);
+ DUMP_REG(USIM_RXFIFO_BYTECNT);
+ DUMP_REG(USIM_WWT);
+ DUMP_REG(USIM_CONF6);
+ DUMP_REG(USIM_IO_DIRECT);
+ DUMP_REG(USIM_TX_BLOCK);
+
+ return 0;
+}
+
+static int usim_regdump_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, usim_regdump_show, inode->i_private);
+}
+
+static const struct file_operations usim_regdump_fops = {
+ .open = usim_regdump_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int usim_init_debugfs(struct usim *usim)
+{
+ int ret;
+ struct dentry *root;
+ struct dentry *file;
+
+ root = debugfs_create_dir("usim", NULL);
+ if (!root) {
+ ret = -ENOMEM;
+ goto err0;
+ }
+
+ file = debugfs_create_file("regdump", S_IRUGO, root, usim,
+ &usim_regdump_fops);
+ if (!file) {
+ ret = -ENOMEM;
+ goto err1;
+ }
+
+ usim->debugfs_root = root;
+
+ return 0;
+err1:
+ debugfs_remove_recursive(root);
+err0:
+ return ret;
+}
+#endif
+
+static int usim_pm_init(struct usim *usim)
+{
+ int ret = 0;
+
+ usim->usim0_fck = clk_get(usim->dev, "usim0_fck");
+ if (IS_ERR(usim->usim0_fck)) {
+ ret = PTR_ERR(usim->usim0_fck);
+ dev_err(usim->dev, "usim0_fck failed error:%d\n", ret);
+ return -1;
+ }
+ usim->dpll_core_m4_ck = clk_get(usim->dev, "dpll_core_m4_ck");
+ if (IS_ERR(usim->dpll_core_m4_ck)) {
+ ret = PTR_ERR(usim->dpll_core_m4_ck);
+ dev_err(usim->dev, "dpll_core_m4_ck failed error:%d\n", ret);
+ return -1;
+ }
+ ret = clk_set_parent(usim->usim0_fck, usim->dpll_core_m4_ck);
+ if (ret != 0)
+ dev_dbg(usim->dev, "clk set parent failed: %d\n", ret);
+
+ usim->usim_dbclk = clk_get(usim->dev, "usim_dbck");
+ if (IS_ERR(usim->usim_dbclk)) {
+ ret = PTR_ERR(usim->usim_dbclk);
+ dev_err(usim->dev, "usim_dbck failed error:%d\n", ret);
+ return -1;
+ }
+
+ usim->clkdiv32k_ick = clk_get(usim->dev, "clkdiv32k_ick");
+ if (IS_ERR(usim->usim_dbclk)) {
+ ret = PTR_ERR(usim->clkdiv32k_ick);
+ dev_err(usim->dev, "clkdiv32k_ick failed error:%d\n", ret);
+ return -1;
+ }
+
+ ret = clk_set_parent(usim->usim_dbclk, usim->clkdiv32k_ick);
+ if (ret != 0)
+ dev_dbg(usim->dev, "usim_dbclk set parent failed: %d\n", ret);
+
+ usim->opt_fclk = devm_clk_get(usim->dev, "opt_fck");
+ if (IS_ERR(usim->opt_fclk)) {
+ ret = PTR_ERR(usim->opt_fclk);
+ dev_err(usim->dev, "unable to get fck\n");
+ return ret;
+ }
+
+ usim->opt_fclk32 = devm_clk_get(usim->dev, "opt_fck32");
+ if (IS_ERR(usim->opt_fclk32)) {
+ ret = PTR_ERR(usim->opt_fclk32);
+ dev_err(usim->dev, "unable to get dbclk\n");
+ return ret;
+ }
+
+ return 0;
+}
+static int usim_enable(struct usim *usim)
+{
+ int ret = 0;
+ if (usim->enable == 1)
+ return 0;
+
+ /* enable the clk */
+ pm_runtime_get_sync(usim->dev);
+ clk_enable(usim->opt_fclk32);
+ clk_enable(usim->opt_fclk);
+
+ /* usim init */
+ ret = usim_configure(usim);
+ if (ret)
+ return -EIO;
+
+ usim->enable = 1;
+ return 0;
+}
+
+static void usim_disable(struct usim *usim)
+{
+ int cnt = 0;
+
+ if (usim->enable == 0)
+ return;
+
+ /* reset USIM state for deactivation */
+ for (cnt = 0; cnt < usim->max_slots; cnt++) {
+ usim->slot = cnt;
+ usim_deactivate_card(usim);
+ }
+
+ /* reset default slot and clock */
+ usim->slot = 0;
+ usim->slot_ctx[usim->slot].clock = USIM_SMARTCART_CLOCK_5MHZ;
+
+ /* shutdown phy */
+ if (usim->phy_present == USIM_PHY_PRESENT)
+ usim->phy->set_config(usim->phy, usim->slot, SC_PHY_MODE,
+ SC_PHY_SHUTDOWN);
+ /* disable clk */
+ clk_disable(usim->opt_fclk32);
+ clk_disable(usim->opt_fclk);
+ pm_runtime_put_sync_autosuspend(usim->dev);
+ usim->enable = 0;
+ return;
+}
+
+static int usim_open(struct inode *inode, struct file *file)
+{
+ int ret = 0;
+ struct usim *usim = (struct usim *)dev_get_drvdata(usim_dev.parent);
+
+ if (usim->phy_present == USIM_PHY_NOT_ATTACHED)
+ return -ENXIO;
+
+ file->private_data = usim;
+ ret = usim_enable(usim);
+ if (ret)
+ return -ENXIO;
+
+ return 0;
+}
+
+static int usim_release(struct inode *inode, struct file *file)
+{
+ struct usim *usim = (struct usim *)dev_get_drvdata(usim_dev.parent);
+
+ usim_disable(usim);
+ usim->user_pid = 0;
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int usim_suspend(struct device *dev)
+{
+ /* struct usim *usim = dev_to_usim(dev); */
+ struct usim *usim = dev_to_usim(dev);
+ if (usim->atrdone == 1) {
+ dev_dbg(usim->dev, "card is active state, aborting suspend");
+ return -EBUSY;
+ }
+ usim_disable(usim);
+ return 0;
+}
+
+static int usim_resume(struct device *dev)
+{
+ /* struct usim *usim = dev_to_usim(dev); */
+ struct usim *usim = dev_to_usim(dev);
+ usim_enable(usim);
+ return 0;
+}
+
+static const struct dev_pm_ops usim_pm_ops = {
+ .suspend = usim_suspend,
+ .resume = usim_resume,
+};
+
+#define USIM_PM_OPS (&usim_pm_ops)
+#else
+#define USIM_PM_OPS NULL
+#endif
+
+static int usim_notify(struct notifier_block *self, unsigned long action, void
+ *data)
+{
+ struct usim *usim = (struct usim *)data;
+ int event = action & SC_PHY_NOTIFICATION_ACTION_MASK;
+ int slot = (action & SC_PHY_NOTIFICATION_SLOT_MASK) >>
+ SC_PHY_NOTIFICATION_SLOT_SHIFT;
+ int t_slot = 0;
+
+ dev_dbg(usim->dev, "%s:action:%ld", __func__, action);
+ /* if phy is removed using rmmod or by some other mech..
+ * then put phy state in unknown, at this point usim also required to
+ * gets removed from the system, if it is inserted as module and
+ * dependent on phy
+ */
Why would the phy be removed? A better mechanism than signals and
notifiers is needed here.
OK
+ if (action == SC_PHY_REMOVED)
+ usim->phy_present = USIM_PHY_NOT_ATTACHED;
+
+ if (event & SC_PHY_CARD_INSERTED)
+ usim_send_signal(usim, USIM_EVENT_CARD_INSERT);
+
+ if (action & SC_PHY_CARD_REMOVED) {
+ usim_send_signal(usim, USIM_EVENT_CARD_REMOVE);
+ dev_dbg(usim->dev, "slot is:%d", slot);
+ /* de-activate USIM & PHY state machine for the slot */
+ t_slot = usim->slot;
+ usim->slot = slot;
+ usim_deactivate_card(usim);
+ usim->slot = t_slot;
+ }
+
+ if (action & SC_PHY_CARD_OVERHEAT)
+ usim_send_signal(usim, USIM_EVENT_CARD_OVERHEAT);
+
+ if (action & SC_PHY_CARD_ATR_TIMEOUT)
+ usim_send_signal(usim, USIM_EVENT_TIMEOUT);
+
+ if (action & SC_PHY_CARD_SYNC_ACT_COMPLETE) {
+ usim->atrdone = 1;
+ wake_up(&atr_wait);
+ }
+ return NOTIFY_OK;
+}
+
+static struct notifier_block usim_nb = {
+ .notifier_call = usim_notify,
+};
+
+static const struct file_operations usim_fops = {
+ .owner = THIS_MODULE,
+ .read = usim_read,
+ .open = usim_open,
+ .unlocked_ioctl = usim_ioctl,
+ .release = usim_release,
+};
+
+static int usim_probe(struct platform_device *pdev)
+{
+ struct device_node *node = pdev->dev.of_node;
+ struct device *dev = &pdev->dev;
+
+ struct usim *usim = NULL;
+ struct device_node *phy_node = NULL;
+ struct resource *res = NULL;
+ void __iomem *base = NULL;
+ const __be32 *parp = NULL;
+ struct i2c_client *phy_i2c = NULL;
+
+ int ret = 0;
+ int version = 0;
+ int cnt = 0;
+ u32 prop = 0;
+ int lenp = 0;
Remove unnecessary initialization.
+
+ if (!node) {
+ dev_err(dev, "device node not found\n");
+ return -EINVAL;
+ }
+
+ usim = devm_kzalloc(dev, sizeof(*usim), GFP_KERNEL);
+ if (!usim) {
+ dev_err(dev, "not enough memory\n");
+ return -ENOMEM;
+ }
+
+ usim->irq = platform_get_irq(pdev, 0);
+ if (usim->irq < 0) {
+ dev_err(dev, "missing IRQ resource\n");
+ ret = -EINVAL;
+ goto usim_err_ret;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(dev, "missing memory base resource\n");
+ ret = -EINVAL;
ENODEV is probably more appropriate.
OK
+ goto usim_err_ret;
+ }
+
+ base = devm_ioremap_nocache(dev, res->start, resource_size(res));
devm_ioremap_resource
Undefined ? I couldn't get your point. Could you explain bit more.
+ if (!base) {
+ dev_err(dev, "ioremap failed\n");
+ ret = -ENOMEM;
+ goto usim_err_ret;
+ }
+
+ usim->dev = &pdev->dev;
+ usim->base = base;
+ usim->max_slots = 1;
+ usim->phy_present = USIM_PHY_NOT_PRESENT;
+ usim->enable = 0;
+
+ /* default slot will be zero : user card */
+ usim->slot = 0;
+
+ ret = devm_request_irq(dev, usim->irq, usim_interrupt, 0,
+ "usim", usim);
+ if (ret) {
+ dev_err(dev, "fail request IRQ #%d --> %d\n", usim->irq, ret);
+ goto usim_err_ret;
+ return ret;
+ }
+
+ /*
+ * Populate all the child nodes here...
+ */
+ ret = of_platform_populate(node, NULL, NULL, &pdev->dev);
+
+ /* get phy details */
+ parp = of_get_property(node, "phy", &lenp);
+ if (parp == NULL || lenp != (sizeof(void *))) {
+ dev_dbg(usim->dev, "parp is null!,no phy");
+ } else {
+ /* get phy node */
+ phy_node = of_find_node_by_phandle(be32_to_cpup(parp));
+ if (phy_node == NULL) {
+ dev_err(usim->dev, "\n phy node is null");
+ ret = -EPROBE_DEFER;
+ goto usim_err_ret;
+ }
+ phy_i2c = of_find_i2c_device_by_node(phy_node);
+ if (phy_i2c == NULL) {
+ dev_err(usim->dev, "\n phy i2c is null");
+ ret = -EPROBE_DEFER;
+ goto usim_err_ret;
+ }
+ /* get phy interface */
+ usim->phy = (struct sc_phy *)i2c_get_clientdata(phy_i2c);
+ if (usim->phy == NULL) {
+ dev_err(usim->dev, "phy data is null");
+ ret = -EPROBE_DEFER;
+ goto usim_err_ret;
+ }
+ usim->phy_present = USIM_PHY_PRESENT;
+
+ ret = of_property_read_u32(node, "phy-slots", &prop);
+ /* if phy-slot is not declared then assume one phy slot */
+ usim->max_slots = prop > 0 ? prop : 1;
+ }
+
+ dev_dbg(usim->dev, "usim max slot:%d", usim->max_slots);
+ /* initialize slot context*/
+ if (usim->max_slots > USIM_MAX_SLOTS) {
+ ret = -EINVAL;
+ goto usim_err_ret;
+ }
+
+ usim->slot_ctx = kmalloc(usim->max_slots *
+ sizeof(struct usim_slotcontext), GFP_KERNEL);
+ if (!usim->slot_ctx)
+ return -ENOMEM;
+
+ for (cnt = 0; cnt < usim->max_slots; cnt++) {
+ /* default protocol */
+ usim->slot_ctx[cnt].protocol = 0;
+ usim->slot_ctx[cnt].emv = true;
+ usim_set_cardmode(usim, cnt, USIM_CARD_MODE_ASYNC);
+ }
+
+ dev_set_drvdata(dev, usim);
+ ret = usim_pm_init(usim);
+ if (ret)
+ goto usim_err_ret;
+
+ /* enable the clock */
+ pm_runtime_enable(usim->dev);
+ pm_runtime_set_active(usim->dev);
+ spin_lock_init(&usim->lock);
+
+ usim_dev.minor = MISC_DYNAMIC_MINOR;
+ usim_dev.name = "usim";
+ usim_dev.fops = &usim_fops;
+ usim_dev.parent = &(pdev->dev);
+
+ ret = misc_register(&usim_dev);
+ if (ret) {
+ pr_err("unable to register a misc device\n");
+ goto usim_err_reg;
+ }
+#ifdef CONFIG_DEBUG_FS
+ ret = usim_init_debugfs(usim);
+ if (ret) {
+ dev_err(dev, "Debugfs init failed\n");
+ goto usim_err_reg;
+ }
+#endif
+ /* set default ICC clock : 5Mhz */
+ usim->slot_ctx[usim->slot].clock = USIM_SMARTCART_CLOCK_5MHZ;
+
+ /* get the clock & do usim configuration */
+ usim_enable(usim);
+ if (ret)
+ goto usim_err_reg;
+ dev_info(usim->dev, "usim driver initialized\n");
+
+ /* register notifier */
+ if (usim->phy_present)
+ usim->phy->register_notify(usim->phy, &usim_nb, (void *)usim);
+
+ /* get usim version */
+ version = usim_get_version(usim);
+ dev_info(usim->dev, "version is:%0x", version);
+
+ usim_disable(usim);
+ return 0;
+
+usim_err_reg:
+ pm_runtime_set_suspended(usim->dev);
+ pm_runtime_disable(usim->dev);
+ misc_deregister(&usim_dev);
+
+usim_err_ret:
+ if (usim)
+ kfree(usim->slot_ctx);
+ return ret;
+}
+
+static int usim_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct usim *usim = dev_to_usim(dev);
+
+ usim_disable(usim);
+ /* unregister notifier, applicable only when phy present and phy state
+ * is not unknown i.e. - phy has not been removed using rmmod */
+ if (usim->phy_present == USIM_PHY_PRESENT)
+ usim->phy->unregister_notify(usim->phy, &usim_nb);
+
+#ifdef CONFIG_DEBUG_FS
+ debugfs_remove_recursive(usim->debugfs_root);
+#endif
+ if (!IS_ERR(usim->usim_dbclk))
+ clk_put(usim->usim_dbclk);
+ if (!IS_ERR(usim->clkdiv32k_ick))
+ clk_put(usim->clkdiv32k_ick);
+ if (!IS_ERR(usim->usim0_fck))
+ clk_put(usim->usim0_fck);
+ if (!IS_ERR(usim->dpll_core_m4_ck))
+ clk_put(usim->dpll_core_m4_ck);
+
+ if (!IS_ERR(usim->opt_fclk))
+ devm_clk_put(usim->dev, usim->opt_fclk);
+ if (!IS_ERR(usim->opt_fclk32))
+ devm_clk_put(usim->dev, usim->opt_fclk32);
+ /* disable pm runtime */
+ pm_runtime_set_suspended(usim->dev);
+ pm_runtime_disable(usim->dev);
+
+ kfree(usim->slot_ctx);
+ misc_deregister(&usim_dev);
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id usim_id_table[] = {
+ { .compatible = "ti,usim" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, usim_id_table);
+#endif
+
+static struct platform_driver usim_driver = {
+ .driver = {
+ .name = "usim",
+ .owner = THIS_MODULE,
+ .pm = USIM_PM_OPS,
+ .of_match_table = of_match_ptr(usim_id_table),
+ },
+ .probe = usim_probe,
+ .remove = usim_remove,
+};
+
+static int __init usim_init(void)
+{
+ return platform_driver_register(&usim_driver);
+}
+
+static void __exit usim_exit(void)
+{
+ platform_driver_unregister(&usim_driver);
+}
+
+late_initcall(usim_init);
+module_exit(usim_exit);
+
+MODULE_AUTHOR("Maulik Mankad <maulik@xxxxxx>");
+MODULE_DESCRIPTION("USIM Driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/ti-usim.h b/include/linux/ti-usim.h
new file mode 100644
index 0000000..e9794df
--- /dev/null
+++ b/include/linux/ti-usim.h
Isn't this a uapi header?
@@ -0,0 +1,111 @@
+/*
+ * ti-usim.h - Header file for USIM SmartCard interface
+ *
+ * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __TI_USIM_H__
+#define __TI_USIM_H__
+
+#include <linux/types.h>
+#include <linux/ioctl.h>
+
+#define USIM_IOCTL 0xFE
+
+enum usim_param {
+ USIM_PARAM_CWT = 0,
+ USIM_PARAM_WWT,
+ USIM_PARAM_CGT,
+ USIM_PARAM_BWT,
+ USIM_PARAM_EDCTYPE,
+ USIM_PARAM_LRCCHECK,
+ USIM_PARAM_C4,
+ USIM_PARAM_C8,
+ USIM_PARAM_PROTOCOL,
+ USIM_PARAM_VOLTAGE,
+ USIM_PARAM_EMV,
+ USIM_PARAM_FI,
+ USIM_PARAM_DI,
+ USIM_PARAM_CODING_CONV,
+ USIM_PARAM_CLOCK_STOP,
+ USIM_PARAM_SMARTCARD_CLOCK,
+ USIM_PARAM_SMARTCARD_MODE,
+ USIM_PARAM_CARD_PIN_VCC,
+ USIM_PARAM_CARD_PIN_RST,
+ USIM_PARAM_CARD_PIN_CLK,
+ USIM_PARAM_CARD_PIN_IO,
+ USIM_PARAM_CARD_PIN_C4,
+ USIM_PARAM_CARD_PIN_C8,
+};
+
+enum usim_card_mode {
+ USIM_CARD_MODE_ASYNC = 0, /* asynchronous mode */
+ USIM_CARD_MODE_SYNC_TYPE1, /* synchronous mode: Type 1 */
+ USIM_CARD_MODE_SYNC_TYPE2, /* synchronous mode: Type 2 */
+ USIM_CARD_MODE_SYNC_OTHER, /* Any other synchronous type */
+};
+struct usim_data {
+ int slot;
+ int rxexplen;
+ int txlen;
+ unsigned char apdu[256];
+};
+
+struct usim_config {
+ enum usim_param attr;
Probably not a good idea to have an undefined size element in a user
ABI struct...
--+ unsigned int value;
+};
+
+#define USIM_SIGID 51
+
+#define USIM_IOCTL_GET_PROVIDER_VERSION _IOR(USIM_IOCTL, 0, int)
+#define USIM_IOCTL_ACTIVATE_CARD _IO(USIM_IOCTL, 1)
+#define USIM_IOCTL_DEACTIVATE_CARD _IO(USIM_IOCTL, 2)
+#define USIM_IOCTL_WARM_RESET _IO(USIM_IOCTL, 3)
+#define USIM_IOCTL_GET_ATR _IOR(USIM_IOCTL, 4, char *)
+#define USIM_IOCTL_SEND_DATA _IOW(USIM_IOCTL, 5, struct usim_data)
+#define USIM_IOCTL_SET_CONFIG _IOW(USIM_IOCTL, 6, struct usim_config)
+#define USIM_IOCTL_GET_CONFIG _IOW(USIM_IOCTL, 7, struct usim_config)
+#define USIM_IOCTL_GET_CARD_PRESENCE _IOR(USIM_IOCTL, 8, int)
+#define USIM_IOCTL_REGISTER_PID _IOW(USIM_IOCTL, 9, int)
+
+#define USIM_MAX_ATRLENGTH 0xFF
+#define USIM_MAX_APDU_LENGTH 0xFE
+
+enum usim_smartcard_clock {
+ USIM_SMARTCART_CLOCK_3_3MHZ = 0x1,
+ USIM_SMARTCART_CLOCK_4MHZ = 0x2,
+ USIM_SMARTCART_CLOCK_5MHZ = 0x3,
+ USIM_SMARTCART_CLOCK_6_6MHZ = 0x4,
+ USIM_SMARTCART_CLOCK_10MHZ = 0x5,
+ USIM_SMARTCART_CLOCK_20MHZ = 0x6,
+};
+
+enum usim_event {
+ USIM_EVENT_CARD_INSERT = 0x1,
+ USIM_EVENT_CARD_REMOVE = 0x2,
+ USIM_EVENT_TIMEOUT = 0x4,
+ USIM_EVENT_ERR_TXRETRY = 0x8,
+ USIM_EVENT_ERR_LRC = 0x10,
+ USIM_EVENT_ERR_PARITY = 0x20,
+ USIM_EVENT_ERR_FRAME = 0x40,
+ USIM_EVENT_PHYERR = 0x80,
+ USIM_EVENT_CARD_OVERHEAT = 0x100,
+};
+
+enum usim_card_voltage {
+ USIM_CARD_5V = 0,
+ USIM_CARD_3V,
+ USIM_CARD_1_8V
+};
+
+#endif /* __TI_USIM_H__ */
--
1.7.9.5