Merge "usb: core: Limit HCD old enumeration scheme request to root ports"
diff --git a/Documentation/usb/uicc.txt b/Documentation/usb/uicc.txt
new file mode 100644
index 0000000..7bf4d86
--- /dev/null
+++ b/Documentation/usb/uicc.txt
@@ -0,0 +1,161 @@
+Introduction
+============
+
+This feature requires supporting Mass Storage and Integrated Circuit Card
+interfaces exposed by the UICC (Universal Integrated Circuit Card) device.
+The MSM acts as a USB host and UICC acts as a peripheral. The UICC device
+that is used here is also referred to as Mega-SIM. This feature will be
+supported on MSM8x26.
+
+Hardware description
+====================
+
+The USB3503 HSIC (High Speed Inter Chip) hub's down stream port is modified
+to support Inter-Chip USB for connecting the UICC device. The USB3503 is
+connected to MSM via HSIC interface. The UICC device operates in Full Speed
+mode.
+
+The UICC device will support CCID (Integrated Circuit Card interface Device)
+specification. This interface supports 1 Bulk In, 1 Bulk Out and 1 Interrupt
+endpoint. The Interrupt endpoint is used by the device to send asynchronous
+notifications like card insertion/removal and hardware error events.
+The Bulk endpoints are used for the data communication.
+
+The UICC device will support the Mass Storage Bulk Only 1.0 specification.
+It supports SCSI Transparent subclass '06', corresponding to support of the
+SCSI Primary Command set. It implements SCSI Peripheral Device Type '00'
+(TYPE_DISK) corresponding to a direct access SCSI block device.
+
+Software description
+====================
+
+The MSM HSIC controller driver(drivers/usb/host/ehci-msm-hsic.c) takes care
+of HSIC PHY and link management. The USB3503 HSIC hub is managed by the SMSC
+hub driver(drivers/misc/smsc_hubc.c). Both these drivers are well tested on
+APQ8074 dragon board and are re-used to support this feature.
+
+The mass storage interface is managed by the standard Linux USB storage driver.
+This driver interfaces with SCSI and block layers to export the disk to
+user space.
+
+A new USB driver is implemented to manage the CCID interface. This driver is
+referred to as USB CCID driver in this document. This driver is implemented
+as a pass-through module and provides the character device interface to
+user space. The CCID specification is implemented in the user space.
+
+The CCID command and responses are exchanged over the Bulk endpoints. The
+user space application uses write() and read() calls to send commands and
+receive responses.
+
+The CCID class specific requests are sent over the control endpoint. As
+control requests have a specific format, ioctls are implemented.
+
+The UICC device sends asynchronous notifications over the interrupt endpoint.
+The card insertion/removal and hardware error events are sent to user space
+via an ioctl().
+
+Design Goals:
+============
+
+1. Re-use the existing services available in user space. This is achieved
+by implementing the kernel USB CCID driver as a pass-through module.
+
+2. Support runtime card insertion/removal.
+
+3. Support runtime power management.
+
+4. Support Multiple card configuration. More than 1 IC can be connected to
+the USB UICC device.
+
+Power Management
+================
+
+The USB core uses the runtime PM framework to auto suspend the USB devices that
+are not in use. The Auto-suspend is forbidden for all devices except hub class
+devices. The USB CCID driver enables auto-suspend for the UICC device.
+
+An USB device can be suspended only when all of its interfaces are suspended.
+The USB storage interface device acts as a parent device to the underlying
+SCSI host, target and block devices. Runtime PM must be enabled for the
+SCSI device to allow USB storage interface suspend. The SCSI device runtime
+suspend and auto-suspend timeout will be configured from user space via sysfs
+files.
+
+The HSIC platform device and USB3503 HUB device will be runtime suspended
+only after the USB UICC device is suspended.
+
+SMP/multi-core
+==============
+
+The USB CCID driver does not allow multiple clients to open the device file
+concurrently. -EBUSY will be returned if open() is attempted when the
+file is already opened.
+
+The write() and read() methods are implemented synchronously. If another
+write() is called when a previous write() is in progress, -EBUSY is
+returned. The same is applicable to read().
+
+Mutexes will be used to prevent concurrent open(), read() and write() access.
+
+Interface
+=========
+
+A character device file (/dev/ccid_bridge) will be exposed by the USB CCID
+driver. open(), read(), write(), ioctl() and close() methods are implemented.
+This device node is accessible only to the root by default. User space init
+or udev scripts should change the permissions of this device file if it needs
+to be accessed by non-root applications.
+
+open(): The open() is blocked until the UICC device is detected and the CCID
+interface probe is completed. Returns the appropriate error code in case of
+failure.
+
+read(): An URB is submitted on the Bulk In endpoint. The read() is blocked
+until the URB is completed and the data is copied to the user space buffer
+upon success. An appropriate error code is returned in case of failure.
+-ENODEV must be treated as a serious error and no further I/O will be
+attempted.
+
+write(): An URB is submitted on the Bulk Out endpoint. The write() is blocked
+until the URB is completed. An appropriate error code is returned in case of
+failure. -ENODEV must be treated as a serious error and no further I/O will be
+attempted.
+
+ioctl(): The ioctl() method is required for facilitating Control transfers and
+Interrupt transfers.
+
+USB_CCID_GET_CLASS_DESC: This read-only ioctl returns the smart card class
+descriptor as described in the 5.1 section of USB smart card class spec.
+
+USB_CCID_ABORT: This write-only ioctl sends A ABORT class specific request on
+control endpoint. The class request details are mentioned in section 5.3.1.
+
+USB_CCID_GET_CLOCK_FREQUENCIES: This read and write ioctl returns the clock
+frequencies supported by the CCID device. A GET_CLOCK_FREQUENCIES class request
+is sent on the control endpoint. The class request details are mentioned in
+section 5.3.2.
+
+USB_CCID_GET_DATA_RATES: This read and write ioctl returns the data rates
+supported by the CCID device. A GET_DATA_RATES class request is sent on the
+control endpoint. The class request details are mentioned in section 5.3.3.
+
+USB_CCID_GET_EVENT: This read-only ioctl returns the asynchronous event sent
+by the UICC device. The ioctl() is blocked until such event is received from
+the UICC device. This ioctl() returns -ENOENT error code when the device
+does not have an interrupt endpoint and does not support remote wakeup
+capability.
+
+close(): Cancels any ongoing I/O before it returns.
+
+Config options
+==============
+
+Turn on USB_EHCI_MSM_HSIC, USB_HSIC_SMSC_HUB and USB_CCID_BRIDGE configs to
+enable this feature.
+
+References
+==========
+
+Specification for Integrated Circuit(s) Cards Interface Devices
+
+Smart Cards; UICC-Terminal interface; Physical and logical characteristics
diff --git a/arch/arm/boot/dts/msm8974-v2.dtsi b/arch/arm/boot/dts/msm8974-v2.dtsi
index 9176117..5607257 100644
--- a/arch/arm/boot/dts/msm8974-v2.dtsi
+++ b/arch/arm/boot/dts/msm8974-v2.dtsi
@@ -122,7 +122,7 @@
qcom,dec-ocmem-ab-ib = <0 0>,
<176000 519000>,
<456000 519000>,
- <864000 519000>,
+ <864000 629000>,
<1728000 1038000>,
<2766000 1661000>,
<3456000 2076000>,
diff --git a/arch/arm/configs/msm8226-perf_defconfig b/arch/arm/configs/msm8226-perf_defconfig
index 6b1f88d..6c3898c 100644
--- a/arch/arm/configs/msm8226-perf_defconfig
+++ b/arch/arm/configs/msm8226-perf_defconfig
@@ -359,6 +359,7 @@
CONFIG_USB_EHCI_MSM=y
CONFIG_USB_EHCI_MSM_HSIC=y
CONFIG_USB_ACM=y
+CONFIG_USB_CCID_BRIDGE=y
CONFIG_USB_STORAGE=y
CONFIG_USB_STORAGE_DATAFAB=y
CONFIG_USB_STORAGE_FREECOM=y
diff --git a/arch/arm/configs/msm8226_defconfig b/arch/arm/configs/msm8226_defconfig
index 2d54549..e14118d 100644
--- a/arch/arm/configs/msm8226_defconfig
+++ b/arch/arm/configs/msm8226_defconfig
@@ -383,6 +383,7 @@
CONFIG_USB_EHCI_MSM=y
CONFIG_USB_EHCI_MSM_HSIC=y
CONFIG_USB_ACM=y
+CONFIG_USB_CCID_BRIDGE=y
CONFIG_USB_STORAGE=y
CONFIG_USB_STORAGE_DATAFAB=y
CONFIG_USB_STORAGE_FREECOM=y
diff --git a/arch/arm/mach-msm/clock-mdss-8974.c b/arch/arm/mach-msm/clock-mdss-8974.c
index d1b1885..3ea719f 100644
--- a/arch/arm/mach-msm/clock-mdss-8974.c
+++ b/arch/arm/mach-msm/clock-mdss-8974.c
@@ -151,6 +151,7 @@
#define PLL_POLL_MAX_READS 10
#define PLL_POLL_TIMEOUT_US 50
+#define SEQ_M_MAX_COUNTER 7
static long vco_cached_rate;
static unsigned char *mdss_dsi_base;
@@ -1034,12 +1035,12 @@
static void dsi_pll_toggle_lock_detect(void)
{
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_LKDET_CFG2,
- 0x05);
+ 0x0d);
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_LKDET_CFG2,
- 0x04);
+ 0x0c);
udelay(1);
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_LKDET_CFG2,
- 0x05);
+ 0x0d);
}
static int dsi_pll_lock_status(void)
@@ -1093,24 +1094,25 @@
* the updates to take effect. These delays are necessary for the
* PLL to successfully lock
*/
+ DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_CAL_CFG1, 0x34);
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x01);
udelay(200);
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x05);
udelay(200);
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x0f);
- udelay(1000);
+ udelay(600);
pll_locked = dsi_pll_toggle_lock_detect_and_check_status();
- for (i = 0; (i < 4) && !pll_locked; i++) {
+ for (i = 0; (i < SEQ_M_MAX_COUNTER) && !pll_locked; i++) {
+ DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_PWRGEN_CFG,
+ 0x00);
+ udelay(50);
DSS_REG_W(mdss_dsi_base,
- DSI_0_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x07);
- if (i != 0)
- DSS_REG_W(mdss_dsi_base,
- DSI_0_PHY_PLL_UNIPHY_PLL_CAL_CFG1, 0x34);
- udelay(1);
+ DSI_0_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x05);
+ udelay(100);
DSS_REG_W(mdss_dsi_base,
DSI_0_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x0f);
- udelay(1000);
+ udelay(600);
pll_locked = dsi_pll_toggle_lock_detect_and_check_status();
}
@@ -1134,6 +1136,8 @@
* the updates to take effect. These delays are necessary for the
* PLL to successfully lock
*/
+ DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_PWRGEN_CFG, 0x00);
+ udelay(50);
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x01);
udelay(200);
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x05);
@@ -1145,7 +1149,7 @@
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x07);
udelay(200);
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x0f);
- udelay(1000);
+ udelay(600);
pll_locked = dsi_pll_toggle_lock_detect_and_check_status();
pr_debug("%s: PLL status = %s\n", __func__,
@@ -1165,6 +1169,8 @@
* the updates to take effect. These delays are necessary for the
* PLL to successfully lock
*/
+ DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_PWRGEN_CFG, 0x00);
+ udelay(50);
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x01);
udelay(200);
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x05);
@@ -1174,7 +1180,7 @@
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x0d);
udelay(200);
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x0f);
- udelay(1000);
+ udelay(600);
pll_locked = dsi_pll_toggle_lock_detect_and_check_status();
pr_debug("%s: PLL status = %s\n", __func__,
@@ -1194,12 +1200,14 @@
* the updates to take effect. These delays are necessary for the
* PLL to successfully lock
*/
+ DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_PWRGEN_CFG, 0x00);
+ udelay(50);
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x01);
udelay(200);
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x05);
udelay(200);
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x0f);
- udelay(1000);
+ udelay(600);
pll_locked = dsi_pll_toggle_lock_detect_and_check_status();
pr_debug("%s: PLL status = %s\n", __func__,
@@ -1219,6 +1227,8 @@
* the updates to take effect. These delays are necessary for the
* PLL to successfully lock
*/
+ DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_PWRGEN_CFG, 0x00);
+ udelay(50);
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x01);
udelay(200);
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x05);
@@ -1226,7 +1236,7 @@
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x0d);
udelay(1);
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x0f);
- udelay(1000);
+ udelay(600);
pll_locked = dsi_pll_toggle_lock_detect_and_check_status();
pr_debug("%s: PLL status = %s\n", __func__,
@@ -1258,10 +1268,10 @@
for (i = 0; i < 3; i++) {
/* DSI Uniphy lock detect setting */
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_LKDET_CFG2,
- 0x04);
+ 0x0c);
udelay(100);
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_LKDET_CFG2,
- 0x05);
+ 0x0d);
udelay(500);
/* poll for PLL ready status */
max_reads = 5;
@@ -1459,7 +1469,7 @@
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_CHGPUMP_CFG, 0x02);
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_CAL_CFG3, 0x2b);
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_CAL_CFG4, 0x66);
- DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_LKDET_CFG2, 0x05);
+ DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_LKDET_CFG2, 0x0d);
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_SDM_CFG1,
(u32)(sdm_cfg1 & 0xff));
@@ -1478,7 +1488,7 @@
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_VCOLPF_CFG, 0x71);
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_SDM_CFG0,
(u32)sdm_cfg0);
- DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_CAL_CFG0, 0x0a);
+ DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_CAL_CFG0, 0x12);
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_CAL_CFG6, 0x30);
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_CAL_CFG7, 0x00);
DSS_REG_W(mdss_dsi_base, DSI_0_PHY_PLL_UNIPHY_PLL_CAL_CFG8, 0x60);
@@ -1640,13 +1650,14 @@
.ref_clk_rate = 19200000,
.min_rate = 350000000,
.max_rate = 750000000,
- .pll_en_seq_cnt = 6,
+ .pll_en_seq_cnt = 7,
.pll_enable_seqs[0] = dsi_pll_enable_seq_m,
- .pll_enable_seqs[1] = dsi_pll_enable_seq_d,
+ .pll_enable_seqs[1] = dsi_pll_enable_seq_m,
.pll_enable_seqs[2] = dsi_pll_enable_seq_d,
- .pll_enable_seqs[3] = dsi_pll_enable_seq_f1,
- .pll_enable_seqs[4] = dsi_pll_enable_seq_c,
- .pll_enable_seqs[5] = dsi_pll_enable_seq_e,
+ .pll_enable_seqs[3] = dsi_pll_enable_seq_d,
+ .pll_enable_seqs[4] = dsi_pll_enable_seq_f1,
+ .pll_enable_seqs[5] = dsi_pll_enable_seq_c,
+ .pll_enable_seqs[6] = dsi_pll_enable_seq_e,
.lpfr_lut_size = 10,
.lpfr_lut = (struct lpfr_cfg[]){
{479500000, 8},
diff --git a/drivers/gpu/ion/ion_heap.c b/drivers/gpu/ion/ion_heap.c
index 9d33bf4..73dd868 100644
--- a/drivers/gpu/ion/ion_heap.c
+++ b/drivers/gpu/ion/ion_heap.c
@@ -108,8 +108,7 @@
*
* Note that the `pages' array should be composed of all 4K pages.
*/
-int ion_heap_pages_zero(struct page **pages, int num_pages,
- bool should_invalidate)
+int ion_heap_pages_zero(struct page **pages, int num_pages)
{
int i, j, k, npages_to_vmap;
void *ptr = NULL;
@@ -143,19 +142,17 @@
return -ENOMEM;
memset(ptr, 0, npages_to_vmap * PAGE_SIZE);
- if (should_invalidate) {
- /*
- * invalidate the cache to pick up the zeroing
- */
- for (k = 0; k < npages_to_vmap; k++) {
- void *p = kmap_atomic(pages[i + k]);
- phys_addr_t phys = page_to_phys(
- pages[i + k]);
+ /*
+ * invalidate the cache to pick up the zeroing
+ */
+ for (k = 0; k < npages_to_vmap; k++) {
+ void *p = kmap_atomic(pages[i + k]);
+ phys_addr_t phys = page_to_phys(
+ pages[i + k]);
- dmac_inv_range(p, p + PAGE_SIZE);
- outer_inv_range(phys, phys + PAGE_SIZE);
- kunmap_atomic(p);
- }
+ dmac_inv_range(p, p + PAGE_SIZE);
+ outer_inv_range(phys, phys + PAGE_SIZE);
+ kunmap_atomic(p);
}
vunmap(ptr);
}
@@ -196,8 +193,7 @@
pages_mem->free_fn(pages_mem->pages);
}
-int ion_heap_high_order_page_zero(struct page *page,
- int order, bool should_invalidate)
+int ion_heap_high_order_page_zero(struct page *page, int order)
{
int i, ret;
struct pages_mem pages_mem;
@@ -210,8 +206,7 @@
for (i = 0; i < (1 << order); ++i)
pages_mem.pages[i] = page + i;
- ret = ion_heap_pages_zero(pages_mem.pages, npages,
- should_invalidate);
+ ret = ion_heap_pages_zero(pages_mem.pages, npages);
ion_heap_free_pages_mem(&pages_mem);
return ret;
}
@@ -240,8 +235,7 @@
pages_mem.pages[npages++] = page + j;
}
- ret = ion_heap_pages_zero(pages_mem.pages, npages,
- ion_buffer_cached(buffer));
+ ret = ion_heap_pages_zero(pages_mem.pages, npages);
ion_heap_free_pages_mem(&pages_mem);
return ret;
}
diff --git a/drivers/gpu/ion/ion_page_pool.c b/drivers/gpu/ion/ion_page_pool.c
index 94f9445..cc2a36d 100644
--- a/drivers/gpu/ion/ion_page_pool.c
+++ b/drivers/gpu/ion/ion_page_pool.c
@@ -40,8 +40,7 @@
return NULL;
if (pool->gfp_mask & __GFP_ZERO)
- if (ion_heap_high_order_page_zero(
- page, pool->order, pool->should_invalidate))
+ if (ion_heap_high_order_page_zero(page, pool->order))
goto error_free_pages;
sg_init_table(&sg, 1);
@@ -175,8 +174,7 @@
return nr_freed;
}
-struct ion_page_pool *ion_page_pool_create(gfp_t gfp_mask, unsigned int order,
- bool should_invalidate)
+struct ion_page_pool *ion_page_pool_create(gfp_t gfp_mask, unsigned int order)
{
struct ion_page_pool *pool = kmalloc(sizeof(struct ion_page_pool),
GFP_KERNEL);
@@ -188,7 +186,6 @@
INIT_LIST_HEAD(&pool->high_items);
pool->gfp_mask = gfp_mask;
pool->order = order;
- pool->should_invalidate = should_invalidate;
mutex_init(&pool->mutex);
plist_node_init(&pool->list, order);
diff --git a/drivers/gpu/ion/ion_priv.h b/drivers/gpu/ion/ion_priv.h
index aa0a9e2..c57efc1 100644
--- a/drivers/gpu/ion/ion_priv.h
+++ b/drivers/gpu/ion/ion_priv.h
@@ -234,11 +234,9 @@
void ion_heap_unmap_kernel(struct ion_heap *, struct ion_buffer *);
int ion_heap_map_user(struct ion_heap *, struct ion_buffer *,
struct vm_area_struct *);
-int ion_heap_pages_zero(struct page **pages, int num_pages,
- bool should_invalidate);
+int ion_heap_pages_zero(struct page **pages, int num_pages);
int ion_heap_buffer_zero(struct ion_buffer *buffer);
-int ion_heap_high_order_page_zero(struct page *page,
- int order, bool should_invalidate);
+int ion_heap_high_order_page_zero(struct page *page, int order);
/**
* ion_heap_init_deferred_free -- initialize deferred free functionality
@@ -357,8 +355,6 @@
* @gfp_mask: gfp_mask to use from alloc
* @order: order of pages in the pool
* @list: plist node for list of pools
- * @should_invalidate: whether or not the cache needs to be invalidated at
- * page allocation time.
*
* Allows you to keep a pool of pre allocated pages to use from your heap.
* Keeping a pool of pages that is ready for dma, ie any cached mapping have
@@ -374,11 +370,9 @@
gfp_t gfp_mask;
unsigned int order;
struct plist_node list;
- bool should_invalidate;
};
-struct ion_page_pool *ion_page_pool_create(gfp_t gfp_mask, unsigned int order,
- bool should_invalidate);
+struct ion_page_pool *ion_page_pool_create(gfp_t gfp_mask, unsigned int order);
void ion_page_pool_destroy(struct ion_page_pool *);
void *ion_page_pool_alloc(struct ion_page_pool *);
void ion_page_pool_free(struct ion_page_pool *, struct page *);
diff --git a/drivers/gpu/ion/ion_system_heap.c b/drivers/gpu/ion/ion_system_heap.c
index 8e885b2..be1a89c 100644
--- a/drivers/gpu/ion/ion_system_heap.c
+++ b/drivers/gpu/ion/ion_system_heap.c
@@ -355,8 +355,7 @@
* nothing. If it succeeds you'll eventually need to use
* ion_system_heap_destroy_pools to destroy the pools.
*/
-static int ion_system_heap_create_pools(struct ion_page_pool **pools,
- bool should_invalidate)
+static int ion_system_heap_create_pools(struct ion_page_pool **pools)
{
int i;
for (i = 0; i < num_orders; i++) {
@@ -365,8 +364,7 @@
if (orders[i] > 4)
gfp_flags = high_order_gfp_flags;
- pool = ion_page_pool_create(gfp_flags, orders[i],
- should_invalidate);
+ pool = ion_page_pool_create(gfp_flags, orders[i]);
if (!pool)
goto err_create_pool;
pools[i] = pool;
@@ -397,10 +395,10 @@
if (!heap->cached_pools)
goto err_alloc_cached_pools;
- if (ion_system_heap_create_pools(heap->uncached_pools, false))
+ if (ion_system_heap_create_pools(heap->uncached_pools))
goto err_create_uncached_pools;
- if (ion_system_heap_create_pools(heap->cached_pools, true))
+ if (ion_system_heap_create_pools(heap->cached_pools))
goto err_create_cached_pools;
heap->heap.shrinker.shrink = ion_system_heap_shrink;
diff --git a/drivers/gpu/msm/adreno_coresight.c b/drivers/gpu/msm/adreno_coresight.c
index 1b827ff..d0ba145 100644
--- a/drivers/gpu/msm/adreno_coresight.c
+++ b/drivers/gpu/msm/adreno_coresight.c
@@ -55,7 +55,12 @@
int adreno_coresight_enable(struct coresight_device *csdev)
{
struct kgsl_device *device = dev_get_drvdata(csdev->dev.parent);
- struct adreno_device *adreno_dev = ADRENO_DEVICE(device);
+ struct adreno_device *adreno_dev;
+
+ if (device == NULL)
+ return -ENODEV;
+
+ adreno_dev = ADRENO_DEVICE(device);
/* Check if coresight compatible device, return error otherwise */
if (adreno_dev->gpudev->coresight_enable)
@@ -80,7 +85,12 @@
void adreno_coresight_disable(struct coresight_device *csdev)
{
struct kgsl_device *device = dev_get_drvdata(csdev->dev.parent);
- struct adreno_device *adreno_dev = ADRENO_DEVICE(device);
+ struct adreno_device *adreno_dev;
+
+ if (device == NULL)
+ return;
+
+ adreno_dev = ADRENO_DEVICE(device);
/* Check if coresight compatible device, bail otherwise */
if (adreno_dev->gpudev->coresight_disable)
@@ -134,6 +144,10 @@
struct kgsl_device *device = dev_get_drvdata(dev->parent);
struct coresight_attr *csight_attr = container_of(attr,
struct coresight_attr, attr);
+
+ if (device == NULL)
+ return -ENODEV;
+
return coresight_read_reg(device, csight_attr->regname, buf);
}
@@ -142,11 +156,16 @@
const char *buf, size_t size)
{
struct kgsl_device *device = dev_get_drvdata(dev->parent);
- struct adreno_device *adreno_dev = ADRENO_DEVICE(device);
+ struct adreno_device *adreno_dev;
struct coresight_attr *csight_attr = container_of(attr,
struct coresight_attr, attr);
unsigned int regval = 0;
+ if (device == NULL)
+ return -ENODEV;
+
+ adreno_dev = ADRENO_DEVICE(device);
+
regval = coresight_convert_reg(buf);
if (adreno_dev->gpudev->coresight_config_debug_reg)
diff --git a/drivers/usb/class/Kconfig b/drivers/usb/class/Kconfig
index 2519e32..f7321e5 100644
--- a/drivers/usb/class/Kconfig
+++ b/drivers/usb/class/Kconfig
@@ -50,3 +50,15 @@
To compile this driver as a module, choose M here: the
module will be called usbtmc.
+
+config USB_CCID_BRIDGE
+ tristate "USB Smart Card Class (CCID) support"
+ help
+ Say Y here if you want to connect a USB Smart Card device that
+ follows the USB.org specification for Integrated Circuit(s) Cards
+ Interface Devices to your computer's USB port. This module
+ provides a character device interface to exchange the messages.
+ Ioctls facilitate control transfers and interrupt transfers.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ccid_bridge.
diff --git a/drivers/usb/class/Makefile b/drivers/usb/class/Makefile
index 32e8527..c2ee6f3 100644
--- a/drivers/usb/class/Makefile
+++ b/drivers/usb/class/Makefile
@@ -7,3 +7,4 @@
obj-$(CONFIG_USB_PRINTER) += usblp.o
obj-$(CONFIG_USB_WDM) += cdc-wdm.o
obj-$(CONFIG_USB_TMC) += usbtmc.o
+obj-$(CONFIG_USB_CCID_BRIDGE) += ccid_bridge.o
diff --git a/drivers/usb/class/ccid_bridge.c b/drivers/usb/class/ccid_bridge.c
new file mode 100644
index 0000000..a3e100a
--- /dev/null
+++ b/drivers/usb/class/ccid_bridge.c
@@ -0,0 +1,885 @@
+/* Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt "\n", __func__
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+#include <linux/wait.h>
+#include <linux/cdev.h>
+
+#include <linux/usb/ccid_bridge.h>
+
+#define CCID_CLASS_DECRIPTOR_TYPE 0x21
+#define CCID_NOTIFY_SLOT_CHANGE 0x50
+#define CCID_NOTIFY_HARDWARE_ERROR 0x51
+#define CCID_ABORT_REQ 0x1
+#define CCID_GET_CLK_FREQ_REQ 0x2
+#define CCID_GET_DATA_RATES 0x3
+
+#define CCID_BRIDGE_MSG_SZ 512
+#define CCID_BRIDGE_OPEN_TIMEOUT 500 /* msec */
+#define CCID_CONTROL_TIMEOUT 500 /* msec */
+#define CCID_BRIDGE_MSG_TIMEOUT 500 /* msec */
+
+struct ccid_bridge {
+ struct usb_device *udev;
+ struct usb_interface *intf;
+ unsigned int in_pipe;
+ unsigned int out_pipe;
+ unsigned int int_pipe;
+ struct urb *inturb;
+ struct urb *readurb;
+ struct urb *writeurb;
+
+ bool opened;
+ bool events_supported;
+ bool is_suspended;
+ struct mutex open_mutex;
+ struct mutex write_mutex;
+ struct mutex read_mutex;
+ struct mutex event_mutex;
+ int write_result;
+ int read_result;
+ int event_result;
+ wait_queue_head_t open_wq;
+ wait_queue_head_t write_wq;
+ wait_queue_head_t read_wq;
+ wait_queue_head_t event_wq;
+ struct usb_ccid_event cur_event;
+ void *intbuf;
+
+ dev_t chrdev;
+ struct cdev cdev;
+ struct class *class;
+ struct device *device;
+};
+
+static struct ccid_bridge *__ccid_bridge_dev;
+
+static void ccid_bridge_out_cb(struct urb *urb)
+{
+ struct ccid_bridge *ccid = urb->context;
+
+ if (urb->dev->state == USB_STATE_NOTATTACHED)
+ ccid->write_result = -ENODEV;
+ else
+ ccid->write_result = urb->status ? : urb->actual_length;
+
+ pr_debug("write result = %d", ccid->write_result);
+ wake_up(&ccid->write_wq);
+}
+
+static void ccid_bridge_in_cb(struct urb *urb)
+{
+ struct ccid_bridge *ccid = urb->context;
+
+ if (urb->dev->state == USB_STATE_NOTATTACHED)
+ ccid->read_result = -ENODEV;
+ else
+ ccid->read_result = urb->status ? : urb->actual_length;
+
+ pr_debug("read result = %d", ccid->read_result);
+ wake_up(&ccid->read_wq);
+}
+
+static void ccid_bridge_int_cb(struct urb *urb)
+{
+ struct ccid_bridge *ccid = urb->context;
+ u8 *msg_type;
+ bool wakeup = true;
+
+ if (urb->dev->state == USB_STATE_NOTATTACHED || (urb->status &&
+ urb->status != -ENOENT)) {
+ ccid->event_result = -ENODEV;
+ wakeup = true;
+ goto out;
+ }
+
+ /*
+ * Don't wakeup the event ioctl process during suspend.
+ * The suspend state is not visible to user space.
+ * we wake up the process after resume to send RESUME
+ * event if the device supports remote wakeup.
+ */
+ if (urb->status == -ENOENT && !urb->actual_length) {
+ ccid->event_result = -ENOENT;
+ wakeup = false;
+ goto out;
+ }
+
+ ccid->event_result = 0;
+ msg_type = urb->transfer_buffer;
+ switch (*msg_type) {
+ case CCID_NOTIFY_SLOT_CHANGE:
+ pr_debug("NOTIFY_SLOT_CHANGE event arrived");
+ ccid->cur_event.event = USB_CCID_NOTIFY_SLOT_CHANGE_EVENT;
+ ccid->cur_event.u.notify.slot_icc_state = *(++msg_type);
+ break;
+ case CCID_NOTIFY_HARDWARE_ERROR:
+ pr_debug("NOTIFY_HARDWARE_ERROR event arrived");
+ ccid->cur_event.event = USB_CCID_HARDWARE_ERROR_EVENT;
+ ccid->cur_event.u.error.slot = *(++msg_type);
+ ccid->cur_event.u.error.seq = *(++msg_type);
+ ccid->cur_event.u.error.error_code = *(++msg_type);
+ break;
+ default:
+ pr_err("UNKNOWN event arrived\n");
+ ccid->event_result = -EINVAL;
+ }
+
+out:
+ pr_debug("returning %d", ccid->event_result);
+ if (wakeup)
+ wake_up(&ccid->event_wq);
+}
+
+static int ccid_bridge_submit_inturb(struct ccid_bridge *ccid)
+{
+ int ret = 0;
+
+ /*
+ * Don't resume the bus to submit an interrupt URB.
+ * We submit the URB in resume path. This is important.
+ * Because the device will be in suspend state during
+ * multiple system suspend/resume cycles. The user space
+ * process comes here during system resume after it is
+ * unfrozen.
+ */
+ if (!ccid->int_pipe || ccid->is_suspended)
+ goto out;
+
+ ret = usb_autopm_get_interface(ccid->intf);
+ if (ret < 0) {
+ pr_debug("fail to get autopm with %d\n", ret);
+ goto out;
+ }
+ ret = usb_submit_urb(ccid->inturb, GFP_KERNEL);
+ if (ret < 0)
+ pr_err("fail to submit int urb with %d\n", ret);
+ usb_autopm_put_interface(ccid->intf);
+
+out:
+ pr_debug("returning %d", ret);
+ return ret;
+}
+
+static int ccid_bridge_get_event(struct ccid_bridge *ccid)
+{
+ int ret = 0;
+
+ /*
+ * The first event returned after the device resume
+ * will be RESUME event. This event is set by
+ * the resume.
+ */
+ if (ccid->cur_event.event)
+ goto out;
+
+ ccid->event_result = -EINPROGRESS;
+
+ ret = ccid_bridge_submit_inturb(ccid);
+ if (ret < 0)
+ goto out;
+
+ /*
+ * Wait for the notification on interrupt endpoint
+ * or remote wakeup event from the resume. The
+ * int urb completion handler and resume callback
+ * take care of setting the current event.
+ */
+ mutex_unlock(&ccid->event_mutex);
+ ret = wait_event_interruptible(ccid->event_wq,
+ (ccid->event_result != -EINPROGRESS));
+ mutex_lock(&ccid->event_mutex);
+
+ if (ret == -ERESTARTSYS) /* interrupted */
+ usb_kill_urb(ccid->inturb);
+ else
+ ret = ccid->event_result;
+out:
+ pr_debug("returning %d", ret);
+ return ret;
+}
+
+static int ccid_bridge_open(struct inode *ip, struct file *fp)
+{
+ struct ccid_bridge *ccid = container_of(ip->i_cdev,
+ struct ccid_bridge, cdev);
+ int ret;
+
+ pr_debug("called");
+
+ mutex_lock(&ccid->open_mutex);
+ if (ccid->opened) {
+ ret = -EBUSY;
+ goto out;
+ }
+ mutex_unlock(&ccid->open_mutex);
+
+ ret = wait_event_interruptible_timeout(ccid->open_wq,
+ ccid->intf != NULL, msecs_to_jiffies(
+ CCID_BRIDGE_OPEN_TIMEOUT));
+
+ mutex_lock(&ccid->open_mutex);
+
+ if (ret != -ERESTARTSYS && ccid->intf) {
+ fp->private_data = ccid;
+ ccid->opened = true;
+ ret = 0;
+ } else if (!ret) { /* timed out */
+ ret = -ENODEV;
+ }
+out:
+ mutex_unlock(&ccid->open_mutex);
+ pr_debug("returning %d", ret);
+ return ret;
+}
+
+static ssize_t ccid_bridge_write(struct file *fp, const char __user *ubuf,
+ size_t count, loff_t *pos)
+{
+ struct ccid_bridge *ccid = fp->private_data;
+ int ret;
+ char *kbuf;
+
+ pr_debug("called with %d", count);
+
+ if (!ccid->intf) {
+ pr_debug("intf is not active");
+ return -ENODEV;
+ }
+
+ mutex_lock(&ccid->write_mutex);
+
+ if (!count || count > CCID_BRIDGE_MSG_SZ) {
+ pr_err("invalid count");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ kbuf = kmalloc(count, GFP_KERNEL);
+ if (!kbuf) {
+ pr_err("fail to allocate memory");
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ ret = copy_from_user(kbuf, ubuf, count);
+ if (ret) {
+ pr_err("fail to copy user buf");
+ ret = -EFAULT;
+ goto free_kbuf;
+ }
+
+ ret = usb_autopm_get_interface(ccid->intf);
+ if (ret) {
+ pr_err("fail to get autopm with %d", ret);
+ goto free_kbuf;
+ }
+
+ ccid->write_result = 0;
+
+ usb_fill_bulk_urb(ccid->writeurb, ccid->udev, ccid->out_pipe,
+ kbuf, count, ccid_bridge_out_cb, ccid);
+ ret = usb_submit_urb(ccid->writeurb, GFP_KERNEL);
+ if (ret < 0) {
+ pr_err("urb submit fail with %d", ret);
+ goto put_pm;
+ }
+
+ ret = wait_event_interruptible_timeout(ccid->write_wq,
+ ccid->write_result != 0,
+ msecs_to_jiffies(CCID_BRIDGE_MSG_TIMEOUT));
+ if (!ret || ret == -ERESTARTSYS) { /* timedout or interrupted */
+ usb_kill_urb(ccid->writeurb);
+ if (!ret)
+ ret = -ETIMEDOUT;
+ } else {
+ ret = ccid->write_result;
+ }
+
+ pr_debug("returning %d", ret);
+
+put_pm:
+ if (ret != -ENODEV)
+ usb_autopm_put_interface(ccid->intf);
+free_kbuf:
+ kfree(kbuf);
+out:
+ mutex_unlock(&ccid->write_mutex);
+ return ret;
+
+}
+
+static ssize_t ccid_bridge_read(struct file *fp, char __user *ubuf,
+ size_t count, loff_t *pos)
+{
+ struct ccid_bridge *ccid = fp->private_data;
+ int ret;
+ char *kbuf;
+
+ pr_debug("called with %d", count);
+ if (!ccid->intf) {
+ pr_debug("intf is not active");
+ return -ENODEV;
+ }
+
+ mutex_lock(&ccid->read_mutex);
+
+ if (!count || count > CCID_BRIDGE_MSG_SZ) {
+ pr_err("invalid count");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ kbuf = kmalloc(count, GFP_KERNEL);
+ if (!kbuf) {
+ pr_err("fail to allocate memory");
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ ret = usb_autopm_get_interface(ccid->intf);
+ if (ret) {
+ pr_err("fail to get autopm with %d", ret);
+ goto free_kbuf;
+ }
+
+ ccid->read_result = 0;
+
+ usb_fill_bulk_urb(ccid->readurb, ccid->udev, ccid->in_pipe,
+ kbuf, count, ccid_bridge_in_cb, ccid);
+ ret = usb_submit_urb(ccid->readurb, GFP_KERNEL);
+ if (ret < 0) {
+ pr_err("urb submit fail with %d", ret);
+ if (ret != -ENODEV)
+ usb_autopm_put_interface(ccid->intf);
+ goto free_kbuf;
+ }
+
+
+ ret = wait_event_interruptible_timeout(ccid->read_wq,
+ ccid->read_result != 0,
+ msecs_to_jiffies(CCID_BRIDGE_MSG_TIMEOUT));
+ if (!ret || ret == -ERESTARTSYS) { /* timedout or interrupted */
+ usb_kill_urb(ccid->readurb);
+ if (!ret)
+ ret = -ETIMEDOUT;
+ } else {
+ ret = ccid->read_result;
+ }
+
+
+ if (ret > 0) {
+ if (copy_to_user(ubuf, kbuf, ret))
+ ret = -EFAULT;
+ }
+
+ usb_autopm_put_interface(ccid->intf);
+ pr_debug("returning %d", ret);
+
+free_kbuf:
+ kfree(kbuf);
+out:
+ mutex_unlock(&ccid->read_mutex);
+ return ret;
+}
+
+static long
+ccid_bridge_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
+{
+ struct ccid_bridge *ccid = fp->private_data;
+ char *buf;
+ struct usb_ccid_data data;
+ struct usb_ccid_abort abort;
+ struct usb_descriptor_header *header;
+ int ret;
+ struct usb_device *udev = ccid->udev;
+ __u8 intf = ccid->intf->cur_altsetting->desc.bInterfaceNumber;
+ __u8 breq = 0;
+
+ if (!ccid->intf) {
+ pr_debug("intf is not active");
+ return -ENODEV;
+ }
+
+ mutex_lock(&ccid->event_mutex);
+ switch (cmd) {
+ case USB_CCID_GET_CLASS_DESC:
+ pr_debug("GET_CLASS_DESC ioctl called");
+ ret = copy_from_user(&data, (void __user *)arg, sizeof(data));
+ if (ret) {
+ ret = -EFAULT;
+ break;
+ }
+ ret = __usb_get_extra_descriptor(udev->rawdescriptors[0],
+ le16_to_cpu(udev->config[0].desc.wTotalLength),
+ CCID_CLASS_DECRIPTOR_TYPE, (void **) &buf);
+ if (ret) {
+ ret = -ENOENT;
+ break;
+ }
+ header = (struct usb_descriptor_header *) buf;
+ if (data.length != header->bLength) {
+ ret = -EINVAL;
+ break;
+ }
+ ret = copy_to_user((void __user *)data.data, buf, data.length);
+ if (ret)
+ ret = -EFAULT;
+ break;
+ case USB_CCID_GET_CLOCK_FREQUENCIES:
+ pr_debug("GET_CLOCK_FREQUENCIES ioctl called");
+ breq = CCID_GET_CLK_FREQ_REQ;
+ /* fall through */
+ case USB_CCID_GET_DATA_RATES:
+ if (!breq) {
+ pr_debug("GET_DATA_RATES ioctl called");
+ breq = CCID_GET_DATA_RATES;
+ }
+ ret = copy_from_user(&data, (void __user *)arg, sizeof(data));
+ if (ret) {
+ ret = -EFAULT;
+ break;
+ }
+ buf = kmalloc(data.length, GFP_KERNEL);
+ if (!buf) {
+ ret = -ENOMEM;
+ break;
+ }
+ ret = usb_autopm_get_interface(ccid->intf);
+ if (ret < 0) {
+ pr_debug("fail to get autopm with %d", ret);
+ break;
+ }
+ ret = usb_control_msg(ccid->udev,
+ usb_rcvctrlpipe(ccid->udev, 0),
+ breq, (USB_DIR_IN | USB_TYPE_CLASS |
+ USB_RECIP_INTERFACE), 0, intf, buf,
+ data.length, CCID_CONTROL_TIMEOUT);
+ usb_autopm_put_interface(ccid->intf);
+ if (ret == data.length) {
+ ret = copy_to_user((void __user *)data.data, buf,
+ data.length);
+ if (ret)
+ ret = -EFAULT;
+ } else {
+ if (ret > 0)
+ ret = -EPIPE;
+ }
+ kfree(buf);
+ break;
+ case USB_CCID_ABORT:
+ pr_debug("ABORT ioctl called");
+ breq = CCID_ABORT_REQ;
+ ret = copy_from_user(&abort, (void __user *)arg, sizeof(abort));
+ if (ret) {
+ ret = -EFAULT;
+ break;
+ }
+ ret = usb_autopm_get_interface(ccid->intf);
+ if (ret < 0) {
+ pr_debug("fail to get autopm with %d", ret);
+ break;
+ }
+ ret = usb_control_msg(ccid->udev,
+ usb_sndctrlpipe(ccid->udev, 0),
+ breq, (USB_DIR_OUT | USB_TYPE_CLASS |
+ USB_RECIP_INTERFACE),
+ (abort.seq << 8) | abort.slot, intf, NULL,
+ 0, CCID_CONTROL_TIMEOUT);
+ if (ret < 0)
+ pr_err("abort request failed with err %d\n", ret);
+ usb_autopm_put_interface(ccid->intf);
+ break;
+ case USB_CCID_GET_EVENT:
+ pr_debug("GET_EVENT ioctl called");
+ if (!ccid->events_supported) {
+ ret = -ENOENT;
+ break;
+ }
+ ret = ccid_bridge_get_event(ccid);
+ if (ret == 0) {
+ ret = copy_to_user((void __user *)arg, &ccid->cur_event,
+ sizeof(ccid->cur_event));
+ if (ret)
+ ret = -EFAULT;
+ }
+ ccid->cur_event.event = 0;
+ break;
+ default:
+ pr_err("UNKNOWN ioctl called");
+ ret = -EINVAL;
+ break;
+ }
+
+ mutex_unlock(&ccid->event_mutex);
+ pr_debug("returning %d", ret);
+ return ret;
+}
+
+static int ccid_bridge_release(struct inode *ip, struct file *fp)
+{
+ struct ccid_bridge *ccid = fp->private_data;
+
+ pr_debug("called");
+
+ usb_kill_urb(ccid->writeurb);
+ usb_kill_urb(ccid->readurb);
+ if (ccid->int_pipe)
+ usb_kill_urb(ccid->inturb);
+
+ ccid->event_result = -EIO;
+ wake_up(&ccid->event_wq);
+
+ mutex_lock(&ccid->open_mutex);
+ ccid->opened = false;
+ mutex_unlock(&ccid->open_mutex);
+ return 0;
+}
+
+static const struct file_operations ccid_bridge_fops = {
+ .owner = THIS_MODULE,
+ .open = ccid_bridge_open,
+ .write = ccid_bridge_write,
+ .read = ccid_bridge_read,
+ .unlocked_ioctl = ccid_bridge_ioctl,
+ .release = ccid_bridge_release,
+};
+
+static int ccid_bridge_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ struct ccid_bridge *ccid = usb_get_intfdata(intf);
+ int ret = 0;
+
+ pr_debug("called");
+
+ if (!ccid->opened)
+ goto out;
+
+ mutex_lock(&ccid->event_mutex);
+ if (ccid->int_pipe) {
+ usb_kill_urb(ccid->inturb);
+ if (ccid->event_result != -ENOENT) {
+ ret = -EBUSY;
+ goto rel_mutex;
+ }
+ }
+
+ ccid->is_suspended = true;
+rel_mutex:
+ mutex_unlock(&ccid->event_mutex);
+out:
+ pr_debug("returning %d", ret);
+ return ret;
+}
+
+static int ccid_bridge_resume(struct usb_interface *intf)
+{
+ struct ccid_bridge *ccid = usb_get_intfdata(intf);
+ int ret;
+
+ pr_debug("called");
+
+ if (!ccid->opened)
+ goto out;
+
+ mutex_lock(&ccid->event_mutex);
+
+ ccid->is_suspended = false;
+
+ if (device_can_wakeup(&ccid->udev->dev)) {
+ ccid->event_result = 0;
+ ccid->cur_event.event = USB_CCID_RESUME_EVENT;
+ wake_up(&ccid->event_wq);
+ } else if (ccid->int_pipe) {
+ ccid->event_result = -EINPROGRESS;
+ ret = usb_submit_urb(ccid->inturb, GFP_KERNEL);
+ if (ret < 0)
+ pr_debug("fail to submit inturb with %d\n", ret);
+ }
+
+ mutex_unlock(&ccid->event_mutex);
+out:
+ return 0;
+}
+
+static int
+ccid_bridge_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+ struct ccid_bridge *ccid = __ccid_bridge_dev;
+ struct usb_host_interface *intf_desc;
+ struct usb_endpoint_descriptor *ep_desc;
+ struct usb_host_endpoint *ep;
+ __u8 epin_addr = 0, epout_addr = 0, epint_addr = 0;
+ int i, ret;
+
+ intf_desc = intf->cur_altsetting;
+
+ if (intf_desc->desc.bNumEndpoints > 3)
+ return -ENODEV;
+
+ for (i = 0; i < intf_desc->desc.bNumEndpoints; i++) {
+ ep_desc = &intf_desc->endpoint[i].desc;
+
+ if (usb_endpoint_is_bulk_in(ep_desc))
+ epin_addr = ep_desc->bEndpointAddress;
+ else if (usb_endpoint_is_bulk_out(ep_desc))
+ epout_addr = ep_desc->bEndpointAddress;
+ else if (usb_endpoint_is_int_in(ep_desc))
+ epint_addr = ep_desc->bEndpointAddress;
+ else
+ return -ENODEV;
+ }
+
+ if (!epin_addr || !epout_addr)
+ return -ENODEV;
+
+ ccid->udev = usb_get_dev(interface_to_usbdev(intf));
+ ccid->in_pipe = usb_rcvbulkpipe(ccid->udev, epin_addr);
+ ccid->out_pipe = usb_sndbulkpipe(ccid->udev, epout_addr);
+ if (epint_addr)
+ ccid->int_pipe = usb_rcvbulkpipe(ccid->udev, epint_addr);
+
+ ccid->writeurb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!ccid->writeurb) {
+ pr_err("fail to allocate write urb");
+ ret = -ENOMEM;
+ goto put_udev;
+ }
+ ccid->readurb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!ccid->readurb) {
+ pr_err("fail to allocate read urb");
+ ret = -ENOMEM;
+ goto free_writeurb;
+ }
+
+ if (ccid->int_pipe) {
+ pr_debug("interrupt endpoint is present");
+ ep = usb_pipe_endpoint(ccid->udev, ccid->int_pipe);
+ ccid->inturb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!ccid->inturb) {
+ pr_err("fail to allocate int urb");
+ ret = -ENOMEM;
+ goto free_readurb;
+ }
+ ccid->intbuf = kmalloc(usb_endpoint_maxp(&ep->desc),
+ GFP_KERNEL);
+ if (!ccid->intbuf) {
+ pr_err("fail to allocated int buf");
+ ret = -ENOMEM;
+ goto free_inturb;
+ }
+ usb_fill_int_urb(ccid->inturb, ccid->udev,
+ usb_rcvintpipe(ccid->udev, epint_addr),
+ ccid->intbuf, usb_endpoint_maxp(&ep->desc),
+ ccid_bridge_int_cb, ccid,
+ ep->desc.bInterval);
+ }
+
+ if (ccid->int_pipe || device_can_wakeup(&ccid->udev->dev)) {
+ pr_debug("event support is present");
+ ccid->events_supported = true;
+ }
+
+ usb_set_intfdata(intf, ccid);
+
+ mutex_lock(&ccid->open_mutex);
+ ccid->intf = intf;
+ wake_up(&ccid->open_wq);
+ mutex_unlock(&ccid->open_mutex);
+
+ pr_info("success");
+ return 0;
+
+free_inturb:
+ if (ccid->int_pipe)
+ usb_free_urb(ccid->inturb);
+free_readurb:
+ usb_free_urb(ccid->readurb);
+free_writeurb:
+ usb_free_urb(ccid->writeurb);
+put_udev:
+ usb_put_dev(ccid->udev);
+ return ret;
+}
+
+static void ccid_bridge_disconnect(struct usb_interface *intf)
+{
+ struct ccid_bridge *ccid = usb_get_intfdata(intf);
+
+ pr_debug("called");
+
+ usb_kill_urb(ccid->writeurb);
+ usb_kill_urb(ccid->readurb);
+ if (ccid->int_pipe)
+ usb_kill_urb(ccid->inturb);
+
+ ccid->event_result = -ENODEV;
+ wake_up(&ccid->event_wq);
+
+ /*
+ * This would synchronize any ongoing read/write/ioctl.
+ * After acquiring the mutex, we can safely set
+ * intf to NULL.
+ */
+ mutex_lock(&ccid->open_mutex);
+ mutex_lock(&ccid->write_mutex);
+ mutex_lock(&ccid->read_mutex);
+ mutex_lock(&ccid->event_mutex);
+
+ usb_free_urb(ccid->writeurb);
+ usb_free_urb(ccid->readurb);
+ if (ccid->int_pipe) {
+ usb_free_urb(ccid->inturb);
+ kfree(ccid->intbuf);
+ ccid->int_pipe = 0;
+ }
+
+ ccid->intf = NULL;
+
+ mutex_unlock(&ccid->event_mutex);
+ mutex_unlock(&ccid->read_mutex);
+ mutex_unlock(&ccid->write_mutex);
+ mutex_unlock(&ccid->open_mutex);
+
+}
+
+static const struct usb_device_id ccid_bridge_ids[] = {
+ { USB_INTERFACE_INFO(USB_CLASS_CSCID, 0, 0) },
+
+ {} /* terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, ccid_bridge_ids);
+
+static struct usb_driver ccid_bridge_driver = {
+ .name = "ccid_bridge",
+ .probe = ccid_bridge_probe,
+ .disconnect = ccid_bridge_disconnect,
+ .suspend = ccid_bridge_suspend,
+ .resume = ccid_bridge_resume,
+ .id_table = ccid_bridge_ids,
+ .supports_autosuspend = 1,
+};
+
+static int __init ccid_bridge_init(void)
+{
+ int ret;
+ struct ccid_bridge *ccid;
+
+ ccid = kzalloc(sizeof(*ccid), GFP_KERNEL);
+ if (!ccid) {
+ pr_err("Fail to allocate ccid");
+ ret = -ENOMEM;
+ goto out;
+ }
+ __ccid_bridge_dev = ccid;
+
+ mutex_init(&ccid->open_mutex);
+ mutex_init(&ccid->write_mutex);
+ mutex_init(&ccid->read_mutex);
+ mutex_init(&ccid->event_mutex);
+
+ init_waitqueue_head(&ccid->open_wq);
+ init_waitqueue_head(&ccid->write_wq);
+ init_waitqueue_head(&ccid->read_wq);
+ init_waitqueue_head(&ccid->event_wq);
+
+ ret = usb_register(&ccid_bridge_driver);
+ if (ret < 0) {
+ pr_err("Fail to register ccid usb driver with %d", ret);
+ goto free_ccid;
+ }
+
+ ret = alloc_chrdev_region(&ccid->chrdev, 0, 1, "ccid_bridge");
+ if (ret < 0) {
+ pr_err("Fail to allocate ccid char dev region with %d", ret);
+ goto unreg_driver;
+ }
+ ccid->class = class_create(THIS_MODULE, "ccid_bridge");
+ if (IS_ERR(ccid->class)) {
+ ret = PTR_ERR(ccid->class);
+ pr_err("Fail to create ccid class with %d", ret);
+ goto unreg_chrdev;
+ }
+ cdev_init(&ccid->cdev, &ccid_bridge_fops);
+ ccid->cdev.owner = THIS_MODULE;
+
+ ret = cdev_add(&ccid->cdev, ccid->chrdev, 1);
+ if (ret < 0) {
+ pr_err("Fail to add ccid cdev with %d", ret);
+ goto destroy_class;
+ }
+ ccid->device = device_create(ccid->class,
+ NULL, ccid->chrdev, NULL,
+ "ccid_bridge");
+ if (IS_ERR(ccid->device)) {
+ ret = PTR_ERR(ccid->device);
+ pr_err("Fail to create ccid device with %d", ret);
+ goto del_cdev;
+ }
+
+ pr_info("success");
+
+ return 0;
+
+del_cdev:
+ cdev_del(&ccid->cdev);
+destroy_class:
+ class_destroy(ccid->class);
+unreg_chrdev:
+ unregister_chrdev_region(ccid->chrdev, 1);
+unreg_driver:
+ usb_deregister(&ccid_bridge_driver);
+free_ccid:
+ mutex_destroy(&ccid->open_mutex);
+ mutex_destroy(&ccid->write_mutex);
+ mutex_destroy(&ccid->read_mutex);
+ mutex_destroy(&ccid->event_mutex);
+ kfree(ccid);
+ __ccid_bridge_dev = NULL;
+out:
+ return ret;
+}
+
+static void __exit ccid_bridge_exit(void)
+{
+ struct ccid_bridge *ccid = __ccid_bridge_dev;
+
+ pr_debug("called");
+ device_destroy(ccid->class, ccid->chrdev);
+ cdev_del(&ccid->cdev);
+ class_destroy(ccid->class);
+ unregister_chrdev_region(ccid->chrdev, 1);
+
+ usb_deregister(&ccid_bridge_driver);
+
+ mutex_destroy(&ccid->open_mutex);
+ mutex_destroy(&ccid->write_mutex);
+ mutex_destroy(&ccid->read_mutex);
+ mutex_destroy(&ccid->event_mutex);
+
+ kfree(ccid);
+ __ccid_bridge_dev = NULL;
+}
+
+module_init(ccid_bridge_init);
+module_exit(ccid_bridge_exit);
+
+MODULE_DESCRIPTION("USB CCID bridge driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/usb/Kbuild b/include/linux/usb/Kbuild
index 087d163..fe4d6da 100644
--- a/include/linux/usb/Kbuild
+++ b/include/linux/usb/Kbuild
@@ -1,4 +1,5 @@
header-y += audio.h
+header-y += ccid_bridge.h
header-y += cdc.h
header-y += ch9.h
header-y += ch11.h
diff --git a/include/linux/usb/ccid_bridge.h b/include/linux/usb/ccid_bridge.h
new file mode 100644
index 0000000..1d1b895
--- /dev/null
+++ b/include/linux/usb/ccid_bridge.h
@@ -0,0 +1,64 @@
+#ifndef __UAPI_USB_CCID_BRIDGE_H
+#define __UAPI_USB_CCID_BRIDGE_H
+
+#include <linux/ioctl.h>
+
+/**
+ * struct usb_ccid_data - Used to receive the CCID class descriptor,
+ * clock rates and data rates supported by the device.
+ * @length: The length of the buffer.
+ * @data: The buffer as it is returned by the device for GET_DESCRIPTOR,
+ * GET_CLOCK_FREQUENCIES and GET_DATA_RATES requests.
+ */
+struct usb_ccid_data {
+ uint8_t length;
+ void *data;
+};
+
+/**
+ * struct usb_ccid_abort - Used to abort an already sent command.
+ * @seq: The sequence number of the command.
+ * @slot: The slot of the IC, on which the command is sent.
+ */
+struct usb_ccid_abort {
+ uint8_t seq;
+ uint8_t slot;
+};
+
+#define USB_CCID_NOTIFY_SLOT_CHANGE_EVENT 1
+#define USB_CCID_HARDWARE_ERROR_EVENT 2
+#define USB_CCID_RESUME_EVENT 3
+/**
+ * struct usb_ccid_event - Used to receive notify slot change or hardware
+ * error event.
+ * @notify: If the event is USB_CCID_NOTIFY_SLOT_CHANGE_EVENT, slot_icc_state
+ * has the information about the current slots state.
+ * @error: If the event is USB_CCID_HARDWARE_ERROR_EVENT, error has
+ * information about the hardware error condition.
+ */
+struct usb_ccid_event {
+ uint8_t event;
+ union {
+ struct {
+ uint8_t slot_icc_state;
+ } notify;
+
+ struct {
+ uint8_t slot;
+ uint8_t seq;
+ uint8_t error_code;
+ } error;
+ } u;
+};
+
+#define USB_CCID_GET_CLASS_DESC _IOWR('C', 0, struct usb_ccid_data)
+
+#define USB_CCID_GET_CLOCK_FREQUENCIES _IOWR('C', 1, struct usb_ccid_data)
+
+#define USB_CCID_GET_DATA_RATES _IOWR('C', 2, struct usb_ccid_data)
+
+#define USB_CCID_ABORT _IOW('C', 3, struct usb_ccid_abort)
+
+#define USB_CCID_GET_EVENT _IOR('C', 4, struct usb_ccid_event)
+
+#endif /* __UAPI_USB_CCID_BRIDGE_H */