diff options
56 files changed, 7697 insertions, 1267 deletions
diff --git a/Documentation/DocBook/uio-howto.tmpl b/Documentation/DocBook/uio-howto.tmpl index ac3d0018140c..fdbf86fcfccd 100644 --- a/Documentation/DocBook/uio-howto.tmpl +++ b/Documentation/DocBook/uio-howto.tmpl @@ -719,6 +719,62 @@ framework to set up sysfs files for this region. Simply leave it alone. </para> </sect1> +<sect1 id="using uio_dmem_genirq"> +<title>Using uio_dmem_genirq for platform devices</title> + <para> + In addition to statically allocated memory ranges, they may also be + a desire to use dynamically allocated regions in a user space driver. + In particular, being able to access memory made available through the + dma-mapping API, may be particularly useful. The + <varname>uio_dmem_genirq</varname> driver provides a way to accomplish + this. + </para> + <para> + This driver is used in a similar manner to the + <varname>"uio_pdrv_genirq"</varname> driver with respect to interrupt + configuration and handling. + </para> + <para> + Set the <varname>.name</varname> element of + <varname>struct platform_device</varname> to + <varname>"uio_dmem_genirq"</varname> to use this driver. + </para> + <para> + When using this driver, fill in the <varname>.platform_data</varname> + element of <varname>struct platform_device</varname>, which is of type + <varname>struct uio_dmem_genirq_pdata</varname> and which contains the + following elements: + </para> + <itemizedlist> + <listitem><varname>struct uio_info uioinfo</varname>: The same + structure used as the <varname>uio_pdrv_genirq</varname> platform + data</listitem> + <listitem><varname>unsigned int *dynamic_region_sizes</varname>: + Pointer to list of sizes of dynamic memory regions to be mapped into + user space. + </listitem> + <listitem><varname>unsigned int num_dynamic_regions</varname>: + Number of elements in <varname>dynamic_region_sizes</varname> array. + </listitem> + </itemizedlist> + <para> + The dynamic regions defined in the platform data will be appended to + the <varname> mem[] </varname> array after the platform device + resources, which implies that the total number of static and dynamic + memory regions cannot exceed <varname>MAX_UIO_MAPS</varname>. + </para> + <para> + The dynamic memory regions will be allocated when the UIO device file, + <varname>/dev/uioX</varname> is opened. + Simiar to static memory resources, the memory region information for + dynamic regions is then visible via sysfs at + <varname>/sys/class/uio/uioX/maps/mapY/*</varname>. + The dynmaic memory regions will be freed when the UIO device file is + closed. When no processes are holding the device file open, the address + returned to userspace is DMA_ERROR_CODE. + </para> +</sect1> + </chapter> <chapter id="userspace_driver" xreflabel="Writing a driver in user space"> diff --git a/arch/arm/include/asm/io.h b/arch/arm/include/asm/io.h index 42f042ee4ada..652b56086de7 100644 --- a/arch/arm/include/asm/io.h +++ b/arch/arm/include/asm/io.h @@ -374,7 +374,7 @@ extern void pci_iounmap(struct pci_dev *dev, void __iomem *addr); #ifdef CONFIG_MMU #define ARCH_HAS_VALID_PHYS_ADDR_RANGE -extern int valid_phys_addr_range(unsigned long addr, size_t size); +extern int valid_phys_addr_range(phys_addr_t addr, size_t size); extern int valid_mmap_phys_addr_range(unsigned long pfn, size_t size); extern int devmem_is_allowed(unsigned long pfn); #endif diff --git a/arch/arm/mm/mmap.c b/arch/arm/mm/mmap.c index ce8cb1970d7a..89f2b7f7b042 100644 --- a/arch/arm/mm/mmap.c +++ b/arch/arm/mm/mmap.c @@ -279,7 +279,7 @@ void arch_pick_mmap_layout(struct mm_struct *mm) * You really shouldn't be using read() or write() on /dev/mem. This * might go away in the future. */ -int valid_phys_addr_range(unsigned long addr, size_t size) +int valid_phys_addr_range(phys_addr_t addr, size_t size) { if (addr < PHYS_OFFSET) return 0; diff --git a/arch/ia64/include/asm/io.h b/arch/ia64/include/asm/io.h index 2c26321c28c3..74a7cc3293bc 100644 --- a/arch/ia64/include/asm/io.h +++ b/arch/ia64/include/asm/io.h @@ -90,7 +90,7 @@ phys_to_virt (unsigned long address) #define ARCH_HAS_VALID_PHYS_ADDR_RANGE extern u64 kern_mem_attribute (unsigned long phys_addr, unsigned long size); -extern int valid_phys_addr_range (unsigned long addr, size_t count); /* efi.c */ +extern int valid_phys_addr_range (phys_addr_t addr, size_t count); /* efi.c */ extern int valid_mmap_phys_addr_range (unsigned long pfn, size_t count); /* diff --git a/arch/ia64/kernel/efi.c b/arch/ia64/kernel/efi.c index d37bbd48637f..f034563aeae5 100644 --- a/arch/ia64/kernel/efi.c +++ b/arch/ia64/kernel/efi.c @@ -870,7 +870,7 @@ kern_mem_attribute (unsigned long phys_addr, unsigned long size) EXPORT_SYMBOL(kern_mem_attribute); int -valid_phys_addr_range (unsigned long phys_addr, unsigned long size) +valid_phys_addr_range (phys_addr_t phys_addr, unsigned long size) { u64 attr; diff --git a/arch/sh/include/asm/io.h b/arch/sh/include/asm/io.h index 73a23f4617a3..629db2ad7916 100644 --- a/arch/sh/include/asm/io.h +++ b/arch/sh/include/asm/io.h @@ -382,7 +382,7 @@ static inline int iounmap_fixed(void __iomem *addr) { return -EINVAL; } #define xlate_dev_kmem_ptr(p) p #define ARCH_HAS_VALID_PHYS_ADDR_RANGE -int valid_phys_addr_range(unsigned long addr, size_t size); +int valid_phys_addr_range(phys_addr_t addr, size_t size); int valid_mmap_phys_addr_range(unsigned long pfn, size_t size); #endif /* __KERNEL__ */ diff --git a/arch/sh/mm/mmap.c b/arch/sh/mm/mmap.c index afeb710ec5c3..80bf494ddbcb 100644 --- a/arch/sh/mm/mmap.c +++ b/arch/sh/mm/mmap.c @@ -238,7 +238,7 @@ bottomup: * You really shouldn't be using read() or write() on /dev/mem. This * might go away in the future. */ -int valid_phys_addr_range(unsigned long addr, size_t count) +int valid_phys_addr_range(phys_addr_t addr, size_t count) { if (addr < __MEMORY_START) return 0; diff --git a/drivers/char/mem.c b/drivers/char/mem.c index 0537903c985b..c6fa3bc2baa8 100644 --- a/drivers/char/mem.c +++ b/drivers/char/mem.c @@ -48,7 +48,7 @@ static inline unsigned long size_inside_page(unsigned long start, } #ifndef ARCH_HAS_VALID_PHYS_ADDR_RANGE -static inline int valid_phys_addr_range(unsigned long addr, size_t count) +static inline int valid_phys_addr_range(phys_addr_t addr, size_t count) { return addr + count <= __pa(high_memory); } @@ -96,7 +96,7 @@ void __weak unxlate_dev_mem_ptr(unsigned long phys, void *addr) static ssize_t read_mem(struct file *file, char __user *buf, size_t count, loff_t *ppos) { - unsigned long p = *ppos; + phys_addr_t p = *ppos; ssize_t read, sz; char *ptr; @@ -153,7 +153,7 @@ static ssize_t read_mem(struct file *file, char __user *buf, static ssize_t write_mem(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { - unsigned long p = *ppos; + phys_addr_t p = *ppos; ssize_t written, sz; unsigned long copied; void *ptr; @@ -226,7 +226,7 @@ int __weak phys_mem_access_prot_allowed(struct file *file, * */ #ifdef pgprot_noncached -static int uncached_access(struct file *file, unsigned long addr) +static int uncached_access(struct file *file, phys_addr_t addr) { #if defined(CONFIG_IA64) /* @@ -258,7 +258,7 @@ static pgprot_t phys_mem_access_prot(struct file *file, unsigned long pfn, unsigned long size, pgprot_t vma_prot) { #ifdef pgprot_noncached - unsigned long offset = pfn << PAGE_SHIFT; + phys_addr_t offset = pfn << PAGE_SHIFT; if (uncached_access(file, offset)) return pgprot_noncached(vma_prot); diff --git a/drivers/char/pc8736x_gpio.c b/drivers/char/pc8736x_gpio.c index b304ec052501..3f79a9fb6b1b 100644 --- a/drivers/char/pc8736x_gpio.c +++ b/drivers/char/pc8736x_gpio.c @@ -345,8 +345,7 @@ static void __exit pc8736x_gpio_cleanup(void) unregister_chrdev_region(MKDEV(major,0), PC8736X_GPIO_CT); release_region(pc8736x_gpio_base, PC8736X_GPIO_RANGE); - platform_device_del(pdev); - platform_device_put(pdev); + platform_device_unregister(pdev); } module_init(pc8736x_gpio_init); diff --git a/drivers/hv/Kconfig b/drivers/hv/Kconfig index 70f5dde1cc52..b38ef6d8d049 100644 --- a/drivers/hv/Kconfig +++ b/drivers/hv/Kconfig @@ -13,4 +13,10 @@ config HYPERV_UTILS help Select this option to enable the Hyper-V Utilities. +config HYPERV_BALLOON + tristate "Microsoft Hyper-V Balloon driver" + depends on HYPERV + help + Select this option to enable Hyper-V Balloon driver. + endmenu diff --git a/drivers/hv/Makefile b/drivers/hv/Makefile index a23938b991c9..e6abfa02d8b7 100644 --- a/drivers/hv/Makefile +++ b/drivers/hv/Makefile @@ -1,5 +1,6 @@ obj-$(CONFIG_HYPERV) += hv_vmbus.o obj-$(CONFIG_HYPERV_UTILS) += hv_utils.o +obj-$(CONFIG_HYPERV_BALLOON) += hv_balloon.o hv_vmbus-y := vmbus_drv.o \ hv.o connection.o channel.o \ diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index f4c3d28cd1fc..773a2f25a8f0 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -33,14 +33,6 @@ #define NUM_PAGES_SPANNED(addr, len) \ ((PAGE_ALIGN(addr + len) >> PAGE_SHIFT) - (addr >> PAGE_SHIFT)) -/* Internal routines */ -static int create_gpadl_header( - void *kbuffer, /* must be phys and virt contiguous */ - u32 size, /* page-size multiple */ - struct vmbus_channel_msginfo **msginfo, - u32 *messagecount); -static void vmbus_setevent(struct vmbus_channel *channel); - /* * vmbus_setevent- Trigger an event notification on the specified * channel. diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index 2b8b8d4558d2..2f84c5cff8d4 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -265,14 +265,9 @@ static void vmbus_onoffer(struct vmbus_channel_message_header *hdr) { struct vmbus_channel_offer_channel *offer; struct vmbus_channel *newchannel; - uuid_le *guidtype; - uuid_le *guidinstance; offer = (struct vmbus_channel_offer_channel *)hdr; - guidtype = &offer->offer.if_type; - guidinstance = &offer->offer.if_instance; - /* Allocate the channel object and save this offer. */ newchannel = alloc_channel(); if (!newchannel) { @@ -470,7 +465,6 @@ static void vmbus_onversion_response( { struct vmbus_channel_msginfo *msginfo; struct vmbus_channel_message_header *requestheader; - struct vmbus_channel_initiate_contact *initiate; struct vmbus_channel_version_response *version_response; unsigned long flags; @@ -484,8 +478,6 @@ static void vmbus_onversion_response( if (requestheader->msgtype == CHANNELMSG_INITIATE_CONTACT) { - initiate = - (struct vmbus_channel_initiate_contact *)requestheader; memcpy(&msginfo->response.version_response, version_response, sizeof(struct vmbus_channel_version_response)); diff --git a/drivers/hv/hv_balloon.c b/drivers/hv/hv_balloon.c new file mode 100644 index 000000000000..bbc497373aaf --- /dev/null +++ b/drivers/hv/hv_balloon.c @@ -0,0 +1,1041 @@ +/* + * Copyright (c) 2012, Microsoft Corporation. + * + * Author: + * K. Y. Srinivasan <kys@microsoft.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License 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, GOOD TITLE or + * NON INFRINGEMENT. See the GNU General Public License for more + * details. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/mman.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/kthread.h> +#include <linux/completion.h> +#include <linux/memory_hotplug.h> +#include <linux/memory.h> +#include <linux/notifier.h> +#include <linux/mman.h> +#include <linux/percpu_counter.h> + +#include <linux/hyperv.h> + +/* + * We begin with definitions supporting the Dynamic Memory protocol + * with the host. + * + * Begin protocol definitions. + */ + + + +/* + * Protocol versions. The low word is the minor version, the high word the major + * version. + * + * History: + * Initial version 1.0 + * Changed to 0.1 on 2009/03/25 + * Changes to 0.2 on 2009/05/14 + * Changes to 0.3 on 2009/12/03 + * Changed to 1.0 on 2011/04/05 + */ + +#define DYNMEM_MAKE_VERSION(Major, Minor) ((__u32)(((Major) << 16) | (Minor))) +#define DYNMEM_MAJOR_VERSION(Version) ((__u32)(Version) >> 16) +#define DYNMEM_MINOR_VERSION(Version) ((__u32)(Version) & 0xff) + +enum { + DYNMEM_PROTOCOL_VERSION_1 = DYNMEM_MAKE_VERSION(0, 3), + DYNMEM_PROTOCOL_VERSION_2 = DYNMEM_MAKE_VERSION(1, 0), + + DYNMEM_PROTOCOL_VERSION_WIN7 = DYNMEM_PROTOCOL_VERSION_1, + DYNMEM_PROTOCOL_VERSION_WIN8 = DYNMEM_PROTOCOL_VERSION_2, + + DYNMEM_PROTOCOL_VERSION_CURRENT = DYNMEM_PROTOCOL_VERSION_WIN8 +}; + + + +/* + * Message Types + */ + +enum dm_message_type { + /* + * Version 0.3 + */ + DM_ERROR = 0, + DM_VERSION_REQUEST = 1, + DM_VERSION_RESPONSE = 2, + DM_CAPABILITIES_REPORT = 3, + DM_CAPABILITIES_RESPONSE = 4, + DM_STATUS_REPORT = 5, + DM_BALLOON_REQUEST = 6, + DM_BALLOON_RESPONSE = 7, + DM_UNBALLOON_REQUEST = 8, + DM_UNBALLOON_RESPONSE = 9, + DM_MEM_HOT_ADD_REQUEST = 10, + DM_MEM_HOT_ADD_RESPONSE = 11, + DM_VERSION_03_MAX = 11, + /* + * Version 1.0. + */ + DM_INFO_MESSAGE = 12, + DM_VERSION_1_MAX = 12 +}; + + +/* + * Structures defining the dynamic memory management + * protocol. + */ + +union dm_version { + struct { + __u16 minor_version; + __u16 major_version; + }; + __u32 version; +} __packed; + + +union dm_caps { + struct { + __u64 balloon:1; + __u64 hot_add:1; + __u64 reservedz:62; + } cap_bits; + __u64 caps; +} __packed; + +union dm_mem_page_range { + struct { + /* + * The PFN number of the first page in the range. + * 40 bits is the architectural limit of a PFN + * number for AMD64. + */ + __u64 start_page:40; + /* + * The number of pages in the range. + */ + __u64 page_cnt:24; + } finfo; + __u64 page_range; +} __packed; + + + +/* + * The header for all dynamic memory messages: + * + * type: Type of the message. + * size: Size of the message in bytes; including the header. + * trans_id: The guest is responsible for manufacturing this ID. + */ + +struct dm_header { + __u16 type; + __u16 size; + __u32 trans_id; +} __packed; + +/* + * A generic message format for dynamic memory. + * Specific message formats are defined later in the file. + */ + +struct dm_message { + struct dm_header hdr; + __u8 data[]; /* enclosed message */ +} __packed; + + +/* + * Specific message types supporting the dynamic memory protocol. + */ + +/* + * Version negotiation message. Sent from the guest to the host. + * The guest is free to try different versions until the host + * accepts the version. + * + * dm_version: The protocol version requested. + * is_last_attempt: If TRUE, this is the last version guest will request. + * reservedz: Reserved field, set to zero. + */ + +struct dm_version_request { + struct dm_header hdr; + union dm_version version; + __u32 is_last_attempt:1; + __u32 reservedz:31; +} __packed; + +/* + * Version response message; Host to Guest and indicates + * if the host has accepted the version sent by the guest. + * + * is_accepted: If TRUE, host has accepted the version and the guest + * should proceed to the next stage of the protocol. FALSE indicates that + * guest should re-try with a different version. + * + * reservedz: Reserved field, set to zero. + */ + +struct dm_version_response { + struct dm_header hdr; + __u64 is_accepted:1; + __u64 reservedz:63; +} __packed; + +/* + * Message reporting capabilities. This is sent from the guest to the + * host. + */ + +struct dm_capabilities { + struct dm_header hdr; + union dm_caps caps; + __u64 min_page_cnt; + __u64 max_page_number; +} __packed; + +/* + * Response to the capabilities message. This is sent from the host to the + * guest. This message notifies if the host has accepted the guest's + * capabilities. If the host has not accepted, the guest must shutdown + * the service. + * + * is_accepted: Indicates if the host has accepted guest's capabilities. + * reservedz: Must be 0. + */ + +struct dm_capabilities_resp_msg { + struct dm_header hdr; + __u64 is_accepted:1; + __u64 reservedz:63; +} __packed; + +/* + * This message is used to report memory pressure from the guest. + * This message is not part of any transaction and there is no + * response to this message. + * + * num_avail: Available memory in pages. + * num_committed: Committed memory in pages. + * page_file_size: The accumulated size of all page files + * in the system in pages. + * zero_free: The nunber of zero and free pages. + * page_file_writes: The writes to the page file in pages. + * io_diff: An indicator of file cache efficiency or page file activity, + * calculated as File Cache Page Fault Count - Page Read Count. + * This value is in pages. + * + * Some of these metrics are Windows specific and fortunately + * the algorithm on the host side that computes the guest memory + * pressure only uses num_committed value. + */ + +struct dm_status { + struct dm_header hdr; + __u64 num_avail; + __u64 num_committed; + __u64 page_file_size; + __u64 zero_free; + __u32 page_file_writes; + __u32 io_diff; +} __packed; + + +/* + * Message to ask the guest to allocate memory - balloon up message. + * This message is sent from the host to the guest. The guest may not be + * able to allocate as much memory as requested. + * + * num_pages: number of pages to allocate. + */ + +struct dm_balloon { + struct dm_header hdr; + __u32 num_pages; + __u32 reservedz; +} __packed; + + +/* + * Balloon response message; this message is sent from the guest + * to the host in response to the balloon message. + * + * reservedz: Reserved; must be set to zero. + * more_pages: If FALSE, this is the last message of the transaction. + * if TRUE there will atleast one more message from the guest. + * + * range_count: The number of ranges in the range array. + * + * range_array: An array of page ranges returned to the host. + * + */ + +struct dm_balloon_response { + struct dm_header hdr; + __u32 reservedz; + __u32 more_pages:1; + __u32 range_count:31; + union dm_mem_page_range range_array[]; +} __packed; + +/* + * Un-balloon message; this message is sent from the host + * to the guest to give guest more memory. + * + * more_pages: If FALSE, this is the last message of the transaction. + * if TRUE there will atleast one more message from the guest. + * + * reservedz: Reserved; must be set to zero. + * + * range_count: The number of ranges in the range array. + * + * range_array: An array of page ranges returned to the host. + * + */ + +struct dm_unballoon_request { + struct dm_header hdr; + __u32 more_pages:1; + __u32 reservedz:31; + __u32 range_count; + union dm_mem_page_range range_array[]; +} __packed; + +/* + * Un-balloon response message; this message is sent from the guest + * to the host in response to an unballoon request. + * + */ + +struct dm_unballoon_response { + struct dm_header hdr; +} __packed; + + +/* + * Hot add request message. Message sent from the host to the guest. + * + * mem_range: Memory range to hot add. + * + * On Linux we currently don't support this since we cannot hot add + * arbitrary granularity of memory. + */ + +struct dm_hot_add { + struct dm_header hdr; + union dm_mem_page_range range; +} __packed; + +/* + * Hot add response message. + * This message is sent by the guest to report the status of a hot add request. + * If page_count is less than the requested page count, then the host should + * assume all further hot add requests will fail, since this indicates that + * the guest has hit an upper physical memory barrier. + * + * Hot adds may also fail due to low resources; in this case, the guest must + * not complete this message until the hot add can succeed, and the host must + * not send a new hot add request until the response is sent. + * If VSC fails to hot add memory DYNMEM_NUMBER_OF_UNSUCCESSFUL_HOTADD_ATTEMPTS + * times it fails the request. + * + * + * page_count: number of pages that were successfully hot added. + * + * result: result of the operation 1: success, 0: failure. + * + */ + +struct dm_hot_add_response { + struct dm_header hdr; + __u32 page_count; + __u32 result; +} __packed; + +/* + * Types of information sent from host to the guest. + */ + +enum dm_info_type { + INFO_TYPE_MAX_PAGE_CNT = 0, + MAX_INFO_TYPE +}; + + +/* + * Header for the information message. + */ + +struct dm_info_header { + enum dm_info_type type; + __u32 data_size; +} __packed; + +/* + * This message is sent from the host to the guest to pass + * some relevant information (win8 addition). + * + * reserved: no used. + * info_size: size of the information blob. + * info: information blob. + */ + +struct dm_info_msg { + struct dm_info_header header; + __u32 reserved; + __u32 info_size; + __u8 info[]; +}; + +/* + * End protocol definitions. + */ + +static bool hot_add; +static bool do_hot_add; + +module_param(hot_add, bool, (S_IRUGO | S_IWUSR)); +MODULE_PARM_DESC(hot_add, "If set attempt memory hot_add"); + +static atomic_t trans_id = ATOMIC_INIT(0); + +static int dm_ring_size = (5 * PAGE_SIZE); + +/* + * Driver specific state. + */ + +enum hv_dm_state { + DM_INITIALIZING = 0, + DM_INITIALIZED, + DM_BALLOON_UP, + DM_BALLOON_DOWN, + DM_HOT_ADD, + DM_INIT_ERROR +}; + + +static __u8 recv_buffer[PAGE_SIZE]; +static __u8 *send_buffer; +#define PAGES_IN_2M 512 + +struct hv_dynmem_device { + struct hv_device *dev; + enum hv_dm_state state; + struct completion host_event; + struct completion config_event; + + /* + * Number of pages we have currently ballooned out. + */ + unsigned int num_pages_ballooned; + + /* + * This thread handles both balloon/hot-add + * requests from the host as well as notifying + * the host with regards to memory pressure in + * the guest. + */ + struct task_struct *thread; + + /* + * We start with the highest version we can support + * and downgrade based on the host; we save here the + * next version to try. + */ + __u32 next_version; +}; + +static struct hv_dynmem_device dm_device; + +static void hot_add_req(struct hv_dynmem_device *dm, struct dm_hot_add *msg) +{ + + struct dm_hot_add_response resp; + + if (do_hot_add) { + + pr_info("Memory hot add not supported\n"); + + /* + * Currently we do not support hot add. + * Just fail the request. + */ + } + + memset(&resp, 0, sizeof(struct dm_hot_add_response)); + resp.hdr.type = DM_MEM_HOT_ADD_RESPONSE; + resp.hdr.size = sizeof(struct dm_hot_add_response); + resp.hdr.trans_id = atomic_inc_return(&trans_id); + + resp.page_count = 0; + resp.result = 0; + + dm->state = DM_INITIALIZED; + vmbus_sendpacket(dm->dev->channel, &resp, + sizeof(struct dm_hot_add_response), + (unsigned long)NULL, + VM_PKT_DATA_INBAND, 0); + +} + +static void process_info(struct hv_dynmem_device *dm, struct dm_info_msg *msg) +{ + switch (msg->header.type) { + case INFO_TYPE_MAX_PAGE_CNT: + pr_info("Received INFO_TYPE_MAX_PAGE_CNT\n"); + pr_info("Data Size is %d\n", msg->header.data_size); + break; + default: + pr_info("Received Unknown type: %d\n", msg->header.type); + } +} + +/* + * Post our status as it relates memory pressure to the + * host. Host expects the guests to post this status + * periodically at 1 second intervals. + * + * The metrics specified in this protocol are very Windows + * specific and so we cook up numbers here to convey our memory + * pressure. + */ + +static void post_status(struct hv_dynmem_device *dm) +{ + struct dm_status status; + + + memset(&status, 0, sizeof(struct dm_status)); + status.hdr.type = DM_STATUS_REPORT; + status.hdr.size = sizeof(struct dm_status); + status.hdr.trans_id = atomic_inc_return(&trans_id); + + + status.num_committed = vm_memory_committed(); + + vmbus_sendpacket(dm->dev->channel, &status, + sizeof(struct dm_status), + (unsigned long)NULL, + VM_PKT_DATA_INBAND, 0); + +} + + + +void free_balloon_pages(struct hv_dynmem_device *dm, + union dm_mem_page_range *range_array) +{ + int num_pages = range_array->finfo.page_cnt; + __u64 start_frame = range_array->finfo.start_page; + struct page *pg; + int i; + + for (i = 0; i < num_pages; i++) { + pg = pfn_to_page(i + start_frame); + __free_page(pg); + dm->num_pages_ballooned--; + } +} + + + +static int alloc_balloon_pages(struct hv_dynmem_device *dm, int num_pages, + struct dm_balloon_response *bl_resp, int alloc_unit, + bool *alloc_error) +{ + int i = 0; + struct page *pg; + + if (num_pages < alloc_unit) + return 0; + + for (i = 0; (i * alloc_unit) < num_pages; i++) { + if (bl_resp->hdr.size + sizeof(union dm_mem_page_range) > + PAGE_SIZE) + return i * alloc_unit; + + /* + * We execute this code in a thread context. Furthermore, + * we don't want the kernel to try too hard. + */ + pg = alloc_pages(GFP_HIGHUSER | __GFP_NORETRY | + __GFP_NOMEMALLOC | __GFP_NOWARN, + get_order(alloc_unit << PAGE_SHIFT)); + + if (!pg) { + *alloc_error = true; + return i * alloc_unit; + } + + + dm->num_pages_ballooned += alloc_unit; + + bl_resp->range_count++; + bl_resp->range_array[i].finfo.start_page = + page_to_pfn(pg); + bl_resp->range_array[i].finfo.page_cnt = alloc_unit; + bl_resp->hdr.size += sizeof(union dm_mem_page_range); + + } + + return num_pages; +} + + + +static void balloon_up(struct hv_dynmem_device *dm, struct dm_balloon *req) +{ + int num_pages = req->num_pages; + int num_ballooned = 0; + struct dm_balloon_response *bl_resp; + int alloc_unit; + int ret; + bool alloc_error = false; + bool done = false; + int i; + + + /* + * Currently, we only support 4k allocations. + */ + alloc_unit = 1; + + while (!done) { + bl_resp = (struct dm_balloon_response *)send_buffer; + memset(send_buffer, 0, PAGE_SIZE); + bl_resp->hdr.type = DM_BALLOON_RESPONSE; + bl_resp->hdr.trans_id = atomic_inc_return(&trans_id); + bl_resp->hdr.size = sizeof(struct dm_balloon_response); + bl_resp->more_pages = 1; + + + num_pages -= num_ballooned; + num_ballooned = alloc_balloon_pages(dm, num_pages, + bl_resp, alloc_unit, + &alloc_error); + + if ((alloc_error) || (num_ballooned == num_pages)) { + bl_resp->more_pages = 0; + done = true; + dm->state = DM_INITIALIZED; + } + + /* + * We are pushing a lot of data through the channel; + * deal with transient failures caused because of the + * lack of space in the ring buffer. + */ + + do { + ret = vmbus_sendpacket(dm_device.dev->channel, + bl_resp, + bl_resp->hdr.size, + (unsigned long)NULL, + VM_PKT_DATA_INBAND, 0); + + if (ret == -EAGAIN) + msleep(20); + + } while (ret == -EAGAIN); + + if (ret) { + /* + * Free up the memory we allocatted. + */ + pr_info("Balloon response failed\n"); + + for (i = 0; i < bl_resp->range_count; i++) + free_balloon_pages(dm, + &bl_resp->range_array[i]); + + done = true; + } + } + +} + +static void balloon_down(struct hv_dynmem_device *dm, + struct dm_unballoon_request *req) +{ + union dm_mem_page_range *range_array = req->range_array; + int range_count = req->range_count; + struct dm_unballoon_response resp; + int i; + + for (i = 0; i < range_count; i++) + free_balloon_pages(dm, &range_array[i]); + + if (req->more_pages == 1) + return; + + memset(&resp, 0, sizeof(struct dm_unballoon_response)); + resp.hdr.type = DM_UNBALLOON_RESPONSE; + resp.hdr.trans_id = atomic_inc_return(&trans_id); + resp.hdr.size = sizeof(struct dm_unballoon_response); + + vmbus_sendpacket(dm_device.dev->channel, &resp, + sizeof(struct dm_unballoon_response), + (unsigned long)NULL, + VM_PKT_DATA_INBAND, 0); + + dm->state = DM_INITIALIZED; +} + +static void balloon_onchannelcallback(void *context); + +static int dm_thread_func(void *dm_dev) +{ + struct hv_dynmem_device *dm = dm_dev; + int t; + unsigned long scan_start; + + while (!kthread_should_stop()) { + t = wait_for_completion_timeout(&dm_device.config_event, 1*HZ); + /* + * The host expects us to post information on the memory + * pressure every second. + */ + + if (t == 0) + post_status(dm); + + scan_start = jiffies; + switch (dm->state) { + case DM_BALLOON_UP: + balloon_up(dm, (struct dm_balloon *)recv_buffer); + break; + + case DM_HOT_ADD: + hot_add_req(dm, (struct dm_hot_add *)recv_buffer); + break; + default: + break; + } + + if (!time_in_range(jiffies, scan_start, scan_start + HZ)) + post_status(dm); + + } + + return 0; +} + + +static void version_resp(struct hv_dynmem_device *dm, + struct dm_version_response *vresp) +{ + struct dm_version_request version_req; + int ret; + + if (vresp->is_accepted) { + /* + * We are done; wakeup the + * context waiting for version + * negotiation. + */ + complete(&dm->host_event); + return; + } + /* + * If there are more versions to try, continue + * with negotiations; if not + * shutdown the service since we are not able + * to negotiate a suitable version number + * with the host. + */ + if (dm->next_version == 0) + goto version_error; + + dm->next_version = 0; + memset(&version_req, 0, sizeof(struct dm_version_request)); + version_req.hdr.type = DM_VERSION_REQUEST; + version_req.hdr.size = sizeof(struct dm_version_request); + version_req.hdr.trans_id = atomic_inc_return(&trans_id); + version_req.version.version = DYNMEM_PROTOCOL_VERSION_WIN7; + version_req.is_last_attempt = 1; + + ret = vmbus_sendpacket(dm->dev->channel, &version_req, + sizeof(struct dm_version_request), + (unsigned long)NULL, + VM_PKT_DATA_INBAND, 0); + + if (ret) + goto version_error; + + return; + +version_error: + dm->state = DM_INIT_ERROR; + complete(&dm->host_event); +} + +static void cap_resp(struct hv_dynmem_device *dm, + struct dm_capabilities_resp_msg *cap_resp) +{ + if (!cap_resp->is_accepted) { + pr_info("Capabilities not accepted by host\n"); + dm->state = DM_INIT_ERROR; + } + complete(&dm->host_event); +} + +static void balloon_onchannelcallback(void *context) +{ + struct hv_device *dev = context; + u32 recvlen; + u64 requestid; + struct dm_message *dm_msg; + struct dm_header *dm_hdr; + struct hv_dynmem_device *dm = hv_get_drvdata(dev); + + memset(recv_buffer, 0, sizeof(recv_buffer)); + vmbus_recvpacket(dev->channel, recv_buffer, + PAGE_SIZE, &recvlen, &requestid); + + if (recvlen > 0) { + dm_msg = (struct dm_message *)recv_buffer; + dm_hdr = &dm_msg->hdr; + + switch (dm_hdr->type) { + case DM_VERSION_RESPONSE: + version_resp(dm, + (struct dm_version_response *)dm_msg); + break; + + case DM_CAPABILITIES_RESPONSE: + cap_resp(dm, + (struct dm_capabilities_resp_msg *)dm_msg); + break; + + case DM_BALLOON_REQUEST: + dm->state = DM_BALLOON_UP; + complete(&dm->config_event); + break; + + case DM_UNBALLOON_REQUEST: + dm->state = DM_BALLOON_DOWN; + balloon_down(dm, + (struct dm_unballoon_request *)recv_buffer); + break; + + case DM_MEM_HOT_ADD_REQUEST: + dm->state = DM_HOT_ADD; + complete(&dm->config_event); + break; + + case DM_INFO_MESSAGE: + process_info(dm, (struct dm_info_msg *)dm_msg); + break; + + default: + pr_err("Unhandled message: type: %d\n", dm_hdr->type); + + } + } + +} + +static int balloon_probe(struct hv_device *dev, + const struct hv_vmbus_device_id *dev_id) +{ + int ret, t; + struct dm_version_request version_req; + struct dm_capabilities cap_msg; + + do_hot_add = hot_add; + + /* + * First allocate a send buffer. + */ + + send_buffer = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!send_buffer) + return -ENOMEM; + + ret = vmbus_open(dev->channel, dm_ring_size, dm_ring_size, NULL, 0, + balloon_onchannelcallback, dev); + + if (ret) + return ret; + + dm_device.dev = dev; + dm_device.state = DM_INITIALIZING; + dm_device.next_version = DYNMEM_PROTOCOL_VERSION_WIN7; + init_completion(&dm_device.host_event); + init_completion(&dm_device.config_event); + + dm_device.thread = + kthread_run(dm_thread_func, &dm_device, "hv_balloon"); + if (IS_ERR(dm_device.thread)) { + ret = PTR_ERR(dm_device.thread); + goto probe_error0; + } + + hv_set_drvdata(dev, &dm_device); + /* + * Initiate the hand shake with the host and negotiate + * a version that the host can support. We start with the + * highest version number and go down if the host cannot + * support it. + */ + memset(&version_req, 0, sizeof(struct dm_version_request)); + version_req.hdr.type = DM_VERSION_REQUEST; + version_req.hdr.size = sizeof(struct dm_version_request); + version_req.hdr.trans_id = atomic_inc_return(&trans_id); + version_req.version.version = DYNMEM_PROTOCOL_VERSION_WIN8; + version_req.is_last_attempt = 0; + + ret = vmbus_sendpacket(dev->channel, &version_req, + sizeof(struct dm_version_request), + (unsigned long)NULL, + VM_PKT_DATA_INBAND, + VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); + if (ret) + goto probe_error1; + + t = wait_for_completion_timeout(&dm_device.host_event, 5*HZ); + if (t == 0) { + ret = -ETIMEDOUT; + goto probe_error1; + } + + /* + * If we could not negotiate a compatible version with the host + * fail the probe function. + */ + if (dm_device.state == DM_INIT_ERROR) { + ret = -ETIMEDOUT; + goto probe_error1; + } + /* + * Now submit our capabilities to the host. + */ + memset(&cap_msg, 0, sizeof(struct dm_capabilities)); + cap_msg.hdr.type = DM_CAPABILITIES_REPORT; + cap_msg.hdr.size = sizeof(struct dm_capabilities); + cap_msg.hdr.trans_id = atomic_inc_return(&trans_id); + + cap_msg.caps.cap_bits.balloon = 1; + /* + * While we currently don't support hot-add, + * we still advertise this capability since the + * host requires that guests partcipating in the + * dynamic memory protocol support hot add. + */ + cap_msg.caps.cap_bits.hot_add = 1; + + /* + * Currently the host does not use these + * values and we set them to what is done in the + * Windows driver. + */ + cap_msg.min_page_cnt = 0; + cap_msg.max_page_number = -1; + + ret = vmbus_sendpacket(dev->channel, &cap_msg, + sizeof(struct dm_capabilities), + (unsigned long)NULL, + VM_PKT_DATA_INBAND, + VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); + if (ret) + goto probe_error1; + + t = wait_for_completion_timeout(&dm_device.host_event, 5*HZ); + if (t == 0) { + ret = -ETIMEDOUT; + goto probe_error1; + } + + /* + * If the host does not like our capabilities, + * fail the probe function. + */ + if (dm_device.state == DM_INIT_ERROR) { + ret = -ETIMEDOUT; + goto probe_error1; + } + + dm_device.state = DM_INITIALIZED; + + return 0; + +probe_error1: + kthread_stop(dm_device.thread); + +probe_error0: + vmbus_close(dev->channel); + return ret; +} + +static int balloon_remove(struct hv_device *dev) +{ + struct hv_dynmem_device *dm = hv_get_drvdata(dev); + + if (dm->num_pages_ballooned != 0) + pr_warn("Ballooned pages: %d\n", dm->num_pages_ballooned); + + vmbus_close(dev->channel); + kthread_stop(dm->thread); + + return 0; +} + +static const struct hv_vmbus_device_id id_table[] = { + /* Dynamic Memory Class ID */ + /* 525074DC-8985-46e2-8057-A307DC18A502 */ + { VMBUS_DEVICE(0xdc, 0x74, 0x50, 0X52, 0x85, 0x89, 0xe2, 0x46, + 0x80, 0x57, 0xa3, 0x07, 0xdc, 0x18, 0xa5, 0x02) + }, + { }, +}; + +MODULE_DEVICE_TABLE(vmbus, id_table); + +static struct hv_driver balloon_drv = { + .name = "hv_balloon", + .id_table = id_table, + .probe = balloon_probe, + .remove = balloon_remove, +}; + +static int __init init_balloon_drv(void) +{ + + return vmbus_driver_register(&balloon_drv); +} + +static void exit_balloon_drv(void) +{ + + vmbus_driver_unregister(&balloon_drv); +} + +module_init(init_balloon_drv); +module_exit(exit_balloon_drv); + +MODULE_DESCRIPTION("Hyper-V Balloon"); +MODULE_VERSION(HV_DRV_VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/memstick/host/Kconfig b/drivers/memstick/host/Kconfig index cc0997a05171..4f7a17fd1aa7 100644 --- a/drivers/memstick/host/Kconfig +++ b/drivers/memstick/host/Kconfig @@ -42,3 +42,13 @@ config MEMSTICK_R592 To compile this driver as a module, choose M here: the module will be called r592. + +config MEMSTICK_REALTEK_PCI + tristate "Realtek PCI-E Memstick Card Interface Driver" + depends on MFD_RTSX_PCI + help + Say Y here to include driver code to support Memstick card interface + of Realtek PCI-E card reader + + To compile this driver as a module, choose M here: the module will + be called rtsx_pci_ms. diff --git a/drivers/memstick/host/Makefile b/drivers/memstick/host/Makefile index 31ba8d378e46..af3459d7686e 100644 --- a/drivers/memstick/host/Makefile +++ b/drivers/memstick/host/Makefile @@ -5,3 +5,4 @@ obj-$(CONFIG_MEMSTICK_TIFM_MS) += tifm_ms.o obj-$(CONFIG_MEMSTICK_JMICRON_38X) += jmb38x_ms.o obj-$(CONFIG_MEMSTICK_R592) += r592.o +obj-$(CONFIG_MEMSTICK_REALTEK_PCI) += rtsx_pci_ms.o diff --git a/drivers/memstick/host/rtsx_pci_ms.c b/drivers/memstick/host/rtsx_pci_ms.c new file mode 100644 index 000000000000..f5ddb82dadb7 --- /dev/null +++ b/drivers/memstick/host/rtsx_pci_ms.c @@ -0,0 +1,641 @@ +/* Realtek PCI-Express Memstick Card Interface driver + * + * Copyright(c) 2009 Realtek Semiconductor Corp. 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 as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Author: + * Wei WANG <wei_wang@realsil.com.cn> + * No. 450, Shenhu Road, Suzhou Industry Park, Suzhou, China + */ + +#include <linux/module.h> +#include <linux/highmem.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/memstick.h> +#include <linux/mfd/rtsx_pci.h> +#include <asm/unaligned.h> + +struct realtek_pci_ms { + struct platform_device *pdev; + struct rtsx_pcr *pcr; + struct memstick_host *msh; + struct memstick_request *req; + + struct mutex host_mutex; + struct work_struct handle_req; + + u8 ssc_depth; + unsigned int clock; + unsigned char ifmode; + bool eject; +}; + +static inline struct device *ms_dev(struct realtek_pci_ms *host) +{ + return &(host->pdev->dev); +} + +static inline void ms_clear_error(struct realtek_pci_ms *host) +{ + rtsx_pci_write_register(host->pcr, CARD_STOP, + MS_STOP | MS_CLR_ERR, MS_STOP | MS_CLR_ERR); +} + +#ifdef DEBUG + +static void ms_print_debug_regs(struct realtek_pci_ms *host) +{ + struct rtsx_pcr *pcr = host->pcr; + u16 i; + u8 *ptr; + + /* Print MS host internal registers */ + rtsx_pci_init_cmd(pcr); + for (i = 0xFD40; i <= 0xFD44; i++) + rtsx_pci_add_cmd(pcr, READ_REG_CMD, i, 0, 0); + for (i = 0xFD52; i <= 0xFD69; i++) + rtsx_pci_add_cmd(pcr, READ_REG_CMD, i, 0, 0); + rtsx_pci_send_cmd(pcr, 100); + + ptr = rtsx_pci_get_cmd_data(pcr); + for (i = 0xFD40; i <= 0xFD44; i++) + dev_dbg(ms_dev(host), "0x%04X: 0x%02x\n", i, *(ptr++)); + for (i = 0xFD52; i <= 0xFD69; i++) + dev_dbg(ms_dev(host), "0x%04X: 0x%02x\n", i, *(ptr++)); +} + +#else + +#define ms_print_debug_regs(host) + +#endif + +static int ms_power_on(struct realtek_pci_ms *host) +{ + struct rtsx_pcr *pcr = host->pcr; + int err; + + rtsx_pci_init_cmd(pcr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_SELECT, 0x07, MS_MOD_SEL); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_SHARE_MODE, + CARD_SHARE_MASK, CARD_SHARE_48_MS); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_CLK_EN, + MS_CLK_EN, MS_CLK_EN); + err = rtsx_pci_send_cmd(pcr, 100); + if (err < 0) + return err; + + err = rtsx_pci_card_pull_ctl_enable(pcr, RTSX_MS_CARD); + if (err < 0) + return err; + + err = rtsx_pci_card_power_on(pcr, RTSX_MS_CARD); + if (err < 0) + return err; + + /* Wait ms power stable */ + msleep(150); + + err = rtsx_pci_write_register(pcr, CARD_OE, + MS_OUTPUT_EN, MS_OUTPUT_EN); + if (err < 0) + return err; + + return 0; +} + +static int ms_power_off(struct realtek_pci_ms *host) +{ + struct rtsx_pcr *pcr = host->pcr; + int err; + + rtsx_pci_init_cmd(pcr); + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_CLK_EN, MS_CLK_EN, 0); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_OE, MS_OUTPUT_EN, 0); + + err = rtsx_pci_send_cmd(pcr, 100); + if (err < 0) + return err; + + err = rtsx_pci_card_power_off(pcr, RTSX_MS_CARD); + if (err < 0) + return err; + + return rtsx_pci_card_pull_ctl_disable(pcr, RTSX_MS_CARD); +} + +static int ms_transfer_data(struct realtek_pci_ms *host, unsigned char data_dir, + u8 tpc, u8 cfg, struct scatterlist *sg) +{ + struct rtsx_pcr *pcr = host->pcr; + int err; + unsigned int length = sg->length; + u16 sec_cnt = (u16)(length / 512); + u8 val, trans_mode, dma_dir; + + dev_dbg(ms_dev(host), "%s: tpc = 0x%02x, data_dir = %s, length = %d\n", + __func__, tpc, (data_dir == READ) ? "READ" : "WRITE", + length); + + if (data_dir == READ) { + dma_dir = DMA_DIR_FROM_CARD; + trans_mode = MS_TM_AUTO_READ; + } else { + dma_dir = DMA_DIR_TO_CARD; + trans_mode = MS_TM_AUTO_WRITE; + } + + rtsx_pci_init_cmd(pcr); + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_TPC, 0xFF, tpc); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_SECTOR_CNT_H, + 0xFF, (u8)(sec_cnt >> 8)); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_SECTOR_CNT_L, + 0xFF, (u8)sec_cnt); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_TRANS_CFG, 0xFF, cfg); + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, IRQSTAT0, + DMA_DONE_INT, DMA_DONE_INT); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, DMATC3, 0xFF, (u8)(length >> 24)); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, DMATC2, 0xFF, (u8)(length >> 16)); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, DMATC1, 0xFF, (u8)(length >> 8)); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, DMATC0, 0xFF, (u8)length); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, DMACTL, + 0x03 | DMA_PACK_SIZE_MASK, dma_dir | DMA_EN | DMA_512); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_DATA_SOURCE, + 0x01, RING_BUFFER); + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_TRANSFER, + 0xFF, MS_TRANSFER_START | trans_mode); + rtsx_pci_add_cmd(pcr, CHECK_REG_CMD, MS_TRANSFER, + MS_TRANSFER_END, MS_TRANSFER_END); + + rtsx_pci_send_cmd_no_wait(pcr); + + err = rtsx_pci_transfer_data(pcr, sg, 1, data_dir == READ, 10000); + if (err < 0) { + ms_clear_error(host); + return err; + } + + rtsx_pci_read_register(pcr, MS_TRANS_CFG, &val); + if (val & (MS_INT_CMDNK | MS_INT_ERR | MS_CRC16_ERR | MS_RDY_TIMEOUT)) + return -EIO; + + return 0; +} + +static int ms_write_bytes(struct realtek_pci_ms *host, u8 tpc, + u8 cfg, u8 cnt, u8 *data, u8 *int_reg) +{ + struct rtsx_pcr *pcr = host->pcr; + int err, i; + + dev_dbg(ms_dev(host), "%s: tpc = 0x%02x\n", __func__, tpc); + + if (!data) + return -EINVAL; + + rtsx_pci_init_cmd(pcr); + + for (i = 0; i < cnt; i++) + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, + PPBUF_BASE2 + i, 0xFF, data[i]); + if (cnt % 2) + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, + PPBUF_BASE2 + i, 0xFF, 0xFF); + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_TPC, 0xFF, tpc); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_BYTE_CNT, 0xFF, cnt); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_TRANS_CFG, 0xFF, cfg); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_DATA_SOURCE, + 0x01, PINGPONG_BUFFER); + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_TRANSFER, + 0xFF, MS_TRANSFER_START | MS_TM_WRITE_BYTES); + rtsx_pci_add_cmd(pcr, CHECK_REG_CMD, MS_TRANSFER, + MS_TRANSFER_END, MS_TRANSFER_END); + if (int_reg) + rtsx_pci_add_cmd(pcr, READ_REG_CMD, MS_TRANS_CFG, 0, 0); + + err = rtsx_pci_send_cmd(pcr, 5000); + if (err < 0) { + u8 val; + + rtsx_pci_read_register(pcr, MS_TRANS_CFG, &val); + dev_dbg(ms_dev(host), "MS_TRANS_CFG: 0x%02x\n", val); + + if (int_reg) + *int_reg = val & 0x0F; + + ms_print_debug_regs(host); + + ms_clear_error(host); + + if (!(tpc & 0x08)) { + if (val & MS_CRC16_ERR) + return -EIO; + } else { + if (!(val & 0x80)) { + if (val & (MS_INT_ERR | MS_INT_CMDNK)) + return -EIO; + } + } + + return -ETIMEDOUT; + } + + if (int_reg) { + u8 *ptr = rtsx_pci_get_cmd_data(pcr) + 1; + *int_reg = *ptr & 0x0F; + } + + return 0; +} + +static int ms_read_bytes(struct realtek_pci_ms *host, u8 tpc, + u8 cfg, u8 cnt, u8 *data, u8 *int_reg) +{ + struct rtsx_pcr *pcr = host->pcr; + int err, i; + u8 *ptr; + + dev_dbg(ms_dev(host), "%s: tpc = 0x%02x\n", __func__, tpc); + + if (!data) + return -EINVAL; + + rtsx_pci_init_cmd(pcr); + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_TPC, 0xFF, tpc); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_BYTE_CNT, 0xFF, cnt); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_TRANS_CFG, 0xFF, cfg); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_DATA_SOURCE, + 0x01, PINGPONG_BUFFER); + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, MS_TRANSFER, + 0xFF, MS_TRANSFER_START | MS_TM_READ_BYTES); + rtsx_pci_add_cmd(pcr, CHECK_REG_CMD, MS_TRANSFER, + MS_TRANSFER_END, MS_TRANSFER_END); + for (i = 0; i < cnt - 1; i++) + rtsx_pci_add_cmd(pcr, READ_REG_CMD, PPBUF_BASE2 + i, 0, 0); + if (cnt % 2) + rtsx_pci_add_cmd(pcr, READ_REG_CMD, PPBUF_BASE2 + cnt, 0, 0); + else + rtsx_pci_add_cmd(pcr, READ_REG_CMD, + PPBUF_BASE2 + cnt - 1, 0, 0); + if (int_reg) + rtsx_pci_add_cmd(pcr, READ_REG_CMD, MS_TRANS_CFG, 0, 0); + + err = rtsx_pci_send_cmd(pcr, 5000); + if (err < 0) { + u8 val; + + rtsx_pci_read_register(pcr, MS_TRANS_CFG, &val); + dev_dbg(ms_dev(host), "MS_TRANS_CFG: 0x%02x\n", val); + + if (int_reg) + *int_reg = val & 0x0F; + + ms_print_debug_regs(host); + + ms_clear_error(host); + + if (!(tpc & 0x08)) { + if (val & MS_CRC16_ERR) + return -EIO; + } else { + if (!(val & 0x80)) { + if (val & (MS_INT_ERR | MS_INT_CMDNK)) + return -EIO; + } + } + + return -ETIMEDOUT; + } + + ptr = rtsx_pci_get_cmd_data(pcr) + 1; + for (i = 0; i < cnt; i++) + data[i] = *ptr++; + + if (int_reg) + *int_reg = *ptr & 0x0F; + + return 0; +} + +static int rtsx_pci_ms_issue_cmd(struct realtek_pci_ms *host) +{ + struct memstick_request *req = host->req; + int err = 0; + u8 cfg = 0, int_reg; + + dev_dbg(ms_dev(host), "%s\n", __func__); + + if (req->need_card_int) { + if (host->ifmode != MEMSTICK_SERIAL) + cfg = WAIT_INT; + } + + if (req->long_data) { + err = ms_transfer_data(host, req->data_dir, + req->tpc, cfg, &(req->sg)); + } else { + if (req->data_dir == READ) { + err = ms_read_bytes(host, req->tpc, cfg, + req->data_len, req->data, &int_reg); + } else { + err = ms_write_bytes(host, req->tpc, cfg, + req->data_len, req->data, &int_reg); + } + } + if (err < 0) + return err; + + if (req->need_card_int && (host->ifmode == MEMSTICK_SERIAL)) { + err = ms_read_bytes(host, MS_TPC_GET_INT, + NO_WAIT_INT, 1, &int_reg, NULL); + if (err < 0) + return err; + } + + if (req->need_card_int) { + dev_dbg(ms_dev(host), "int_reg: 0x%02x\n", int_reg); + + if (int_reg & MS_INT_CMDNK) + req->int_reg |= MEMSTICK_INT_CMDNAK; + if (int_reg & MS_INT_BREQ) + req->int_reg |= MEMSTICK_INT_BREQ; + if (int_reg & MS_INT_ERR) + req->int_reg |= MEMSTICK_INT_ERR; + if (int_reg & MS_INT_CED) + req->int_reg |= MEMSTICK_INT_CED; + } + + return 0; +} + +static void rtsx_pci_ms_handle_req(struct work_struct *work) +{ + struct realtek_pci_ms *host = container_of(work, + struct realtek_pci_ms, handle_req); + struct rtsx_pcr *pcr = host->pcr; + struct memstick_host *msh = host->msh; + int rc; + + mutex_lock(&pcr->pcr_mutex); + + rtsx_pci_start_run(pcr); + + rtsx_pci_switch_clock(host->pcr, host->clock, host->ssc_depth, + false, true, false); + rtsx_pci_write_register(pcr, CARD_SELECT, 0x07, MS_MOD_SEL); + rtsx_pci_write_register(pcr, CARD_SHARE_MODE, + CARD_SHARE_MASK, CARD_SHARE_48_MS); + + if (!host->req) { + do { + rc = memstick_next_req(msh, &host->req); + dev_dbg(ms_dev(host), "next req %d\n", rc); + + if (!rc) + host->req->error = rtsx_pci_ms_issue_cmd(host); + } while (!rc); + } + + mutex_unlock(&pcr->pcr_mutex); +} + +static void rtsx_pci_ms_request(struct memstick_host *msh) +{ + struct realtek_pci_ms *host = memstick_priv(msh); + + dev_dbg(ms_dev(host), "--> %s\n", __func__); + + schedule_work(&host->handle_req); +} + +static int rtsx_pci_ms_set_param(struct memstick_host *msh, + enum memstick_param param, int value) +{ + struct realtek_pci_ms *host = memstick_priv(msh); + struct rtsx_pcr *pcr = host->pcr; + unsigned int clock = 0; + u8 ssc_depth = 0; + int err; + + dev_dbg(ms_dev(host), "%s: param = %d, value = %d\n", + __func__, param, value); + + switch (param) { + case MEMSTICK_POWER: + if (value == MEMSTICK_POWER_ON) + err = ms_power_on(host); + else if (value == MEMSTICK_POWER_OFF) + err = ms_power_off(host); + else + return -EINVAL; + break; + + case MEMSTICK_INTERFACE: + if (value == MEMSTICK_SERIAL) { + clock = 19000000; + ssc_depth = RTSX_SSC_DEPTH_500K; + + err = rtsx_pci_write_register(pcr, MS_CFG, + 0x18, MS_BUS_WIDTH_1); + if (err < 0) + return err; + } else if (value == MEMSTICK_PAR4) { + clock = 39000000; + ssc_depth = RTSX_SSC_DEPTH_1M; + + err = rtsx_pci_write_register(pcr, MS_CFG, + 0x58, MS_BUS_WIDTH_4 | PUSH_TIME_ODD); + if (err < 0) + return err; + } else { + return -EINVAL; + } + + err = rtsx_pci_switch_clock(pcr, clock, + ssc_depth, false, true, false); + if (err < 0) + return err; + + host->ssc_depth = ssc_depth; + host->clock = clock; + host->ifmode = value; + break; + } + + return 0; +} + +#ifdef CONFIG_PM + +static int rtsx_pci_ms_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct realtek_pci_ms *host = platform_get_drvdata(pdev); + struct memstick_host *msh = host->msh; + + dev_dbg(ms_dev(host), "--> %s\n", __func__); + + memstick_suspend_host(msh); + return 0; +} + +static int rtsx_pci_ms_resume(struct platform_device *pdev) +{ + struct realtek_pci_ms *host = platform_get_drvdata(pdev); + struct memstick_host *msh = host->msh; + + dev_dbg(ms_dev(host), "--> %s\n", __func__); + + memstick_resume_host(msh); + return 0; +} + +#else /* CONFIG_PM */ + +#define rtsx_pci_ms_suspend NULL +#define rtsx_pci_ms_resume NULL + +#endif /* CONFIG_PM */ + +static void rtsx_pci_ms_card_event(struct platform_device *pdev) +{ + struct realtek_pci_ms *host = platform_get_drvdata(pdev); + + memstick_detect_change(host->msh); +} + +static int rtsx_pci_ms_drv_probe(struct platform_device *pdev) +{ + struct memstick_host *msh; + struct realtek_pci_ms *host; + struct rtsx_pcr *pcr; + struct pcr_handle *handle = pdev->dev.platform_data; + int rc; + + if (!handle) + return -ENXIO; + + pcr = handle->pcr; + if (!pcr) + return -ENXIO; + + dev_dbg(&(pdev->dev), + ": Realtek PCI-E Memstick controller found\n"); + + msh = memstick_alloc_host(sizeof(*host), &pdev->dev); + if (!msh) + return -ENOMEM; + + host = memstick_priv(msh); + host->pcr = pcr; + host->msh = msh; + host->pdev = pdev; + platform_set_drvdata(pdev, host); + pcr->slots[RTSX_MS_CARD].p_dev = pdev; + pcr->slots[RTSX_MS_CARD].card_event = rtsx_pci_ms_card_event; + + mutex_init(&host->host_mutex); + + INIT_WORK(&host->handle_req, rtsx_pci_ms_handle_req); + msh->request = rtsx_pci_ms_request; + msh->set_param = rtsx_pci_ms_set_param; + msh->caps = MEMSTICK_CAP_PAR4; + + rc = memstick_add_host(msh); + if (rc) { + memstick_free_host(msh); + return rc; + } + + return 0; +} + +static int rtsx_pci_ms_drv_remove(struct platform_device *pdev) +{ + struct realtek_pci_ms *host = platform_get_drvdata(pdev); + struct rtsx_pcr *pcr; + struct memstick_host *msh; + int rc; + + if (!host) + return 0; + + pcr = host->pcr; + pcr->slots[RTSX_MS_CARD].p_dev = NULL; + pcr->slots[RTSX_MS_CARD].card_event = NULL; + msh = host->msh; + host->eject = true; + + mutex_lock(&host->host_mutex); + if (host->req) { + dev_dbg(&(pdev->dev), + "%s: Controller removed during transfer\n", + dev_name(&msh->dev)); + + rtsx_pci_complete_unfinished_transfer(pcr); + + host->req->error = -ENOMEDIUM; + do { + rc = memstick_next_req(msh, &host->req); + if (!rc) + host->req->error = -ENOMEDIUM; + } while (!rc); + } + mutex_unlock(&host->host_mutex); + + memstick_remove_host(msh); + memstick_free_host(msh); + + platform_set_drvdata(pdev, NULL); + + dev_dbg(&(pdev->dev), + ": Realtek PCI-E Memstick controller has been removed\n"); + + return 0; +} + +static struct platform_device_id rtsx_pci_ms_ids[] = { + { + .name = DRV_NAME_RTSX_PCI_MS, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(platform, rtsx_pci_ms_ids); + +static struct platform_driver rtsx_pci_ms_driver = { + .probe = rtsx_pci_ms_drv_probe, + .remove = rtsx_pci_ms_drv_remove, + .id_table = rtsx_pci_ms_ids, + .suspend = rtsx_pci_ms_suspend, + .resume = rtsx_pci_ms_resume, + .driver = { + .owner = THIS_MODULE, + .name = DRV_NAME_RTSX_PCI_MS, + }, +}; +module_platform_driver(rtsx_pci_ms_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Wei WANG <wei_wang@realsil.com.cn>"); +MODULE_DESCRIPTION("Realtek PCI-E Memstick Card Host Driver"); diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index acab3ef8a310..2c10938b3567 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -63,6 +63,16 @@ config MFD_SM501_GPIO lines on the SM501. The platform data is used to supply the base number for the first GPIO line to register. +config MFD_RTSX_PCI + tristate "Support for Realtek PCI-E card reader" + depends on PCI + select MFD_CORE + help + This supports for Realtek PCI-Express card reader including rts5209, + rts5229, rtl8411, etc. Realtek card reader supports access to many + types of memory cards, such as Memory Stick, Memory Stick Pro, + Secure Digital and MultiMediaCard. + config MFD_ASIC3 bool "Support for Compaq ASIC3" depends on GENERIC_HARDIRQS && GPIOLIB && ARM diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index d8ccb630ddb0..b53db06d1b46 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -9,6 +9,9 @@ obj-$(CONFIG_MFD_88PM805) += 88pm805.o 88pm80x.o obj-$(CONFIG_MFD_SM501) += sm501.o obj-$(CONFIG_MFD_ASIC3) += asic3.o tmio_core.o +rtsx_pci-objs := rtsx_pcr.o rts5209.o rts5229.o rtl8411.o +obj-$(CONFIG_MFD_RTSX_PCI) += rtsx_pci.o + obj-$(CONFIG_HTC_EGPIO) += htc-egpio.o obj-$(CONFIG_HTC_PASIC3) += htc-pasic3.o obj-$(CONFIG_HTC_I2CPLD) += htc-i2cpld.o diff --git a/drivers/mfd/rtl8411.c b/drivers/mfd/rtl8411.c new file mode 100644 index 000000000000..89f046ca9e41 --- /dev/null +++ b/drivers/mfd/rtl8411.c @@ -0,0 +1,251 @@ +/* Driver for Realtek PCI-Express card reader + * + * Copyright(c) 2009 Realtek Semiconductor Corp. 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 as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Author: + * Wei WANG <wei_wang@realsil.com.cn> + * No. 450, Shenhu Road, Suzhou Industry Park, Suzhou, China + */ + +#include <linux/module.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/mfd/rtsx_pci.h> + +#include "rtsx_pcr.h" + +static u8 rtl8411_get_ic_version(struct rtsx_pcr *pcr) +{ + u8 val; + + rtsx_pci_read_register(pcr, SYS_VER, &val); + return val & 0x0F; +} + +static int rtl8411_extra_init_hw(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, CD_PAD_CTL, + CD_DISABLE_MASK | CD_AUTO_DISABLE, CD_ENABLE); +} + +static int rtl8411_turn_on_led(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, CARD_GPIO, 0x01, 0x00); +} + +static int rtl8411_turn_off_led(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, CARD_GPIO, 0x01, 0x01); +} + +static int rtl8411_enable_auto_blink(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, CARD_AUTO_BLINK, 0xFF, 0x0D); +} + +static int rtl8411_disable_auto_blink(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, CARD_AUTO_BLINK, 0x08, 0x00); +} + +static int rtl8411_card_power_on(struct rtsx_pcr *pcr, int card) +{ + int err; + + rtsx_pci_init_cmd(pcr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_PWR_CTL, + BPP_POWER_MASK, BPP_POWER_5_PERCENT_ON); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, LDO_CTL, + BPP_LDO_POWB, BPP_LDO_SUSPEND); + err = rtsx_pci_send_cmd(pcr, 100); + if (err < 0) + return err; + + /* To avoid too large in-rush current */ + udelay(150); + + err = rtsx_pci_write_register(pcr, CARD_PWR_CTL, + BPP_POWER_MASK, BPP_POWER_10_PERCENT_ON); + if (err < 0) + return err; + + udelay(150); + + err = rtsx_pci_write_register(pcr, CARD_PWR_CTL, + BPP_POWER_MASK, BPP_POWER_15_PERCENT_ON); + if (err < 0) + return err; + + udelay(150); + + err = rtsx_pci_write_register(pcr, CARD_PWR_CTL, + BPP_POWER_MASK, BPP_POWER_ON); + if (err < 0) + return err; + + return rtsx_pci_write_register(pcr, LDO_CTL, BPP_LDO_POWB, BPP_LDO_ON); +} + +static int rtl8411_card_power_off(struct rtsx_pcr *pcr, int card) +{ + int err; + + err = rtsx_pci_write_register(pcr, CARD_PWR_CTL, + BPP_POWER_MASK, BPP_POWER_OFF); + if (err < 0) + return err; + + return rtsx_pci_write_register(pcr, LDO_CTL, + BPP_LDO_POWB, BPP_LDO_SUSPEND); +} + +static unsigned int rtl8411_cd_deglitch(struct rtsx_pcr *pcr) +{ + unsigned int card_exist; + + card_exist = rtsx_pci_readl(pcr, RTSX_BIPR); + card_exist &= CARD_EXIST; + if (!card_exist) { + /* Enable card CD */ + rtsx_pci_write_register(pcr, CD_PAD_CTL, + CD_DISABLE_MASK, CD_ENABLE); + /* Enable card interrupt */ + rtsx_pci_write_register(pcr, EFUSE_CONTENT, 0xe0, 0x00); + return 0; + } + + if (hweight32(card_exist) > 1) { + rtsx_pci_write_register(pcr, CARD_PWR_CTL, + BPP_POWER_MASK, BPP_POWER_5_PERCENT_ON); + msleep(100); + + card_exist = rtsx_pci_readl(pcr, RTSX_BIPR); + if (card_exist & MS_EXIST) + card_exist = MS_EXIST; + else if (card_exist & SD_EXIST) + card_exist = SD_EXIST; + else + card_exist = 0; + + rtsx_pci_write_register(pcr, CARD_PWR_CTL, + BPP_POWER_MASK, BPP_POWER_OFF); + + dev_dbg(&(pcr->pci->dev), + "After CD deglitch, card_exist = 0x%x\n", + card_exist); + } + + if (card_exist & MS_EXIST) { + /* Disable SD interrupt */ + rtsx_pci_write_register(pcr, EFUSE_CONTENT, 0xe0, 0x40); + rtsx_pci_write_register(pcr, CD_PAD_CTL, + CD_DISABLE_MASK, MS_CD_EN_ONLY); + } else if (card_exist & SD_EXIST) { + /* Disable MS interrupt */ + rtsx_pci_write_register(pcr, EFUSE_CONTENT, 0xe0, 0x80); + rtsx_pci_write_register(pcr, CD_PAD_CTL, + CD_DISABLE_MASK, SD_CD_EN_ONLY); + } + + return card_exist; +} + +static const struct pcr_ops rtl8411_pcr_ops = { + .extra_init_hw = rtl8411_extra_init_hw, + .optimize_phy = NULL, + .turn_on_led = rtl8411_turn_on_led, + .turn_off_led = rtl8411_turn_off_led, + .enable_auto_blink = rtl8411_enable_auto_blink, + .disable_auto_blink = rtl8411_disable_auto_blink, + .card_power_on = rtl8411_card_power_on, + .card_power_off = rtl8411_card_power_off, + .cd_deglitch = rtl8411_cd_deglitch, +}; + +/* SD Pull Control Enable: + * SD_DAT[3:0] ==> pull up + * SD_CD ==> pull up + * SD_WP ==> pull up + * SD_CMD ==> pull up + * SD_CLK ==> pull down + */ +static const u32 rtl8411_sd_pull_ctl_enable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL1, 0xAA), + RTSX_REG_PAIR(CARD_PULL_CTL2, 0xAA), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0xA9), + RTSX_REG_PAIR(CARD_PULL_CTL4, 0x09), + RTSX_REG_PAIR(CARD_PULL_CTL5, 0x09), + RTSX_REG_PAIR(CARD_PULL_CTL6, 0x04), + 0, +}; + +/* SD Pull Control Disable: + * SD_DAT[3:0] ==> pull down + * SD_CD ==> pull up + * SD_WP ==> pull down + * SD_CMD ==> pull down + * SD_CLK ==> pull down + */ +static const u32 rtl8411_sd_pull_ctl_disable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL1, 0x65), + RTSX_REG_PAIR(CARD_PULL_CTL2, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0x95), + RTSX_REG_PAIR(CARD_PULL_CTL4, 0x09), + RTSX_REG_PAIR(CARD_PULL_CTL5, 0x05), + RTSX_REG_PAIR(CARD_PULL_CTL6, 0x04), + 0, +}; + +/* MS Pull Control Enable: + * MS CD ==> pull up + * others ==> pull down + */ +static const u32 rtl8411_ms_pull_ctl_enable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL1, 0x65), + RTSX_REG_PAIR(CARD_PULL_CTL2, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0x95), + RTSX_REG_PAIR(CARD_PULL_CTL4, 0x05), + RTSX_REG_PAIR(CARD_PULL_CTL5, 0x05), + RTSX_REG_PAIR(CARD_PULL_CTL6, 0x04), + 0, +}; + +/* MS Pull Control Disable: + * MS CD ==> pull up + * others ==> pull down + */ +static const u32 rtl8411_ms_pull_ctl_disable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL1, 0x65), + RTSX_REG_PAIR(CARD_PULL_CTL2, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0x95), + RTSX_REG_PAIR(CARD_PULL_CTL4, 0x09), + RTSX_REG_PAIR(CARD_PULL_CTL5, 0x05), + RTSX_REG_PAIR(CARD_PULL_CTL6, 0x04), + 0, +}; + +void rtl8411_init_params(struct rtsx_pcr *pcr) +{ + pcr->extra_caps = EXTRA_CAPS_SD_SDR50 | EXTRA_CAPS_SD_SDR104; + pcr->num_slots = 2; + pcr->ops = &rtl8411_pcr_ops; + + pcr->ic_version = rtl8411_get_ic_version(pcr); + pcr->sd_pull_ctl_enable_tbl = rtl8411_sd_pull_ctl_enable_tbl; + pcr->sd_pull_ctl_disable_tbl = rtl8411_sd_pull_ctl_disable_tbl; + pcr->ms_pull_ctl_enable_tbl = rtl8411_ms_pull_ctl_enable_tbl; + pcr->ms_pull_ctl_disable_tbl = rtl8411_ms_pull_ctl_disable_tbl; +} diff --git a/drivers/mfd/rts5209.c b/drivers/mfd/rts5209.c new file mode 100644 index 000000000000..283a4f148084 --- /dev/null +++ b/drivers/mfd/rts5209.c @@ -0,0 +1,223 @@ +/* Driver for Realtek PCI-Express card reader + * + * Copyright(c) 2009 Realtek Semiconductor Corp. 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 as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Author: + * Wei WANG <wei_wang@realsil.com.cn> + * No. 450, Shenhu Road, Suzhou Industry Park, Suzhou, China + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/mfd/rtsx_pci.h> + +#include "rtsx_pcr.h" + +static u8 rts5209_get_ic_version(struct rtsx_pcr *pcr) +{ + u8 val; + + val = rtsx_pci_readb(pcr, 0x1C); + return val & 0x0F; +} + +static void rts5209_init_vendor_cfg(struct rtsx_pcr *pcr) +{ + u32 val; + + rtsx_pci_read_config_dword(pcr, 0x724, &val); + dev_dbg(&(pcr->pci->dev), "Cfg 0x724: 0x%x\n", val); + + if (!(val & 0x80)) { + if (val & 0x08) + pcr->ms_pmos = false; + else + pcr->ms_pmos = true; + } +} + +static int rts5209_extra_init_hw(struct rtsx_pcr *pcr) +{ + rtsx_pci_init_cmd(pcr); + + /* Turn off LED */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_GPIO, 0xFF, 0x03); + /* Configure GPIO as output */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_GPIO_DIR, 0xFF, 0x03); + + return rtsx_pci_send_cmd(pcr, 100); +} + +static int rts5209_optimize_phy(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_phy_register(pcr, 0x00, 0xB966); +} + +static int rts5209_turn_on_led(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, CARD_GPIO, 0x01, 0x00); +} + +static int rts5209_turn_off_led(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, CARD_GPIO, 0x01, 0x01); +} + +static int rts5209_enable_auto_blink(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, CARD_AUTO_BLINK, 0xFF, 0x0D); +} + +static int rts5209_disable_auto_blink(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, CARD_AUTO_BLINK, 0x08, 0x00); +} + +static int rts5209_card_power_on(struct rtsx_pcr *pcr, int card) +{ + int err; + u8 pwr_mask, partial_pwr_on, pwr_on; + + pwr_mask = SD_POWER_MASK; + partial_pwr_on = SD_PARTIAL_POWER_ON; + pwr_on = SD_POWER_ON; + + if (pcr->ms_pmos && (card == RTSX_MS_CARD)) { + pwr_mask = MS_POWER_MASK; + partial_pwr_on = MS_PARTIAL_POWER_ON; + pwr_on = MS_POWER_ON; + } + + rtsx_pci_init_cmd(pcr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_PWR_CTL, + pwr_mask, partial_pwr_on); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PWR_GATE_CTRL, + LDO3318_PWR_MASK, 0x04); + err = rtsx_pci_send_cmd(pcr, 100); + if (err < 0) + return err; + + /* To avoid too large in-rush current */ + udelay(150); + + rtsx_pci_init_cmd(pcr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_PWR_CTL, pwr_mask, pwr_on); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PWR_GATE_CTRL, + LDO3318_PWR_MASK, 0x00); + err = rtsx_pci_send_cmd(pcr, 100); + if (err < 0) + return err; + + return 0; +} + +static int rts5209_card_power_off(struct rtsx_pcr *pcr, int card) +{ + u8 pwr_mask, pwr_off; + + pwr_mask = SD_POWER_MASK; + pwr_off = SD_POWER_OFF; + + if (pcr->ms_pmos && (card == RTSX_MS_CARD)) { + pwr_mask = MS_POWER_MASK; + pwr_off = MS_POWER_OFF; + } + + rtsx_pci_init_cmd(pcr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_PWR_CTL, + pwr_mask | PMOS_STRG_MASK, pwr_off | PMOS_STRG_400mA); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PWR_GATE_CTRL, + LDO3318_PWR_MASK, 0X06); + return rtsx_pci_send_cmd(pcr, 100); +} + +static const struct pcr_ops rts5209_pcr_ops = { + .extra_init_hw = rts5209_extra_init_hw, + .optimize_phy = rts5209_optimize_phy, + .turn_on_led = rts5209_turn_on_led, + .turn_off_led = rts5209_turn_off_led, + .enable_auto_blink = rts5209_enable_auto_blink, + .disable_auto_blink = rts5209_disable_auto_blink, + .card_power_on = rts5209_card_power_on, + .card_power_off = rts5209_card_power_off, + .cd_deglitch = NULL, +}; + +/* SD Pull Control Enable: + * SD_DAT[3:0] ==> pull up + * SD_CD ==> pull up + * SD_WP ==> pull up + * SD_CMD ==> pull up + * SD_CLK ==> pull down + */ +static const u32 rts5209_sd_pull_ctl_enable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL1, 0xAA), + RTSX_REG_PAIR(CARD_PULL_CTL2, 0xAA), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0xE9), + 0, +}; + +/* SD Pull Control Disable: + * SD_DAT[3:0] ==> pull down + * SD_CD ==> pull up + * SD_WP ==> pull down + * SD_CMD ==> pull down + * SD_CLK ==> pull down + */ +static const u32 rts5209_sd_pull_ctl_disable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL1, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL2, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0xD5), + 0, +}; + +/* MS Pull Control Enable: + * MS CD ==> pull up + * others ==> pull down + */ +static const u32 rts5209_ms_pull_ctl_enable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL4, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL5, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL6, 0x15), + 0, +}; + +/* MS Pull Control Disable: + * MS CD ==> pull up + * others ==> pull down + */ +static const u32 rts5209_ms_pull_ctl_disable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL4, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL5, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL6, 0x15), + 0, +}; + +void rts5209_init_params(struct rtsx_pcr *pcr) +{ + pcr->extra_caps = EXTRA_CAPS_SD_SDR50 | + EXTRA_CAPS_SD_SDR104 | EXTRA_CAPS_MMC_8BIT; + pcr->num_slots = 2; + pcr->ops = &rts5209_pcr_ops; + + rts5209_init_vendor_cfg(pcr); + + pcr->ic_version = rts5209_get_ic_version(pcr); + pcr->sd_pull_ctl_enable_tbl = rts5209_sd_pull_ctl_enable_tbl; + pcr->sd_pull_ctl_disable_tbl = rts5209_sd_pull_ctl_disable_tbl; + pcr->ms_pull_ctl_enable_tbl = rts5209_ms_pull_ctl_enable_tbl; + pcr->ms_pull_ctl_disable_tbl = rts5209_ms_pull_ctl_disable_tbl; +} diff --git a/drivers/mfd/rts5229.c b/drivers/mfd/rts5229.c new file mode 100644 index 000000000000..b9dbab266fda --- /dev/null +++ b/drivers/mfd/rts5229.c @@ -0,0 +1,205 @@ +/* Driver for Realtek PCI-Express card reader + * + * Copyright(c) 2009 Realtek Semiconductor Corp. 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 as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Author: + * Wei WANG <wei_wang@realsil.com.cn> + * No. 450, Shenhu Road, Suzhou Industry Park, Suzhou, China + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/mfd/rtsx_pci.h> + +#include "rtsx_pcr.h" + +static u8 rts5229_get_ic_version(struct rtsx_pcr *pcr) +{ + u8 val; + + rtsx_pci_read_register(pcr, DUMMY_REG_RESET_0, &val); + return val & 0x0F; +} + +static int rts5229_extra_init_hw(struct rtsx_pcr *pcr) +{ + rtsx_pci_init_cmd(pcr); + + /* Configure GPIO as output */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, GPIO_CTL, 0x02, 0x02); + /* Switch LDO3318 source from DV33 to card_3v3 */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, LDO_PWR_SEL, 0x03, 0x00); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, LDO_PWR_SEL, 0x03, 0x01); + /* LED shine disabled, set initial shine cycle period */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, OLT_LED_CTL, 0x0F, 0x02); + + return rtsx_pci_send_cmd(pcr, 100); +} + +static int rts5229_optimize_phy(struct rtsx_pcr *pcr) +{ + /* Optimize RX sensitivity */ + return rtsx_pci_write_phy_register(pcr, 0x00, 0xBA42); +} + +static int rts5229_turn_on_led(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, GPIO_CTL, 0x02, 0x02); +} + +static int rts5229_turn_off_led(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, GPIO_CTL, 0x02, 0x00); +} + +static int rts5229_enable_auto_blink(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, OLT_LED_CTL, 0x08, 0x08); +} + +static int rts5229_disable_auto_blink(struct rtsx_pcr *pcr) +{ + return rtsx_pci_write_register(pcr, OLT_LED_CTL, 0x08, 0x00); +} + +static int rts5229_card_power_on(struct rtsx_pcr *pcr, int card) +{ + int err; + + rtsx_pci_init_cmd(pcr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_PWR_CTL, + SD_POWER_MASK, SD_PARTIAL_POWER_ON); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PWR_GATE_CTRL, + LDO3318_PWR_MASK, 0x02); + err = rtsx_pci_send_cmd(pcr, 100); + if (err < 0) + return err; + + /* To avoid too large in-rush current */ + udelay(150); + + rtsx_pci_init_cmd(pcr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_PWR_CTL, + SD_POWER_MASK, SD_POWER_ON); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PWR_GATE_CTRL, + LDO3318_PWR_MASK, 0x06); + err = rtsx_pci_send_cmd(pcr, 100); + if (err < 0) + return err; + + return 0; +} + +static int rts5229_card_power_off(struct rtsx_pcr *pcr, int card) +{ + rtsx_pci_init_cmd(pcr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_PWR_CTL, + SD_POWER_MASK | PMOS_STRG_MASK, + SD_POWER_OFF | PMOS_STRG_400mA); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PWR_GATE_CTRL, + LDO3318_PWR_MASK, 0X00); + return rtsx_pci_send_cmd(pcr, 100); +} + +static const struct pcr_ops rts5229_pcr_ops = { + .extra_init_hw = rts5229_extra_init_hw, + .optimize_phy = rts5229_optimize_phy, + .turn_on_led = rts5229_turn_on_led, + .turn_off_led = rts5229_turn_off_led, + .enable_auto_blink = rts5229_enable_auto_blink, + .disable_auto_blink = rts5229_disable_auto_blink, + .card_power_on = rts5229_card_power_on, + .card_power_off = rts5229_card_power_off, + .cd_deglitch = NULL, +}; + +/* SD Pull Control Enable: + * SD_DAT[3:0] ==> pull up + * SD_CD ==> pull up + * SD_WP ==> pull up + * SD_CMD ==> pull up + * SD_CLK ==> pull down + */ +static const u32 rts5229_sd_pull_ctl_enable_tbl1[] = { + RTSX_REG_PAIR(CARD_PULL_CTL2, 0xAA), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0xE9), + 0, +}; + +/* For RTS5229 version C */ +static const u32 rts5229_sd_pull_ctl_enable_tbl2[] = { + RTSX_REG_PAIR(CARD_PULL_CTL2, 0xAA), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0xD9), + 0, +}; + +/* SD Pull Control Disable: + * SD_DAT[3:0] ==> pull down + * SD_CD ==> pull up + * SD_WP ==> pull down + * SD_CMD ==> pull down + * SD_CLK ==> pull down + */ +static const u32 rts5229_sd_pull_ctl_disable_tbl1[] = { + RTSX_REG_PAIR(CARD_PULL_CTL2, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0xD5), + 0, +}; + +/* For RTS5229 version C */ +static const u32 rts5229_sd_pull_ctl_disable_tbl2[] = { + RTSX_REG_PAIR(CARD_PULL_CTL2, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL3, 0xE5), + 0, +}; + +/* MS Pull Control Enable: + * MS CD ==> pull up + * others ==> pull down + */ +static const u32 rts5229_ms_pull_ctl_enable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL5, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL6, 0x15), + 0, +}; + +/* MS Pull Control Disable: + * MS CD ==> pull up + * others ==> pull down + */ +static const u32 rts5229_ms_pull_ctl_disable_tbl[] = { + RTSX_REG_PAIR(CARD_PULL_CTL5, 0x55), + RTSX_REG_PAIR(CARD_PULL_CTL6, 0x15), + 0, +}; + +void rts5229_init_params(struct rtsx_pcr *pcr) +{ + pcr->extra_caps = EXTRA_CAPS_SD_SDR50 | EXTRA_CAPS_SD_SDR104; + pcr->num_slots = 2; + pcr->ops = &rts5229_pcr_ops; + + pcr->ic_version = rts5229_get_ic_version(pcr); + if (pcr->ic_version == IC_VER_C) { + pcr->sd_pull_ctl_enable_tbl = rts5229_sd_pull_ctl_enable_tbl2; + pcr->sd_pull_ctl_disable_tbl = rts5229_sd_pull_ctl_disable_tbl2; + } else { + pcr->sd_pull_ctl_enable_tbl = rts5229_sd_pull_ctl_enable_tbl1; + pcr->sd_pull_ctl_disable_tbl = rts5229_sd_pull_ctl_disable_tbl1; + } + pcr->ms_pull_ctl_enable_tbl = rts5229_ms_pull_ctl_enable_tbl; + pcr->ms_pull_ctl_disable_tbl = rts5229_ms_pull_ctl_disable_tbl; +} diff --git a/drivers/mfd/rtsx_pcr.c b/drivers/mfd/rtsx_pcr.c new file mode 100644 index 000000000000..56d4377c62c2 --- /dev/null +++ b/drivers/mfd/rtsx_pcr.c @@ -0,0 +1,1251 @@ +/* Driver for Realtek PCI-Express card reader + * + * Copyright(c) 2009 Realtek Semiconductor Corp. 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 as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Author: + * Wei WANG <wei_wang@realsil.com.cn> + * No. 450, Shenhu Road, Suzhou Industry Park, Suzhou, China + */ + +#include <linux/pci.h> +#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <linux/highmem.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/idr.h> +#include <linux/platform_device.h> +#include <linux/mfd/core.h> +#include <linux/mfd/rtsx_pci.h> +#include <asm/unaligned.h> + +#include "rtsx_pcr.h" + +static bool msi_en = true; +module_param(msi_en, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(msi_en, "Enable MSI"); + +static DEFINE_IDR(rtsx_pci_idr); +static DEFINE_SPINLOCK(rtsx_pci_lock); + +static struct mfd_cell rtsx_pcr_cells[] = { + [RTSX_SD_CARD] = { + .name = DRV_NAME_RTSX_PCI_SDMMC, + }, + [RTSX_MS_CARD] = { + .name = DRV_NAME_RTSX_PCI_MS, + }, +}; + +static DEFINE_PCI_DEVICE_TABLE(rtsx_pci_ids) = { + { PCI_DEVICE(0x10EC, 0x5209), PCI_CLASS_OTHERS << 16, 0xFF0000 }, + { PCI_DEVICE(0x10EC, 0x5229), PCI_CLASS_OTHERS << 16, 0xFF0000 }, + { PCI_DEVICE(0x10EC, 0x5289), PCI_CLASS_OTHERS << 16, 0xFF0000 }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, rtsx_pci_ids); + +void rtsx_pci_start_run(struct rtsx_pcr *pcr) +{ + /* If pci device removed, don't queue idle work any more */ + if (pcr->remove_pci) + return; + + if (pcr->state != PDEV_STAT_RUN) { + pcr->state = PDEV_STAT_RUN; + if (pcr->ops->enable_auto_blink) + pcr->ops->enable_auto_blink(pcr); + } + + mod_delayed_work(system_wq, &pcr->idle_work, msecs_to_jiffies(200)); +} +EXPORT_SYMBOL_GPL(rtsx_pci_start_run); + +int rtsx_pci_write_register(struct rtsx_pcr *pcr, u16 addr, u8 mask, u8 data) +{ + int i; + u32 val = HAIMR_WRITE_START; + + val |= (u32)(addr & 0x3FFF) << 16; + val |= (u32)mask << 8; + val |= (u32)data; + + rtsx_pci_writel(pcr, RTSX_HAIMR, val); + + for (i = 0; i < MAX_RW_REG_CNT; i++) { + val = rtsx_pci_readl(pcr, RTSX_HAIMR); + if ((val & HAIMR_TRANS_END) == 0) { + if (data != (u8)val) + return -EIO; + return 0; + } + } + + return -ETIMEDOUT; +} +EXPORT_SYMBOL_GPL(rtsx_pci_write_register); + +int rtsx_pci_read_register(struct rtsx_pcr *pcr, u16 addr, u8 *data) +{ + u32 val = HAIMR_READ_START; + int i; + + val |= (u32)(addr & 0x3FFF) << 16; + rtsx_pci_writel(pcr, RTSX_HAIMR, val); + + for (i = 0; i < MAX_RW_REG_CNT; i++) { + val = rtsx_pci_readl(pcr, RTSX_HAIMR); + if ((val & HAIMR_TRANS_END) == 0) + break; + } + + if (i >= MAX_RW_REG_CNT) + return -ETIMEDOUT; + + if (data) + *data = (u8)(val & 0xFF); + + return 0; +} +EXPORT_SYMBOL_GPL(rtsx_pci_read_register); + +int rtsx_pci_write_phy_register(struct rtsx_pcr *pcr, u8 addr, u16 val) +{ + int err, i, finished = 0; + u8 tmp; + + rtsx_pci_init_cmd(pcr); + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PHYDATA0, 0xFF, (u8)val); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PHYDATA1, 0xFF, (u8)(val >> 8)); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PHYADDR, 0xFF, addr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PHYRWCTL, 0xFF, 0x81); + + err = rtsx_pci_send_cmd(pcr, 100); + if (err < 0) + return err; + + for (i = 0; i < 100000; i++) { + err = rtsx_pci_read_register(pcr, PHYRWCTL, &tmp); + if (err < 0) + return err; + + if (!(tmp & 0x80)) { + finished = 1; + break; + } + } + + if (!finished) + return -ETIMEDOUT; + + return 0; +} +EXPORT_SYMBOL_GPL(rtsx_pci_write_phy_register); + +int rtsx_pci_read_phy_register(struct rtsx_pcr *pcr, u8 addr, u16 *val) +{ + int err, i, finished = 0; + u16 data; + u8 *ptr, tmp; + + rtsx_pci_init_cmd(pcr); + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PHYADDR, 0xFF, addr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PHYRWCTL, 0xFF, 0x80); + + err = rtsx_pci_send_cmd(pcr, 100); + if (err < 0) + return err; + + for (i = 0; i < 100000; i++) { + err = rtsx_pci_read_register(pcr, PHYRWCTL, &tmp); + if (err < 0) + return err; + + if (!(tmp & 0x80)) { + finished = 1; + break; + } + } + + if (!finished) + return -ETIMEDOUT; + + rtsx_pci_init_cmd(pcr); + + rtsx_pci_add_cmd(pcr, READ_REG_CMD, PHYDATA0, 0, 0); + rtsx_pci_add_cmd(pcr, READ_REG_CMD, PHYDATA1, 0, 0); + + err = rtsx_pci_send_cmd(pcr, 100); + if (err < 0) + return err; + + ptr = rtsx_pci_get_cmd_data(pcr); + data = ((u16)ptr[1] << 8) | ptr[0]; + + if (val) + *val = data; + + return 0; +} +EXPORT_SYMBOL_GPL(rtsx_pci_read_phy_register); + +void rtsx_pci_stop_cmd(struct rtsx_pcr *pcr) +{ + rtsx_pci_writel(pcr, RTSX_HCBCTLR, STOP_CMD); + rtsx_pci_writel(pcr, RTSX_HDBCTLR, STOP_DMA); + + rtsx_pci_write_register(pcr, DMACTL, 0x80, 0x80); + rtsx_pci_write_register(pcr, RBCTL, 0x80, 0x80); +} +EXPORT_SYMBOL_GPL(rtsx_pci_stop_cmd); + +void rtsx_pci_add_cmd(struct rtsx_pcr *pcr, + u8 cmd_type, u16 reg_addr, u8 mask, u8 data) +{ + unsigned long flags; + u32 val = 0; + u32 *ptr = (u32 *)(pcr->host_cmds_ptr); + + val |= (u32)(cmd_type & 0x03) << 30; + val |= (u32)(reg_addr & 0x3FFF) << 16; + val |= (u32)mask << 8; + val |= (u32)data; + + spin_lock_irqsave(&pcr->lock, flags); + ptr += pcr->ci; + if (pcr->ci < (HOST_CMDS_BUF_LEN / 4)) { + put_unaligned_le32(val, ptr); + ptr++; + pcr->ci++; + } + spin_unlock_irqrestore(&pcr->lock, flags); +} +EXPORT_SYMBOL_GPL(rtsx_pci_add_cmd); + +void rtsx_pci_send_cmd_no_wait(struct rtsx_pcr *pcr) +{ + u32 val = 1 << 31; + + rtsx_pci_writel(pcr, RTSX_HCBAR, pcr->host_cmds_addr); + + val |= (u32)(pcr->ci * 4) & 0x00FFFFFF; + /* Hardware Auto Response */ + val |= 0x40000000; + rtsx_pci_writel(pcr, RTSX_HCBCTLR, val); +} +EXPORT_SYMBOL_GPL(rtsx_pci_send_cmd_no_wait); + +int rtsx_pci_send_cmd(struct rtsx_pcr *pcr, int timeout) +{ + struct completion trans_done; + u32 val = 1 << 31; + long timeleft; + unsigned long flags; + int err = 0; + + spin_lock_irqsave(&pcr->lock, flags); + + /* set up data structures for the wakeup system */ + pcr->done = &trans_done; + pcr->trans_result = TRANS_NOT_READY; + init_completion(&trans_done); + + rtsx_pci_writel(pcr, RTSX_HCBAR, pcr->host_cmds_addr); + + val |= (u32)(pcr->ci * 4) & 0x00FFFFFF; + /* Hardware Auto Response */ + val |= 0x40000000; + rtsx_pci_writel(pcr, RTSX_HCBCTLR, val); + + spin_unlock_irqrestore(&pcr->lock, flags); + + /* Wait for TRANS_OK_INT */ + timeleft = wait_for_completion_interruptible_timeout( + &trans_done, msecs_to_jiffies(timeout)); + if (timeleft <= 0) { + dev_dbg(&(pcr->pci->dev), "Timeout (%s %d)\n", + __func__, __LINE__); + err = -ETIMEDOUT; + goto finish_send_cmd; + } + + spin_lock_irqsave(&pcr->lock, flags); + if (pcr->trans_result == TRANS_RESULT_FAIL) + err = -EINVAL; + else if (pcr->trans_result == TRANS_RESULT_OK) + err = 0; + else if (pcr->trans_result == TRANS_NO_DEVICE) + err = -ENODEV; + spin_unlock_irqrestore(&pcr->lock, flags); + +finish_send_cmd: + spin_lock_irqsave(&pcr->lock, flags); + pcr->done = NULL; + spin_unlock_irqrestore(&pcr->lock, flags); + + if ((err < 0) && (err != -ENODEV)) + rtsx_pci_stop_cmd(pcr); + + if (pcr->finish_me) + complete(pcr->finish_me); + + return err; +} +EXPORT_SYMBOL_GPL(rtsx_pci_send_cmd); + +static void rtsx_pci_add_sg_tbl(struct rtsx_pcr *pcr, + dma_addr_t addr, unsigned int len, int end) +{ + u64 *ptr = (u64 *)(pcr->host_sg_tbl_ptr) + pcr->sgi; + u64 val; + u8 option = SG_VALID | SG_TRANS_DATA; + + dev_dbg(&(pcr->pci->dev), "DMA addr: 0x%x, Len: 0x%x\n", + (unsigned int)addr, len); + + if (end) + option |= SG_END; + val = ((u64)addr << 32) | ((u64)len << 12) | option; + + put_unaligned_le64(val, ptr); + ptr++; + pcr->sgi++; +} + +int rtsx_pci_transfer_data(struct rtsx_pcr *pcr, struct scatterlist *sglist, + int num_sg, bool read, int timeout) +{ + struct completion trans_done; + u8 dir; + int err = 0, i, count; + long timeleft; + unsigned long flags; + struct scatterlist *sg; + enum dma_data_direction dma_dir; + u32 val; + dma_addr_t addr; + unsigned int len; + + dev_dbg(&(pcr->pci->dev), "--> %s: num_sg = %d\n", __func__, num_sg); + + /* don't transfer data during abort processing */ + if (pcr->remove_pci) + return -EINVAL; + + if ((sglist == NULL) || (num_sg <= 0)) + return -EINVAL; + + if (read) { + dir = DEVICE_TO_HOST; + dma_dir = DMA_FROM_DEVICE; + } else { + dir = HOST_TO_DEVICE; + dma_dir = DMA_TO_DEVICE; + } + + count = dma_map_sg(&(pcr->pci->dev), sglist, num_sg, dma_dir); + if (count < 1) { + dev_err(&(pcr->pci->dev), "scatterlist map failed\n"); + return -EINVAL; + } + dev_dbg(&(pcr->pci->dev), "DMA mapping count: %d\n", count); + + val = ((u32)(dir & 0x01) << 29) | TRIG_DMA | ADMA_MODE; + pcr->sgi = 0; + for_each_sg(sglist, sg, count, i) { + addr = sg_dma_address(sg); + len = sg_dma_len(sg); + rtsx_pci_add_sg_tbl(pcr, addr, len, i == count - 1); + } + + spin_lock_irqsave(&pcr->lock, flags); + + pcr->done = &trans_done; + pcr->trans_result = TRANS_NOT_READY; + init_completion(&trans_done); + rtsx_pci_writel(pcr, RTSX_HDBAR, pcr->host_sg_tbl_addr); + rtsx_pci_writel(pcr, RTSX_HDBCTLR, val); + + spin_unlock_irqrestore(&pcr->lock, flags); + + timeleft = wait_for_completion_interruptible_timeout( + &trans_done, msecs_to_jiffies(timeout)); + if (timeleft <= 0) { + dev_dbg(&(pcr->pci->dev), "Timeout (%s %d)\n", + __func__, __LINE__); + err = -ETIMEDOUT; + goto out; + } + + spin_lock_irqsave(&pcr->lock, flags); + + if (pcr->trans_result == TRANS_RESULT_FAIL) + err = -EINVAL; + else if (pcr->trans_result == TRANS_NO_DEVICE) + err = -ENODEV; + + spin_unlock_irqrestore(&pcr->lock, flags); + +out: + spin_lock_irqsave(&pcr->lock, flags); + pcr->done = NULL; + spin_unlock_irqrestore(&pcr->lock, flags); + + dma_unmap_sg(&(pcr->pci->dev), sglist, num_sg, dma_dir); + + if ((err < 0) && (err != -ENODEV)) + rtsx_pci_stop_cmd(pcr); + + if (pcr->finish_me) + complete(pcr->finish_me); + + return err; +} +EXPORT_SYMBOL_GPL(rtsx_pci_transfer_data); + +int rtsx_pci_read_ppbuf(struct rtsx_pcr *pcr, u8 *buf, int buf_len) +{ + int err; + int i, j; + u16 reg; + u8 *ptr; + + if (buf_len > 512) + buf_len = 512; + + ptr = buf; + reg = PPBUF_BASE2; + for (i = 0; i < buf_len / 256; i++) { + rtsx_pci_init_cmd(pcr); + + for (j = 0; j < 256; j++) + rtsx_pci_add_cmd(pcr, READ_REG_CMD, reg++, 0, 0); + + err = rtsx_pci_send_cmd(pcr, 250); + if (err < 0) + return err; + + memcpy(ptr, rtsx_pci_get_cmd_data(pcr), 256); + ptr += 256; + } + + if (buf_len % 256) { + rtsx_pci_init_cmd(pcr); + + for (j = 0; j < buf_len % 256; j++) + rtsx_pci_add_cmd(pcr, READ_REG_CMD, reg++, 0, 0); + + err = rtsx_pci_send_cmd(pcr, 250); + if (err < 0) + return err; + } + + memcpy(ptr, rtsx_pci_get_cmd_data(pcr), buf_len % 256); + + return 0; +} +EXPORT_SYMBOL_GPL(rtsx_pci_read_ppbuf); + +int rtsx_pci_write_ppbuf(struct rtsx_pcr *pcr, u8 *buf, int buf_len) +{ + int err; + int i, j; + u16 reg; + u8 *ptr; + + if (buf_len > 512) + buf_len = 512; + + ptr = buf; + reg = PPBUF_BASE2; + for (i = 0; i < buf_len / 256; i++) { + rtsx_pci_init_cmd(pcr); + + for (j = 0; j < 256; j++) { + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, + reg++, 0xFF, *ptr); + ptr++; + } + + err = rtsx_pci_send_cmd(pcr, 250); + if (err < 0) + return err; + } + + if (buf_len % 256) { + rtsx_pci_init_cmd(pcr); + + for (j = 0; j < buf_len % 256; j++) { + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, + reg++, 0xFF, *ptr); + ptr++; + } + + err = rtsx_pci_send_cmd(pcr, 250); + if (err < 0) + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(rtsx_pci_write_ppbuf); + +static int rtsx_pci_set_pull_ctl(struct rtsx_pcr *pcr, const u32 *tbl) +{ + int err; + + rtsx_pci_init_cmd(pcr); + + while (*tbl & 0xFFFF0000) { + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, + (u16)(*tbl >> 16), 0xFF, (u8)(*tbl)); + tbl++; + } + + err = rtsx_pci_send_cmd(pcr, 100); + if (err < 0) + return err; + + return 0; +} + +int rtsx_pci_card_pull_ctl_enable(struct rtsx_pcr *pcr, int card) +{ + const u32 *tbl; + + if (card == RTSX_SD_CARD) + tbl = pcr->sd_pull_ctl_enable_tbl; + else if (card == RTSX_MS_CARD) + tbl = pcr->ms_pull_ctl_enable_tbl; + else + return -EINVAL; + + return rtsx_pci_set_pull_ctl(pcr, tbl); +} +EXPORT_SYMBOL_GPL(rtsx_pci_card_pull_ctl_enable); + +int rtsx_pci_card_pull_ctl_disable(struct rtsx_pcr *pcr, int card) +{ + const u32 *tbl; + + if (card == RTSX_SD_CARD) + tbl = pcr->sd_pull_ctl_disable_tbl; + else if (card == RTSX_MS_CARD) + tbl = pcr->ms_pull_ctl_disable_tbl; + else + return -EINVAL; + + + return rtsx_pci_set_pull_ctl(pcr, tbl); +} +EXPORT_SYMBOL_GPL(rtsx_pci_card_pull_ctl_disable); + +static void rtsx_pci_enable_bus_int(struct rtsx_pcr *pcr) +{ + pcr->bier = TRANS_OK_INT_EN | TRANS_FAIL_INT_EN | SD_INT_EN; + + if (pcr->num_slots > 1) + pcr->bier |= MS_INT_EN; + + /* Enable Bus Interrupt */ + rtsx_pci_writel(pcr, RTSX_BIER, pcr->bier); + + dev_dbg(&(pcr->pci->dev), "RTSX_BIER: 0x%08x\n", pcr->bier); +} + +static inline u8 double_ssc_depth(u8 depth) +{ + return ((depth > 1) ? (depth - 1) : depth); +} + +static u8 revise_ssc_depth(u8 ssc_depth, u8 div) +{ + if (div > CLK_DIV_1) { + if (ssc_depth > (div - 1)) + ssc_depth -= (div - 1); + else + ssc_depth = SSC_DEPTH_4M; + } + + return ssc_depth; +} + +int rtsx_pci_switch_clock(struct rtsx_pcr *pcr, unsigned int card_clock, + u8 ssc_depth, bool initial_mode, bool double_clk, bool vpclk) +{ + int err, clk; + u8 N, min_N, max_N, clk_divider; + u8 mcu_cnt, div, max_div; + u8 depth[] = { + [RTSX_SSC_DEPTH_4M] = SSC_DEPTH_4M, + [RTSX_SSC_DEPTH_2M] = SSC_DEPTH_2M, + [RTSX_SSC_DEPTH_1M] = SSC_DEPTH_1M, + [RTSX_SSC_DEPTH_500K] = SSC_DEPTH_500K, + [RTSX_SSC_DEPTH_250K] = SSC_DEPTH_250K, + }; + + if (initial_mode) { + /* We use 250k(around) here, in initial stage */ + clk_divider = SD_CLK_DIVIDE_128; + card_clock = 30000000; + } else { + clk_divider = SD_CLK_DIVIDE_0; + } + err = rtsx_pci_write_register(pcr, SD_CFG1, + SD_CLK_DIVIDE_MASK, clk_divider); + if (err < 0) + return err; + + card_clock /= 1000000; + dev_dbg(&(pcr->pci->dev), "Switch card clock to %dMHz\n", card_clock); + + min_N = 80; + max_N = 208; + max_div = CLK_DIV_8; + + clk = card_clock; + if (!initial_mode && double_clk) + clk = card_clock * 2; + dev_dbg(&(pcr->pci->dev), + "Internal SSC clock: %dMHz (cur_clock = %d)\n", + clk, pcr->cur_clock); + + if (clk == pcr->cur_clock) + return 0; + + N = (u8)(clk - 2); + if ((clk <= 2) || (N > max_N)) + return -EINVAL; + + mcu_cnt = (u8)(125/clk + 3); + if (mcu_cnt > 15) + mcu_cnt = 15; + + /* Make sure that the SSC clock div_n is equal or greater than min_N */ + div = CLK_DIV_1; + while ((N < min_N) && (div < max_div)) { + N = (N + 2) * 2 - 2; + div++; + } + dev_dbg(&(pcr->pci->dev), "N = %d, div = %d\n", N, div); + + ssc_depth = depth[ssc_depth]; + if (double_clk) + ssc_depth = double_ssc_depth(ssc_depth); + + ssc_depth = revise_ssc_depth(ssc_depth, div); + dev_dbg(&(pcr->pci->dev), "ssc_depth = %d\n", ssc_depth); + + rtsx_pci_init_cmd(pcr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CLK_CTL, + CLK_LOW_FREQ, CLK_LOW_FREQ); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CLK_DIV, + 0xFF, (div << 4) | mcu_cnt); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SSC_CTL1, SSC_RSTB, 0); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SSC_CTL2, + SSC_DEPTH_MASK, ssc_depth); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SSC_DIV_N_0, 0xFF, N); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SSC_CTL1, SSC_RSTB, SSC_RSTB); + if (vpclk) { + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_VPCLK0_CTL, + PHASE_NOT_RESET, 0); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_VPCLK0_CTL, + PHASE_NOT_RESET, PHASE_NOT_RESET); + } + + err = rtsx_pci_send_cmd(pcr, 2000); + if (err < 0) + return err; + + /* Wait SSC clock stable */ + udelay(10); + err = rtsx_pci_write_register(pcr, CLK_CTL, CLK_LOW_FREQ, 0); + if (err < 0) + return err; + + pcr->cur_clock = clk; + return 0; +} +EXPORT_SYMBOL_GPL(rtsx_pci_switch_clock); + +int rtsx_pci_card_power_on(struct rtsx_pcr *pcr, int card) +{ + if (pcr->ops->card_power_on) + return pcr->ops->card_power_on(pcr, card); + + return 0; +} +EXPORT_SYMBOL_GPL(rtsx_pci_card_power_on); + +int rtsx_pci_card_power_off(struct rtsx_pcr *pcr, int card) +{ + if (pcr->ops->card_power_off) + return pcr->ops->card_power_off(pcr, card); + + return 0; +} +EXPORT_SYMBOL_GPL(rtsx_pci_card_power_off); + +unsigned int rtsx_pci_card_exist(struct rtsx_pcr *pcr) +{ + unsigned int val; + + val = rtsx_pci_readl(pcr, RTSX_BIPR); + if (pcr->ops->cd_deglitch) + val = pcr->ops->cd_deglitch(pcr); + + return val; +} +EXPORT_SYMBOL_GPL(rtsx_pci_card_exist); + +void rtsx_pci_complete_unfinished_transfer(struct rtsx_pcr *pcr) +{ + struct completion finish; + + pcr->finish_me = &finish; + init_completion(&finish); + + if (pcr->done) + complete(pcr->done); + + if (!pcr->remove_pci) + rtsx_pci_stop_cmd(pcr); + + wait_for_completion_interruptible_timeout(&finish, + msecs_to_jiffies(2)); + pcr->finish_me = NULL; +} +EXPORT_SYMBOL_GPL(rtsx_pci_complete_unfinished_transfer); + +static void rtsx_pci_card_detect(struct work_struct *work) +{ + struct delayed_work *dwork; + struct rtsx_pcr *pcr; + unsigned long flags; + unsigned int card_detect = 0; + u32 irq_status; + + dwork = to_delayed_work(work); + pcr = container_of(dwork, struct rtsx_pcr, carddet_work); + + dev_dbg(&(pcr->pci->dev), "--> %s\n", __func__); + + spin_lock_irqsave(&pcr->lock, flags); + + irq_status = rtsx_pci_readl(pcr, RTSX_BIPR); + dev_dbg(&(pcr->pci->dev), "irq_status: 0x%08x\n", irq_status); + + if (pcr->card_inserted || pcr->card_removed) { + dev_dbg(&(pcr->pci->dev), + "card_inserted: 0x%x, card_removed: 0x%x\n", + pcr->card_inserted, pcr->card_removed); + + if (pcr->ops->cd_deglitch) + pcr->card_inserted = pcr->ops->cd_deglitch(pcr); + + card_detect = pcr->card_inserted | pcr->card_removed; + pcr->card_inserted = 0; + pcr->card_removed = 0; + } + + spin_unlock_irqrestore(&pcr->lock, flags); + + if (card_detect & SD_EXIST) + pcr->slots[RTSX_SD_CARD].card_event( + pcr->slots[RTSX_SD_CARD].p_dev); + if (card_detect & MS_EXIST) + pcr->slots[RTSX_MS_CARD].card_event( + pcr->slots[RTSX_MS_CARD].p_dev); +} + +static irqreturn_t rtsx_pci_isr(int irq, void *dev_id) +{ + struct rtsx_pcr *pcr = dev_id; + u32 int_reg; + + if (!pcr) + return IRQ_NONE; + + spin_lock(&pcr->lock); + + int_reg = rtsx_pci_readl(pcr, RTSX_BIPR); + /* Clear interrupt flag */ + rtsx_pci_writel(pcr, RTSX_BIPR, int_reg); + if ((int_reg & pcr->bier) == 0) { + spin_unlock(&pcr->lock); + return IRQ_NONE; + } + if (int_reg == 0xFFFFFFFF) { + spin_unlock(&pcr->lock); + return IRQ_HANDLED; + } + + int_reg &= (pcr->bier | 0x7FFFFF); + + if (int_reg & SD_INT) { + if (int_reg & SD_EXIST) { + pcr->card_inserted |= SD_EXIST; + } else { + pcr->card_removed |= SD_EXIST; + pcr->card_inserted &= ~SD_EXIST; + } + } + + if (int_reg & MS_INT) { + if (int_reg & MS_EXIST) { + pcr->card_inserted |= MS_EXIST; + } else { + pcr->card_removed |= MS_EXIST; + pcr->card_inserted &= ~MS_EXIST; + } + } + + if (pcr->card_inserted || pcr->card_removed) + schedule_delayed_work(&pcr->carddet_work, + msecs_to_jiffies(200)); + + if (int_reg & (NEED_COMPLETE_INT | DELINK_INT)) { + if (int_reg & (TRANS_FAIL_INT | DELINK_INT)) { + pcr->trans_result = TRANS_RESULT_FAIL; + if (pcr->done) + complete(pcr->done); + } else if (int_reg & TRANS_OK_INT) { + pcr->trans_result = TRANS_RESULT_OK; + if (pcr->done) + complete(pcr->done); + } + } + + spin_unlock(&pcr->lock); + return IRQ_HANDLED; +} + +static int rtsx_pci_acquire_irq(struct rtsx_pcr *pcr) +{ + dev_info(&(pcr->pci->dev), "%s: pcr->msi_en = %d, pci->irq = %d\n", + __func__, pcr->msi_en, pcr->pci->irq); + + if (request_irq(pcr->pci->irq, rtsx_pci_isr, + pcr->msi_en ? 0 : IRQF_SHARED, + DRV_NAME_RTSX_PCI, pcr)) { + dev_err(&(pcr->pci->dev), + "rtsx_sdmmc: unable to grab IRQ %d, disabling device\n", + pcr->pci->irq); + return -1; + } + + pcr->irq = pcr->pci->irq; + pci_intx(pcr->pci, !pcr->msi_en); + + return 0; +} + +static void rtsx_pci_idle_work(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct rtsx_pcr *pcr = container_of(dwork, struct rtsx_pcr, idle_work); + + dev_dbg(&(pcr->pci->dev), "--> %s\n", __func__); + + mutex_lock(&pcr->pcr_mutex); + + pcr->state = PDEV_STAT_IDLE; + + if (pcr->ops->disable_auto_blink) + pcr->ops->disable_auto_blink(pcr); + if (pcr->ops->turn_off_led) + pcr->ops->turn_off_led(pcr); + + mutex_unlock(&pcr->pcr_mutex); +} + +static int rtsx_pci_init_hw(struct rtsx_pcr *pcr) +{ + int err; + + rtsx_pci_writel(pcr, RTSX_HCBAR, pcr->host_cmds_addr); + + rtsx_pci_enable_bus_int(pcr); + + /* Power on SSC */ + err = rtsx_pci_write_register(pcr, FPDCTL, SSC_POWER_DOWN, 0); + if (err < 0) + return err; + + /* Wait SSC power stable */ + udelay(200); + + if (pcr->ops->optimize_phy) { + err = pcr->ops->optimize_phy(pcr); + if (err < 0) + return err; + } + + rtsx_pci_init_cmd(pcr); + + /* Set mcu_cnt to 7 to ensure data can be sampled properly */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CLK_DIV, 0x07, 0x07); + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, HOST_SLEEP_STATE, 0x03, 0x00); + /* Disable card clock */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_CLK_EN, 0x1E, 0); + /* Reset ASPM state to default value */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, ASPM_FORCE_CTL, 0x3F, 0); + /* Reset delink mode */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CHANGE_LINK_STATE, 0x0A, 0); + /* Card driving select */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD30_DRIVE_SEL, + 0x07, DRIVER_TYPE_D); + /* Enable SSC Clock */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SSC_CTL1, + 0xFF, SSC_8X_EN | SSC_SEL_4M); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SSC_CTL2, 0xFF, 0x12); + /* Disable cd_pwr_save */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CHANGE_LINK_STATE, 0x16, 0x10); + /* Clear Link Ready Interrupt */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, IRQSTAT0, + LINK_RDY_INT, LINK_RDY_INT); + /* Enlarge the estimation window of PERST# glitch + * to reduce the chance of invalid card interrupt + */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PERST_GLITCH_WIDTH, 0xFF, 0x80); + /* Update RC oscillator to 400k + * bit[0] F_HIGH: for RC oscillator, Rst_value is 1'b1 + * 1: 2M 0: 400k + */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, RCCTL, 0x01, 0x00); + /* Set interrupt write clear + * bit 1: U_elbi_if_rd_clr_en + * 1: Enable ELBI interrupt[31:22] & [7:0] flag read clear + * 0: ELBI interrupt flag[31:22] & [7:0] only can be write clear + */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, NFTS_TX_CTRL, 0x02, 0); + /* Force CLKREQ# PIN to drive 0 to request clock */ + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, PETXCFG, 0x08, 0x08); + + err = rtsx_pci_send_cmd(pcr, 100); + if (err < 0) + return err; + + /* Enable clk_request_n to enable clock power management */ + rtsx_pci_write_config_byte(pcr, 0x81, 1); + /* Enter L1 when host tx idle */ + rtsx_pci_write_config_byte(pcr, 0x70F, 0x5B); + + if (pcr->ops->extra_init_hw) { + err = pcr->ops->extra_init_hw(pcr); + if (err < 0) + return err; + } + + return 0; +} + +static int rtsx_pci_init_chip(struct rtsx_pcr *pcr) +{ + int err; + + spin_lock_init(&pcr->lock); + mutex_init(&pcr->pcr_mutex); + + switch (PCI_PID(pcr)) { + default: + case 0x5209: + rts5209_init_params(pcr); + break; + + case 0x5229: + rts5229_init_params(pcr); + break; + + case 0x5289: + rtl8411_init_params(pcr); + break; + } + + dev_dbg(&(pcr->pci->dev), "PID: 0x%04x, IC version: 0x%02x\n", + PCI_PID(pcr), pcr->ic_version); + + pcr->slots = kcalloc(pcr->num_slots, sizeof(struct rtsx_slot), + GFP_KERNEL); + if (!pcr->slots) + return -ENOMEM; + + pcr->state = PDEV_STAT_IDLE; + err = rtsx_pci_init_hw(pcr); + if (err < 0) { + kfree(pcr->slots); + return err; + } + + return 0; +} + +static int __devinit rtsx_pci_probe(struct pci_dev *pcidev, + const struct pci_device_id *id) +{ + struct rtsx_pcr *pcr; + struct pcr_handle *handle; + u32 base, len; + int ret, i; + + dev_dbg(&(pcidev->dev), + ": Realtek PCI-E Card Reader found at %s [%04x:%04x] (rev %x)\n", + pci_name(pcidev), (int)pcidev->vendor, (int)pcidev->device, + (int)pcidev->revision); + + ret = pci_enable_device(pcidev); + if (ret) + return ret; + + ret = pci_request_regions(pcidev, DRV_NAME_RTSX_PCI); + if (ret) + goto disable; + + pcr = kzalloc(sizeof(*pcr), GFP_KERNEL); + if (!pcr) { + ret = -ENOMEM; + goto release_pci; + } + + handle = kzalloc(sizeof(*handle), GFP_KERNEL); + if (!handle) { + ret = -ENOMEM; + goto free_pcr; + } + handle->pcr = pcr; + + if (!idr_pre_get(&rtsx_pci_idr, GFP_KERNEL)) { + ret = -ENOMEM; + goto free_handle; + } + + spin_lock(&rtsx_pci_lock); + ret = idr_get_new(&rtsx_pci_idr, pcr, &pcr->id); + spin_unlock(&rtsx_pci_lock); + if (ret) + goto free_handle; + + pcr->pci = pcidev; + dev_set_drvdata(&pcidev->dev, handle); + + len = pci_resource_len(pcidev, 0); + base = pci_resource_start(pcidev, 0); + pcr->remap_addr = ioremap_nocache(base, len); + if (!pcr->remap_addr) { + ret = -ENOMEM; + goto free_host; + } + + pcr->rtsx_resv_buf = dma_alloc_coherent(&(pcidev->dev), + RTSX_RESV_BUF_LEN, &(pcr->rtsx_resv_buf_addr), + GFP_KERNEL); + if (pcr->rtsx_resv_buf == NULL) { + ret = -ENXIO; + goto unmap; + } + pcr->host_cmds_ptr = pcr->rtsx_resv_buf; + pcr->host_cmds_addr = pcr->rtsx_resv_buf_addr; + pcr->host_sg_tbl_ptr = pcr->rtsx_resv_buf + HOST_CMDS_BUF_LEN; + pcr->host_sg_tbl_addr = pcr->rtsx_resv_buf_addr + HOST_CMDS_BUF_LEN; + + pcr->card_inserted = 0; + pcr->card_removed = 0; + INIT_DELAYED_WORK(&pcr->carddet_work, rtsx_pci_card_detect); + INIT_DELAYED_WORK(&pcr->idle_work, rtsx_pci_idle_work); + + pcr->msi_en = msi_en; + if (pcr->msi_en) { + ret = pci_enable_msi(pcidev); + if (ret < 0) + pcr->msi_en = false; + } + + ret = rtsx_pci_acquire_irq(pcr); + if (ret < 0) + goto free_dma; + + pci_set_master(pcidev); + synchronize_irq(pcr->irq); + + ret = rtsx_pci_init_chip(pcr); + if (ret < 0) + goto disable_irq; + + for (i = 0; i < ARRAY_SIZE(rtsx_pcr_cells); i++) { + rtsx_pcr_cells[i].platform_data = handle; + rtsx_pcr_cells[i].pdata_size = sizeof(*handle); + } + ret = mfd_add_devices(&pcidev->dev, pcr->id, rtsx_pcr_cells, + ARRAY_SIZE(rtsx_pcr_cells), NULL, 0, NULL); + if (ret < 0) + goto disable_irq; + + schedule_delayed_work(&pcr->idle_work, msecs_to_jiffies(200)); + + return 0; + +disable_irq: + free_irq(pcr->irq, (void *)pcr); +free_dma: + dma_free_coherent(&(pcr->pci->dev), RTSX_RESV_BUF_LEN, + pcr->rtsx_resv_buf, pcr->rtsx_resv_buf_addr); +unmap: + iounmap(pcr->remap_addr); +free_host: + dev_set_drvdata(&pcidev->dev, NULL); +free_handle: + kfree(handle); +free_pcr: + kfree(pcr); +release_pci: + pci_release_regions(pcidev); +disable: + pci_disable_device(pcidev); + + return ret; +} + +static void __devexit rtsx_pci_remove(struct pci_dev *pcidev) +{ + struct pcr_handle *handle = pci_get_drvdata(pcidev); + struct rtsx_pcr *pcr = handle->pcr; + + pcr->remove_pci = true; + + cancel_delayed_work(&pcr->carddet_work); + cancel_delayed_work(&pcr->idle_work); + + mfd_remove_devices(&pcidev->dev); + + dma_free_coherent(&(pcr->pci->dev), RTSX_RESV_BUF_LEN, + pcr->rtsx_resv_buf, pcr->rtsx_resv_buf_addr); + free_irq(pcr->irq, (void *)pcr); + if (pcr->msi_en) + pci_disable_msi(pcr->pci); + iounmap(pcr->remap_addr); + + dev_set_drvdata(&pcidev->dev, NULL); + pci_release_regions(pcidev); + pci_disable_device(pcidev); + + spin_lock(&rtsx_pci_lock); + idr_remove(&rtsx_pci_idr, pcr->id); + spin_unlock(&rtsx_pci_lock); + + kfree(pcr->slots); + kfree(pcr); + kfree(handle); + + dev_dbg(&(pcidev->dev), + ": Realtek PCI-E Card Reader at %s [%04x:%04x] has been removed\n", + pci_name(pcidev), (int)pcidev->vendor, (int)pcidev->device); +} + +#ifdef CONFIG_PM + +static int rtsx_pci_suspend(struct pci_dev *pcidev, pm_message_t state) +{ + struct pcr_handle *handle; + struct rtsx_pcr *pcr; + int ret = 0; + + dev_dbg(&(pcidev->dev), "--> %s\n", __func__); + + handle = pci_get_drvdata(pcidev); + pcr = handle->pcr; + + cancel_delayed_work(&pcr->carddet_work); + cancel_delayed_work(&pcr->idle_work); + + mutex_lock(&pcr->pcr_mutex); + + if (pcr->ops->turn_off_led) + pcr->ops->turn_off_led(pcr); + + rtsx_pci_writel(pcr, RTSX_BIER, 0); + pcr->bier = 0; + + rtsx_pci_write_register(pcr, PETXCFG, 0x08, 0x08); + rtsx_pci_write_register(pcr, HOST_SLEEP_STATE, 0x03, 0x02); + + pci_save_state(pcidev); + pci_enable_wake(pcidev, pci_choose_state(pcidev, state), 0); + pci_disable_device(pcidev); + pci_set_power_state(pcidev, pci_choose_state(pcidev, state)); + + mutex_unlock(&pcr->pcr_mutex); + return ret; +} + +static int rtsx_pci_resume(struct pci_dev *pcidev) +{ + struct pcr_handle *handle; + struct rtsx_pcr *pcr; + int ret = 0; + + dev_dbg(&(pcidev->dev), "--> %s\n", __func__); + + handle = pci_get_drvdata(pcidev); + pcr = handle->pcr; + + mutex_lock(&pcr->pcr_mutex); + + pci_set_power_state(pcidev, PCI_D0); + pci_restore_state(pcidev); + ret = pci_enable_device(pcidev); + if (ret) + goto out; + pci_set_master(pcidev); + + ret = rtsx_pci_write_register(pcr, HOST_SLEEP_STATE, 0x03, 0x00); + if (ret) + goto out; + + ret = rtsx_pci_init_hw(pcr); + if (ret) + goto out; + + schedule_delayed_work(&pcr->idle_work, msecs_to_jiffies(200)); + +out: + mutex_unlock(&pcr->pcr_mutex); + return ret; +} + +#else /* CONFIG_PM */ + +#define rtsx_pci_suspend NULL +#define rtsx_pci_resume NULL + +#endif /* CONFIG_PM */ + +static struct pci_driver rtsx_pci_driver = { + .name = DRV_NAME_RTSX_PCI, + .id_table = rtsx_pci_ids, + .probe = rtsx_pci_probe, + .remove = __devexit_p(rtsx_pci_remove), + .suspend = rtsx_pci_suspend, + .resume = rtsx_pci_resume, +}; +module_pci_driver(rtsx_pci_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Wei WANG <wei_wang@realsil.com.cn>"); +MODULE_DESCRIPTION("Realtek PCI-E Card Reader Driver"); diff --git a/drivers/mfd/rtsx_pcr.h b/drivers/mfd/rtsx_pcr.h new file mode 100644 index 000000000000..12462c1df1a9 --- /dev/null +++ b/drivers/mfd/rtsx_pcr.h @@ -0,0 +1,32 @@ +/* Driver for Realtek PCI-Express card reader + * + * Copyright(c) 2009 Realtek Semiconductor Corp. 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 as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Author: + * Wei WANG <wei_wang@realsil.com.cn> + * No. 450, Shenhu Road, Suzhou Industry Park, Suzhou, China + */ + +#ifndef __RTSX_PCR_H +#define __RTSX_PCR_H + +#include <linux/mfd/rtsx_pci.h> + +void rts5209_init_params(struct rtsx_pcr *pcr); +void rts5229_init_params(struct rtsx_pcr *pcr); +void rtl8411_init_params(struct rtsx_pcr *pcr); + +#endif diff --git a/drivers/misc/apds9802als.c b/drivers/misc/apds9802als.c index 0314773f6db3..94923d259910 100644 --- a/drivers/misc/apds9802als.c +++ b/drivers/misc/apds9802als.c @@ -68,7 +68,7 @@ static int als_wait_for_data_ready(struct device *dev) ret = i2c_smbus_read_byte_data(client, 0x86); } while (!(ret & 0x80) && retry--); - if (!retry) { + if (retry < 0) { dev_warn(dev, "timeout waiting for data ready\n"); return -ETIMEDOUT; } diff --git a/drivers/misc/hpilo.c b/drivers/misc/hpilo.c index 12ccdf94e4fa..b362d938e929 100644 --- a/drivers/misc/hpilo.c +++ b/drivers/misc/hpilo.c @@ -30,7 +30,7 @@ static struct class *ilo_class; static unsigned int ilo_major; -static unsigned int max_ccb = MIN_CCB; +static unsigned int max_ccb = 16; static char ilo_hwdev[MAX_ILO_DEV]; static inline int get_entry_id(int entry) @@ -725,6 +725,9 @@ static void ilo_remove(struct pci_dev *pdev) int i, minor; struct ilo_hwinfo *ilo_hw = pci_get_drvdata(pdev); + if (!ilo_hw) + return; + clear_device(ilo_hw); minor = MINOR(ilo_hw->cdev.dev); @@ -751,9 +754,13 @@ static void ilo_remove(struct pci_dev *pdev) static int __devinit ilo_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { - int devnum, minor, start, error; + int devnum, minor, start, error = 0; struct ilo_hwinfo *ilo_hw; + /* Ignore subsystem_device = 0x1979 (set by BIOS) */ + if (pdev->subsystem_device == 0x1979) + goto out; + if (max_ccb > MAX_CCB) max_ccb = MAX_CCB; else if (max_ccb < MIN_CCB) @@ -892,14 +899,14 @@ static void __exit ilo_exit(void) class_destroy(ilo_class); } -MODULE_VERSION("1.3"); +MODULE_VERSION("1.4"); MODULE_ALIAS(ILO_NAME); MODULE_DESCRIPTION(ILO_NAME); MODULE_AUTHOR("David Altobelli <david.altobelli@hp.com>"); MODULE_LICENSE("GPL v2"); module_param(max_ccb, uint, 0444); -MODULE_PARM_DESC(max_ccb, "Maximum number of HP iLO channels to attach (8)"); +MODULE_PARM_DESC(max_ccb, "Maximum number of HP iLO channels to attach (16)"); module_init(ilo_init); module_exit(ilo_exit); diff --git a/drivers/misc/mei/Makefile b/drivers/misc/mei/Makefile index 57168db6c7e5..0017842e166c 100644 --- a/drivers/misc/mei/Makefile +++ b/drivers/misc/mei/Makefile @@ -8,4 +8,5 @@ mei-objs += interrupt.o mei-objs += interface.o mei-objs += iorw.o mei-objs += main.o +mei-objs += amthif.o mei-objs += wd.o diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c new file mode 100644 index 000000000000..095d0595a49e --- /dev/null +++ b/drivers/misc/mei/amthif.c @@ -0,0 +1,732 @@ +/* + * + * Intel Management Engine Interface (Intel MEI) Linux driver + * Copyright (c) 2003-2012, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + */ + +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/aio.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <linux/ioctl.h> +#include <linux/cdev.h> +#include <linux/list.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/uuid.h> +#include <linux/jiffies.h> +#include <linux/uaccess.h> + + +#include "mei_dev.h" +#include "hw.h" +#include <linux/mei.h> +#include "interface.h" + +const uuid_le mei_amthi_guid = UUID_LE(0x12f80028, 0xb4b7, 0x4b2d, 0xac, + 0xa8, 0x46, 0xe0, 0xff, 0x65, + 0x81, 0x4c); + +/** + * mei_amthif_reset_params - initializes mei device iamthif + * + * @dev: the device structure + */ +void mei_amthif_reset_params(struct mei_device *dev) +{ + /* reset iamthif parameters. */ + dev->iamthif_current_cb = NULL; + dev->iamthif_msg_buf_size = 0; + dev->iamthif_msg_buf_index = 0; + dev->iamthif_canceled = false; + dev->iamthif_ioctl = false; + dev->iamthif_state = MEI_IAMTHIF_IDLE; + dev->iamthif_timer = 0; +} + +/** + * mei_amthif_host_init_ - mei initialization amthif client. + * + * @dev: the device structure + * + */ +void mei_amthif_host_init(struct mei_device *dev) +{ + int i; + unsigned char *msg_buf; + + mei_cl_init(&dev->iamthif_cl, dev); + dev->iamthif_cl.state = MEI_FILE_DISCONNECTED; + + /* find ME amthi client */ + i = mei_me_cl_link(dev, &dev->iamthif_cl, + &mei_amthi_guid, MEI_IAMTHIF_HOST_CLIENT_ID); + if (i < 0) { + dev_info(&dev->pdev->dev, "failed to find iamthif client.\n"); + return; + } + + /* Assign iamthif_mtu to the value received from ME */ + + dev->iamthif_mtu = dev->me_clients[i].props.max_msg_length; + dev_dbg(&dev->pdev->dev, "IAMTHIF_MTU = %d\n", + dev->me_clients[i].props.max_msg_length); + + kfree(dev->iamthif_msg_buf); + dev->iamthif_msg_buf = NULL; + + /* allocate storage for ME message buffer */ + msg_buf = kcalloc(dev->iamthif_mtu, + sizeof(unsigned char), GFP_KERNEL); + if (!msg_buf) { + dev_dbg(&dev->pdev->dev, "memory allocation for ME message buffer failed.\n"); + return; + } + + dev->iamthif_msg_buf = msg_buf; + + if (mei_connect(dev, &dev->iamthif_cl)) { + dev_dbg(&dev->pdev->dev, "Failed to connect to AMTHI client\n"); + dev->iamthif_cl.state = MEI_FILE_DISCONNECTED; + dev->iamthif_cl.host_client_id = 0; + } else { + dev->iamthif_cl.timer_count = MEI_CONNECT_TIMEOUT; + } +} + +/** + * mei_amthif_find_read_list_entry - finds a amthilist entry for current file + * + * @dev: the device structure + * @file: pointer to file object + * + * returns returned a list entry on success, NULL on failure. + */ +struct mei_cl_cb *mei_amthif_find_read_list_entry(struct mei_device *dev, + struct file *file) +{ + struct mei_cl_cb *pos = NULL; + struct mei_cl_cb *next = NULL; + + list_for_each_entry_safe(pos, next, + &dev->amthif_rd_complete_list.list, list) { + if (pos->cl && pos->cl == &dev->iamthif_cl && + pos->file_object == file) + return pos; + } + return NULL; +} + + +/** + * mei_amthif_read - read data from AMTHIF client + * + * @dev: the device structure + * @if_num: minor number + * @file: pointer to file object + * @*ubuf: pointer to user data in user space + * @length: data length to read + * @offset: data read offset + * + * Locking: called under "dev->device_lock" lock + * + * returns + * returned data length on success, + * zero if no data to read, + * negative on failure. + */ +int mei_amthif_read(struct mei_device *dev, struct file *file, + char __user *ubuf, size_t length, loff_t *offset) +{ + int rets; + int wait_ret; + struct mei_cl_cb *cb = NULL; + struct mei_cl *cl = file->private_data; + unsigned long timeout; + int i; + + /* Only Posible if we are in timeout */ + if (!cl || cl != &dev->iamthif_cl) { + dev_dbg(&dev->pdev->dev, "bad file ext.\n"); + return -ETIMEDOUT; + } + + i = mei_me_cl_by_id(dev, dev->iamthif_cl.me_client_id); + + if (i < 0) { + dev_dbg(&dev->pdev->dev, "amthi client not found.\n"); + return -ENODEV; + } + dev_dbg(&dev->pdev->dev, "checking amthi data\n"); + cb = mei_amthif_find_read_list_entry(dev, file); + + /* Check for if we can block or not*/ + if (cb == NULL && file->f_flags & O_NONBLOCK) + return -EAGAIN; + + + dev_dbg(&dev->pdev->dev, "waiting for amthi data\n"); + while (cb == NULL) { + /* unlock the Mutex */ + mutex_unlock(&dev->device_lock); + + wait_ret = wait_event_interruptible(dev->iamthif_cl.wait, + (cb = mei_amthif_find_read_list_entry(dev, file))); + + if (wait_ret) + return -ERESTARTSYS; + + dev_dbg(&dev->pdev->dev, "woke up from sleep\n"); + + /* Locking again the Mutex */ + mutex_lock(&dev->device_lock); + } + + + dev_dbg(&dev->pdev->dev, "Got amthi data\n"); + dev->iamthif_timer = 0; + + if (cb) { + timeout = cb->read_time + + mei_secs_to_jiffies(MEI_IAMTHIF_READ_TIMER); + dev_dbg(&dev->pdev->dev, "amthi timeout = %lud\n", + timeout); + + if (time_after(jiffies, timeout)) { + dev_dbg(&dev->pdev->dev, "amthi Time out\n"); + /* 15 sec for the message has expired */ + list_del(&cb->list); + rets = -ETIMEDOUT; + goto free; + } + } + /* if the whole message will fit remove it from the list */ + if (cb->buf_idx >= *offset && length >= (cb->buf_idx - *offset)) + list_del(&cb->list); + else if (cb->buf_idx > 0 && cb->buf_idx <= *offset) { + /* end of the message has been reached */ + list_del(&cb->list); + rets = 0; + goto free; + } + /* else means that not full buffer will be read and do not + * remove message from deletion list + */ + + dev_dbg(&dev->pdev->dev, "amthi cb->response_buffer size - %d\n", + cb->response_buffer.size); + dev_dbg(&dev->pdev->dev, "amthi cb->buf_idx - %lu\n", cb->buf_idx); + + /* length is being turncated to PAGE_SIZE, however, + * the buf_idx may point beyond */ + length = min_t(size_t, length, (cb->buf_idx - *offset)); + + if (copy_to_user(ubuf, cb->response_buffer.data + *offset, length)) + rets = -EFAULT; + else { + rets = length; + if ((*offset + length) < cb->buf_idx) { + *offset += length; + goto out; + } + } +free: + dev_dbg(&dev->pdev->dev, "free amthi cb memory.\n"); + *offset = 0; + mei_io_cb_free(cb); +out: + return rets; +} + +/** + * mei_amthif_send_cmd - send amthif command to the ME + * + * @dev: the device structure + * @cb: mei call back struct + * + * returns 0 on success, <0 on failure. + * + */ +static int mei_amthif_send_cmd(struct mei_device *dev, struct mei_cl_cb *cb) +{ + struct mei_msg_hdr mei_hdr; + int ret; + + if (!dev || !cb) + return -ENODEV; + + dev_dbg(&dev->pdev->dev, "write data to amthi client.\n"); + + dev->iamthif_state = MEI_IAMTHIF_WRITING; + dev->iamthif_current_cb = cb; + dev->iamthif_file_object = cb->file_object; + dev->iamthif_canceled = false; + dev->iamthif_ioctl = true; + dev->iamthif_msg_buf_size = cb->request_buffer.size; + memcpy(dev->iamthif_msg_buf, cb->request_buffer.data, + cb->request_buffer.size); + + ret = mei_flow_ctrl_creds(dev, &dev->iamthif_cl); + if (ret < 0) + return ret; + + if (ret && dev->mei_host_buffer_is_empty) { + ret = 0; + dev->mei_host_buffer_is_empty = false; + if (cb->request_buffer.size > mei_hbuf_max_data(dev)) { + mei_hdr.length = mei_hbuf_max_data(dev); + mei_hdr.msg_complete = 0; + } else { + mei_hdr.length = cb->request_buffer.size; + mei_hdr.msg_complete = 1; + } + + mei_hdr.host_addr = dev->iamthif_cl.host_client_id; + mei_hdr.me_addr = dev->iamthif_cl.me_client_id; + mei_hdr.reserved = 0; + dev->iamthif_msg_buf_index += mei_hdr.length; + if (mei_write_message(dev, &mei_hdr, + (unsigned char *)(dev->iamthif_msg_buf), + mei_hdr.length)) + return -ENODEV; + + if (mei_hdr.msg_complete) { + if (mei_flow_ctrl_reduce(dev, &dev->iamthif_cl)) + return -ENODEV; + dev->iamthif_flow_control_pending = true; + dev->iamthif_state = MEI_IAMTHIF_FLOW_CONTROL; + dev_dbg(&dev->pdev->dev, "add amthi cb to write waiting list\n"); + dev->iamthif_current_cb = cb; + dev->iamthif_file_object = cb->file_object; + list_add_tail(&cb->list, &dev->write_waiting_list.list); + } else { + dev_dbg(&dev->pdev->dev, "message does not complete, so add amthi cb to write list.\n"); + list_add_tail(&cb->list, &dev->write_list.list); + } + } else { + if (!(dev->mei_host_buffer_is_empty)) + dev_dbg(&dev->pdev->dev, "host buffer is not empty"); + + dev_dbg(&dev->pdev->dev, "No flow control credentials, so add iamthif cb to write list.\n"); + list_add_tail(&cb->list, &dev->write_list.list); + } + return 0; +} + +/** + * mei_amthif_write - write amthif data to amthif client + * + * @dev: the device structure + * @cb: mei call back struct + * + * returns 0 on success, <0 on failure. + * + */ +int mei_amthif_write(struct mei_device *dev, struct mei_cl_cb *cb) +{ + int ret; + + if (!dev || !cb) + return -ENODEV; + + ret = mei_io_cb_alloc_resp_buf(cb, dev->iamthif_mtu); + if (ret) + return ret; + + cb->fop_type = MEI_FOP_IOCTL; + + if (!list_empty(&dev->amthif_cmd_list.list) || + dev->iamthif_state != MEI_IAMTHIF_IDLE) { + dev_dbg(&dev->pdev->dev, + "amthif state = %d\n", dev->iamthif_state); + dev_dbg(&dev->pdev->dev, "AMTHIF: add cb to the wait list\n"); + list_add_tail(&cb->list, &dev->amthif_cmd_list.list); + return 0; + } + return mei_amthif_send_cmd(dev, cb); +} +/** + * mei_amthif_run_next_cmd + * + * @dev: the device structure + * + * returns 0 on success, <0 on failure. + */ +void mei_amthif_run_next_cmd(struct mei_device *dev) +{ + struct mei_cl_cb *pos = NULL; + struct mei_cl_cb *next = NULL; + int status; + + if (!dev) + return; + + dev->iamthif_msg_buf_size = 0; + dev->iamthif_msg_buf_index = 0; + dev->iamthif_canceled = false; + dev->iamthif_ioctl = true; + dev->iamthif_state = MEI_IAMTHIF_IDLE; + dev->iamthif_timer = 0; + dev->iamthif_file_object = NULL; + + dev_dbg(&dev->pdev->dev, "complete amthi cmd_list cb.\n"); + + list_for_each_entry_safe(pos, next, &dev->amthif_cmd_list.list, list) { + list_del(&pos->list); + + if (pos->cl && pos->cl == &dev->iamthif_cl) { + status = mei_amthif_send_cmd(dev, pos); + if (status) { + dev_dbg(&dev->pdev->dev, + "amthi write failed status = %d\n", + status); + return; + } + break; + } + } +} + + +unsigned int mei_amthif_poll(struct mei_device *dev, + struct file *file, poll_table *wait) +{ + unsigned int mask = 0; + mutex_unlock(&dev->device_lock); + poll_wait(file, &dev->iamthif_cl.wait, wait); + mutex_lock(&dev->device_lock); + if (dev->iamthif_state == MEI_IAMTHIF_READ_COMPLETE && + dev->iamthif_file_object == file) { + mask |= (POLLIN | POLLRDNORM); + dev_dbg(&dev->pdev->dev, "run next amthi cb\n"); + mei_amthif_run_next_cmd(dev); + } + return mask; +} + + + +/** + * mei_amthif_irq_process_completed - processes completed iamthif operation. + * + * @dev: the device structure. + * @slots: free slots. + * @cb_pos: callback block. + * @cl: private data of the file object. + * @cmpl_list: complete list. + * + * returns 0, OK; otherwise, error. + */ +int mei_amthif_irq_process_completed(struct mei_device *dev, s32 *slots, + struct mei_cl_cb *cb_pos, + struct mei_cl *cl, + struct mei_cl_cb *cmpl_list) +{ + struct mei_msg_hdr *mei_hdr; + + if ((*slots * sizeof(u32)) >= (sizeof(struct mei_msg_hdr) + + dev->iamthif_msg_buf_size - + dev->iamthif_msg_buf_index)) { + mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; + mei_hdr->host_addr = cl->host_client_id; + mei_hdr->me_addr = cl->me_client_id; + mei_hdr->length = dev->iamthif_msg_buf_size - + dev->iamthif_msg_buf_index; + mei_hdr->msg_complete = 1; + mei_hdr->reserved = 0; + + *slots -= mei_data2slots(mei_hdr->length); + + if (mei_write_message(dev, mei_hdr, + (dev->iamthif_msg_buf + + dev->iamthif_msg_buf_index), + mei_hdr->length)) { + dev->iamthif_state = MEI_IAMTHIF_IDLE; + cl->status = -ENODEV; + list_del(&cb_pos->list); + return -ENODEV; + } else { + if (mei_flow_ctrl_reduce(dev, cl)) + return -ENODEV; + dev->iamthif_msg_buf_index += mei_hdr->length; + cb_pos->buf_idx = dev->iamthif_msg_buf_index; + cl->status = 0; + dev->iamthif_state = MEI_IAMTHIF_FLOW_CONTROL; + dev->iamthif_flow_control_pending = true; + /* save iamthif cb sent to amthi client */ + dev->iamthif_current_cb = cb_pos; + list_move_tail(&cb_pos->list, + &dev->write_waiting_list.list); + + } + } else if (*slots == dev->hbuf_depth) { + /* buffer is still empty */ + mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; + mei_hdr->host_addr = cl->host_client_id; + mei_hdr->me_addr = cl->me_client_id; + mei_hdr->length = + (*slots * sizeof(u32)) - sizeof(struct mei_msg_hdr); + mei_hdr->msg_complete = 0; + mei_hdr->reserved = 0; + + *slots -= mei_data2slots(mei_hdr->length); + + if (mei_write_message(dev, mei_hdr, + (dev->iamthif_msg_buf + + dev->iamthif_msg_buf_index), + mei_hdr->length)) { + cl->status = -ENODEV; + list_del(&cb_pos->list); + } else { + dev->iamthif_msg_buf_index += mei_hdr->length; + } + return -EMSGSIZE; + } else { + return -EBADMSG; + } + + return 0; +} + +/** + * mei_amthif_irq_read_message - read routine after ISR to + * handle the read amthi message + * + * @complete_list: An instance of our list structure + * @dev: the device structure + * @mei_hdr: header of amthi message + * + * returns 0 on success, <0 on failure. + */ +int mei_amthif_irq_read_message(struct mei_cl_cb *complete_list, + struct mei_device *dev, struct mei_msg_hdr *mei_hdr) +{ + struct mei_cl_cb *cb; + unsigned char *buffer; + + BUG_ON(mei_hdr->me_addr != dev->iamthif_cl.me_client_id); + BUG_ON(dev->iamthif_state != MEI_IAMTHIF_READING); + + buffer = dev->iamthif_msg_buf + dev->iamthif_msg_buf_index; + BUG_ON(dev->iamthif_mtu < dev->iamthif_msg_buf_index + mei_hdr->length); + + mei_read_slots(dev, buffer, mei_hdr->length); + + dev->iamthif_msg_buf_index += mei_hdr->length; + + if (!mei_hdr->msg_complete) + return 0; + + dev_dbg(&dev->pdev->dev, + "amthi_message_buffer_index =%d\n", + mei_hdr->length); + + dev_dbg(&dev->pdev->dev, "completed amthi read.\n "); + if (!dev->iamthif_current_cb) + return -ENODEV; + + cb = dev->iamthif_current_cb; + dev->iamthif_current_cb = NULL; + + if (!cb->cl) + return -ENODEV; + + dev->iamthif_stall_timer = 0; + cb->buf_idx = dev->iamthif_msg_buf_index; + cb->read_time = jiffies; + if (dev->iamthif_ioctl && cb->cl == &dev->iamthif_cl) { + /* found the iamthif cb */ + dev_dbg(&dev->pdev->dev, "complete the amthi read cb.\n "); + dev_dbg(&dev->pdev->dev, "add the amthi read cb to complete.\n "); + list_add_tail(&cb->list, &complete_list->list); + } + return 0; +} + +/** + * mei_amthif_irq_read - prepares to read amthif data. + * + * @dev: the device structure. + * @slots: free slots. + * + * returns 0, OK; otherwise, error. + */ +int mei_amthif_irq_read(struct mei_device *dev, s32 *slots) +{ + + if (((*slots) * sizeof(u32)) < (sizeof(struct mei_msg_hdr) + + sizeof(struct hbm_flow_control))) { + return -EMSGSIZE; + } + *slots -= mei_data2slots(sizeof(struct hbm_flow_control)); + if (mei_send_flow_control(dev, &dev->iamthif_cl)) { + dev_dbg(&dev->pdev->dev, "iamthif flow control failed\n"); + return -EIO; + } + + dev_dbg(&dev->pdev->dev, "iamthif flow control success\n"); + dev->iamthif_state = MEI_IAMTHIF_READING; + dev->iamthif_flow_control_pending = false; + dev->iamthif_msg_buf_index = 0; + dev->iamthif_msg_buf_size = 0; + dev->iamthif_stall_timer = MEI_IAMTHIF_STALL_TIMER; + dev->mei_host_buffer_is_empty = mei_hbuf_is_empty(dev); + return 0; +} + +/** + * mei_amthif_complete - complete amthif callback. + * + * @dev: the device structure. + * @cb_pos: callback block. + */ +void mei_amthif_complete(struct mei_device *dev, struct mei_cl_cb *cb) +{ + if (dev->iamthif_canceled != 1) { + dev->iamthif_state = MEI_IAMTHIF_READ_COMPLETE; + dev->iamthif_stall_timer = 0; + memcpy(cb->response_buffer.data, + dev->iamthif_msg_buf, + dev->iamthif_msg_buf_index); + list_add_tail(&cb->list, &dev->amthif_rd_complete_list.list); + dev_dbg(&dev->pdev->dev, "amthi read completed\n"); + dev->iamthif_timer = jiffies; + dev_dbg(&dev->pdev->dev, "dev->iamthif_timer = %ld\n", + dev->iamthif_timer); + } else { + mei_amthif_run_next_cmd(dev); + } + + dev_dbg(&dev->pdev->dev, "completing amthi call back.\n"); + wake_up_interruptible(&dev->iamthif_cl.wait); +} + +/** + * mei_clear_list - removes all callbacks associated with file + * from mei_cb_list + * + * @dev: device structure. + * @file: file structure + * @mei_cb_list: callbacks list + * + * mei_clear_list is called to clear resources associated with file + * when application calls close function or Ctrl-C was pressed + * + * returns true if callback removed from the list, false otherwise + */ +static bool mei_clear_list(struct mei_device *dev, + const struct file *file, struct list_head *mei_cb_list) +{ + struct mei_cl_cb *cb_pos = NULL; + struct mei_cl_cb *cb_next = NULL; + bool removed = false; + + /* list all list member */ + list_for_each_entry_safe(cb_pos, cb_next, mei_cb_list, list) { + /* check if list member associated with a file */ + if (file == cb_pos->file_object) { + /* remove member from the list */ + list_del(&cb_pos->list); + /* check if cb equal to current iamthif cb */ + if (dev->iamthif_current_cb == cb_pos) { + dev->iamthif_current_cb = NULL; + /* send flow control to iamthif client */ + mei_send_flow_control(dev, &dev->iamthif_cl); + } + /* free all allocated buffers */ + mei_io_cb_free(cb_pos); + cb_pos = NULL; + removed = true; + } + } + return removed; +} + +/** + * mei_clear_lists - removes all callbacks associated with file + * + * @dev: device structure + * @file: file structure + * + * mei_clear_lists is called to clear resources associated with file + * when application calls close function or Ctrl-C was pressed + * + * returns true if callback removed from the list, false otherwise + */ +static bool mei_clear_lists(struct mei_device *dev, struct file *file) +{ + bool removed = false; + + /* remove callbacks associated with a file */ + mei_clear_list(dev, file, &dev->amthif_cmd_list.list); + if (mei_clear_list(dev, file, &dev->amthif_rd_complete_list.list)) + removed = true; + + mei_clear_list(dev, file, &dev->ctrl_rd_list.list); + + if (mei_clear_list(dev, file, &dev->ctrl_wr_list.list)) + removed = true; + + if (mei_clear_list(dev, file, &dev->write_waiting_list.list)) + removed = true; + + if (mei_clear_list(dev, file, &dev->write_list.list)) + removed = true; + + /* check if iamthif_current_cb not NULL */ + if (dev->iamthif_current_cb && !removed) { + /* check file and iamthif current cb association */ + if (dev->iamthif_current_cb->file_object == file) { + /* remove cb */ + mei_io_cb_free(dev->iamthif_current_cb); + dev->iamthif_current_cb = NULL; + removed = true; + } + } + return removed; +} + +/** +* mei_amthif_release - the release function +* +* @inode: pointer to inode structure +* @file: pointer to file structure +* +* returns 0 on success, <0 on error +*/ +int mei_amthif_release(struct mei_device *dev, struct file *file) +{ + if (dev->open_handle_count > 0) + dev->open_handle_count--; + + if (dev->iamthif_file_object == file && + dev->iamthif_state != MEI_IAMTHIF_IDLE) { + + dev_dbg(&dev->pdev->dev, "amthi canceled iamthif state %d\n", + dev->iamthif_state); + dev->iamthif_canceled = true; + if (dev->iamthif_state == MEI_IAMTHIF_READ_COMPLETE) { + dev_dbg(&dev->pdev->dev, "run next amthi iamthif cb\n"); + mei_amthif_run_next_cmd(dev); + } + } + + if (mei_clear_lists(dev, file)) + dev->iamthif_state = MEI_IAMTHIF_IDLE; + + return 0; +} diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h index 9700532f02f6..be8ca6b333ca 100644 --- a/drivers/misc/mei/hw.h +++ b/drivers/misc/mei/hw.h @@ -20,16 +20,16 @@ #include <linux/uuid.h> /* - * Timeouts + * Timeouts in Seconds */ -#define MEI_INTEROP_TIMEOUT (HZ * 7) -#define MEI_CONNECT_TIMEOUT 3 /* at least 2 seconds */ +#define MEI_INTEROP_TIMEOUT 7 /* Timeout on ready message */ +#define MEI_CONNECT_TIMEOUT 3 /* HPS: at least 2 seconds */ -#define CONNECT_TIMEOUT 15 /* HPS definition */ -#define INIT_CLIENTS_TIMEOUT 15 /* HPS definition */ +#define MEI_CL_CONNECT_TIMEOUT 15 /* HPS: Client Connect Timeout */ +#define MEI_CLIENTS_INIT_TIMEOUT 15 /* HPS: Clients Enumeration Timeout */ -#define IAMTHIF_STALL_TIMER 12 /* seconds */ -#define IAMTHIF_READ_TIMER 10000 /* ms */ +#define MEI_IAMTHIF_STALL_TIMER 12 /* HPS */ +#define MEI_IAMTHIF_READ_TIMER 10 /* HPS */ /* * Internal Clients Number @@ -293,6 +293,14 @@ struct hbm_props_response { struct mei_client_properties client_properties; } __packed; +/** + * struct hbm_client_connect_request - connect/disconnect request + * + * @hbm_cmd - bus message command header + * @me_addr - address of the client in ME + * @host_addr - address of the client in the driver + * @reserved + */ struct hbm_client_connect_request { u8 hbm_cmd; u8 me_addr; @@ -300,6 +308,14 @@ struct hbm_client_connect_request { u8 reserved; } __packed; +/** + * struct hbm_client_connect_response - connect/disconnect response + * + * @hbm_cmd - bus message command header + * @me_addr - address of the client in ME + * @host_addr - address of the client in the driver + * @status - status of the request + */ struct hbm_client_connect_response { u8 hbm_cmd; u8 me_addr; @@ -307,12 +323,6 @@ struct hbm_client_connect_response { u8 status; } __packed; -struct hbm_client_disconnect_request { - u8 hbm_cmd; - u8 me_addr; - u8 host_addr; - u8 reserved[1]; -} __packed; #define MEI_FC_MESSAGE_RESERVED_LENGTH 5 diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 98f1430e3e14..4fcb0bb2c9f0 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -43,39 +43,21 @@ const char *mei_dev_state_str(int state) } -const uuid_le mei_amthi_guid = UUID_LE(0x12f80028, 0xb4b7, 0x4b2d, 0xac, - 0xa8, 0x46, 0xe0, 0xff, 0x65, - 0x81, 0x4c); - -/** - * mei_io_list_init - Sets up a queue list. - * - * @list: An instance io list structure - * @dev: the device structure - */ -void mei_io_list_init(struct mei_io_list *list) -{ - /* initialize our queue list */ - INIT_LIST_HEAD(&list->mei_cb.cb_list); -} - /** * mei_io_list_flush - removes list entry belonging to cl. * * @list: An instance of our list structure * @cl: private data of the file object */ -void mei_io_list_flush(struct mei_io_list *list, struct mei_cl *cl) +void mei_io_list_flush(struct mei_cl_cb *list, struct mei_cl *cl) { struct mei_cl_cb *pos; struct mei_cl_cb *next; - list_for_each_entry_safe(pos, next, &list->mei_cb.cb_list, cb_list) { - if (pos->file_private) { - struct mei_cl *cl_tmp; - cl_tmp = (struct mei_cl *)pos->file_private; - if (mei_cl_cmp_id(cl, cl_tmp)) - list_del(&pos->cb_list); + list_for_each_entry_safe(pos, next, &list->list, list) { + if (pos->cl) { + if (mei_cl_cmp_id(cl, pos->cl)) + list_del(&pos->list); } } } @@ -96,31 +78,14 @@ int mei_cl_flush_queues(struct mei_cl *cl) mei_io_list_flush(&cl->dev->write_waiting_list, cl); mei_io_list_flush(&cl->dev->ctrl_wr_list, cl); mei_io_list_flush(&cl->dev->ctrl_rd_list, cl); - mei_io_list_flush(&cl->dev->amthi_cmd_list, cl); - mei_io_list_flush(&cl->dev->amthi_read_complete_list, cl); + mei_io_list_flush(&cl->dev->amthif_cmd_list, cl); + mei_io_list_flush(&cl->dev->amthif_rd_complete_list, cl); return 0; } /** - * mei_reset_iamthif_params - initializes mei device iamthif - * - * @dev: the device structure - */ -static void mei_reset_iamthif_params(struct mei_device *dev) -{ - /* reset iamthif parameters. */ - dev->iamthif_current_cb = NULL; - dev->iamthif_msg_buf_size = 0; - dev->iamthif_msg_buf_index = 0; - dev->iamthif_canceled = false; - dev->iamthif_ioctl = false; - dev->iamthif_state = MEI_IAMTHIF_IDLE; - dev->iamthif_timer = 0; -} - -/** * init_mei_device - allocates and initializes the mei device structure * * @pdev: The pci device structure @@ -144,16 +109,14 @@ struct mei_device *mei_device_init(struct pci_dev *pdev) init_waitqueue_head(&dev->wait_stop_wd); dev->dev_state = MEI_DEV_INITIALIZING; dev->iamthif_state = MEI_IAMTHIF_IDLE; - dev->wd_interface_reg = false; - mei_io_list_init(&dev->read_list); mei_io_list_init(&dev->write_list); mei_io_list_init(&dev->write_waiting_list); mei_io_list_init(&dev->ctrl_wr_list); mei_io_list_init(&dev->ctrl_rd_list); - mei_io_list_init(&dev->amthi_cmd_list); - mei_io_list_init(&dev->amthi_read_complete_list); + mei_io_list_init(&dev->amthif_cmd_list); + mei_io_list_init(&dev->amthif_rd_complete_list); dev->pdev = pdev; return dev; } @@ -196,7 +159,8 @@ int mei_hw_init(struct mei_device *dev) if (!dev->recvd_msg) { mutex_unlock(&dev->device_lock); err = wait_event_interruptible_timeout(dev->wait_recvd_msg, - dev->recvd_msg, MEI_INTEROP_TIMEOUT); + dev->recvd_msg, + mei_secs_to_jiffies(MEI_INTEROP_TIMEOUT)); mutex_lock(&dev->device_lock); } @@ -317,14 +281,12 @@ void mei_reset(struct mei_device *dev, int interrupts_enabled) cl_pos->timer_count = 0; } /* remove entry if already in list */ - dev_dbg(&dev->pdev->dev, "list del iamthif and wd file list.\n"); - mei_remove_client_from_file_list(dev, - dev->wd_cl.host_client_id); + dev_dbg(&dev->pdev->dev, "remove iamthif and wd from the file list.\n"); + mei_me_cl_unlink(dev, &dev->wd_cl); - mei_remove_client_from_file_list(dev, - dev->iamthif_cl.host_client_id); + mei_me_cl_unlink(dev, &dev->iamthif_cl); - mei_reset_iamthif_params(dev); + mei_amthif_reset_params(dev); dev->extra_write_index = 0; } @@ -351,10 +313,9 @@ void mei_reset(struct mei_device *dev, int interrupts_enabled) } } /* remove all waiting requests */ - list_for_each_entry_safe(cb_pos, cb_next, - &dev->write_list.mei_cb.cb_list, cb_list) { - list_del(&cb_pos->cb_list); - mei_free_cb_private(cb_pos); + list_for_each_entry_safe(cb_pos, cb_next, &dev->write_list.list, list) { + list_del(&cb_pos->list); + mei_io_cb_free(cb_pos); } } @@ -394,7 +355,7 @@ void mei_host_start_message(struct mei_device *dev) mei_reset(dev, 1); } dev->init_clients_state = MEI_START_MESSAGE; - dev->init_clients_timer = INIT_CLIENTS_TIMEOUT; + dev->init_clients_timer = MEI_CLIENTS_INIT_TIMEOUT; return ; } @@ -427,7 +388,7 @@ void mei_host_enum_clients_message(struct mei_device *dev) mei_reset(dev, 1); } dev->init_clients_state = MEI_ENUM_CLIENTS_MESSAGE; - dev->init_clients_timer = INIT_CLIENTS_TIMEOUT; + dev->init_clients_timer = MEI_CLIENTS_INIT_TIMEOUT; return; } @@ -515,7 +476,7 @@ int mei_host_client_properties(struct mei_device *dev) return -EIO; } - dev->init_clients_timer = INIT_CLIENTS_TIMEOUT; + dev->init_clients_timer = MEI_CLIENTS_INIT_TIMEOUT; dev->me_client_index = b; return 1; } @@ -557,17 +518,20 @@ int mei_me_cl_by_uuid(const struct mei_device *dev, const uuid_le *cuuid) /** - * mei_me_cl_update_filext - searches for ME client guid - * sets client_id in mei_file_private if found + * mei_me_cl_link - create link between host and me clinet and add + * me_cl to the list + * * @dev: the device structure - * @cl: private file structure to set client_id in - * @cuuid: searched uuid of ME client - * @client_id: id of host client to be set in file private structure + * @cl: link between me and host client assocated with opened file descriptor + * @cuuid: uuid of ME client + * @client_id: id of the host client * - * returns ME client index + * returns ME client index if ME client + * -EINVAL on incorrect values + * -ENONET if client not found */ -int mei_me_cl_update_filext(struct mei_device *dev, struct mei_cl *cl, - const uuid_le *cuuid, u8 host_cl_id) +int mei_me_cl_link(struct mei_device *dev, struct mei_cl *cl, + const uuid_le *cuuid, u8 host_cl_id) { int i; @@ -587,54 +551,22 @@ int mei_me_cl_update_filext(struct mei_device *dev, struct mei_cl *cl, return -ENOENT; } - /** - * host_init_iamthif - mei initialization iamthif client. + * mei_me_cl_unlink - remove me_cl from the list * * @dev: the device structure - * + * @host_client_id: host client id to be removed */ -void mei_host_init_iamthif(struct mei_device *dev) +void mei_me_cl_unlink(struct mei_device *dev, struct mei_cl *cl) { - int i; - unsigned char *msg_buf; - - mei_cl_init(&dev->iamthif_cl, dev); - dev->iamthif_cl.state = MEI_FILE_DISCONNECTED; - - /* find ME amthi client */ - i = mei_me_cl_update_filext(dev, &dev->iamthif_cl, - &mei_amthi_guid, MEI_IAMTHIF_HOST_CLIENT_ID); - if (i < 0) { - dev_dbg(&dev->pdev->dev, "failed to find iamthif client.\n"); - return; - } - - /* Assign iamthif_mtu to the value received from ME */ - - dev->iamthif_mtu = dev->me_clients[i].props.max_msg_length; - dev_dbg(&dev->pdev->dev, "IAMTHIF_MTU = %d\n", - dev->me_clients[i].props.max_msg_length); - - kfree(dev->iamthif_msg_buf); - dev->iamthif_msg_buf = NULL; - - /* allocate storage for ME message buffer */ - msg_buf = kcalloc(dev->iamthif_mtu, - sizeof(unsigned char), GFP_KERNEL); - if (!msg_buf) { - dev_dbg(&dev->pdev->dev, "memory allocation for ME message buffer failed.\n"); - return; - } - - dev->iamthif_msg_buf = msg_buf; - - if (mei_connect(dev, &dev->iamthif_cl)) { - dev_dbg(&dev->pdev->dev, "Failed to connect to AMTHI client\n"); - dev->iamthif_cl.state = MEI_FILE_DISCONNECTED; - dev->iamthif_cl.host_client_id = 0; - } else { - dev->iamthif_cl.timer_count = CONNECT_TIMEOUT; + struct mei_cl *pos, *next; + list_for_each_entry_safe(pos, next, &dev->file_list, link) { + if (cl->host_client_id == pos->host_client_id) { + dev_dbg(&dev->pdev->dev, "remove host client = %d, ME client = %d\n", + pos->host_client_id, pos->me_client_id); + list_del_init(&pos->link); + break; + } } } @@ -671,9 +603,8 @@ struct mei_cl *mei_cl_allocate(struct mei_device *dev) */ int mei_disconnect_host_client(struct mei_device *dev, struct mei_cl *cl) { - int rets, err; - long timeout = 15; /* 15 seconds */ struct mei_cl_cb *cb; + int rets, err; if (!dev || !cl) return -ENODEV; @@ -681,13 +612,11 @@ int mei_disconnect_host_client(struct mei_device *dev, struct mei_cl *cl) if (cl->state != MEI_FILE_DISCONNECTING) return 0; - cb = kzalloc(sizeof(struct mei_cl_cb), GFP_KERNEL); + cb = mei_io_cb_init(cl, NULL); if (!cb) return -ENOMEM; - INIT_LIST_HEAD(&cb->cb_list); - cb->file_private = cl; - cb->major_file_operations = MEI_CLOSE; + cb->fop_type = MEI_FOP_CLOSE; if (dev->mei_host_buffer_is_empty) { dev->mei_host_buffer_is_empty = false; if (mei_disconnect(dev, cl)) { @@ -696,17 +625,17 @@ int mei_disconnect_host_client(struct mei_device *dev, struct mei_cl *cl) goto free; } mdelay(10); /* Wait for hardware disconnection ready */ - list_add_tail(&cb->cb_list, &dev->ctrl_rd_list.mei_cb.cb_list); + list_add_tail(&cb->list, &dev->ctrl_rd_list.list); } else { dev_dbg(&dev->pdev->dev, "add disconnect cb to control write list\n"); - list_add_tail(&cb->cb_list, - &dev->ctrl_wr_list.mei_cb.cb_list); + list_add_tail(&cb->list, &dev->ctrl_wr_list.list); + } mutex_unlock(&dev->device_lock); err = wait_event_timeout(dev->wait_recvd_msg, - (MEI_FILE_DISCONNECTED == cl->state), - timeout * HZ); + MEI_FILE_DISCONNECTED == cl->state, + mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT)); mutex_lock(&dev->device_lock); if (MEI_FILE_DISCONNECTED == cl->state) { @@ -728,29 +657,7 @@ int mei_disconnect_host_client(struct mei_device *dev, struct mei_cl *cl) mei_io_list_flush(&dev->ctrl_rd_list, cl); mei_io_list_flush(&dev->ctrl_wr_list, cl); free: - mei_free_cb_private(cb); + mei_io_cb_free(cb); return rets; } -/** - * mei_remove_client_from_file_list - - * removes file private data from device file list - * - * @dev: the device structure - * @host_client_id: host client id to be removed - */ -void mei_remove_client_from_file_list(struct mei_device *dev, - u8 host_client_id) -{ - struct mei_cl *cl_pos = NULL; - struct mei_cl *cl_next = NULL; - list_for_each_entry_safe(cl_pos, cl_next, &dev->file_list, link) { - if (host_client_id == cl_pos->host_client_id) { - dev_dbg(&dev->pdev->dev, "remove host client = %d, ME client = %d\n", - cl_pos->host_client_id, - cl_pos->me_client_id); - list_del_init(&cl_pos->link); - break; - } - } -} diff --git a/drivers/misc/mei/interface.c b/drivers/misc/mei/interface.c index 509c3957ff45..6b50cf0253e5 100644 --- a/drivers/misc/mei/interface.c +++ b/drivers/misc/mei/interface.c @@ -352,26 +352,24 @@ int mei_other_client_is_connecting(struct mei_device *dev, int mei_disconnect(struct mei_device *dev, struct mei_cl *cl) { struct mei_msg_hdr *mei_hdr; - struct hbm_client_disconnect_request *mei_cli_disconnect; + struct hbm_client_connect_request *req; mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; mei_hdr->host_addr = 0; mei_hdr->me_addr = 0; - mei_hdr->length = sizeof(struct hbm_client_disconnect_request); + mei_hdr->length = sizeof(struct hbm_client_connect_request); mei_hdr->msg_complete = 1; mei_hdr->reserved = 0; - mei_cli_disconnect = - (struct hbm_client_disconnect_request *) &dev->wr_msg_buf[1]; - memset(mei_cli_disconnect, 0, sizeof(*mei_cli_disconnect)); - mei_cli_disconnect->host_addr = cl->host_client_id; - mei_cli_disconnect->me_addr = cl->me_client_id; - mei_cli_disconnect->hbm_cmd = CLIENT_DISCONNECT_REQ_CMD; - mei_cli_disconnect->reserved[0] = 0; + req = (struct hbm_client_connect_request *)&dev->wr_msg_buf[1]; + memset(req, 0, sizeof(*req)); + req->host_addr = cl->host_client_id; + req->me_addr = cl->me_client_id; + req->hbm_cmd = CLIENT_DISCONNECT_REQ_CMD; + req->reserved = 0; - return mei_write_message(dev, mei_hdr, - (unsigned char *) mei_cli_disconnect, - sizeof(struct hbm_client_disconnect_request)); + return mei_write_message(dev, mei_hdr, (unsigned char *)req, + sizeof(struct hbm_client_connect_request)); } /** diff --git a/drivers/misc/mei/interrupt.c b/drivers/misc/mei/interrupt.c index 3533edde04a5..f8821015f3f6 100644 --- a/drivers/misc/mei/interrupt.c +++ b/drivers/misc/mei/interrupt.c @@ -57,14 +57,14 @@ irqreturn_t mei_interrupt_quick_handler(int irq, void *dev_id) */ static void _mei_cmpl(struct mei_cl *cl, struct mei_cl_cb *cb_pos) { - if (cb_pos->major_file_operations == MEI_WRITE) { - mei_free_cb_private(cb_pos); + if (cb_pos->fop_type == MEI_FOP_WRITE) { + mei_io_cb_free(cb_pos); cb_pos = NULL; cl->writing_state = MEI_WRITE_COMPLETE; if (waitqueue_active(&cl->tx_wait)) wake_up_interruptible(&cl->tx_wait); - } else if (cb_pos->major_file_operations == MEI_READ && + } else if (cb_pos->fop_type == MEI_FOP_READ && MEI_READING == cl->reading_state) { cl->reading_state = MEI_READ_COMPLETE; if (waitqueue_active(&cl->rx_wait)) @@ -74,94 +74,6 @@ static void _mei_cmpl(struct mei_cl *cl, struct mei_cl_cb *cb_pos) } /** - * _mei_cmpl_iamthif - processes completed iamthif operation. - * - * @dev: the device structure. - * @cb_pos: callback block. - */ -static void _mei_cmpl_iamthif(struct mei_device *dev, struct mei_cl_cb *cb_pos) -{ - if (dev->iamthif_canceled != 1) { - dev->iamthif_state = MEI_IAMTHIF_READ_COMPLETE; - dev->iamthif_stall_timer = 0; - memcpy(cb_pos->response_buffer.data, - dev->iamthif_msg_buf, - dev->iamthif_msg_buf_index); - list_add_tail(&cb_pos->cb_list, - &dev->amthi_read_complete_list.mei_cb.cb_list); - dev_dbg(&dev->pdev->dev, "amthi read completed.\n"); - dev->iamthif_timer = jiffies; - dev_dbg(&dev->pdev->dev, "dev->iamthif_timer = %ld\n", - dev->iamthif_timer); - } else { - mei_run_next_iamthif_cmd(dev); - } - - dev_dbg(&dev->pdev->dev, "completing amthi call back.\n"); - wake_up_interruptible(&dev->iamthif_cl.wait); -} - - -/** - * mei_irq_thread_read_amthi_message - bottom half read routine after ISR to - * handle the read amthi message data processing. - * - * @complete_list: An instance of our list structure - * @dev: the device structure - * @mei_hdr: header of amthi message - * - * returns 0 on success, <0 on failure. - */ -static int mei_irq_thread_read_amthi_message(struct mei_io_list *complete_list, - struct mei_device *dev, - struct mei_msg_hdr *mei_hdr) -{ - struct mei_cl *cl; - struct mei_cl_cb *cb; - unsigned char *buffer; - - BUG_ON(mei_hdr->me_addr != dev->iamthif_cl.me_client_id); - BUG_ON(dev->iamthif_state != MEI_IAMTHIF_READING); - - buffer = dev->iamthif_msg_buf + dev->iamthif_msg_buf_index; - BUG_ON(dev->iamthif_mtu < dev->iamthif_msg_buf_index + mei_hdr->length); - - mei_read_slots(dev, buffer, mei_hdr->length); - - dev->iamthif_msg_buf_index += mei_hdr->length; - - if (!mei_hdr->msg_complete) - return 0; - - dev_dbg(&dev->pdev->dev, - "amthi_message_buffer_index =%d\n", - mei_hdr->length); - - dev_dbg(&dev->pdev->dev, "completed amthi read.\n "); - if (!dev->iamthif_current_cb) - return -ENODEV; - - cb = dev->iamthif_current_cb; - dev->iamthif_current_cb = NULL; - - cl = (struct mei_cl *)cb->file_private; - if (!cl) - return -ENODEV; - - dev->iamthif_stall_timer = 0; - cb->information = dev->iamthif_msg_buf_index; - cb->read_time = jiffies; - if (dev->iamthif_ioctl && cl == &dev->iamthif_cl) { - /* found the iamthif cb */ - dev_dbg(&dev->pdev->dev, "complete the amthi read cb.\n "); - dev_dbg(&dev->pdev->dev, "add the amthi read cb to complete.\n "); - list_add_tail(&cb->cb_list, - &complete_list->mei_cb.cb_list); - } - return 0; -} - -/** * _mei_irq_thread_state_ok - checks if mei header matches file private data * * @cl: private data of the file object @@ -188,7 +100,7 @@ static int _mei_irq_thread_state_ok(struct mei_cl *cl, * * returns 0 on success, <0 on failure. */ -static int mei_irq_thread_read_client_message(struct mei_io_list *complete_list, +static int mei_irq_thread_read_client_message(struct mei_cl_cb *complete_list, struct mei_device *dev, struct mei_msg_hdr *mei_hdr) { @@ -197,36 +109,36 @@ static int mei_irq_thread_read_client_message(struct mei_io_list *complete_list, unsigned char *buffer = NULL; dev_dbg(&dev->pdev->dev, "start client msg\n"); - if (list_empty(&dev->read_list.mei_cb.cb_list)) + if (list_empty(&dev->read_list.list)) goto quit; - list_for_each_entry_safe(cb_pos, cb_next, - &dev->read_list.mei_cb.cb_list, cb_list) { - cl = (struct mei_cl *)cb_pos->file_private; + list_for_each_entry_safe(cb_pos, cb_next, &dev->read_list.list, list) { + cl = cb_pos->cl; if (cl && _mei_irq_thread_state_ok(cl, mei_hdr)) { cl->reading_state = MEI_READING; - buffer = cb_pos->response_buffer.data + cb_pos->information; + buffer = cb_pos->response_buffer.data + cb_pos->buf_idx; if (cb_pos->response_buffer.size < - mei_hdr->length + cb_pos->information) { + mei_hdr->length + cb_pos->buf_idx) { dev_dbg(&dev->pdev->dev, "message overflow.\n"); - list_del(&cb_pos->cb_list); + list_del(&cb_pos->list); return -ENOMEM; } if (buffer) mei_read_slots(dev, buffer, mei_hdr->length); - cb_pos->information += mei_hdr->length; + cb_pos->buf_idx += mei_hdr->length; if (mei_hdr->msg_complete) { cl->status = 0; - list_del(&cb_pos->cb_list); + list_del(&cb_pos->list); dev_dbg(&dev->pdev->dev, "completed read H cl = %d, ME cl = %d, length = %lu\n", cl->host_client_id, cl->me_client_id, - cb_pos->information); - list_add_tail(&cb_pos->cb_list, - &complete_list->mei_cb.cb_list); + cb_pos->buf_idx); + + list_add_tail(&cb_pos->list, + &complete_list->list); } break; @@ -246,37 +158,6 @@ quit: } /** - * _mei_irq_thread_iamthif_read - prepares to read iamthif data. - * - * @dev: the device structure. - * @slots: free slots. - * - * returns 0, OK; otherwise, error. - */ -static int _mei_irq_thread_iamthif_read(struct mei_device *dev, s32 *slots) -{ - - if (((*slots) * sizeof(u32)) < (sizeof(struct mei_msg_hdr) - + sizeof(struct hbm_flow_control))) { - return -EMSGSIZE; - } - *slots -= mei_data2slots(sizeof(struct hbm_flow_control)); - if (mei_send_flow_control(dev, &dev->iamthif_cl)) { - dev_dbg(&dev->pdev->dev, "iamthif flow control failed\n"); - return -EIO; - } - - dev_dbg(&dev->pdev->dev, "iamthif flow control success\n"); - dev->iamthif_state = MEI_IAMTHIF_READING; - dev->iamthif_flow_control_pending = false; - dev->iamthif_msg_buf_index = 0; - dev->iamthif_msg_buf_size = 0; - dev->iamthif_stall_timer = IAMTHIF_STALL_TIMER; - dev->mei_host_buffer_is_empty = mei_hbuf_is_empty(dev); - return 0; -} - -/** * _mei_irq_thread_close - processes close related operation. * * @dev: the device structure. @@ -290,26 +171,24 @@ static int _mei_irq_thread_iamthif_read(struct mei_device *dev, s32 *slots) static int _mei_irq_thread_close(struct mei_device *dev, s32 *slots, struct mei_cl_cb *cb_pos, struct mei_cl *cl, - struct mei_io_list *cmpl_list) + struct mei_cl_cb *cmpl_list) { if ((*slots * sizeof(u32)) < (sizeof(struct mei_msg_hdr) + - sizeof(struct hbm_client_disconnect_request))) + sizeof(struct hbm_client_connect_request))) return -EBADMSG; - *slots -= mei_data2slots(sizeof(struct hbm_client_disconnect_request)); + *slots -= mei_data2slots(sizeof(struct hbm_client_connect_request)); if (mei_disconnect(dev, cl)) { cl->status = 0; - cb_pos->information = 0; - list_move_tail(&cb_pos->cb_list, - &cmpl_list->mei_cb.cb_list); + cb_pos->buf_idx = 0; + list_move_tail(&cb_pos->list, &cmpl_list->list); return -EMSGSIZE; } else { cl->state = MEI_FILE_DISCONNECTING; cl->status = 0; - cb_pos->information = 0; - list_move_tail(&cb_pos->cb_list, - &dev->ctrl_rd_list.mei_cb.cb_list); + cb_pos->buf_idx = 0; + list_move_tail(&cb_pos->list, &dev->ctrl_rd_list.list); cl->timer_count = MEI_CONNECT_TIMEOUT; } @@ -356,7 +235,7 @@ static void mei_client_connect_response(struct mei_device *dev, { struct mei_cl *cl; - struct mei_cl_cb *cb_pos = NULL, *cb_next = NULL; + struct mei_cl_cb *pos = NULL, *next = NULL; dev_dbg(&dev->pdev->dev, "connect_response:\n" @@ -374,7 +253,7 @@ static void mei_client_connect_response(struct mei_device *dev, mei_watchdog_register(dev); /* next step in the state maching */ - mei_host_init_iamthif(dev); + mei_amthif_host_init(dev); return; } @@ -382,17 +261,16 @@ static void mei_client_connect_response(struct mei_device *dev, dev->iamthif_state = MEI_IAMTHIF_IDLE; return; } - list_for_each_entry_safe(cb_pos, cb_next, - &dev->ctrl_rd_list.mei_cb.cb_list, cb_list) { + list_for_each_entry_safe(pos, next, &dev->ctrl_rd_list.list, list) { - cl = (struct mei_cl *)cb_pos->file_private; + cl = pos->cl; if (!cl) { - list_del(&cb_pos->cb_list); + list_del(&pos->list); return; } - if (MEI_IOCTL == cb_pos->major_file_operations) { + if (pos->fop_type == MEI_FOP_IOCTL) { if (is_treat_specially_client(cl, rs)) { - list_del(&cb_pos->cb_list); + list_del(&pos->list); cl->status = 0; cl->timer_count = 0; break; @@ -411,7 +289,7 @@ static void mei_client_disconnect_response(struct mei_device *dev, struct hbm_client_connect_response *rs) { struct mei_cl *cl; - struct mei_cl_cb *cb_pos = NULL, *cb_next = NULL; + struct mei_cl_cb *pos = NULL, *next = NULL; dev_dbg(&dev->pdev->dev, "disconnect_response:\n" @@ -422,12 +300,11 @@ static void mei_client_disconnect_response(struct mei_device *dev, rs->host_addr, rs->status); - list_for_each_entry_safe(cb_pos, cb_next, - &dev->ctrl_rd_list.mei_cb.cb_list, cb_list) { - cl = (struct mei_cl *)cb_pos->file_private; + list_for_each_entry_safe(pos, next, &dev->ctrl_rd_list.list, list) { + cl = pos->cl; if (!cl) { - list_del(&cb_pos->cb_list); + list_del(&pos->list); return; } @@ -435,7 +312,7 @@ static void mei_client_disconnect_response(struct mei_device *dev, if (cl->host_client_id == rs->host_addr && cl->me_client_id == rs->me_addr) { - list_del(&cb_pos->cb_list); + list_del(&pos->list); if (!rs->status) cl->state = MEI_FILE_DISCONNECTED; @@ -537,10 +414,10 @@ static void mei_client_flow_control_response(struct mei_device *dev, * returns !=0, same; 0,not. */ static int same_disconn_addr(struct mei_cl *cl, - struct hbm_client_disconnect_request *disconn) + struct hbm_client_connect_request *req) { - return (cl->host_client_id == disconn->host_addr && - cl->me_client_id == disconn->me_addr); + return (cl->host_client_id == req->host_addr && + cl->me_client_id == req->me_addr); } /** @@ -550,7 +427,7 @@ static int same_disconn_addr(struct mei_cl *cl, * @disconnect_req: disconnect request bus message. */ static void mei_client_disconnect_request(struct mei_device *dev, - struct hbm_client_disconnect_request *disconnect_req) + struct hbm_client_connect_request *disconnect_req) { struct mei_msg_hdr *mei_hdr; struct hbm_client_connect_response *disconnect_res; @@ -607,10 +484,10 @@ static void mei_irq_thread_read_bus_message(struct mei_device *dev, struct hbm_host_version_response *version_res; struct hbm_client_connect_response *connect_res; struct hbm_client_connect_response *disconnect_res; + struct hbm_client_connect_request *disconnect_req; struct hbm_flow_control *flow_control; struct hbm_props_response *props_res; struct hbm_host_enum_response *enum_res; - struct hbm_client_disconnect_request *disconnect_req; struct hbm_host_stop_request *host_stop_req; int res; @@ -734,7 +611,7 @@ static void mei_irq_thread_read_bus_message(struct mei_device *dev, * will be received */ if (mei_wd_host_init(dev)) - mei_host_init_iamthif(dev); + mei_amthif_host_init(dev); } } else { @@ -776,8 +653,7 @@ static void mei_irq_thread_read_bus_message(struct mei_device *dev, case CLIENT_DISCONNECT_REQ_CMD: /* search for client */ - disconnect_req = - (struct hbm_client_disconnect_request *) mei_msg; + disconnect_req = (struct hbm_client_connect_request *)mei_msg; mei_client_disconnect_request(dev, disconnect_req); break; @@ -821,12 +697,12 @@ static void mei_irq_thread_read_bus_message(struct mei_device *dev, static int _mei_irq_thread_read(struct mei_device *dev, s32 *slots, struct mei_cl_cb *cb_pos, struct mei_cl *cl, - struct mei_io_list *cmpl_list) + struct mei_cl_cb *cmpl_list) { if ((*slots * sizeof(u32)) < (sizeof(struct mei_msg_hdr) + sizeof(struct hbm_flow_control))) { /* return the cancel routine */ - list_del(&cb_pos->cb_list); + list_del(&cb_pos->list); return -EBADMSG; } @@ -834,11 +710,11 @@ static int _mei_irq_thread_read(struct mei_device *dev, s32 *slots, if (mei_send_flow_control(dev, cl)) { cl->status = -ENODEV; - cb_pos->information = 0; - list_move_tail(&cb_pos->cb_list, &cmpl_list->mei_cb.cb_list); + cb_pos->buf_idx = 0; + list_move_tail(&cb_pos->list, &cmpl_list->list); return -ENODEV; } - list_move_tail(&cb_pos->cb_list, &dev->read_list.mei_cb.cb_list); + list_move_tail(&cb_pos->list, &dev->read_list.list); return 0; } @@ -858,12 +734,12 @@ static int _mei_irq_thread_read(struct mei_device *dev, s32 *slots, static int _mei_irq_thread_ioctl(struct mei_device *dev, s32 *slots, struct mei_cl_cb *cb_pos, struct mei_cl *cl, - struct mei_io_list *cmpl_list) + struct mei_cl_cb *cmpl_list) { if ((*slots * sizeof(u32)) < (sizeof(struct mei_msg_hdr) + sizeof(struct hbm_client_connect_request))) { /* return the cancel routine */ - list_del(&cb_pos->cb_list); + list_del(&cb_pos->list); return -EBADMSG; } @@ -871,12 +747,11 @@ static int _mei_irq_thread_ioctl(struct mei_device *dev, s32 *slots, *slots -= mei_data2slots(sizeof(struct hbm_client_connect_request)); if (mei_connect(dev, cl)) { cl->status = -ENODEV; - cb_pos->information = 0; - list_del(&cb_pos->cb_list); + cb_pos->buf_idx = 0; + list_del(&cb_pos->list); return -ENODEV; } else { - list_move_tail(&cb_pos->cb_list, - &dev->ctrl_rd_list.mei_cb.cb_list); + list_move_tail(&cb_pos->list, &dev->ctrl_rd_list.list); cl->timer_count = MEI_CONNECT_TIMEOUT; } return 0; @@ -896,45 +771,41 @@ static int _mei_irq_thread_ioctl(struct mei_device *dev, s32 *slots, static int _mei_irq_thread_cmpl(struct mei_device *dev, s32 *slots, struct mei_cl_cb *cb_pos, struct mei_cl *cl, - struct mei_io_list *cmpl_list) + struct mei_cl_cb *cmpl_list) { struct mei_msg_hdr *mei_hdr; if ((*slots * sizeof(u32)) >= (sizeof(struct mei_msg_hdr) + - (cb_pos->request_buffer.size - - cb_pos->information))) { + (cb_pos->request_buffer.size - cb_pos->buf_idx))) { mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; mei_hdr->host_addr = cl->host_client_id; mei_hdr->me_addr = cl->me_client_id; - mei_hdr->length = cb_pos->request_buffer.size - - cb_pos->information; + mei_hdr->length = cb_pos->request_buffer.size - cb_pos->buf_idx; mei_hdr->msg_complete = 1; mei_hdr->reserved = 0; dev_dbg(&dev->pdev->dev, "cb_pos->request_buffer.size =%d" "mei_hdr->msg_complete = %d\n", cb_pos->request_buffer.size, mei_hdr->msg_complete); - dev_dbg(&dev->pdev->dev, "cb_pos->information =%lu\n", - cb_pos->information); + dev_dbg(&dev->pdev->dev, "cb_pos->buf_idx =%lu\n", + cb_pos->buf_idx); dev_dbg(&dev->pdev->dev, "mei_hdr->length =%d\n", mei_hdr->length); *slots -= mei_data2slots(mei_hdr->length); if (mei_write_message(dev, mei_hdr, (unsigned char *) (cb_pos->request_buffer.data + - cb_pos->information), + cb_pos->buf_idx), mei_hdr->length)) { cl->status = -ENODEV; - list_move_tail(&cb_pos->cb_list, - &cmpl_list->mei_cb.cb_list); + list_move_tail(&cb_pos->list, &cmpl_list->list); return -ENODEV; } else { if (mei_flow_ctrl_reduce(dev, cl)) return -ENODEV; cl->status = 0; - cb_pos->information += mei_hdr->length; - list_move_tail(&cb_pos->cb_list, - &dev->write_waiting_list.mei_cb.cb_list); + cb_pos->buf_idx += mei_hdr->length; + list_move_tail(&cb_pos->list, &dev->write_waiting_list.list); } } else if (*slots == dev->hbuf_depth) { /* buffer is still empty */ @@ -949,21 +820,20 @@ static int _mei_irq_thread_cmpl(struct mei_device *dev, s32 *slots, if (mei_write_message(dev, mei_hdr, (unsigned char *) (cb_pos->request_buffer.data + - cb_pos->information), + cb_pos->buf_idx), mei_hdr->length)) { cl->status = -ENODEV; - list_move_tail(&cb_pos->cb_list, - &cmpl_list->mei_cb.cb_list); + list_move_tail(&cb_pos->list, &cmpl_list->list); return -ENODEV; } else { - cb_pos->information += mei_hdr->length; + cb_pos->buf_idx += mei_hdr->length; dev_dbg(&dev->pdev->dev, "cb_pos->request_buffer.size =%d" " mei_hdr->msg_complete = %d\n", cb_pos->request_buffer.size, mei_hdr->msg_complete); - dev_dbg(&dev->pdev->dev, "cb_pos->information =%lu\n", - cb_pos->information); + dev_dbg(&dev->pdev->dev, "cb_pos->buf_idx =%lu\n", + cb_pos->buf_idx); dev_dbg(&dev->pdev->dev, "mei_hdr->length =%d\n", mei_hdr->length); } @@ -976,88 +846,6 @@ static int _mei_irq_thread_cmpl(struct mei_device *dev, s32 *slots, } /** - * _mei_irq_thread_cmpl_iamthif - processes completed iamthif operation. - * - * @dev: the device structure. - * @slots: free slots. - * @cb_pos: callback block. - * @cl: private data of the file object. - * @cmpl_list: complete list. - * - * returns 0, OK; otherwise, error. - */ -static int _mei_irq_thread_cmpl_iamthif(struct mei_device *dev, s32 *slots, - struct mei_cl_cb *cb_pos, - struct mei_cl *cl, - struct mei_io_list *cmpl_list) -{ - struct mei_msg_hdr *mei_hdr; - - if ((*slots * sizeof(u32)) >= (sizeof(struct mei_msg_hdr) + - dev->iamthif_msg_buf_size - - dev->iamthif_msg_buf_index)) { - mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; - mei_hdr->host_addr = cl->host_client_id; - mei_hdr->me_addr = cl->me_client_id; - mei_hdr->length = dev->iamthif_msg_buf_size - - dev->iamthif_msg_buf_index; - mei_hdr->msg_complete = 1; - mei_hdr->reserved = 0; - - *slots -= mei_data2slots(mei_hdr->length); - - if (mei_write_message(dev, mei_hdr, - (dev->iamthif_msg_buf + - dev->iamthif_msg_buf_index), - mei_hdr->length)) { - dev->iamthif_state = MEI_IAMTHIF_IDLE; - cl->status = -ENODEV; - list_del(&cb_pos->cb_list); - return -ENODEV; - } else { - if (mei_flow_ctrl_reduce(dev, cl)) - return -ENODEV; - dev->iamthif_msg_buf_index += mei_hdr->length; - cb_pos->information = dev->iamthif_msg_buf_index; - cl->status = 0; - dev->iamthif_state = MEI_IAMTHIF_FLOW_CONTROL; - dev->iamthif_flow_control_pending = true; - /* save iamthif cb sent to amthi client */ - dev->iamthif_current_cb = cb_pos; - list_move_tail(&cb_pos->cb_list, - &dev->write_waiting_list.mei_cb.cb_list); - - } - } else if (*slots == dev->hbuf_depth) { - /* buffer is still empty */ - mei_hdr = (struct mei_msg_hdr *) &dev->wr_msg_buf[0]; - mei_hdr->host_addr = cl->host_client_id; - mei_hdr->me_addr = cl->me_client_id; - mei_hdr->length = - (*slots * sizeof(u32)) - sizeof(struct mei_msg_hdr); - mei_hdr->msg_complete = 0; - mei_hdr->reserved = 0; - - *slots -= mei_data2slots(mei_hdr->length); - - if (mei_write_message(dev, mei_hdr, - (dev->iamthif_msg_buf + - dev->iamthif_msg_buf_index), - mei_hdr->length)) { - cl->status = -ENODEV; - list_del(&cb_pos->cb_list); - } else { - dev->iamthif_msg_buf_index += mei_hdr->length; - } - return -EMSGSIZE; - } else { - return -EBADMSG; - } - - return 0; -} - -/** * mei_irq_thread_read_handler - bottom half read routine after ISR to * handle the read processing. * @@ -1067,7 +855,7 @@ static int _mei_irq_thread_cmpl_iamthif(struct mei_device *dev, s32 *slots, * * returns 0 on success, <0 on failure. */ -static int mei_irq_thread_read_handler(struct mei_io_list *cmpl_list, +static int mei_irq_thread_read_handler(struct mei_cl_cb *cmpl_list, struct mei_device *dev, s32 *slots) { @@ -1130,8 +918,8 @@ static int mei_irq_thread_read_handler(struct mei_io_list *cmpl_list, dev_dbg(&dev->pdev->dev, "call mei_irq_thread_read_iamthif_message.\n"); dev_dbg(&dev->pdev->dev, "mei_hdr->length =%d\n", mei_hdr->length); - ret = mei_irq_thread_read_amthi_message(cmpl_list, - dev, mei_hdr); + + ret = mei_amthif_irq_read_message(cmpl_list, dev, mei_hdr); if (ret) goto end; @@ -1170,14 +958,13 @@ end: * * returns 0 on success, <0 on failure. */ -static int mei_irq_thread_write_handler(struct mei_io_list *cmpl_list, - struct mei_device *dev, - s32 *slots) +static int mei_irq_thread_write_handler(struct mei_cl_cb *cmpl_list, + struct mei_device *dev, s32 *slots) { struct mei_cl *cl; struct mei_cl_cb *pos = NULL, *next = NULL; - struct mei_io_list *list; + struct mei_cl_cb *list; int ret; if (!mei_hbuf_is_empty(dev)) { @@ -1192,25 +979,24 @@ static int mei_irq_thread_write_handler(struct mei_io_list *cmpl_list, dev_dbg(&dev->pdev->dev, "complete all waiting for write cb.\n"); list = &dev->write_waiting_list; - list_for_each_entry_safe(pos, next, &list->mei_cb.cb_list, cb_list) { - cl = (struct mei_cl *)pos->file_private; + list_for_each_entry_safe(pos, next, &list->list, list) { + cl = pos->cl; if (cl == NULL) continue; cl->status = 0; - list_del(&pos->cb_list); + list_del(&pos->list); if (MEI_WRITING == cl->writing_state && - (pos->major_file_operations == MEI_WRITE) && - (cl != &dev->iamthif_cl)) { + pos->fop_type == MEI_FOP_WRITE && + cl != &dev->iamthif_cl) { dev_dbg(&dev->pdev->dev, "MEI WRITE COMPLETE\n"); cl->writing_state = MEI_WRITE_COMPLETE; - list_add_tail(&pos->cb_list, - &cmpl_list->mei_cb.cb_list); + list_add_tail(&pos->list, &cmpl_list->list); } if (cl == &dev->iamthif_cl) { dev_dbg(&dev->pdev->dev, "check iamthif flow control.\n"); if (dev->iamthif_flow_control_pending) { - ret = _mei_irq_thread_iamthif_read(dev, slots); + ret = mei_amthif_irq_read(dev, slots); if (ret) return ret; } @@ -1251,29 +1037,28 @@ static int mei_irq_thread_write_handler(struct mei_io_list *cmpl_list, /* complete control write list CB */ dev_dbg(&dev->pdev->dev, "complete control write list cb.\n"); - list_for_each_entry_safe(pos, next, - &dev->ctrl_wr_list.mei_cb.cb_list, cb_list) { - cl = (struct mei_cl *) pos->file_private; + list_for_each_entry_safe(pos, next, &dev->ctrl_wr_list.list, list) { + cl = pos->cl; if (!cl) { - list_del(&pos->cb_list); + list_del(&pos->list); return -ENODEV; } - switch (pos->major_file_operations) { - case MEI_CLOSE: + switch (pos->fop_type) { + case MEI_FOP_CLOSE: /* send disconnect message */ ret = _mei_irq_thread_close(dev, slots, pos, cl, cmpl_list); if (ret) return ret; break; - case MEI_READ: + case MEI_FOP_READ: /* send flow control message */ ret = _mei_irq_thread_read(dev, slots, pos, cl, cmpl_list); if (ret) return ret; break; - case MEI_IOCTL: + case MEI_FOP_IOCTL: /* connect message */ if (mei_other_client_is_connecting(dev, cl)) continue; @@ -1290,9 +1075,8 @@ static int mei_irq_thread_write_handler(struct mei_io_list *cmpl_list, } /* complete write list CB */ dev_dbg(&dev->pdev->dev, "complete write list cb.\n"); - list_for_each_entry_safe(pos, next, - &dev->write_list.mei_cb.cb_list, cb_list) { - cl = (struct mei_cl *)pos->file_private; + list_for_each_entry_safe(pos, next, &dev->write_list.list, list) { + cl = pos->cl; if (cl == NULL) continue; @@ -1317,8 +1101,8 @@ static int mei_irq_thread_write_handler(struct mei_io_list *cmpl_list, cl->host_client_id); continue; } - ret = _mei_irq_thread_cmpl_iamthif(dev, slots, pos, - cl, cmpl_list); + ret = mei_amthif_irq_process_completed(dev, slots, pos, + cl, cmpl_list); if (ret) return ret; @@ -1342,7 +1126,6 @@ void mei_timer(struct work_struct *work) unsigned long timeout; struct mei_cl *cl_pos = NULL; struct mei_cl *cl_next = NULL; - struct list_head *amthi_complete_list = NULL; struct mei_cl_cb *cb_pos = NULL; struct mei_cl_cb *cb_next = NULL; @@ -1385,19 +1168,18 @@ void mei_timer(struct work_struct *work) dev->iamthif_state = MEI_IAMTHIF_IDLE; dev->iamthif_timer = 0; - if (dev->iamthif_current_cb) - mei_free_cb_private(dev->iamthif_current_cb); + mei_io_cb_free(dev->iamthif_current_cb); + dev->iamthif_current_cb = NULL; dev->iamthif_file_object = NULL; - dev->iamthif_current_cb = NULL; - mei_run_next_iamthif_cmd(dev); + mei_amthif_run_next_cmd(dev); } } if (dev->iamthif_timer) { timeout = dev->iamthif_timer + - msecs_to_jiffies(IAMTHIF_READ_TIMER); + mei_secs_to_jiffies(MEI_IAMTHIF_READ_TIMER); dev_dbg(&dev->pdev->dev, "dev->iamthif_timer = %ld\n", dev->iamthif_timer); @@ -1411,25 +1193,22 @@ void mei_timer(struct work_struct *work) dev_dbg(&dev->pdev->dev, "freeing AMTHI for other requests\n"); - amthi_complete_list = &dev->amthi_read_complete_list. - mei_cb.cb_list; - - list_for_each_entry_safe(cb_pos, cb_next, amthi_complete_list, cb_list) { + list_for_each_entry_safe(cb_pos, cb_next, + &dev->amthif_rd_complete_list.list, list) { cl_pos = cb_pos->file_object->private_data; /* Finding the AMTHI entry. */ if (cl_pos == &dev->iamthif_cl) - list_del(&cb_pos->cb_list); + list_del(&cb_pos->list); } - if (dev->iamthif_current_cb) - mei_free_cb_private(dev->iamthif_current_cb); + mei_io_cb_free(dev->iamthif_current_cb); + dev->iamthif_current_cb = NULL; dev->iamthif_file_object->private_data = NULL; dev->iamthif_file_object = NULL; - dev->iamthif_current_cb = NULL; dev->iamthif_timer = 0; - mei_run_next_iamthif_cmd(dev); + mei_amthif_run_next_cmd(dev); } } @@ -1451,7 +1230,7 @@ out: irqreturn_t mei_interrupt_thread_handler(int irq, void *dev_id) { struct mei_device *dev = (struct mei_device *) dev_id; - struct mei_io_list complete_list; + struct mei_cl_cb complete_list; struct mei_cl_cb *cb_pos = NULL, *cb_next = NULL; struct mei_cl *cl; s32 slots; @@ -1531,21 +1310,20 @@ end: wake_up_interruptible(&dev->wait_recvd_msg); bus_message_received = false; } - if (list_empty(&complete_list.mei_cb.cb_list)) + if (list_empty(&complete_list.list)) return IRQ_HANDLED; - list_for_each_entry_safe(cb_pos, cb_next, - &complete_list.mei_cb.cb_list, cb_list) { - cl = (struct mei_cl *)cb_pos->file_private; - list_del(&cb_pos->cb_list); + list_for_each_entry_safe(cb_pos, cb_next, &complete_list.list, list) { + cl = cb_pos->cl; + list_del(&cb_pos->list); if (cl) { if (cl != &dev->iamthif_cl) { dev_dbg(&dev->pdev->dev, "completing call back.\n"); _mei_cmpl(cl, cb_pos); cb_pos = NULL; } else if (cl == &dev->iamthif_cl) { - _mei_cmpl_iamthif(dev, cb_pos); + mei_amthif_complete(dev, cb_pos); } } } diff --git a/drivers/misc/mei/iorw.c b/drivers/misc/mei/iorw.c index fcba98eb892e..eb93a1b53b9b 100644 --- a/drivers/misc/mei/iorw.c +++ b/drivers/misc/mei/iorw.c @@ -39,6 +39,95 @@ #include "interface.h" /** + * mei_io_cb_free - free mei_cb_private related memory + * + * @cb: mei callback struct + */ +void mei_io_cb_free(struct mei_cl_cb *cb) +{ + if (cb == NULL) + return; + + kfree(cb->request_buffer.data); + kfree(cb->response_buffer.data); + kfree(cb); +} +/** + * mei_io_cb_init - allocate and initialize io callback + * + * @cl - mei client + * @file: pointer to file structure + * + * returns mei_cl_cb pointer or NULL; + */ +struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, struct file *fp) +{ + struct mei_cl_cb *cb; + + cb = kzalloc(sizeof(struct mei_cl_cb), GFP_KERNEL); + if (!cb) + return NULL; + + mei_io_list_init(cb); + + cb->file_object = fp; + cb->cl = cl; + cb->buf_idx = 0; + return cb; +} + + +/** + * mei_io_cb_alloc_req_buf - allocate request buffer + * + * @cb - io callback structure + * @size: size of the buffer + * + * returns 0 on success + * -EINVAL if cb is NULL + * -ENOMEM if allocation failed + */ +int mei_io_cb_alloc_req_buf(struct mei_cl_cb *cb, size_t length) +{ + if (!cb) + return -EINVAL; + + if (length == 0) + return 0; + + cb->request_buffer.data = kmalloc(length, GFP_KERNEL); + if (!cb->request_buffer.data) + return -ENOMEM; + cb->request_buffer.size = length; + return 0; +} +/** + * mei_io_cb_alloc_req_buf - allocate respose buffer + * + * @cb - io callback structure + * @size: size of the buffer + * + * returns 0 on success + * -EINVAL if cb is NULL + * -ENOMEM if allocation failed + */ +int mei_io_cb_alloc_resp_buf(struct mei_cl_cb *cb, size_t length) +{ + if (!cb) + return -EINVAL; + + if (length == 0) + return 0; + + cb->response_buffer.data = kmalloc(length, GFP_KERNEL); + if (!cb->response_buffer.data) + return -ENOMEM; + cb->response_buffer.size = length; + return 0; +} + + +/** * mei_me_cl_by_id return index to me_clients for client_id * * @dev: the device structure @@ -82,9 +171,7 @@ int mei_ioctl_connect_client(struct file *file, struct mei_cl_cb *cb; struct mei_client *client; struct mei_cl *cl; - struct mei_cl *cl_pos = NULL; - struct mei_cl *cl_next = NULL; - long timeout = CONNECT_TIMEOUT; + long timeout = mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT); int i; int err; int rets; @@ -97,16 +184,14 @@ int mei_ioctl_connect_client(struct file *file, dev_dbg(&dev->pdev->dev, "mei_ioctl_connect_client() Entry\n"); - /* buffered ioctl cb */ - cb = kzalloc(sizeof(struct mei_cl_cb), GFP_KERNEL); + cb = mei_io_cb_init(cl, file); if (!cb) { rets = -ENOMEM; goto end; } - INIT_LIST_HEAD(&cb->cb_list); - cb->major_file_operations = MEI_IOCTL; + cb->fop_type = MEI_FOP_IOCTL; if (dev->dev_state != MEI_DEV_ENABLED) { rets = -ENODEV; @@ -142,21 +227,9 @@ int mei_ioctl_connect_client(struct file *file, goto end; } clear_bit(cl->host_client_id, dev->host_clients_map); - list_for_each_entry_safe(cl_pos, cl_next, - &dev->file_list, link) { - if (mei_cl_cmp_id(cl, cl_pos)) { - dev_dbg(&dev->pdev->dev, - "remove file private data node host" - " client = %d, ME client = %d.\n", - cl_pos->host_client_id, - cl_pos->me_client_id); - list_del(&cl_pos->link); - } + mei_me_cl_unlink(dev, cl); - } - dev_dbg(&dev->pdev->dev, "free file private data memory.\n"); kfree(cl); - cl = NULL; file->private_data = &dev->iamthif_cl; @@ -192,25 +265,19 @@ int mei_ioctl_connect_client(struct file *file, } else { dev_dbg(&dev->pdev->dev, "Sending connect message - succeeded\n"); cl->timer_count = MEI_CONNECT_TIMEOUT; - cb->file_private = cl; - list_add_tail(&cb->cb_list, - &dev->ctrl_rd_list.mei_cb. - cb_list); + list_add_tail(&cb->list, &dev->ctrl_rd_list.list); } } else { dev_dbg(&dev->pdev->dev, "Queuing the connect request due to device busy\n"); - cb->file_private = cl; dev_dbg(&dev->pdev->dev, "add connect cb to control write list.\n"); - list_add_tail(&cb->cb_list, - &dev->ctrl_wr_list.mei_cb.cb_list); + list_add_tail(&cb->list, &dev->ctrl_wr_list.list); } mutex_unlock(&dev->device_lock); err = wait_event_timeout(dev->wait_recvd_msg, (MEI_FILE_CONNECTED == cl->state || - MEI_FILE_DISCONNECTED == cl->state), - timeout * HZ); + MEI_FILE_DISCONNECTED == cl->state), timeout); mutex_lock(&dev->device_lock); if (MEI_FILE_CONNECTED == cl->state) { @@ -234,153 +301,7 @@ int mei_ioctl_connect_client(struct file *file, rets = 0; end: dev_dbg(&dev->pdev->dev, "free connect cb memory."); - kfree(cb); - return rets; -} - -/** - * find_amthi_read_list_entry - finds a amthilist entry for current file - * - * @dev: the device structure - * @file: pointer to file object - * - * returns returned a list entry on success, NULL on failure. - */ -struct mei_cl_cb *find_amthi_read_list_entry( - struct mei_device *dev, - struct file *file) -{ - struct mei_cl *cl_temp; - struct mei_cl_cb *pos = NULL; - struct mei_cl_cb *next = NULL; - - list_for_each_entry_safe(pos, next, - &dev->amthi_read_complete_list.mei_cb.cb_list, cb_list) { - cl_temp = (struct mei_cl *)pos->file_private; - if (cl_temp && cl_temp == &dev->iamthif_cl && - pos->file_object == file) - return pos; - } - return NULL; -} - -/** - * amthi_read - read data from AMTHI client - * - * @dev: the device structure - * @if_num: minor number - * @file: pointer to file object - * @*ubuf: pointer to user data in user space - * @length: data length to read - * @offset: data read offset - * - * Locking: called under "dev->device_lock" lock - * - * returns - * returned data length on success, - * zero if no data to read, - * negative on failure. - */ -int amthi_read(struct mei_device *dev, struct file *file, - char __user *ubuf, size_t length, loff_t *offset) -{ - int rets; - int wait_ret; - struct mei_cl_cb *cb = NULL; - struct mei_cl *cl = file->private_data; - unsigned long timeout; - int i; - - /* Only Posible if we are in timeout */ - if (!cl || cl != &dev->iamthif_cl) { - dev_dbg(&dev->pdev->dev, "bad file ext.\n"); - return -ETIMEDOUT; - } - - i = mei_me_cl_by_id(dev, dev->iamthif_cl.me_client_id); - - if (i < 0) { - dev_dbg(&dev->pdev->dev, "amthi client not found.\n"); - return -ENODEV; - } - dev_dbg(&dev->pdev->dev, "checking amthi data\n"); - cb = find_amthi_read_list_entry(dev, file); - - /* Check for if we can block or not*/ - if (cb == NULL && file->f_flags & O_NONBLOCK) - return -EAGAIN; - - - dev_dbg(&dev->pdev->dev, "waiting for amthi data\n"); - while (cb == NULL) { - /* unlock the Mutex */ - mutex_unlock(&dev->device_lock); - - wait_ret = wait_event_interruptible(dev->iamthif_cl.wait, - (cb = find_amthi_read_list_entry(dev, file))); - - if (wait_ret) - return -ERESTARTSYS; - - dev_dbg(&dev->pdev->dev, "woke up from sleep\n"); - - /* Locking again the Mutex */ - mutex_lock(&dev->device_lock); - } - - - dev_dbg(&dev->pdev->dev, "Got amthi data\n"); - dev->iamthif_timer = 0; - - if (cb) { - timeout = cb->read_time + msecs_to_jiffies(IAMTHIF_READ_TIMER); - dev_dbg(&dev->pdev->dev, "amthi timeout = %lud\n", - timeout); - - if (time_after(jiffies, timeout)) { - dev_dbg(&dev->pdev->dev, "amthi Time out\n"); - /* 15 sec for the message has expired */ - list_del(&cb->cb_list); - rets = -ETIMEDOUT; - goto free; - } - } - /* if the whole message will fit remove it from the list */ - if (cb->information >= *offset && length >= (cb->information - *offset)) - list_del(&cb->cb_list); - else if (cb->information > 0 && cb->information <= *offset) { - /* end of the message has been reached */ - list_del(&cb->cb_list); - rets = 0; - goto free; - } - /* else means that not full buffer will be read and do not - * remove message from deletion list - */ - - dev_dbg(&dev->pdev->dev, "amthi cb->response_buffer size - %d\n", - cb->response_buffer.size); - dev_dbg(&dev->pdev->dev, "amthi cb->information - %lu\n", - cb->information); - - /* length is being turncated to PAGE_SIZE, however, - * the information may be longer */ - length = min_t(size_t, length, (cb->information - *offset)); - - if (copy_to_user(ubuf, cb->response_buffer.data + *offset, length)) - rets = -EFAULT; - else { - rets = length; - if ((*offset + length) < cb->information) { - *offset += length; - goto out; - } - } -free: - dev_dbg(&dev->pdev->dev, "free amthi cb memory.\n"); - *offset = 0; - mei_free_cb_private(cb); -out: + mei_io_cb_free(cb); return rets; } @@ -396,7 +317,7 @@ out: int mei_start_read(struct mei_device *dev, struct mei_cl *cl) { struct mei_cl_cb *cb; - int rets = 0; + int rets; int i; if (cl->state != MEI_FILE_CONNECTED) @@ -405,187 +326,41 @@ int mei_start_read(struct mei_device *dev, struct mei_cl *cl) if (dev->dev_state != MEI_DEV_ENABLED) return -ENODEV; - dev_dbg(&dev->pdev->dev, "check if read is pending.\n"); if (cl->read_pending || cl->read_cb) { dev_dbg(&dev->pdev->dev, "read is pending.\n"); return -EBUSY; } + i = mei_me_cl_by_id(dev, cl->me_client_id); + if (i < 0) { + dev_err(&dev->pdev->dev, "no such me client %d\n", + cl->me_client_id); + return -ENODEV; + } - cb = kzalloc(sizeof(struct mei_cl_cb), GFP_KERNEL); + cb = mei_io_cb_init(cl, NULL); if (!cb) return -ENOMEM; - dev_dbg(&dev->pdev->dev, "allocation call back successful. host client = %d, ME client = %d\n", - cl->host_client_id, cl->me_client_id); - i = mei_me_cl_by_id(dev, cl->me_client_id); - if (i < 0) { - rets = -ENODEV; - goto unlock; - } + rets = mei_io_cb_alloc_resp_buf(cb, + dev->me_clients[i].props.max_msg_length); + if (rets) + goto err; - cb->response_buffer.size = dev->me_clients[i].props.max_msg_length; - cb->response_buffer.data = - kmalloc(cb->response_buffer.size, GFP_KERNEL); - if (!cb->response_buffer.data) { - rets = -ENOMEM; - goto unlock; - } - dev_dbg(&dev->pdev->dev, "allocation call back data success.\n"); - cb->major_file_operations = MEI_READ; - /* make sure information is zero before we start */ - cb->information = 0; - cb->file_private = (void *) cl; + cb->fop_type = MEI_FOP_READ; cl->read_cb = cb; if (dev->mei_host_buffer_is_empty) { dev->mei_host_buffer_is_empty = false; if (mei_send_flow_control(dev, cl)) { rets = -ENODEV; - goto unlock; + goto err; } - list_add_tail(&cb->cb_list, &dev->read_list.mei_cb.cb_list); + list_add_tail(&cb->list, &dev->read_list.list); } else { - list_add_tail(&cb->cb_list, &dev->ctrl_wr_list.mei_cb.cb_list); + list_add_tail(&cb->list, &dev->ctrl_wr_list.list); } return rets; -unlock: - mei_free_cb_private(cb); +err: + mei_io_cb_free(cb); return rets; } -/** - * amthi_write - write iamthif data to amthi client - * - * @dev: the device structure - * @cb: mei call back struct - * - * returns 0 on success, <0 on failure. - */ -int amthi_write(struct mei_device *dev, struct mei_cl_cb *cb) -{ - struct mei_msg_hdr mei_hdr; - int ret; - - if (!dev || !cb) - return -ENODEV; - - dev_dbg(&dev->pdev->dev, "write data to amthi client.\n"); - - dev->iamthif_state = MEI_IAMTHIF_WRITING; - dev->iamthif_current_cb = cb; - dev->iamthif_file_object = cb->file_object; - dev->iamthif_canceled = false; - dev->iamthif_ioctl = true; - dev->iamthif_msg_buf_size = cb->request_buffer.size; - memcpy(dev->iamthif_msg_buf, cb->request_buffer.data, - cb->request_buffer.size); - - ret = mei_flow_ctrl_creds(dev, &dev->iamthif_cl); - if (ret < 0) - return ret; - - if (ret && dev->mei_host_buffer_is_empty) { - ret = 0; - dev->mei_host_buffer_is_empty = false; - if (cb->request_buffer.size > mei_hbuf_max_data(dev)) { - mei_hdr.length = mei_hbuf_max_data(dev); - mei_hdr.msg_complete = 0; - } else { - mei_hdr.length = cb->request_buffer.size; - mei_hdr.msg_complete = 1; - } - - mei_hdr.host_addr = dev->iamthif_cl.host_client_id; - mei_hdr.me_addr = dev->iamthif_cl.me_client_id; - mei_hdr.reserved = 0; - dev->iamthif_msg_buf_index += mei_hdr.length; - if (mei_write_message(dev, &mei_hdr, - (unsigned char *)(dev->iamthif_msg_buf), - mei_hdr.length)) - return -ENODEV; - - if (mei_hdr.msg_complete) { - if (mei_flow_ctrl_reduce(dev, &dev->iamthif_cl)) - return -ENODEV; - dev->iamthif_flow_control_pending = true; - dev->iamthif_state = MEI_IAMTHIF_FLOW_CONTROL; - dev_dbg(&dev->pdev->dev, "add amthi cb to write waiting list\n"); - dev->iamthif_current_cb = cb; - dev->iamthif_file_object = cb->file_object; - list_add_tail(&cb->cb_list, - &dev->write_waiting_list.mei_cb.cb_list); - } else { - dev_dbg(&dev->pdev->dev, "message does not complete, " - "so add amthi cb to write list.\n"); - list_add_tail(&cb->cb_list, - &dev->write_list.mei_cb.cb_list); - } - } else { - if (!(dev->mei_host_buffer_is_empty)) - dev_dbg(&dev->pdev->dev, "host buffer is not empty"); - - dev_dbg(&dev->pdev->dev, "No flow control credentials, " - "so add iamthif cb to write list.\n"); - list_add_tail(&cb->cb_list, &dev->write_list.mei_cb.cb_list); - } - return 0; -} - -/** - * iamthif_ioctl_send_msg - send cmd data to amthi client - * - * @dev: the device structure - * - * returns 0 on success, <0 on failure. - */ -void mei_run_next_iamthif_cmd(struct mei_device *dev) -{ - struct mei_cl *cl_tmp; - struct mei_cl_cb *pos = NULL; - struct mei_cl_cb *next = NULL; - int status; - - if (!dev) - return; - - dev->iamthif_msg_buf_size = 0; - dev->iamthif_msg_buf_index = 0; - dev->iamthif_canceled = false; - dev->iamthif_ioctl = true; - dev->iamthif_state = MEI_IAMTHIF_IDLE; - dev->iamthif_timer = 0; - dev->iamthif_file_object = NULL; - - dev_dbg(&dev->pdev->dev, "complete amthi cmd_list cb.\n"); - - list_for_each_entry_safe(pos, next, - &dev->amthi_cmd_list.mei_cb.cb_list, cb_list) { - list_del(&pos->cb_list); - cl_tmp = (struct mei_cl *)pos->file_private; - - if (cl_tmp && cl_tmp == &dev->iamthif_cl) { - status = amthi_write(dev, pos); - if (status) { - dev_dbg(&dev->pdev->dev, - "amthi write failed status = %d\n", - status); - return; - } - break; - } - } -} - -/** - * mei_free_cb_private - free mei_cb_private related memory - * - * @cb: mei callback struct - */ -void mei_free_cb_private(struct mei_cl_cb *cb) -{ - if (cb == NULL) - return; - - kfree(cb->request_buffer.data); - kfree(cb->response_buffer.data); - kfree(cb); -} diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index e8b0858132c1..251aafff5492 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -90,93 +90,6 @@ static DEFINE_MUTEX(mei_mutex); /** - * mei_clear_list - removes all callbacks associated with file - * from mei_cb_list - * - * @dev: device structure. - * @file: file structure - * @mei_cb_list: callbacks list - * - * mei_clear_list is called to clear resources associated with file - * when application calls close function or Ctrl-C was pressed - * - * returns true if callback removed from the list, false otherwise - */ -static bool mei_clear_list(struct mei_device *dev, - struct file *file, struct list_head *mei_cb_list) -{ - struct mei_cl_cb *cb_pos = NULL; - struct mei_cl_cb *cb_next = NULL; - struct file *file_temp; - bool removed = false; - - /* list all list member */ - list_for_each_entry_safe(cb_pos, cb_next, mei_cb_list, cb_list) { - file_temp = (struct file *)cb_pos->file_object; - /* check if list member associated with a file */ - if (file_temp == file) { - /* remove member from the list */ - list_del(&cb_pos->cb_list); - /* check if cb equal to current iamthif cb */ - if (dev->iamthif_current_cb == cb_pos) { - dev->iamthif_current_cb = NULL; - /* send flow control to iamthif client */ - mei_send_flow_control(dev, &dev->iamthif_cl); - } - /* free all allocated buffers */ - mei_free_cb_private(cb_pos); - cb_pos = NULL; - removed = true; - } - } - return removed; -} - -/** - * mei_clear_lists - removes all callbacks associated with file - * - * @dev: device structure - * @file: file structure - * - * mei_clear_lists is called to clear resources associated with file - * when application calls close function or Ctrl-C was pressed - * - * returns true if callback removed from the list, false otherwise - */ -static bool mei_clear_lists(struct mei_device *dev, struct file *file) -{ - bool removed = false; - - /* remove callbacks associated with a file */ - mei_clear_list(dev, file, &dev->amthi_cmd_list.mei_cb.cb_list); - if (mei_clear_list(dev, file, - &dev->amthi_read_complete_list.mei_cb.cb_list)) - removed = true; - - mei_clear_list(dev, file, &dev->ctrl_rd_list.mei_cb.cb_list); - - if (mei_clear_list(dev, file, &dev->ctrl_wr_list.mei_cb.cb_list)) - removed = true; - - if (mei_clear_list(dev, file, &dev->write_waiting_list.mei_cb.cb_list)) - removed = true; - - if (mei_clear_list(dev, file, &dev->write_list.mei_cb.cb_list)) - removed = true; - - /* check if iamthif_current_cb not NULL */ - if (dev->iamthif_current_cb && !removed) { - /* check file and iamthif current cb association */ - if (dev->iamthif_current_cb->file_object == file) { - /* remove cb */ - mei_free_cb_private(dev->iamthif_current_cb); - dev->iamthif_current_cb = NULL; - removed = true; - } - } - return removed; -} -/** * find_read_list_entry - find read list entry * * @dev: device structure @@ -192,14 +105,9 @@ static struct mei_cl_cb *find_read_list_entry( struct mei_cl_cb *next = NULL; dev_dbg(&dev->pdev->dev, "remove read_list CB\n"); - list_for_each_entry_safe(pos, next, - &dev->read_list.mei_cb.cb_list, cb_list) { - struct mei_cl *cl_temp; - cl_temp = (struct mei_cl *)pos->file_private; - - if (mei_cl_cmp_id(cl, cl_temp)) + list_for_each_entry_safe(pos, next, &dev->read_list.list, list) + if (mei_cl_cmp_id(cl, pos->cl)) return pos; - } return NULL; } @@ -297,67 +205,51 @@ static int mei_release(struct inode *inode, struct file *file) dev = cl->dev; mutex_lock(&dev->device_lock); - if (cl != &dev->iamthif_cl) { - if (cl->state == MEI_FILE_CONNECTED) { - cl->state = MEI_FILE_DISCONNECTING; - dev_dbg(&dev->pdev->dev, - "disconnecting client host client = %d, " - "ME client = %d\n", - cl->host_client_id, - cl->me_client_id); - rets = mei_disconnect_host_client(dev, cl); - } - mei_cl_flush_queues(cl); - dev_dbg(&dev->pdev->dev, "remove client host client = %d, ME client = %d\n", + if (cl == &dev->iamthif_cl) { + rets = mei_amthif_release(dev, file); + goto out; + } + if (cl->state == MEI_FILE_CONNECTED) { + cl->state = MEI_FILE_DISCONNECTING; + dev_dbg(&dev->pdev->dev, + "disconnecting client host client = %d, " + "ME client = %d\n", cl->host_client_id, cl->me_client_id); + rets = mei_disconnect_host_client(dev, cl); + } + mei_cl_flush_queues(cl); + dev_dbg(&dev->pdev->dev, "remove client host client = %d, ME client = %d\n", + cl->host_client_id, + cl->me_client_id); + + if (dev->open_handle_count > 0) { + clear_bit(cl->host_client_id, dev->host_clients_map); + dev->open_handle_count--; + } + mei_me_cl_unlink(dev, cl); - if (dev->open_handle_count > 0) { - clear_bit(cl->host_client_id, dev->host_clients_map); - dev->open_handle_count--; - } - mei_remove_client_from_file_list(dev, cl->host_client_id); - - /* free read cb */ - cb = NULL; - if (cl->read_cb) { - cb = find_read_list_entry(dev, cl); - /* Remove entry from read list */ - if (cb) - list_del(&cb->cb_list); - - cb = cl->read_cb; - cl->read_cb = NULL; - } - - file->private_data = NULL; + /* free read cb */ + cb = NULL; + if (cl->read_cb) { + cb = find_read_list_entry(dev, cl); + /* Remove entry from read list */ + if (cb) + list_del(&cb->list); - if (cb) { - mei_free_cb_private(cb); - cb = NULL; - } - - kfree(cl); - } else { - if (dev->open_handle_count > 0) - dev->open_handle_count--; - - if (dev->iamthif_file_object == file && - dev->iamthif_state != MEI_IAMTHIF_IDLE) { - - dev_dbg(&dev->pdev->dev, "amthi canceled iamthif state %d\n", - dev->iamthif_state); - dev->iamthif_canceled = true; - if (dev->iamthif_state == MEI_IAMTHIF_READ_COMPLETE) { - dev_dbg(&dev->pdev->dev, "run next amthi iamthif cb\n"); - mei_run_next_iamthif_cmd(dev); - } - } + cb = cl->read_cb; + cl->read_cb = NULL; + } - if (mei_clear_lists(dev, file)) - dev->iamthif_state = MEI_IAMTHIF_IDLE; + file->private_data = NULL; + if (cb) { + mei_io_cb_free(cb); + cb = NULL; } + + kfree(cl); +out: mutex_unlock(&dev->device_lock); return rets; } @@ -411,20 +303,19 @@ static ssize_t mei_read(struct file *file, char __user *ubuf, } if (cl == &dev->iamthif_cl) { - rets = amthi_read(dev, file, ubuf, length, offset); + rets = mei_amthif_read(dev, file, ubuf, length, offset); goto out; } - if (cl->read_cb && cl->read_cb->information > *offset) { + if (cl->read_cb && cl->read_cb->buf_idx > *offset) { cb = cl->read_cb; goto copy_buffer; - } else if (cl->read_cb && cl->read_cb->information > 0 && - cl->read_cb->information <= *offset) { + } else if (cl->read_cb && cl->read_cb->buf_idx > 0 && + cl->read_cb->buf_idx <= *offset) { cb = cl->read_cb; rets = 0; goto free; - } else if ((!cl->read_cb || !cl->read_cb->information) && - *offset > 0) { + } else if ((!cl->read_cb || !cl->read_cb->buf_idx) && *offset > 0) { /*Offset needs to be cleaned for contiguous reads*/ *offset = 0; rets = 0; @@ -481,16 +372,15 @@ static ssize_t mei_read(struct file *file, char __user *ubuf, copy_buffer: dev_dbg(&dev->pdev->dev, "cb->response_buffer size - %d\n", cb->response_buffer.size); - dev_dbg(&dev->pdev->dev, "cb->information - %lu\n", - cb->information); - if (length == 0 || ubuf == NULL || *offset > cb->information) { + dev_dbg(&dev->pdev->dev, "cb->buf_idx - %lu\n", cb->buf_idx); + if (length == 0 || ubuf == NULL || *offset > cb->buf_idx) { rets = -EMSGSIZE; goto free; } - /* length is being truncated to PAGE_SIZE, however, */ - /* information size may be longer */ - length = min_t(size_t, length, (cb->information - *offset)); + /* length is being truncated to PAGE_SIZE, + * however buf_idx may point beyond that */ + length = min_t(size_t, length, cb->buf_idx - *offset); if (copy_to_user(ubuf, cb->response_buffer.data + *offset, length)) { rets = -EFAULT; @@ -499,15 +389,15 @@ copy_buffer: rets = length; *offset += length; - if ((unsigned long)*offset < cb->information) + if ((unsigned long)*offset < cb->buf_idx) goto out; free: cb_pos = find_read_list_entry(dev, cl); /* Remove entry from read list */ if (cb_pos) - list_del(&cb_pos->cb_list); - mei_free_cb_private(cb); + list_del(&cb_pos->list); + mei_io_cb_free(cb); cl->reading_state = MEI_IDLE; cl->read_cb = NULL; cl->read_pending = 0; @@ -516,7 +406,6 @@ out: mutex_unlock(&dev->device_lock); return rets; } - /** * mei_write - the write function. * @@ -546,23 +435,39 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, mutex_lock(&dev->device_lock); if (dev->dev_state != MEI_DEV_ENABLED) { - mutex_unlock(&dev->device_lock); - return -ENODEV; + rets = -ENODEV; + goto err; } + i = mei_me_cl_by_id(dev, cl->me_client_id); + if (i < 0) { + rets = -ENODEV; + goto err; + } + if (length > dev->me_clients[i].props.max_msg_length || length <= 0) { + rets = -EMSGSIZE; + goto err; + } + + if (cl->state != MEI_FILE_CONNECTED) { + rets = -ENODEV; + dev_err(&dev->pdev->dev, "host client = %d, is not connected to ME client = %d", + cl->host_client_id, cl->me_client_id); + goto err; + } if (cl == &dev->iamthif_cl) { - write_cb = find_amthi_read_list_entry(dev, file); + write_cb = mei_amthif_find_read_list_entry(dev, file); if (write_cb) { timeout = write_cb->read_time + - msecs_to_jiffies(IAMTHIF_READ_TIMER); + mei_secs_to_jiffies(MEI_IAMTHIF_READ_TIMER); if (time_after(jiffies, timeout) || - cl->reading_state == MEI_READ_COMPLETE) { - *offset = 0; - list_del(&write_cb->cb_list); - mei_free_cb_private(write_cb); - write_cb = NULL; + cl->reading_state == MEI_READ_COMPLETE) { + *offset = 0; + list_del(&write_cb->list); + mei_io_cb_free(write_cb); + write_cb = NULL; } } } @@ -572,8 +477,8 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, *offset = 0; write_cb = find_read_list_entry(dev, cl); if (write_cb) { - list_del(&write_cb->cb_list); - mei_free_cb_private(write_cb); + list_del(&write_cb->list); + mei_io_cb_free(write_cb); write_cb = NULL; cl->reading_state = MEI_IDLE; cl->read_cb = NULL; @@ -583,24 +488,21 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, *offset = 0; - write_cb = kzalloc(sizeof(struct mei_cl_cb), GFP_KERNEL); + write_cb = mei_io_cb_init(cl, file); if (!write_cb) { - mutex_unlock(&dev->device_lock); - return -ENOMEM; + dev_err(&dev->pdev->dev, "write cb allocation failed\n"); + rets = -ENOMEM; + goto err; } + rets = mei_io_cb_alloc_req_buf(write_cb, length); + if (rets) + goto err; - write_cb->file_object = file; - write_cb->file_private = cl; - write_cb->request_buffer.data = kmalloc(length, GFP_KERNEL); - rets = -ENOMEM; - if (!write_cb->request_buffer.data) - goto unlock_dev; - - dev_dbg(&dev->pdev->dev, "length =%d\n", (int) length); + dev_dbg(&dev->pdev->dev, "cb request size = %zd\n", length); - rets = -EFAULT; - if (copy_from_user(write_cb->request_buffer.data, ubuf, length)) - goto unlock_dev; + rets = copy_from_user(write_cb->request_buffer.data, ubuf, length); + if (rets) + goto err; cl->sm_state = 0; if (length == 4 && @@ -612,139 +514,71 @@ static ssize_t mei_write(struct file *file, const char __user *ubuf, write_cb->request_buffer.data, 4) == 0))) cl->sm_state |= MEI_WD_STATE_INDEPENDENCE_MSG_SENT; - INIT_LIST_HEAD(&write_cb->cb_list); if (cl == &dev->iamthif_cl) { - write_cb->response_buffer.data = - kmalloc(dev->iamthif_mtu, GFP_KERNEL); - if (!write_cb->response_buffer.data) { - rets = -ENOMEM; - goto unlock_dev; - } - if (dev->dev_state != MEI_DEV_ENABLED) { - rets = -ENODEV; - goto unlock_dev; - } - i = mei_me_cl_by_id(dev, dev->iamthif_cl.me_client_id); - if (i < 0) { - rets = -ENODEV; - goto unlock_dev; - } - if (length > dev->me_clients[i].props.max_msg_length || - length <= 0) { - rets = -EMSGSIZE; - goto unlock_dev; - } + rets = mei_amthif_write(dev, write_cb); - write_cb->response_buffer.size = dev->iamthif_mtu; - write_cb->major_file_operations = MEI_IOCTL; - write_cb->information = 0; - write_cb->request_buffer.size = length; - if (dev->iamthif_cl.state != MEI_FILE_CONNECTED) { - rets = -ENODEV; - goto unlock_dev; - } - - if (!list_empty(&dev->amthi_cmd_list.mei_cb.cb_list) || - dev->iamthif_state != MEI_IAMTHIF_IDLE) { - dev_dbg(&dev->pdev->dev, "amthi_state = %d\n", - (int) dev->iamthif_state); - dev_dbg(&dev->pdev->dev, "add amthi cb to amthi cmd waiting list\n"); - list_add_tail(&write_cb->cb_list, - &dev->amthi_cmd_list.mei_cb.cb_list); - rets = length; - } else { - dev_dbg(&dev->pdev->dev, "call amthi write\n"); - rets = amthi_write(dev, write_cb); - - if (rets) { - dev_dbg(&dev->pdev->dev, "amthi write failed with status = %d\n", - rets); - goto unlock_dev; - } - rets = length; + if (rets) { + dev_err(&dev->pdev->dev, + "amthi write failed with status = %d\n", rets); + goto err; } mutex_unlock(&dev->device_lock); - return rets; + return length; } - write_cb->major_file_operations = MEI_WRITE; - /* make sure information is zero before we start */ - - write_cb->information = 0; - write_cb->request_buffer.size = length; + write_cb->fop_type = MEI_FOP_WRITE; dev_dbg(&dev->pdev->dev, "host client = %d, ME client = %d\n", cl->host_client_id, cl->me_client_id); - if (cl->state != MEI_FILE_CONNECTED) { - rets = -ENODEV; - dev_dbg(&dev->pdev->dev, "host client = %d, is not connected to ME client = %d", - cl->host_client_id, - cl->me_client_id); - goto unlock_dev; - } - i = mei_me_cl_by_id(dev, cl->me_client_id); - if (i < 0) { - rets = -ENODEV; - goto unlock_dev; - } - if (length > dev->me_clients[i].props.max_msg_length || length <= 0) { - rets = -EINVAL; - goto unlock_dev; - } - write_cb->file_private = cl; - rets = mei_flow_ctrl_creds(dev, cl); if (rets < 0) - goto unlock_dev; + goto err; - if (rets && dev->mei_host_buffer_is_empty) { - rets = 0; - dev->mei_host_buffer_is_empty = false; - if (length > mei_hbuf_max_data(dev)) { - mei_hdr.length = mei_hbuf_max_data(dev); - mei_hdr.msg_complete = 0; - } else { - mei_hdr.length = length; - mei_hdr.msg_complete = 1; - } - mei_hdr.host_addr = cl->host_client_id; - mei_hdr.me_addr = cl->me_client_id; - mei_hdr.reserved = 0; - dev_dbg(&dev->pdev->dev, "call mei_write_message header=%08x.\n", - *((u32 *) &mei_hdr)); - if (mei_write_message(dev, &mei_hdr, - (unsigned char *) (write_cb->request_buffer.data), - mei_hdr.length)) { - rets = -ENODEV; - goto unlock_dev; - } + if (rets == 0 || dev->mei_host_buffer_is_empty == false) { + write_cb->buf_idx = 0; + mei_hdr.msg_complete = 0; cl->writing_state = MEI_WRITING; - write_cb->information = mei_hdr.length; - if (mei_hdr.msg_complete) { - if (mei_flow_ctrl_reduce(dev, cl)) { - rets = -ENODEV; - goto unlock_dev; - } - list_add_tail(&write_cb->cb_list, - &dev->write_waiting_list.mei_cb.cb_list); - } else { - list_add_tail(&write_cb->cb_list, - &dev->write_list.mei_cb.cb_list); - } + goto out; + } + dev->mei_host_buffer_is_empty = false; + if (length > mei_hbuf_max_data(dev)) { + mei_hdr.length = mei_hbuf_max_data(dev); + mei_hdr.msg_complete = 0; } else { + mei_hdr.length = length; + mei_hdr.msg_complete = 1; + } + mei_hdr.host_addr = cl->host_client_id; + mei_hdr.me_addr = cl->me_client_id; + mei_hdr.reserved = 0; + dev_dbg(&dev->pdev->dev, "call mei_write_message header=%08x.\n", + *((u32 *) &mei_hdr)); + if (mei_write_message(dev, &mei_hdr, + write_cb->request_buffer.data, mei_hdr.length)) { + rets = -ENODEV; + goto err; + } + cl->writing_state = MEI_WRITING; + write_cb->buf_idx = mei_hdr.length; - write_cb->information = 0; - cl->writing_state = MEI_WRITING; - list_add_tail(&write_cb->cb_list, - &dev->write_list.mei_cb.cb_list); +out: + if (mei_hdr.msg_complete) { + if (mei_flow_ctrl_reduce(dev, cl)) { + rets = -ENODEV; + goto err; + } + list_add_tail(&write_cb->list, &dev->write_waiting_list.list); + } else { + list_add_tail(&write_cb->list, &dev->write_list.list); } + mutex_unlock(&dev->device_lock); return length; -unlock_dev: +err: mutex_unlock(&dev->device_lock); - mei_free_cb_private(write_cb); + mei_io_cb_free(write_cb); return rets; } @@ -860,15 +694,7 @@ static unsigned int mei_poll(struct file *file, poll_table *wait) if (cl == &dev->iamthif_cl) { - mutex_unlock(&dev->device_lock); - poll_wait(file, &dev->iamthif_cl.wait, wait); - mutex_lock(&dev->device_lock); - if (dev->iamthif_state == MEI_IAMTHIF_READ_COMPLETE && - dev->iamthif_file_object == file) { - mask |= (POLLIN | POLLRDNORM); - dev_dbg(&dev->pdev->dev, "run next amthi cb\n"); - mei_run_next_iamthif_cmd(dev); - } + mask = mei_amthif_poll(dev, file, wait); goto out; } @@ -1087,8 +913,8 @@ static void __devexit mei_remove(struct pci_dev *pdev) /* remove entry if already in list */ dev_dbg(&pdev->dev, "list del iamthif and wd file list.\n"); - mei_remove_client_from_file_list(dev, dev->wd_cl.host_client_id); - mei_remove_client_from_file_list(dev, dev->iamthif_cl.host_client_id); + mei_me_cl_unlink(dev, &dev->wd_cl); + mei_me_cl_unlink(dev, &dev->iamthif_cl); dev->iamthif_current_cb = NULL; dev->me_clients_num = 0; diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index adb35fb9281c..aaee666577b4 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -19,6 +19,7 @@ #include <linux/types.h> #include <linux/watchdog.h> +#include <linux/poll.h> #include <linux/mei.h> #include "hw.h" @@ -125,13 +126,20 @@ enum mei_wd_states { MEI_WD_STOPPING, }; -/* MEI CB */ -enum mei_cb_major_types { - MEI_READ = 0, - MEI_WRITE, - MEI_IOCTL, - MEI_OPEN, - MEI_CLOSE +/** + * enum mei_cb_file_ops - file operation associated with the callback + * @MEI_FOP_READ - read + * @MEI_FOP_WRITE - write + * @MEI_FOP_IOCTL - ioctl + * @MEI_FOP_OPEN - open + * @MEI_FOP_CLOSE - close + */ +enum mei_cb_file_ops { + MEI_FOP_READ = 0, + MEI_FOP_WRITE, + MEI_FOP_IOCTL, + MEI_FOP_OPEN, + MEI_FOP_CLOSE }; /* @@ -143,13 +151,21 @@ struct mei_message_data { }; +struct mei_cl; + +/** + * struct mei_cl_cb - file operation callback structure + * + * @cl - file client who is running this operation + * @fop_type - file operation type + */ struct mei_cl_cb { - struct list_head cb_list; - enum mei_cb_major_types major_file_operations; - void *file_private; + struct list_head list; + struct mei_cl *cl; + enum mei_cb_file_ops fop_type; struct mei_message_data request_buffer; struct mei_message_data response_buffer; - unsigned long information; + unsigned long buf_idx; unsigned long read_time; struct file *file_object; }; @@ -175,10 +191,6 @@ struct mei_cl { struct mei_cl_cb *read_cb; }; -struct mei_io_list { - struct mei_cl_cb mei_cb; -}; - /** * struct mei_deive - MEI private device struct * @hbuf_depth - depth of host(write) buffer @@ -188,16 +200,13 @@ struct mei_device { /* * lists of queues */ - /* array of pointers to aio lists */ - struct mei_io_list read_list; /* driver read queue */ - struct mei_io_list write_list; /* driver write queue */ - struct mei_io_list write_waiting_list; /* write waiting queue */ - struct mei_io_list ctrl_wr_list; /* managed write IOCTL list */ - struct mei_io_list ctrl_rd_list; /* managed read IOCTL list */ - struct mei_io_list amthi_cmd_list; /* amthi list for cmd waiting */ - - /* driver managed amthi list for reading completed amthi cmd data */ - struct mei_io_list amthi_read_complete_list; + /* array of pointers to aio lists */ + struct mei_cl_cb read_list; /* driver read queue */ + struct mei_cl_cb write_list; /* driver write queue */ + struct mei_cl_cb write_waiting_list; /* write waiting queue */ + struct mei_cl_cb ctrl_wr_list; /* managed write IOCTL list */ + struct mei_cl_cb ctrl_rd_list; /* managed read IOCTL list */ + /* * list of files */ @@ -253,12 +262,15 @@ struct mei_device { struct mei_cl wd_cl; enum mei_wd_states wd_state; - bool wd_interface_reg; bool wd_pending; u16 wd_timeout; unsigned char wd_data[MEI_WD_START_MSG_SIZE]; + /* amthif list for cmd waiting */ + struct mei_cl_cb amthif_cmd_list; + /* driver managed amthif list for reading completed amthif cmd data */ + struct mei_cl_cb amthif_rd_complete_list; struct file *iamthif_file_object; struct mei_cl iamthif_cl; struct mei_cl_cb *iamthif_current_cb; @@ -274,6 +286,11 @@ struct mei_device { bool iamthif_canceled; }; +static inline unsigned long mei_secs_to_jiffies(unsigned long sec) +{ + return msecs_to_jiffies(sec * MSEC_PER_SEC); +} + /* * mei init function prototypes @@ -284,21 +301,34 @@ int mei_hw_init(struct mei_device *dev); int mei_task_initialize_clients(void *data); int mei_initialize_clients(struct mei_device *dev); int mei_disconnect_host_client(struct mei_device *dev, struct mei_cl *cl); -void mei_remove_client_from_file_list(struct mei_device *dev, u8 host_client_id); -void mei_host_init_iamthif(struct mei_device *dev); void mei_allocate_me_clients_storage(struct mei_device *dev); -int mei_me_cl_update_filext(struct mei_device *dev, struct mei_cl *cl, +int mei_me_cl_link(struct mei_device *dev, struct mei_cl *cl, const uuid_le *cguid, u8 host_client_id); +void mei_me_cl_unlink(struct mei_device *dev, struct mei_cl *cl); int mei_me_cl_by_uuid(const struct mei_device *dev, const uuid_le *cuuid); int mei_me_cl_by_id(struct mei_device *dev, u8 client_id); /* - * MEI IO List Functions + * MEI IO Functions + */ +struct mei_cl_cb *mei_io_cb_init(struct mei_cl *cl, struct file *fp); +void mei_io_cb_free(struct mei_cl_cb *priv_cb); +int mei_io_cb_alloc_req_buf(struct mei_cl_cb *cb, size_t length); +int mei_io_cb_alloc_resp_buf(struct mei_cl_cb *cb, size_t length); + + +/** + * mei_io_list_init - Sets up a queue list. + * + * @list: An instance cl callback structure */ -void mei_io_list_init(struct mei_io_list *list); -void mei_io_list_flush(struct mei_io_list *list, struct mei_cl *cl); +static inline void mei_io_list_init(struct mei_cl_cb *list) +{ + INIT_LIST_HEAD(&list->list); +} +void mei_io_list_flush(struct mei_cl_cb *list, struct mei_cl *cl); /* * MEI ME Client Functions @@ -347,18 +377,42 @@ int mei_ioctl_connect_client(struct file *file, int mei_start_read(struct mei_device *dev, struct mei_cl *cl); -int amthi_write(struct mei_device *dev, struct mei_cl_cb *priv_cb); -int amthi_read(struct mei_device *dev, struct file *file, - char __user *ubuf, size_t length, loff_t *offset); +/* + * AMTHIF - AMT Host Interface Functions + */ +void mei_amthif_reset_params(struct mei_device *dev); + +void mei_amthif_host_init(struct mei_device *dev); + +int mei_amthif_write(struct mei_device *dev, struct mei_cl_cb *priv_cb); + +int mei_amthif_read(struct mei_device *dev, struct file *file, + char __user *ubuf, size_t length, loff_t *offset); + +unsigned int mei_amthif_poll(struct mei_device *dev, + struct file *file, poll_table *wait); -struct mei_cl_cb *find_amthi_read_list_entry(struct mei_device *dev, +int mei_amthif_release(struct mei_device *dev, struct file *file); + +struct mei_cl_cb *mei_amthif_find_read_list_entry(struct mei_device *dev, struct file *file); -void mei_run_next_iamthif_cmd(struct mei_device *dev); +void mei_amthif_run_next_cmd(struct mei_device *dev); + + +int mei_amthif_read_message(struct mei_cl_cb *complete_list, + struct mei_device *dev, struct mei_msg_hdr *mei_hdr); -void mei_free_cb_private(struct mei_cl_cb *priv_cb); +int mei_amthif_irq_process_completed(struct mei_device *dev, s32 *slots, + struct mei_cl_cb *cb_pos, + struct mei_cl *cl, + struct mei_cl_cb *cmpl_list); +void mei_amthif_complete(struct mei_device *dev, struct mei_cl_cb *cb); +int mei_amthif_irq_read_message(struct mei_cl_cb *complete_list, + struct mei_device *dev, struct mei_msg_hdr *mei_hdr); +int mei_amthif_irq_read(struct mei_device *dev, s32 *slots); /* * Register Access Function diff --git a/drivers/misc/mei/wd.c b/drivers/misc/mei/wd.c index d96c537f046f..636409f9667f 100644 --- a/drivers/misc/mei/wd.c +++ b/drivers/misc/mei/wd.c @@ -62,6 +62,7 @@ static void mei_wd_set_start_timeout(struct mei_device *dev, u16 timeout) */ int mei_wd_host_init(struct mei_device *dev) { + int id; mei_cl_init(&dev->wd_cl, dev); /* look for WD client and connect to it */ @@ -69,12 +70,11 @@ int mei_wd_host_init(struct mei_device *dev) dev->wd_timeout = MEI_WD_DEFAULT_TIMEOUT; dev->wd_state = MEI_WD_IDLE; - /* find ME WD client */ - mei_me_cl_update_filext(dev, &dev->wd_cl, + /* Connect WD ME client to the host client */ + id = mei_me_cl_link(dev, &dev->wd_cl, &mei_wd_guid, MEI_WD_HOST_CLIENT_ID); - dev_dbg(&dev->pdev->dev, "wd: check client\n"); - if (MEI_FILE_CONNECTING != dev->wd_cl.state) { + if (id < 0) { dev_info(&dev->pdev->dev, "wd: failed to find the client\n"); return -ENOENT; } @@ -85,7 +85,7 @@ int mei_wd_host_init(struct mei_device *dev) dev->wd_cl.host_client_id = 0; return -EIO; } - dev->wd_cl.timer_count = CONNECT_TIMEOUT; + dev->wd_cl.timer_count = MEI_CONNECT_TIMEOUT; return 0; } @@ -360,23 +360,20 @@ void mei_watchdog_register(struct mei_device *dev) if (watchdog_register_device(&amt_wd_dev)) { dev_err(&dev->pdev->dev, "wd: unable to register watchdog device.\n"); - dev->wd_interface_reg = false; return; } dev_dbg(&dev->pdev->dev, "wd: successfully register watchdog interface.\n"); - dev->wd_interface_reg = true; watchdog_set_drvdata(&amt_wd_dev, dev); } void mei_watchdog_unregister(struct mei_device *dev) { - if (!dev->wd_interface_reg) + if (test_bit(WDOG_UNREGISTERED, &amt_wd_dev.status)) return; watchdog_set_drvdata(&amt_wd_dev, NULL); watchdog_unregister_device(&amt_wd_dev); - dev->wd_interface_reg = false; } diff --git a/drivers/misc/ti-st/st_core.c b/drivers/misc/ti-st/st_core.c index 46937b107261..b90a2241d79c 100644 --- a/drivers/misc/ti-st/st_core.c +++ b/drivers/misc/ti-st/st_core.c @@ -511,7 +511,6 @@ long st_register(struct st_proto_s *new_proto) unsigned long flags = 0; st_kim_ref(&st_gdata, 0); - pr_info("%s(%d) ", __func__, new_proto->chnl_id); if (st_gdata == NULL || new_proto == NULL || new_proto->recv == NULL || new_proto->reg_complete_cb == NULL) { pr_err("gdata/new_proto/recv or reg_complete_cb not ready"); diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 9bf10e7bbfaf..dfa6d56ff0ff 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -621,3 +621,10 @@ config MMC_USHC Note: These controllers only support SDIO cards and do not support MMC or SD memory cards. + +config MMC_REALTEK_PCI + tristate "Realtek PCI-E SD/MMC Card Interface Driver" + depends on MFD_RTSX_PCI + help + Say Y here to include driver code to support SD/MMC card interface + of Realtek PCI-E card reader diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 17ad0a7ba40b..8aa592d14c4e 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -46,6 +46,8 @@ obj-$(CONFIG_MMC_JZ4740) += jz4740_mmc.o obj-$(CONFIG_MMC_VUB300) += vub300.o obj-$(CONFIG_MMC_USHC) += ushc.o +obj-$(CONFIG_MMC_REALTEK_PCI) += rtsx_pci_sdmmc.o + obj-$(CONFIG_MMC_SDHCI_PLTFM) += sdhci-pltfm.o obj-$(CONFIG_MMC_SDHCI_CNS3XXX) += sdhci-cns3xxx.o obj-$(CONFIG_MMC_SDHCI_ESDHC_IMX) += sdhci-esdhc-imx.o diff --git a/drivers/mmc/host/rtsx_pci_sdmmc.c b/drivers/mmc/host/rtsx_pci_sdmmc.c new file mode 100644 index 000000000000..12eff6f8cab7 --- /dev/null +++ b/drivers/mmc/host/rtsx_pci_sdmmc.c @@ -0,0 +1,1348 @@ +/* Realtek PCI-Express SD/MMC Card Interface driver + * + * Copyright(c) 2009 Realtek Semiconductor Corp. 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 as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Author: + * Wei WANG <wei_wang@realsil.com.cn> + * No. 450, Shenhu Road, Suzhou Industry Park, Suzhou, China + */ + +#include <linux/module.h> +#include <linux/highmem.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/mmc/host.h> +#include <linux/mmc/mmc.h> +#include <linux/mmc/sd.h> +#include <linux/mmc/card.h> +#include <linux/mfd/rtsx_pci.h> +#include <asm/unaligned.h> + +/* SD Tuning Data Structure + * Record continuous timing phase path + */ +struct timing_phase_path { + int start; + int end; + int mid; + int len; +}; + +struct realtek_pci_sdmmc { + struct platform_device *pdev; + struct rtsx_pcr *pcr; + struct mmc_host *mmc; + struct mmc_request *mrq; + + struct mutex host_mutex; + + u8 ssc_depth; + unsigned int clock; + bool vpclk; + bool double_clk; + bool eject; + bool initial_mode; + bool ddr_mode; +}; + +static inline struct device *sdmmc_dev(struct realtek_pci_sdmmc *host) +{ + return &(host->pdev->dev); +} + +static inline void sd_clear_error(struct realtek_pci_sdmmc *host) +{ + rtsx_pci_write_register(host->pcr, CARD_STOP, + SD_STOP | SD_CLR_ERR, SD_STOP | SD_CLR_ERR); +} + +#ifdef DEBUG +static void sd_print_debug_regs(struct realtek_pci_sdmmc *host) +{ + struct rtsx_pcr *pcr = host->pcr; + u16 i; + u8 *ptr; + + /* Print SD host internal registers */ + rtsx_pci_init_cmd(pcr); + for (i = 0xFDA0; i <= 0xFDAE; i++) + rtsx_pci_add_cmd(pcr, READ_REG_CMD, i, 0, 0); + for (i = 0xFD52; i <= 0xFD69; i++) + rtsx_pci_add_cmd(pcr, READ_REG_CMD, i, 0, 0); + rtsx_pci_send_cmd(pcr, 100); + + ptr = rtsx_pci_get_cmd_data(pcr); + for (i = 0xFDA0; i <= 0xFDAE; i++) + dev_dbg(sdmmc_dev(host), "0x%04X: 0x%02x\n", i, *(ptr++)); + for (i = 0xFD52; i <= 0xFD69; i++) + dev_dbg(sdmmc_dev(host), "0x%04X: 0x%02x\n", i, *(ptr++)); +} +#else +#define sd_print_debug_regs(host) +#endif /* DEBUG */ + +static int sd_read_data(struct realtek_pci_sdmmc *host, u8 *cmd, u16 byte_cnt, + u8 *buf, int buf_len, int timeout) +{ + struct rtsx_pcr *pcr = host->pcr; + int err, i; + u8 trans_mode; + + dev_dbg(sdmmc_dev(host), "%s: SD/MMC CMD%d\n", __func__, cmd[0] - 0x40); + + if (!buf) + buf_len = 0; + + if ((cmd[0] & 0x3F) == MMC_SEND_TUNING_BLOCK) + trans_mode = SD_TM_AUTO_TUNING; + else + trans_mode = SD_TM_NORMAL_READ; + + rtsx_pci_init_cmd(pcr); + + for (i = 0; i < 5; i++) + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_CMD0 + i, 0xFF, cmd[i]); + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_BYTE_CNT_L, 0xFF, (u8)byte_cnt); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_BYTE_CNT_H, + 0xFF, (u8)(byte_cnt >> 8)); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_BLOCK_CNT_L, 0xFF, 1); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_BLOCK_CNT_H, 0xFF, 0); + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_CFG2, 0xFF, + SD_CALCULATE_CRC7 | SD_CHECK_CRC16 | + SD_NO_WAIT_BUSY_END | SD_CHECK_CRC7 | SD_RSP_LEN_6); + if (trans_mode != SD_TM_AUTO_TUNING) + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, + CARD_DATA_SOURCE, 0x01, PINGPONG_BUFFER); + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_TRANSFER, + 0xFF, trans_mode | SD_TRANSFER_START); + rtsx_pci_add_cmd(pcr, CHECK_REG_CMD, SD_TRANSFER, + SD_TRANSFER_END, SD_TRANSFER_END); + + err = rtsx_pci_send_cmd(pcr, timeout); + if (err < 0) { + sd_print_debug_regs(host); + dev_dbg(sdmmc_dev(host), + "rtsx_pci_send_cmd fail (err = %d)\n", err); + return err; + } + + if (buf && buf_len) { + err = rtsx_pci_read_ppbuf(pcr, buf, buf_len); + if (err < 0) { + dev_dbg(sdmmc_dev(host), + "rtsx_pci_read_ppbuf fail (err = %d)\n", err); + return err; + } + } + + return 0; +} + +static int sd_write_data(struct realtek_pci_sdmmc *host, u8 *cmd, u16 byte_cnt, + u8 *buf, int buf_len, int timeout) +{ + struct rtsx_pcr *pcr = host->pcr; + int err, i; + u8 trans_mode; + + if (!buf) + buf_len = 0; + + if (buf && buf_len) { + err = rtsx_pci_write_ppbuf(pcr, buf, buf_len); + if (err < 0) { + dev_dbg(sdmmc_dev(host), + "rtsx_pci_write_ppbuf fail (err = %d)\n", err); + return err; + } + } + + trans_mode = cmd ? SD_TM_AUTO_WRITE_2 : SD_TM_AUTO_WRITE_3; + rtsx_pci_init_cmd(pcr); + + if (cmd) { + dev_dbg(sdmmc_dev(host), "%s: SD/MMC CMD %d\n", __func__, + cmd[0] - 0x40); + + for (i = 0; i < 5; i++) + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, + SD_CMD0 + i, 0xFF, cmd[i]); + } + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_BYTE_CNT_L, 0xFF, (u8)byte_cnt); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_BYTE_CNT_H, + 0xFF, (u8)(byte_cnt >> 8)); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_BLOCK_CNT_L, 0xFF, 1); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_BLOCK_CNT_H, 0xFF, 0); + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_CFG2, 0xFF, + SD_CALCULATE_CRC7 | SD_CHECK_CRC16 | + SD_NO_WAIT_BUSY_END | SD_CHECK_CRC7 | SD_RSP_LEN_6); + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_TRANSFER, 0xFF, + trans_mode | SD_TRANSFER_START); + rtsx_pci_add_cmd(pcr, CHECK_REG_CMD, SD_TRANSFER, + SD_TRANSFER_END, SD_TRANSFER_END); + + err = rtsx_pci_send_cmd(pcr, timeout); + if (err < 0) { + sd_print_debug_regs(host); + dev_dbg(sdmmc_dev(host), + "rtsx_pci_send_cmd fail (err = %d)\n", err); + return err; + } + + return 0; +} + +static void sd_send_cmd_get_rsp(struct realtek_pci_sdmmc *host, + struct mmc_command *cmd) +{ + struct rtsx_pcr *pcr = host->pcr; + u8 cmd_idx = (u8)cmd->opcode; + u32 arg = cmd->arg; + int err = 0; + int timeout = 100; + int i; + u8 *ptr; + int stat_idx = 0; + u8 rsp_type; + int rsp_len = 5; + + dev_dbg(sdmmc_dev(host), "%s: SD/MMC CMD %d, arg = 0x%08x\n", + __func__, cmd_idx, arg); + + /* Response type: + * R0 + * R1, R5, R6, R7 + * R1b + * R2 + * R3, R4 + */ + switch (mmc_resp_type(cmd)) { + case MMC_RSP_NONE: + rsp_type = SD_RSP_TYPE_R0; + rsp_len = 0; + break; + case MMC_RSP_R1: + rsp_type = SD_RSP_TYPE_R1; + break; + case MMC_RSP_R1B: + rsp_type = SD_RSP_TYPE_R1b; + break; + case MMC_RSP_R2: + rsp_type = SD_RSP_TYPE_R2; + rsp_len = 16; + break; + case MMC_RSP_R3: + rsp_type = SD_RSP_TYPE_R3; + break; + default: + dev_dbg(sdmmc_dev(host), "cmd->flag is not valid\n"); + err = -EINVAL; + goto out; + } + + if (rsp_type == SD_RSP_TYPE_R1b) + timeout = 3000; + + if (cmd->opcode == SD_SWITCH_VOLTAGE) { + err = rtsx_pci_write_register(pcr, SD_BUS_STAT, + 0xFF, SD_CLK_TOGGLE_EN); + if (err < 0) + goto out; + } + + rtsx_pci_init_cmd(pcr); + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_CMD0, 0xFF, 0x40 | cmd_idx); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_CMD1, 0xFF, (u8)(arg >> 24)); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_CMD2, 0xFF, (u8)(arg >> 16)); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_CMD3, 0xFF, (u8)(arg >> 8)); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_CMD4, 0xFF, (u8)arg); + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_CFG2, 0xFF, rsp_type); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_DATA_SOURCE, + 0x01, PINGPONG_BUFFER); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_TRANSFER, + 0xFF, SD_TM_CMD_RSP | SD_TRANSFER_START); + rtsx_pci_add_cmd(pcr, CHECK_REG_CMD, SD_TRANSFER, + SD_TRANSFER_END | SD_STAT_IDLE, + SD_TRANSFER_END | SD_STAT_IDLE); + + if (rsp_type == SD_RSP_TYPE_R2) { + /* Read data from ping-pong buffer */ + for (i = PPBUF_BASE2; i < PPBUF_BASE2 + 16; i++) + rtsx_pci_add_cmd(pcr, READ_REG_CMD, (u16)i, 0, 0); + stat_idx = 16; + } else if (rsp_type != SD_RSP_TYPE_R0) { + /* Read data from SD_CMDx registers */ + for (i = SD_CMD0; i <= SD_CMD4; i++) + rtsx_pci_add_cmd(pcr, READ_REG_CMD, (u16)i, 0, 0); + stat_idx = 5; + } + + rtsx_pci_add_cmd(pcr, READ_REG_CMD, SD_STAT1, 0, 0); + + err = rtsx_pci_send_cmd(pcr, timeout); + if (err < 0) { + sd_print_debug_regs(host); + sd_clear_error(host); + dev_dbg(sdmmc_dev(host), + "rtsx_pci_send_cmd error (err = %d)\n", err); + goto out; + } + + if (rsp_type == SD_RSP_TYPE_R0) { + err = 0; + goto out; + } + + /* Eliminate returned value of CHECK_REG_CMD */ + ptr = rtsx_pci_get_cmd_data(pcr) + 1; + + /* Check (Start,Transmission) bit of Response */ + if ((ptr[0] & 0xC0) != 0) { + err = -EILSEQ; + dev_dbg(sdmmc_dev(host), "Invalid response bit\n"); + goto out; + } + + /* Check CRC7 */ + if (!(rsp_type & SD_NO_CHECK_CRC7)) { + if (ptr[stat_idx] & SD_CRC7_ERR) { + err = -EILSEQ; + dev_dbg(sdmmc_dev(host), "CRC7 error\n"); + goto out; + } + } + + if (rsp_type == SD_RSP_TYPE_R2) { + for (i = 0; i < 4; i++) { + cmd->resp[i] = get_unaligned_be32(ptr + 1 + i * 4); + dev_dbg(sdmmc_dev(host), "cmd->resp[%d] = 0x%08x\n", + i, cmd->resp[i]); + } + } else { + cmd->resp[0] = get_unaligned_be32(ptr + 1); + dev_dbg(sdmmc_dev(host), "cmd->resp[0] = 0x%08x\n", + cmd->resp[0]); + } + +out: + cmd->error = err; +} + +static int sd_rw_multi(struct realtek_pci_sdmmc *host, struct mmc_request *mrq) +{ + struct rtsx_pcr *pcr = host->pcr; + struct mmc_host *mmc = host->mmc; + struct mmc_card *card = mmc->card; + struct mmc_data *data = mrq->data; + int uhs = mmc_sd_card_uhs(card); + int read = (data->flags & MMC_DATA_READ) ? 1 : 0; + u8 cfg2, trans_mode; + int err; + size_t data_len = data->blksz * data->blocks; + + if (read) { + cfg2 = SD_CALCULATE_CRC7 | SD_CHECK_CRC16 | + SD_NO_WAIT_BUSY_END | SD_CHECK_CRC7 | SD_RSP_LEN_0; + trans_mode = SD_TM_AUTO_READ_3; + } else { + cfg2 = SD_NO_CALCULATE_CRC7 | SD_CHECK_CRC16 | + SD_NO_WAIT_BUSY_END | SD_NO_CHECK_CRC7 | SD_RSP_LEN_0; + trans_mode = SD_TM_AUTO_WRITE_3; + } + + if (!uhs) + cfg2 |= SD_NO_CHECK_WAIT_CRC_TO; + + rtsx_pci_init_cmd(pcr); + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_BYTE_CNT_L, 0xFF, 0x00); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_BYTE_CNT_H, 0xFF, 0x02); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_BLOCK_CNT_L, + 0xFF, (u8)data->blocks); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_BLOCK_CNT_H, + 0xFF, (u8)(data->blocks >> 8)); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, + CARD_DATA_SOURCE, 0x01, RING_BUFFER); + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, IRQSTAT0, + DMA_DONE_INT, DMA_DONE_INT); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, DMATC3, + 0xFF, (u8)(data_len >> 24)); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, DMATC2, + 0xFF, (u8)(data_len >> 16)); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, DMATC1, + 0xFF, (u8)(data_len >> 8)); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, DMATC0, 0xFF, (u8)data_len); + if (read) { + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, DMACTL, + 0x03 | DMA_PACK_SIZE_MASK, + DMA_DIR_FROM_CARD | DMA_EN | DMA_512); + } else { + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, DMACTL, + 0x03 | DMA_PACK_SIZE_MASK, + DMA_DIR_TO_CARD | DMA_EN | DMA_512); + } + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_DATA_SOURCE, + 0x01, RING_BUFFER); + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_TRANSFER, 0xFF, + trans_mode | SD_TRANSFER_START); + rtsx_pci_add_cmd(pcr, CHECK_REG_CMD, SD_TRANSFER, + SD_TRANSFER_END, SD_TRANSFER_END); + + rtsx_pci_send_cmd_no_wait(pcr); + + err = rtsx_pci_transfer_data(pcr, data->sg, data->sg_len, read, 10000); + if (err < 0) { + sd_clear_error(host); + return err; + } + + return 0; +} + +static inline void sd_enable_initial_mode(struct realtek_pci_sdmmc *host) +{ + rtsx_pci_write_register(host->pcr, SD_CFG1, + SD_CLK_DIVIDE_MASK, SD_CLK_DIVIDE_128); +} + +static inline void sd_disable_initial_mode(struct realtek_pci_sdmmc *host) +{ + rtsx_pci_write_register(host->pcr, SD_CFG1, + SD_CLK_DIVIDE_MASK, SD_CLK_DIVIDE_0); +} + +static void sd_normal_rw(struct realtek_pci_sdmmc *host, + struct mmc_request *mrq) +{ + struct mmc_command *cmd = mrq->cmd; + struct mmc_data *data = mrq->data; + u8 _cmd[5], *buf; + + _cmd[0] = 0x40 | (u8)cmd->opcode; + put_unaligned_be32(cmd->arg, (u32 *)(&_cmd[1])); + + buf = kzalloc(data->blksz, GFP_NOIO); + if (!buf) { + cmd->error = -ENOMEM; + return; + } + + if (data->flags & MMC_DATA_READ) { + if (host->initial_mode) + sd_disable_initial_mode(host); + + cmd->error = sd_read_data(host, _cmd, (u16)data->blksz, buf, + data->blksz, 200); + + if (host->initial_mode) + sd_enable_initial_mode(host); + + sg_copy_from_buffer(data->sg, data->sg_len, buf, data->blksz); + } else { + sg_copy_to_buffer(data->sg, data->sg_len, buf, data->blksz); + + cmd->error = sd_write_data(host, _cmd, (u16)data->blksz, buf, + data->blksz, 200); + } + + kfree(buf); +} + +static int sd_change_phase(struct realtek_pci_sdmmc *host, u8 sample_point) +{ + struct rtsx_pcr *pcr = host->pcr; + int err; + + dev_dbg(sdmmc_dev(host), "%s: sample_point = %d\n", + __func__, sample_point); + + rtsx_pci_init_cmd(pcr); + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CLK_CTL, CHANGE_CLK, CHANGE_CLK); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_VPRX_CTL, 0x1F, sample_point); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_VPCLK0_CTL, PHASE_NOT_RESET, 0); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_VPCLK0_CTL, + PHASE_NOT_RESET, PHASE_NOT_RESET); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CLK_CTL, CHANGE_CLK, 0); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_CFG1, SD_ASYNC_FIFO_NOT_RST, 0); + + err = rtsx_pci_send_cmd(pcr, 100); + if (err < 0) + return err; + + return 0; +} + +static u8 sd_search_final_phase(struct realtek_pci_sdmmc *host, u32 phase_map) +{ + struct timing_phase_path path[MAX_PHASE + 1]; + int i, j, cont_path_cnt; + int new_block, max_len, final_path_idx; + u8 final_phase = 0xFF; + + /* Parse phase_map, take it as a bit-ring */ + cont_path_cnt = 0; + new_block = 1; + j = 0; + for (i = 0; i < MAX_PHASE + 1; i++) { + if (phase_map & (1 << i)) { + if (new_block) { + new_block = 0; + j = cont_path_cnt++; + path[j].start = i; + path[j].end = i; + } else { + path[j].end = i; + } + } else { + new_block = 1; + if (cont_path_cnt) { + /* Calculate path length and middle point */ + int idx = cont_path_cnt - 1; + path[idx].len = + path[idx].end - path[idx].start + 1; + path[idx].mid = + path[idx].start + path[idx].len / 2; + } + } + } + + if (cont_path_cnt == 0) { + dev_dbg(sdmmc_dev(host), "No continuous phase path\n"); + goto finish; + } else { + /* Calculate last continuous path length and middle point */ + int idx = cont_path_cnt - 1; + path[idx].len = path[idx].end - path[idx].start + 1; + path[idx].mid = path[idx].start + path[idx].len / 2; + } + + /* Connect the first and last continuous paths if they are adjacent */ + if (!path[0].start && (path[cont_path_cnt - 1].end == MAX_PHASE)) { + /* Using negative index */ + path[0].start = path[cont_path_cnt - 1].start - MAX_PHASE - 1; + path[0].len += path[cont_path_cnt - 1].len; + path[0].mid = path[0].start + path[0].len / 2; + /* Convert negative middle point index to positive one */ + if (path[0].mid < 0) + path[0].mid += MAX_PHASE + 1; + cont_path_cnt--; + } + + /* Choose the longest continuous phase path */ + max_len = 0; + final_phase = 0; + final_path_idx = 0; + for (i = 0; i < cont_path_cnt; i++) { + if (path[i].len > max_len) { + max_len = path[i].len; + final_phase = (u8)path[i].mid; + final_path_idx = i; + } + + dev_dbg(sdmmc_dev(host), "path[%d].start = %d\n", + i, path[i].start); + dev_dbg(sdmmc_dev(host), "path[%d].end = %d\n", + i, path[i].end); + dev_dbg(sdmmc_dev(host), "path[%d].len = %d\n", + i, path[i].len); + dev_dbg(sdmmc_dev(host), "path[%d].mid = %d\n", + i, path[i].mid); + } + +finish: + dev_dbg(sdmmc_dev(host), "Final chosen phase: %d\n", final_phase); + return final_phase; +} + +static void sd_wait_data_idle(struct realtek_pci_sdmmc *host) +{ + int err, i; + u8 val = 0; + + for (i = 0; i < 100; i++) { + err = rtsx_pci_read_register(host->pcr, SD_DATA_STATE, &val); + if (val & SD_DATA_IDLE) + return; + + udelay(100); + } +} + +static int sd_tuning_rx_cmd(struct realtek_pci_sdmmc *host, + u8 opcode, u8 sample_point) +{ + int err; + u8 cmd[5] = {0}; + + err = sd_change_phase(host, sample_point); + if (err < 0) + return err; + + cmd[0] = 0x40 | opcode; + err = sd_read_data(host, cmd, 0x40, NULL, 0, 100); + if (err < 0) { + /* Wait till SD DATA IDLE */ + sd_wait_data_idle(host); + sd_clear_error(host); + return err; + } + + return 0; +} + +static int sd_tuning_phase(struct realtek_pci_sdmmc *host, + u8 opcode, u32 *phase_map) +{ + int err, i; + u32 raw_phase_map = 0; + + for (i = MAX_PHASE; i >= 0; i--) { + err = sd_tuning_rx_cmd(host, opcode, (u8)i); + if (err == 0) + raw_phase_map |= 1 << i; + } + + if (phase_map) + *phase_map = raw_phase_map; + + return 0; +} + +static int sd_tuning_rx(struct realtek_pci_sdmmc *host, u8 opcode) +{ + int err, i; + u32 raw_phase_map[RX_TUNING_CNT] = {0}, phase_map; + u8 final_phase; + + for (i = 0; i < RX_TUNING_CNT; i++) { + err = sd_tuning_phase(host, opcode, &(raw_phase_map[i])); + if (err < 0) + return err; + + if (raw_phase_map[i] == 0) + break; + } + + phase_map = 0xFFFFFFFF; + for (i = 0; i < RX_TUNING_CNT; i++) { + dev_dbg(sdmmc_dev(host), "RX raw_phase_map[%d] = 0x%08x\n", + i, raw_phase_map[i]); + phase_map &= raw_phase_map[i]; + } + dev_dbg(sdmmc_dev(host), "RX phase_map = 0x%08x\n", phase_map); + + if (phase_map) { + final_phase = sd_search_final_phase(host, phase_map); + if (final_phase == 0xFF) + return -EINVAL; + + err = sd_change_phase(host, final_phase); + if (err < 0) + return err; + } else { + return -EINVAL; + } + + return 0; +} + +static void sdmmc_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct realtek_pci_sdmmc *host = mmc_priv(mmc); + struct rtsx_pcr *pcr = host->pcr; + struct mmc_command *cmd = mrq->cmd; + struct mmc_data *data = mrq->data; + unsigned int data_size = 0; + + if (host->eject) { + cmd->error = -ENOMEDIUM; + goto finish; + } + + mutex_lock(&pcr->pcr_mutex); + + rtsx_pci_start_run(pcr); + + rtsx_pci_switch_clock(pcr, host->clock, host->ssc_depth, + host->initial_mode, host->double_clk, host->vpclk); + rtsx_pci_write_register(pcr, CARD_SELECT, 0x07, SD_MOD_SEL); + rtsx_pci_write_register(pcr, CARD_SHARE_MODE, + CARD_SHARE_MASK, CARD_SHARE_48_SD); + + mutex_lock(&host->host_mutex); + host->mrq = mrq; + mutex_unlock(&host->host_mutex); + + if (mrq->data) + data_size = data->blocks * data->blksz; + + if (!data_size || mmc_op_multi(cmd->opcode) || + (cmd->opcode == MMC_READ_SINGLE_BLOCK) || + (cmd->opcode == MMC_WRITE_BLOCK)) { + sd_send_cmd_get_rsp(host, cmd); + + if (!cmd->error && data_size) { + sd_rw_multi(host, mrq); + + if (mmc_op_multi(cmd->opcode) && mrq->stop) + sd_send_cmd_get_rsp(host, mrq->stop); + } + } else { + sd_normal_rw(host, mrq); + } + + if (mrq->data) { + if (cmd->error || data->error) + data->bytes_xfered = 0; + else + data->bytes_xfered = data->blocks * data->blksz; + } + + mutex_unlock(&pcr->pcr_mutex); + +finish: + if (cmd->error) + dev_dbg(sdmmc_dev(host), "cmd->error = %d\n", cmd->error); + + mutex_lock(&host->host_mutex); + host->mrq = NULL; + mutex_unlock(&host->host_mutex); + + mmc_request_done(mmc, mrq); +} + +static int sd_set_bus_width(struct realtek_pci_sdmmc *host, + unsigned char bus_width) +{ + int err = 0; + u8 width[] = { + [MMC_BUS_WIDTH_1] = SD_BUS_WIDTH_1BIT, + [MMC_BUS_WIDTH_4] = SD_BUS_WIDTH_4BIT, + [MMC_BUS_WIDTH_8] = SD_BUS_WIDTH_8BIT, + }; + + if (bus_width <= MMC_BUS_WIDTH_8) + err = rtsx_pci_write_register(host->pcr, SD_CFG1, + 0x03, width[bus_width]); + + return err; +} + +static int sd_power_on(struct realtek_pci_sdmmc *host) +{ + struct rtsx_pcr *pcr = host->pcr; + int err; + + rtsx_pci_init_cmd(pcr); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_SELECT, 0x07, SD_MOD_SEL); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_SHARE_MODE, + CARD_SHARE_MASK, CARD_SHARE_48_SD); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_CLK_EN, + SD_CLK_EN, SD_CLK_EN); + err = rtsx_pci_send_cmd(pcr, 100); + if (err < 0) + return err; + + err = rtsx_pci_card_pull_ctl_enable(pcr, RTSX_SD_CARD); + if (err < 0) + return err; + + err = rtsx_pci_card_power_on(pcr, RTSX_SD_CARD); + if (err < 0) + return err; + + err = rtsx_pci_write_register(pcr, CARD_OE, SD_OUTPUT_EN, SD_OUTPUT_EN); + if (err < 0) + return err; + + return 0; +} + +static int sd_power_off(struct realtek_pci_sdmmc *host) +{ + struct rtsx_pcr *pcr = host->pcr; + int err; + + rtsx_pci_init_cmd(pcr); + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_CLK_EN, SD_CLK_EN, 0); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_OE, SD_OUTPUT_EN, 0); + + err = rtsx_pci_send_cmd(pcr, 100); + if (err < 0) + return err; + + err = rtsx_pci_card_power_off(pcr, RTSX_SD_CARD); + if (err < 0) + return err; + + return rtsx_pci_card_pull_ctl_disable(pcr, RTSX_SD_CARD); +} + +static int sd_set_power_mode(struct realtek_pci_sdmmc *host, + unsigned char power_mode) +{ + int err; + + if (power_mode == MMC_POWER_OFF) + err = sd_power_off(host); + else + err = sd_power_on(host); + + return err; +} + +static int sd_set_timing(struct realtek_pci_sdmmc *host, + unsigned char timing, bool *ddr_mode) +{ + struct rtsx_pcr *pcr = host->pcr; + int err = 0; + + *ddr_mode = false; + + rtsx_pci_init_cmd(pcr); + + switch (timing) { + case MMC_TIMING_UHS_SDR104: + case MMC_TIMING_UHS_SDR50: + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_CFG1, + 0x0C | SD_ASYNC_FIFO_NOT_RST, + SD_30_MODE | SD_ASYNC_FIFO_NOT_RST); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CLK_CTL, + CLK_LOW_FREQ, CLK_LOW_FREQ); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_CLK_SOURCE, 0xFF, + CRC_VAR_CLK0 | SD30_FIX_CLK | SAMPLE_VAR_CLK1); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CLK_CTL, CLK_LOW_FREQ, 0); + break; + + case MMC_TIMING_UHS_DDR50: + *ddr_mode = true; + + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_CFG1, + 0x0C | SD_ASYNC_FIFO_NOT_RST, + SD_DDR_MODE | SD_ASYNC_FIFO_NOT_RST); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CLK_CTL, + CLK_LOW_FREQ, CLK_LOW_FREQ); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_CLK_SOURCE, 0xFF, + CRC_VAR_CLK0 | SD30_FIX_CLK | SAMPLE_VAR_CLK1); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CLK_CTL, CLK_LOW_FREQ, 0); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_PUSH_POINT_CTL, + DDR_VAR_TX_CMD_DAT, DDR_VAR_TX_CMD_DAT); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_SAMPLE_POINT_CTL, + DDR_VAR_RX_DAT | DDR_VAR_RX_CMD, + DDR_VAR_RX_DAT | DDR_VAR_RX_CMD); + break; + + case MMC_TIMING_MMC_HS: + case MMC_TIMING_SD_HS: + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_CFG1, + 0x0C, SD_20_MODE); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CLK_CTL, + CLK_LOW_FREQ, CLK_LOW_FREQ); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_CLK_SOURCE, 0xFF, + CRC_FIX_CLK | SD30_VAR_CLK0 | SAMPLE_VAR_CLK1); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CLK_CTL, CLK_LOW_FREQ, 0); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_PUSH_POINT_CTL, + SD20_TX_SEL_MASK, SD20_TX_14_AHEAD); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_SAMPLE_POINT_CTL, + SD20_RX_SEL_MASK, SD20_RX_14_DELAY); + break; + + default: + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, + SD_CFG1, 0x0C, SD_20_MODE); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CLK_CTL, + CLK_LOW_FREQ, CLK_LOW_FREQ); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CARD_CLK_SOURCE, 0xFF, + CRC_FIX_CLK | SD30_VAR_CLK0 | SAMPLE_VAR_CLK1); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, CLK_CTL, CLK_LOW_FREQ, 0); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, + SD_PUSH_POINT_CTL, 0xFF, 0); + rtsx_pci_add_cmd(pcr, WRITE_REG_CMD, SD_SAMPLE_POINT_CTL, + SD20_RX_SEL_MASK, SD20_RX_POS_EDGE); + break; + } + + err = rtsx_pci_send_cmd(pcr, 100); + + return err; +} + +static void sdmmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct realtek_pci_sdmmc *host = mmc_priv(mmc); + struct rtsx_pcr *pcr = host->pcr; + + if (host->eject) + return; + + mutex_lock(&pcr->pcr_mutex); + + rtsx_pci_start_run(pcr); + + sd_set_bus_width(host, ios->bus_width); + sd_set_power_mode(host, ios->power_mode); + sd_set_timing(host, ios->timing, &host->ddr_mode); + + host->vpclk = false; + host->double_clk = true; + + switch (ios->timing) { + case MMC_TIMING_UHS_SDR104: + case MMC_TIMING_UHS_SDR50: + host->ssc_depth = RTSX_SSC_DEPTH_2M; + host->vpclk = true; + host->double_clk = false; + break; + case MMC_TIMING_UHS_DDR50: + case MMC_TIMING_UHS_SDR25: + host->ssc_depth = RTSX_SSC_DEPTH_1M; + break; + default: + host->ssc_depth = RTSX_SSC_DEPTH_500K; + break; + } + + host->initial_mode = (ios->clock <= 1000000) ? true : false; + + host->clock = ios->clock; + rtsx_pci_switch_clock(pcr, ios->clock, host->ssc_depth, + host->initial_mode, host->double_clk, host->vpclk); + + mutex_unlock(&pcr->pcr_mutex); +} + +static int sdmmc_get_ro(struct mmc_host *mmc) +{ + struct realtek_pci_sdmmc *host = mmc_priv(mmc); + struct rtsx_pcr *pcr = host->pcr; + int ro = 0; + u32 val; + + if (host->eject) + return -ENOMEDIUM; + + mutex_lock(&pcr->pcr_mutex); + + rtsx_pci_start_run(pcr); + + /* Check SD mechanical write-protect switch */ + val = rtsx_pci_readl(pcr, RTSX_BIPR); + dev_dbg(sdmmc_dev(host), "%s: RTSX_BIPR = 0x%08x\n", __func__, val); + if (val & SD_WRITE_PROTECT) + ro = 1; + + mutex_unlock(&pcr->pcr_mutex); + + return ro; +} + +static int sdmmc_get_cd(struct mmc_host *mmc) +{ + struct realtek_pci_sdmmc *host = mmc_priv(mmc); + struct rtsx_pcr *pcr = host->pcr; + int cd = 0; + u32 val; + + if (host->eject) + return -ENOMEDIUM; + + mutex_lock(&pcr->pcr_mutex); + + rtsx_pci_start_run(pcr); + + /* Check SD card detect */ + val = rtsx_pci_card_exist(pcr); + dev_dbg(sdmmc_dev(host), "%s: RTSX_BIPR = 0x%08x\n", __func__, val); + if (val & SD_EXIST) + cd = 1; + + mutex_unlock(&pcr->pcr_mutex); + + return cd; +} + +static int sd_wait_voltage_stable_1(struct realtek_pci_sdmmc *host) +{ + struct rtsx_pcr *pcr = host->pcr; + int err; + u8 stat; + + /* Reference to Signal Voltage Switch Sequence in SD spec. + * Wait for a period of time so that the card can drive SD_CMD and + * SD_DAT[3:0] to low after sending back CMD11 response. + */ + mdelay(1); + + /* SD_CMD, SD_DAT[3:0] should be driven to low by card; + * If either one of SD_CMD,SD_DAT[3:0] is not low, + * abort the voltage switch sequence; + */ + err = rtsx_pci_read_register(pcr, SD_BUS_STAT, &stat); + if (err < 0) + return err; + + if (stat & (SD_CMD_STATUS | SD_DAT3_STATUS | SD_DAT2_STATUS | + SD_DAT1_STATUS | SD_DAT0_STATUS)) + return -EINVAL; + + /* Stop toggle SD clock */ + err = rtsx_pci_write_register(pcr, SD_BUS_STAT, + 0xFF, SD_CLK_FORCE_STOP); + if (err < 0) + return err; + + return 0; +} + +static int sd_wait_voltage_stable_2(struct realtek_pci_sdmmc *host) +{ + struct rtsx_pcr *pcr = host->pcr; + int err; + u8 stat, mask, val; + + /* Wait 1.8V output of voltage regulator in card stable */ + msleep(50); + + /* Toggle SD clock again */ + err = rtsx_pci_write_register(pcr, SD_BUS_STAT, 0xFF, SD_CLK_TOGGLE_EN); + if (err < 0) + return err; + + /* Wait for a period of time so that the card can drive + * SD_DAT[3:0] to high at 1.8V + */ + msleep(20); + + /* SD_CMD, SD_DAT[3:0] should be pulled high by host */ + err = rtsx_pci_read_register(pcr, SD_BUS_STAT, &stat); + if (err < 0) + return err; + + mask = SD_CMD_STATUS | SD_DAT3_STATUS | SD_DAT2_STATUS | + SD_DAT1_STATUS | SD_DAT0_STATUS; + val = SD_CMD_STATUS | SD_DAT3_STATUS | SD_DAT2_STATUS | + SD_DAT1_STATUS | SD_DAT0_STATUS; + if ((stat & mask) != val) { + dev_dbg(sdmmc_dev(host), + "%s: SD_BUS_STAT = 0x%x\n", __func__, stat); + rtsx_pci_write_register(pcr, SD_BUS_STAT, + SD_CLK_TOGGLE_EN | SD_CLK_FORCE_STOP, 0); + rtsx_pci_write_register(pcr, CARD_CLK_EN, 0xFF, 0); + return -EINVAL; + } + + return 0; +} + +static int sd_change_bank_voltage(struct realtek_pci_sdmmc *host, u8 voltage) +{ + struct rtsx_pcr *pcr = host->pcr; + int err; + + if (voltage == SD_IO_3V3) { + err = rtsx_pci_write_phy_register(pcr, 0x08, 0x4FC0 | 0x24); + if (err < 0) + return err; + } else if (voltage == SD_IO_1V8) { + err = rtsx_pci_write_phy_register(pcr, 0x08, 0x4C40 | 0x24); + if (err < 0) + return err; + } else { + return -EINVAL; + } + + return 0; +} + +static int sdmmc_switch_voltage(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct realtek_pci_sdmmc *host = mmc_priv(mmc); + struct rtsx_pcr *pcr = host->pcr; + int err = 0; + u8 voltage; + + dev_dbg(sdmmc_dev(host), "%s: signal_voltage = %d\n", + __func__, ios->signal_voltage); + + if (host->eject) + return -ENOMEDIUM; + + mutex_lock(&pcr->pcr_mutex); + + rtsx_pci_start_run(pcr); + + if (ios->signal_voltage == MMC_SIGNAL_VOLTAGE_330) + voltage = SD_IO_3V3; + else + voltage = SD_IO_1V8; + + if (voltage == SD_IO_1V8) { + err = rtsx_pci_write_register(pcr, + SD30_DRIVE_SEL, 0x07, DRIVER_TYPE_B); + if (err < 0) + goto out; + + err = sd_wait_voltage_stable_1(host); + if (err < 0) + goto out; + } + + err = sd_change_bank_voltage(host, voltage); + if (err < 0) + goto out; + + if (voltage == SD_IO_1V8) { + err = sd_wait_voltage_stable_2(host); + if (err < 0) + goto out; + } + + /* Stop toggle SD clock in idle */ + err = rtsx_pci_write_register(pcr, SD_BUS_STAT, + SD_CLK_TOGGLE_EN | SD_CLK_FORCE_STOP, 0); + +out: + mutex_unlock(&pcr->pcr_mutex); + + return err; +} + +static int sdmmc_execute_tuning(struct mmc_host *mmc, u32 opcode) +{ + struct realtek_pci_sdmmc *host = mmc_priv(mmc); + struct rtsx_pcr *pcr = host->pcr; + int err = 0; + + if (host->eject) + return -ENOMEDIUM; + + mutex_lock(&pcr->pcr_mutex); + + rtsx_pci_start_run(pcr); + + if (!host->ddr_mode) + err = sd_tuning_rx(host, MMC_SEND_TUNING_BLOCK); + + mutex_unlock(&pcr->pcr_mutex); + + return err; +} + +static const struct mmc_host_ops realtek_pci_sdmmc_ops = { + .request = sdmmc_request, + .set_ios = sdmmc_set_ios, + .get_ro = sdmmc_get_ro, + .get_cd = sdmmc_get_cd, + .start_signal_voltage_switch = sdmmc_switch_voltage, + .execute_tuning = sdmmc_execute_tuning, +}; + +#ifdef CONFIG_PM +static int rtsx_pci_sdmmc_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct realtek_pci_sdmmc *host = platform_get_drvdata(pdev); + struct mmc_host *mmc = host->mmc; + int err; + + dev_dbg(sdmmc_dev(host), "--> %s\n", __func__); + + err = mmc_suspend_host(mmc); + if (err) + return err; + + return 0; +} + +static int rtsx_pci_sdmmc_resume(struct platform_device *pdev) +{ + struct realtek_pci_sdmmc *host = platform_get_drvdata(pdev); + struct mmc_host *mmc = host->mmc; + + dev_dbg(sdmmc_dev(host), "--> %s\n", __func__); + + return mmc_resume_host(mmc); +} +#else /* CONFIG_PM */ +#define rtsx_pci_sdmmc_suspend NULL +#define rtsx_pci_sdmmc_resume NULL +#endif /* CONFIG_PM */ + +static void init_extra_caps(struct realtek_pci_sdmmc *host) +{ + struct mmc_host *mmc = host->mmc; + struct rtsx_pcr *pcr = host->pcr; + + dev_dbg(sdmmc_dev(host), "pcr->extra_caps = 0x%x\n", pcr->extra_caps); + + if (pcr->extra_caps & EXTRA_CAPS_SD_SDR50) + mmc->caps |= MMC_CAP_UHS_SDR50; + if (pcr->extra_caps & EXTRA_CAPS_SD_SDR104) + mmc->caps |= MMC_CAP_UHS_SDR104; + if (pcr->extra_caps & EXTRA_CAPS_SD_DDR50) + mmc->caps |= MMC_CAP_UHS_DDR50; + if (pcr->extra_caps & EXTRA_CAPS_MMC_HSDDR) + mmc->caps |= MMC_CAP_1_8V_DDR; + if (pcr->extra_caps & EXTRA_CAPS_MMC_8BIT) + mmc->caps |= MMC_CAP_8_BIT_DATA; +} + +static void realtek_init_host(struct realtek_pci_sdmmc *host) +{ + struct mmc_host *mmc = host->mmc; + + mmc->f_min = 250000; + mmc->f_max = 208000000; + mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195; + mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SD_HIGHSPEED | + MMC_CAP_MMC_HIGHSPEED | MMC_CAP_BUS_WIDTH_TEST | + MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25; + mmc->max_current_330 = 400; + mmc->max_current_180 = 800; + mmc->ops = &realtek_pci_sdmmc_ops; + + init_extra_caps(host); + + mmc->max_segs = 256; + mmc->max_seg_size = 65536; + mmc->max_blk_size = 512; + mmc->max_blk_count = 65535; + mmc->max_req_size = 524288; +} + +static void rtsx_pci_sdmmc_card_event(struct platform_device *pdev) +{ + struct realtek_pci_sdmmc *host = platform_get_drvdata(pdev); + + mmc_detect_change(host->mmc, 0); +} + +static int rtsx_pci_sdmmc_drv_probe(struct platform_device *pdev) +{ + struct mmc_host *mmc; + struct realtek_pci_sdmmc *host; + struct rtsx_pcr *pcr; + struct pcr_handle *handle = pdev->dev.platform_data; + + if (!handle) + return -ENXIO; + + pcr = handle->pcr; + if (!pcr) + return -ENXIO; + + dev_dbg(&(pdev->dev), ": Realtek PCI-E SDMMC controller found\n"); + + mmc = mmc_alloc_host(sizeof(*host), &pdev->dev); + if (!mmc) + return -ENOMEM; + + host = mmc_priv(mmc); + host->pcr = pcr; + host->mmc = mmc; + host->pdev = pdev; + platform_set_drvdata(pdev, host); + pcr->slots[RTSX_SD_CARD].p_dev = pdev; + pcr->slots[RTSX_SD_CARD].card_event = rtsx_pci_sdmmc_card_event; + + mutex_init(&host->host_mutex); + + realtek_init_host(host); + + mmc_add_host(mmc); + + return 0; +} + +static int rtsx_pci_sdmmc_drv_remove(struct platform_device *pdev) +{ + struct realtek_pci_sdmmc *host = platform_get_drvdata(pdev); + struct rtsx_pcr *pcr; + struct mmc_host *mmc; + + if (!host) + return 0; + + pcr = host->pcr; + pcr->slots[RTSX_SD_CARD].p_dev = NULL; + pcr->slots[RTSX_SD_CARD].card_event = NULL; + mmc = host->mmc; + host->eject = true; + + mutex_lock(&host->host_mutex); + if (host->mrq) { + dev_dbg(&(pdev->dev), + "%s: Controller removed during transfer\n", + mmc_hostname(mmc)); + + rtsx_pci_complete_unfinished_transfer(pcr); + + host->mrq->cmd->error = -ENOMEDIUM; + if (host->mrq->stop) + host->mrq->stop->error = -ENOMEDIUM; + mmc_request_done(mmc, host->mrq); + } + mutex_unlock(&host->host_mutex); + + mmc_remove_host(mmc); + mmc_free_host(mmc); + + platform_set_drvdata(pdev, NULL); + + dev_dbg(&(pdev->dev), + ": Realtek PCI-E SDMMC controller has been removed\n"); + + return 0; +} + +static struct platform_device_id rtsx_pci_sdmmc_ids[] = { + { + .name = DRV_NAME_RTSX_PCI_SDMMC, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(platform, rtsx_pci_sdmmc_ids); + +static struct platform_driver rtsx_pci_sdmmc_driver = { + .probe = rtsx_pci_sdmmc_drv_probe, + .remove = rtsx_pci_sdmmc_drv_remove, + .id_table = rtsx_pci_sdmmc_ids, + .suspend = rtsx_pci_sdmmc_suspend, + .resume = rtsx_pci_sdmmc_resume, + .driver = { + .owner = THIS_MODULE, + .name = DRV_NAME_RTSX_PCI_SDMMC, + }, +}; +module_platform_driver(rtsx_pci_sdmmc_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Wei WANG <wei_wang@realsil.com.cn>"); +MODULE_DESCRIPTION("Realtek PCI-E SD/MMC Card Host Driver"); diff --git a/drivers/parport/Kconfig b/drivers/parport/Kconfig index 4b6e4e7aca8f..0e60438ebe30 100644 --- a/drivers/parport/Kconfig +++ b/drivers/parport/Kconfig @@ -57,8 +57,8 @@ config PARPORT_SERIAL will be called parport_serial. config PARPORT_PC_FIFO - bool "Use FIFO/DMA if available (EXPERIMENTAL)" - depends on PARPORT_PC && EXPERIMENTAL + bool "Use FIFO/DMA if available" + depends on PARPORT_PC help Many parallel port chipsets provide hardware that can speed up printing. Say Y here if you want to take advantage of that. @@ -70,8 +70,8 @@ config PARPORT_PC_FIFO specify which IRQ/DMA to use. config PARPORT_PC_SUPERIO - bool "SuperIO chipset support (EXPERIMENTAL)" - depends on PARPORT_PC && EXPERIMENTAL + bool "SuperIO chipset support" + depends on PARPORT_PC help Saying Y here enables some probes for Super-IO chipsets in order to find out things like base addresses, IRQ lines and DMA channels. It @@ -85,8 +85,8 @@ config PARPORT_PC_PCMCIA ports. If unsure, say N. config PARPORT_IP32 - tristate "SGI IP32 builtin port (EXPERIMENTAL)" - depends on SGI_IP32 && EXPERIMENTAL + tristate "SGI IP32 builtin port" + depends on SGI_IP32 select PARPORT_NOT_PC help Say Y here if you need support for the parallel port on @@ -126,8 +126,8 @@ config PARPORT_GSC select PARPORT_NOT_PC config PARPORT_SUNBPP - tristate "Sparc hardware (EXPERIMENTAL)" - depends on SBUS && EXPERIMENTAL + tristate "Sparc hardware" + depends on SBUS select PARPORT_NOT_PC help This driver provides support for the bidirectional parallel port diff --git a/drivers/uio/Kconfig b/drivers/uio/Kconfig index 6f3ea9bbc818..82e2b89d4480 100644 --- a/drivers/uio/Kconfig +++ b/drivers/uio/Kconfig @@ -44,6 +44,22 @@ config UIO_PDRV_GENIRQ If you don't know what to do here, say N. +config UIO_DMEM_GENIRQ + tristate "Userspace platform driver with generic irq and dynamic memory" + help + Platform driver for Userspace I/O devices, including generic + interrupt handling code. Shared interrupts are not supported. + + Memory regions can be specified with the same platform device + resources as the UIO_PDRV drivers, but dynamic regions can also + be specified. + The number and size of these regions is static, + but the memory allocation is not performed until + the associated device file is opened. The + memory is freed once the uio device is closed. + + If you don't know what to do here, say N. + config UIO_AEC tristate "AEC video timestamp device" depends on PCI diff --git a/drivers/uio/Makefile b/drivers/uio/Makefile index d4dd9a5552f8..b354c539507a 100644 --- a/drivers/uio/Makefile +++ b/drivers/uio/Makefile @@ -2,6 +2,7 @@ obj-$(CONFIG_UIO) += uio.o obj-$(CONFIG_UIO_CIF) += uio_cif.o obj-$(CONFIG_UIO_PDRV) += uio_pdrv.o obj-$(CONFIG_UIO_PDRV_GENIRQ) += uio_pdrv_genirq.o +obj-$(CONFIG_UIO_DMEM_GENIRQ) += uio_dmem_genirq.o obj-$(CONFIG_UIO_AEC) += uio_aec.o obj-$(CONFIG_UIO_SERCOS3) += uio_sercos3.o obj-$(CONFIG_UIO_PCI_GENERIC) += uio_pci_generic.o diff --git a/drivers/uio/uio_dmem_genirq.c b/drivers/uio/uio_dmem_genirq.c new file mode 100644 index 000000000000..4d4dd008c8b9 --- /dev/null +++ b/drivers/uio/uio_dmem_genirq.c @@ -0,0 +1,354 @@ +/* + * drivers/uio/uio_dmem_genirq.c + * + * Userspace I/O platform driver with generic IRQ handling code. + * + * Copyright (C) 2012 Damian Hobson-Garcia + * + * Based on uio_pdrv_genirq.c by Magnus Damm + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <linux/platform_device.h> +#include <linux/uio_driver.h> +#include <linux/spinlock.h> +#include <linux/bitops.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/platform_data/uio_dmem_genirq.h> +#include <linux/stringify.h> +#include <linux/pm_runtime.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> + +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/of_address.h> + +#define DRIVER_NAME "uio_dmem_genirq" + +struct uio_dmem_genirq_platdata { + struct uio_info *uioinfo; + spinlock_t lock; + unsigned long flags; + struct platform_device *pdev; + unsigned int dmem_region_start; + unsigned int num_dmem_regions; + struct mutex alloc_lock; + unsigned int refcnt; +}; + +static int uio_dmem_genirq_open(struct uio_info *info, struct inode *inode) +{ + struct uio_dmem_genirq_platdata *priv = info->priv; + struct uio_mem *uiomem; + int ret = 0; + + uiomem = &priv->uioinfo->mem[priv->dmem_region_start]; + + mutex_lock(&priv->alloc_lock); + while (!priv->refcnt && uiomem < &priv->uioinfo->mem[MAX_UIO_MAPS]) { + void *addr; + if (!uiomem->size) + break; + + addr = dma_alloc_coherent(&priv->pdev->dev, uiomem->size, + (dma_addr_t *)&uiomem->addr, GFP_KERNEL); + if (!addr) { + ret = -ENOMEM; + break; + } + + uiomem->internal_addr = addr; + ++uiomem; + } + priv->refcnt++; + + mutex_unlock(&priv->alloc_lock); + /* Wait until the Runtime PM code has woken up the device */ + pm_runtime_get_sync(&priv->pdev->dev); + return ret; +} + +static int uio_dmem_genirq_release(struct uio_info *info, struct inode *inode) +{ + struct uio_dmem_genirq_platdata *priv = info->priv; + struct uio_mem *uiomem; + + /* Tell the Runtime PM code that the device has become idle */ + pm_runtime_put_sync(&priv->pdev->dev); + + uiomem = &priv->uioinfo->mem[priv->dmem_region_start]; + + mutex_lock(&priv->alloc_lock); + + priv->refcnt--; + while (!priv->refcnt && uiomem < &priv->uioinfo->mem[MAX_UIO_MAPS]) { + if (!uiomem->size) + break; + + dma_free_coherent(&priv->pdev->dev, uiomem->size, + uiomem->internal_addr, uiomem->addr); + uiomem->addr = DMA_ERROR_CODE; + ++uiomem; + } + + mutex_unlock(&priv->alloc_lock); + return 0; +} + +static irqreturn_t uio_dmem_genirq_handler(int irq, struct uio_info *dev_info) +{ + struct uio_dmem_genirq_platdata *priv = dev_info->priv; + + /* Just disable the interrupt in the interrupt controller, and + * remember the state so we can allow user space to enable it later. + */ + + if (!test_and_set_bit(0, &priv->flags)) + disable_irq_nosync(irq); + + return IRQ_HANDLED; +} + +static int uio_dmem_genirq_irqcontrol(struct uio_info *dev_info, s32 irq_on) +{ + struct uio_dmem_genirq_platdata *priv = dev_info->priv; + unsigned long flags; + + /* Allow user space to enable and disable the interrupt + * in the interrupt controller, but keep track of the + * state to prevent per-irq depth damage. + * + * Serialize this operation to support multiple tasks. + */ + + spin_lock_irqsave(&priv->lock, flags); + if (irq_on) { + if (test_and_clear_bit(0, &priv->flags)) + enable_irq(dev_info->irq); + } else { + if (!test_and_set_bit(0, &priv->flags)) + disable_irq(dev_info->irq); + } + spin_unlock_irqrestore(&priv->lock, flags); + + return 0; +} + +static int uio_dmem_genirq_probe(struct platform_device *pdev) +{ + struct uio_dmem_genirq_pdata *pdata = pdev->dev.platform_data; + struct uio_info *uioinfo = &pdata->uioinfo; + struct uio_dmem_genirq_platdata *priv; + struct uio_mem *uiomem; + int ret = -EINVAL; + int i; + + if (!uioinfo) { + int irq; + + /* alloc uioinfo for one device */ + uioinfo = kzalloc(sizeof(*uioinfo), GFP_KERNEL); + if (!uioinfo) { + ret = -ENOMEM; + dev_err(&pdev->dev, "unable to kmalloc\n"); + goto bad2; + } + uioinfo->name = pdev->dev.of_node->name; + uioinfo->version = "devicetree"; + + /* Multiple IRQs are not supported */ + irq = platform_get_irq(pdev, 0); + if (irq == -ENXIO) + uioinfo->irq = UIO_IRQ_NONE; + else + uioinfo->irq = irq; + } + + if (!uioinfo || !uioinfo->name || !uioinfo->version) { + dev_err(&pdev->dev, "missing platform_data\n"); + goto bad0; + } + + if (uioinfo->handler || uioinfo->irqcontrol || + uioinfo->irq_flags & IRQF_SHARED) { + dev_err(&pdev->dev, "interrupt configuration error\n"); + goto bad0; + } + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + ret = -ENOMEM; + dev_err(&pdev->dev, "unable to kmalloc\n"); + goto bad0; + } + + dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); + + priv->uioinfo = uioinfo; + spin_lock_init(&priv->lock); + priv->flags = 0; /* interrupt is enabled to begin with */ + priv->pdev = pdev; + mutex_init(&priv->alloc_lock); + + if (!uioinfo->irq) { + ret = platform_get_irq(pdev, 0); + if (ret < 0) { + dev_err(&pdev->dev, "failed to get IRQ\n"); + goto bad0; + } + uioinfo->irq = ret; + } + uiomem = &uioinfo->mem[0]; + + for (i = 0; i < pdev->num_resources; ++i) { + struct resource *r = &pdev->resource[i]; + + if (r->flags != IORESOURCE_MEM) + continue; + + if (uiomem >= &uioinfo->mem[MAX_UIO_MAPS]) { + dev_warn(&pdev->dev, "device has more than " + __stringify(MAX_UIO_MAPS) + " I/O memory resources.\n"); + break; + } + + uiomem->memtype = UIO_MEM_PHYS; + uiomem->addr = r->start; + uiomem->size = resource_size(r); + ++uiomem; + } + + priv->dmem_region_start = i; + priv->num_dmem_regions = pdata->num_dynamic_regions; + + for (i = 0; i < pdata->num_dynamic_regions; ++i) { + if (uiomem >= &uioinfo->mem[MAX_UIO_MAPS]) { + dev_warn(&pdev->dev, "device has more than " + __stringify(MAX_UIO_MAPS) + " dynamic and fixed memory regions.\n"); + break; + } + uiomem->memtype = UIO_MEM_PHYS; + uiomem->addr = DMA_ERROR_CODE; + uiomem->size = pdata->dynamic_region_sizes[i]; + ++uiomem; + } + + while (uiomem < &uioinfo->mem[MAX_UIO_MAPS]) { + uiomem->size = 0; + ++uiomem; + } + + /* This driver requires no hardware specific kernel code to handle + * interrupts. Instead, the interrupt handler simply disables the + * interrupt in the interrupt controller. User space is responsible + * for performing hardware specific acknowledge and re-enabling of + * the interrupt in the interrupt controller. + * + * Interrupt sharing is not supported. + */ + + uioinfo->handler = uio_dmem_genirq_handler; + uioinfo->irqcontrol = uio_dmem_genirq_irqcontrol; + uioinfo->open = uio_dmem_genirq_open; + uioinfo->release = uio_dmem_genirq_release; + uioinfo->priv = priv; + + /* Enable Runtime PM for this device: + * The device starts in suspended state to allow the hardware to be + * turned off by default. The Runtime PM bus code should power on the + * hardware and enable clocks at open(). + */ + pm_runtime_enable(&pdev->dev); + + ret = uio_register_device(&pdev->dev, priv->uioinfo); + if (ret) { + dev_err(&pdev->dev, "unable to register uio device\n"); + goto bad1; + } + + platform_set_drvdata(pdev, priv); + return 0; + bad1: + kfree(priv); + pm_runtime_disable(&pdev->dev); + bad0: + /* kfree uioinfo for OF */ + if (pdev->dev.of_node) + kfree(uioinfo); + bad2: + return ret; +} + +static int uio_dmem_genirq_remove(struct platform_device *pdev) +{ + struct uio_dmem_genirq_platdata *priv = platform_get_drvdata(pdev); + + uio_unregister_device(priv->uioinfo); + pm_runtime_disable(&pdev->dev); + + priv->uioinfo->handler = NULL; + priv->uioinfo->irqcontrol = NULL; + + /* kfree uioinfo for OF */ + if (pdev->dev.of_node) + kfree(priv->uioinfo); + + kfree(priv); + return 0; +} + +static int uio_dmem_genirq_runtime_nop(struct device *dev) +{ + /* Runtime PM callback shared between ->runtime_suspend() + * and ->runtime_resume(). Simply returns success. + * + * In this driver pm_runtime_get_sync() and pm_runtime_put_sync() + * are used at open() and release() time. This allows the + * Runtime PM code to turn off power to the device while the + * device is unused, ie before open() and after release(). + * + * This Runtime PM callback does not need to save or restore + * any registers since user space is responsbile for hardware + * register reinitialization after open(). + */ + return 0; +} + +static const struct dev_pm_ops uio_dmem_genirq_dev_pm_ops = { + .runtime_suspend = uio_dmem_genirq_runtime_nop, + .runtime_resume = uio_dmem_genirq_runtime_nop, +}; + +#ifdef CONFIG_OF +static const struct of_device_id uio_of_genirq_match[] = { + { /* empty for now */ }, +}; +MODULE_DEVICE_TABLE(of, uio_of_genirq_match); +#else +# define uio_of_genirq_match NULL +#endif + +static struct platform_driver uio_dmem_genirq = { + .probe = uio_dmem_genirq_probe, + .remove = uio_dmem_genirq_remove, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .pm = &uio_dmem_genirq_dev_pm_ops, + .of_match_table = uio_of_genirq_match, + }, +}; + +module_platform_driver(uio_dmem_genirq); + +MODULE_AUTHOR("Damian Hobson-Garcia"); +MODULE_DESCRIPTION("Userspace I/O platform driver with dynamic memory."); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/uio/uio_pdrv.c b/drivers/uio/uio_pdrv.c index 72d3646c7365..39be9e061700 100644 --- a/drivers/uio/uio_pdrv.c +++ b/drivers/uio/uio_pdrv.c @@ -60,6 +60,7 @@ static int uio_pdrv_probe(struct platform_device *pdev) uiomem->memtype = UIO_MEM_PHYS; uiomem->addr = r->start; uiomem->size = resource_size(r); + uiomem->name = r->name; ++uiomem; } diff --git a/drivers/uio/uio_pdrv_genirq.c b/drivers/uio/uio_pdrv_genirq.c index 42202cd83158..ac988ce62f2b 100644 --- a/drivers/uio/uio_pdrv_genirq.c +++ b/drivers/uio/uio_pdrv_genirq.c @@ -172,6 +172,7 @@ static int uio_pdrv_genirq_probe(struct platform_device *pdev) uiomem->memtype = UIO_MEM_PHYS; uiomem->addr = r->start; uiomem->size = resource_size(r); + uiomem->name = r->name; ++uiomem; } diff --git a/drivers/w1/masters/Kconfig b/drivers/w1/masters/Kconfig index 7e984034a11b..c433a746e3f5 100644 --- a/drivers/w1/masters/Kconfig +++ b/drivers/w1/masters/Kconfig @@ -26,7 +26,7 @@ config W1_MASTER_DS2490 config W1_MASTER_DS2482 tristate "Maxim DS2482 I2C to 1-Wire bridge" - depends on I2C && EXPERIMENTAL + depends on I2C help If you say yes here you get support for the Maxim DS2482 I2C to 1-Wire bridge. diff --git a/drivers/w1/masters/ds2482.c b/drivers/w1/masters/ds2482.c index e5f74416d4b7..6429b9e9fb82 100644 --- a/drivers/w1/masters/ds2482.c +++ b/drivers/w1/masters/ds2482.c @@ -505,19 +505,8 @@ static int ds2482_remove(struct i2c_client *client) return 0; } -static int __init sensors_ds2482_init(void) -{ - return i2c_add_driver(&ds2482_driver); -} - -static void __exit sensors_ds2482_exit(void) -{ - i2c_del_driver(&ds2482_driver); -} +module_i2c_driver(ds2482_driver); MODULE_AUTHOR("Ben Gardner <bgardner@wabtec.com>"); MODULE_DESCRIPTION("DS2482 driver"); MODULE_LICENSE("GPL"); - -module_init(sensors_ds2482_init); -module_exit(sensors_ds2482_exit); diff --git a/drivers/xen/xen-selfballoon.c b/drivers/xen/xen-selfballoon.c index 7d041cb6da26..2552d3e0a70f 100644 --- a/drivers/xen/xen-selfballoon.c +++ b/drivers/xen/xen-selfballoon.c @@ -222,7 +222,7 @@ static void selfballoon_process(struct work_struct *work) if (xen_selfballooning_enabled) { cur_pages = totalram_pages; tgt_pages = cur_pages; /* default is no change */ - goal_pages = percpu_counter_read_positive(&vm_committed_as) + + goal_pages = vm_memory_committed() + totalreserve_pages + MB2PAGES(selfballoon_reserved_mb); #ifdef CONFIG_FRONTSWAP diff --git a/include/linux/mfd/rtsx_common.h b/include/linux/mfd/rtsx_common.h new file mode 100644 index 000000000000..a8d393e3066b --- /dev/null +++ b/include/linux/mfd/rtsx_common.h @@ -0,0 +1,48 @@ +/* Driver for Realtek driver-based card reader + * + * Copyright(c) 2009 Realtek Semiconductor Corp. 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 as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Author: + * Wei WANG <wei_wang@realsil.com.cn> + * No. 450, Shenhu Road, Suzhou Industry Park, Suzhou, China + */ + +#ifndef __RTSX_COMMON_H +#define __RTSX_COMMON_H + +#define DRV_NAME_RTSX_PCI "rtsx_pci" +#define DRV_NAME_RTSX_PCI_SDMMC "rtsx_pci_sdmmc" +#define DRV_NAME_RTSX_PCI_MS "rtsx_pci_ms" + +#define RTSX_REG_PAIR(addr, val) (((u32)(addr) << 16) | (u8)(val)) + +#define RTSX_SSC_DEPTH_4M 0x01 +#define RTSX_SSC_DEPTH_2M 0x02 +#define RTSX_SSC_DEPTH_1M 0x03 +#define RTSX_SSC_DEPTH_500K 0x04 +#define RTSX_SSC_DEPTH_250K 0x05 + +#define RTSX_SD_CARD 0 +#define RTSX_MS_CARD 1 + +struct platform_device; + +struct rtsx_slot { + struct platform_device *p_dev; + void (*card_event)(struct platform_device *p_dev); +}; + +#endif diff --git a/include/linux/mfd/rtsx_pci.h b/include/linux/mfd/rtsx_pci.h new file mode 100644 index 000000000000..060b721fcbfb --- /dev/null +++ b/include/linux/mfd/rtsx_pci.h @@ -0,0 +1,794 @@ +/* Driver for Realtek PCI-Express card reader + * + * Copyright(c) 2009 Realtek Semiconductor Corp. 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 as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Author: + * Wei WANG <wei_wang@realsil.com.cn> + * No. 450, Shenhu Road, Suzhou Industry Park, Suzhou, China + */ + +#ifndef __RTSX_PCI_H +#define __RTSX_PCI_H + +#include <linux/sched.h> +#include <linux/pci.h> + +#include "rtsx_common.h" + +#define MAX_RW_REG_CNT 1024 + +/* PCI Operation Register Address */ +#define RTSX_HCBAR 0x00 +#define RTSX_HCBCTLR 0x04 +#define RTSX_HDBAR 0x08 +#define RTSX_HDBCTLR 0x0C +#define RTSX_HAIMR 0x10 +#define RTSX_BIPR 0x14 +#define RTSX_BIER 0x18 + +/* Host command buffer control register */ +#define STOP_CMD (0x01 << 28) + +/* Host data buffer control register */ +#define SDMA_MODE 0x00 +#define ADMA_MODE (0x02 << 26) +#define STOP_DMA (0x01 << 28) +#define TRIG_DMA (0x01 << 31) + +/* Host access internal memory register */ +#define HAIMR_TRANS_START (0x01 << 31) +#define HAIMR_READ 0x00 +#define HAIMR_WRITE (0x01 << 30) +#define HAIMR_READ_START (HAIMR_TRANS_START | HAIMR_READ) +#define HAIMR_WRITE_START (HAIMR_TRANS_START | HAIMR_WRITE) +#define HAIMR_TRANS_END (HAIMR_TRANS_START) + +/* Bus interrupt pending register */ +#define CMD_DONE_INT (1 << 31) +#define DATA_DONE_INT (1 << 30) +#define TRANS_OK_INT (1 << 29) +#define TRANS_FAIL_INT (1 << 28) +#define XD_INT (1 << 27) +#define MS_INT (1 << 26) +#define SD_INT (1 << 25) +#define GPIO0_INT (1 << 24) +#define OC_INT (1 << 23) +#define SD_WRITE_PROTECT (1 << 19) +#define XD_EXIST (1 << 18) +#define MS_EXIST (1 << 17) +#define SD_EXIST (1 << 16) +#define DELINK_INT GPIO0_INT +#define MS_OC_INT (1 << 23) +#define SD_OC_INT (1 << 22) + +#define CARD_INT (XD_INT | MS_INT | SD_INT) +#define NEED_COMPLETE_INT (DATA_DONE_INT | TRANS_OK_INT | TRANS_FAIL_INT) +#define RTSX_INT (CMD_DONE_INT | NEED_COMPLETE_INT | \ + CARD_INT | GPIO0_INT | OC_INT) + +#define CARD_EXIST (XD_EXIST | MS_EXIST | SD_EXIST) + +/* Bus interrupt enable register */ +#define CMD_DONE_INT_EN (1 << 31) +#define DATA_DONE_INT_EN (1 << 30) +#define TRANS_OK_INT_EN (1 << 29) +#define TRANS_FAIL_INT_EN (1 << 28) +#define XD_INT_EN (1 << 27) +#define MS_INT_EN (1 << 26) +#define SD_INT_EN (1 << 25) +#define GPIO0_INT_EN (1 << 24) +#define OC_INT_EN (1 << 23) +#define DELINK_INT_EN GPIO0_INT_EN +#define MS_OC_INT_EN (1 << 23) +#define SD_OC_INT_EN (1 << 22) + +#define READ_REG_CMD 0 +#define WRITE_REG_CMD 1 +#define CHECK_REG_CMD 2 + +/* + * macros for easy use + */ +#define rtsx_pci_writel(pcr, reg, value) \ + iowrite32(value, (pcr)->remap_addr + reg) +#define rtsx_pci_readl(pcr, reg) \ + ioread32((pcr)->remap_addr + reg) +#define rtsx_pci_writew(pcr, reg, value) \ + iowrite16(value, (pcr)->remap_addr + reg) +#define rtsx_pci_readw(pcr, reg) \ + ioread16((pcr)->remap_addr + reg) +#define rtsx_pci_writeb(pcr, reg, value) \ + iowrite8(value, (pcr)->remap_addr + reg) +#define rtsx_pci_readb(pcr, reg) \ + ioread8((pcr)->remap_addr + reg) + +#define rtsx_pci_read_config_byte(pcr, where, val) \ + pci_read_config_byte((pcr)->pci, where, val) + +#define rtsx_pci_write_config_byte(pcr, where, val) \ + pci_write_config_byte((pcr)->pci, where, val) + +#define rtsx_pci_read_config_dword(pcr, where, val) \ + pci_read_config_dword((pcr)->pci, where, val) + +#define rtsx_pci_write_config_dword(pcr, where, val) \ + pci_write_config_dword((pcr)->pci, where, val) + +#define STATE_TRANS_NONE 0 +#define STATE_TRANS_CMD 1 +#define STATE_TRANS_BUF 2 +#define STATE_TRANS_SG 3 + +#define TRANS_NOT_READY 0 +#define TRANS_RESULT_OK 1 +#define TRANS_RESULT_FAIL 2 +#define TRANS_NO_DEVICE 3 + +#define RTSX_RESV_BUF_LEN 4096 +#define HOST_CMDS_BUF_LEN 1024 +#define HOST_SG_TBL_BUF_LEN (RTSX_RESV_BUF_LEN - HOST_CMDS_BUF_LEN) +#define HOST_SG_TBL_ITEMS (HOST_SG_TBL_BUF_LEN / 8) +#define MAX_SG_ITEM_LEN 0x80000 + +#define HOST_TO_DEVICE 0 +#define DEVICE_TO_HOST 1 + +#define MAX_PHASE 31 +#define RX_TUNING_CNT 3 + +/* SG descriptor */ +#define SG_INT 0x04 +#define SG_END 0x02 +#define SG_VALID 0x01 + +#define SG_NO_OP 0x00 +#define SG_TRANS_DATA (0x02 << 4) +#define SG_LINK_DESC (0x03 << 4) + +/* SD bank voltage */ +#define SD_IO_3V3 0 +#define SD_IO_1V8 1 + + +/* Card Clock Enable Register */ +#define SD_CLK_EN 0x04 +#define MS_CLK_EN 0x08 + +/* Card Select Register */ +#define SD_MOD_SEL 2 +#define MS_MOD_SEL 3 + +/* Card Output Enable Register */ +#define SD_OUTPUT_EN 0x04 +#define MS_OUTPUT_EN 0x08 + +/* CARD_SHARE_MODE */ +#define CARD_SHARE_MASK 0x0F +#define CARD_SHARE_MULTI_LUN 0x00 +#define CARD_SHARE_NORMAL 0x00 +#define CARD_SHARE_48_SD 0x04 +#define CARD_SHARE_48_MS 0x08 +/* CARD_SHARE_MODE for barossa */ +#define CARD_SHARE_BAROSSA_SD 0x01 +#define CARD_SHARE_BAROSSA_MS 0x02 + +/* SD30_DRIVE_SEL */ +#define DRIVER_TYPE_A 0x05 +#define DRIVER_TYPE_B 0x03 +#define DRIVER_TYPE_C 0x02 +#define DRIVER_TYPE_D 0x01 + +/* FPDCTL */ +#define SSC_POWER_DOWN 0x01 +#define SD_OC_POWER_DOWN 0x02 +#define ALL_POWER_DOWN 0x07 +#define OC_POWER_DOWN 0x06 + +/* CLK_CTL */ +#define CHANGE_CLK 0x01 + +/* LDO_CTL */ +#define BPP_LDO_POWB 0x03 +#define BPP_LDO_ON 0x00 +#define BPP_LDO_SUSPEND 0x02 +#define BPP_LDO_OFF 0x03 + +/* CD_PAD_CTL */ +#define CD_DISABLE_MASK 0x07 +#define MS_CD_DISABLE 0x04 +#define SD_CD_DISABLE 0x02 +#define XD_CD_DISABLE 0x01 +#define CD_DISABLE 0x07 +#define CD_ENABLE 0x00 +#define MS_CD_EN_ONLY 0x03 +#define SD_CD_EN_ONLY 0x05 +#define XD_CD_EN_ONLY 0x06 +#define FORCE_CD_LOW_MASK 0x38 +#define FORCE_CD_XD_LOW 0x08 +#define FORCE_CD_SD_LOW 0x10 +#define FORCE_CD_MS_LOW 0x20 +#define CD_AUTO_DISABLE 0x40 + +/* SD_STAT1 */ +#define SD_CRC7_ERR 0x80 +#define SD_CRC16_ERR 0x40 +#define SD_CRC_WRITE_ERR 0x20 +#define SD_CRC_WRITE_ERR_MASK 0x1C +#define GET_CRC_TIME_OUT 0x02 +#define SD_TUNING_COMPARE_ERR 0x01 + +/* SD_STAT2 */ +#define SD_RSP_80CLK_TIMEOUT 0x01 + +/* SD_BUS_STAT */ +#define SD_CLK_TOGGLE_EN 0x80 +#define SD_CLK_FORCE_STOP 0x40 +#define SD_DAT3_STATUS 0x10 +#define SD_DAT2_STATUS 0x08 +#define SD_DAT1_STATUS 0x04 +#define SD_DAT0_STATUS 0x02 +#define SD_CMD_STATUS 0x01 + +/* SD_PAD_CTL */ +#define SD_IO_USING_1V8 0x80 +#define SD_IO_USING_3V3 0x7F +#define TYPE_A_DRIVING 0x00 +#define TYPE_B_DRIVING 0x01 +#define TYPE_C_DRIVING 0x02 +#define TYPE_D_DRIVING 0x03 + +/* SD_SAMPLE_POINT_CTL */ +#define DDR_FIX_RX_DAT 0x00 +#define DDR_VAR_RX_DAT 0x80 +#define DDR_FIX_RX_DAT_EDGE 0x00 +#define DDR_FIX_RX_DAT_14_DELAY 0x40 +#define DDR_FIX_RX_CMD 0x00 +#define DDR_VAR_RX_CMD 0x20 +#define DDR_FIX_RX_CMD_POS_EDGE 0x00 +#define DDR_FIX_RX_CMD_14_DELAY 0x10 +#define SD20_RX_POS_EDGE 0x00 +#define SD20_RX_14_DELAY 0x08 +#define SD20_RX_SEL_MASK 0x08 + +/* SD_PUSH_POINT_CTL */ +#define DDR_FIX_TX_CMD_DAT 0x00 +#define DDR_VAR_TX_CMD_DAT 0x80 +#define DDR_FIX_TX_DAT_14_TSU 0x00 +#define DDR_FIX_TX_DAT_12_TSU 0x40 +#define DDR_FIX_TX_CMD_NEG_EDGE 0x00 +#define DDR_FIX_TX_CMD_14_AHEAD 0x20 +#define SD20_TX_NEG_EDGE 0x00 +#define SD20_TX_14_AHEAD 0x10 +#define SD20_TX_SEL_MASK 0x10 +#define DDR_VAR_SDCLK_POL_SWAP 0x01 + +/* SD_TRANSFER */ +#define SD_TRANSFER_START 0x80 +#define SD_TRANSFER_END 0x40 +#define SD_STAT_IDLE 0x20 +#define SD_TRANSFER_ERR 0x10 +/* SD Transfer Mode definition */ +#define SD_TM_NORMAL_WRITE 0x00 +#define SD_TM_AUTO_WRITE_3 0x01 +#define SD_TM_AUTO_WRITE_4 0x02 +#define SD_TM_AUTO_READ_3 0x05 +#define SD_TM_AUTO_READ_4 0x06 +#define SD_TM_CMD_RSP 0x08 +#define SD_TM_AUTO_WRITE_1 0x09 +#define SD_TM_AUTO_WRITE_2 0x0A +#define SD_TM_NORMAL_READ 0x0C +#define SD_TM_AUTO_READ_1 0x0D +#define SD_TM_AUTO_READ_2 0x0E +#define SD_TM_AUTO_TUNING 0x0F + +/* SD_VPTX_CTL / SD_VPRX_CTL */ +#define PHASE_CHANGE 0x80 +#define PHASE_NOT_RESET 0x40 + +/* SD_DCMPS_TX_CTL / SD_DCMPS_RX_CTL */ +#define DCMPS_CHANGE 0x80 +#define DCMPS_CHANGE_DONE 0x40 +#define DCMPS_ERROR 0x20 +#define DCMPS_CURRENT_PHASE 0x1F + +/* SD Configure 1 Register */ +#define SD_CLK_DIVIDE_0 0x00 +#define SD_CLK_DIVIDE_256 0xC0 +#define SD_CLK_DIVIDE_128 0x80 +#define SD_BUS_WIDTH_1BIT 0x00 +#define SD_BUS_WIDTH_4BIT 0x01 +#define SD_BUS_WIDTH_8BIT 0x02 +#define SD_ASYNC_FIFO_NOT_RST 0x10 +#define SD_20_MODE 0x00 +#define SD_DDR_MODE 0x04 +#define SD_30_MODE 0x08 + +#define SD_CLK_DIVIDE_MASK 0xC0 + +/* SD_CMD_STATE */ +#define SD_CMD_IDLE 0x80 + +/* SD_DATA_STATE */ +#define SD_DATA_IDLE 0x80 + +/* DCM_DRP_CTL */ +#define DCM_RESET 0x08 +#define DCM_LOCKED 0x04 +#define DCM_208M 0x00 +#define DCM_TX 0x01 +#define DCM_RX 0x02 + +/* DCM_DRP_TRIG */ +#define DRP_START 0x80 +#define DRP_DONE 0x40 + +/* DCM_DRP_CFG */ +#define DRP_WRITE 0x80 +#define DRP_READ 0x00 +#define DCM_WRITE_ADDRESS_50 0x50 +#define DCM_WRITE_ADDRESS_51 0x51 +#define DCM_READ_ADDRESS_00 0x00 +#define DCM_READ_ADDRESS_51 0x51 + +/* IRQSTAT0 */ +#define DMA_DONE_INT 0x80 +#define SUSPEND_INT 0x40 +#define LINK_RDY_INT 0x20 +#define LINK_DOWN_INT 0x10 + +/* DMACTL */ +#define DMA_RST 0x80 +#define DMA_BUSY 0x04 +#define DMA_DIR_TO_CARD 0x00 +#define DMA_DIR_FROM_CARD 0x02 +#define DMA_EN 0x01 +#define DMA_128 (0 << 4) +#define DMA_256 (1 << 4) +#define DMA_512 (2 << 4) +#define DMA_1024 (3 << 4) +#define DMA_PACK_SIZE_MASK 0x30 + +/* SSC_CTL1 */ +#define SSC_RSTB 0x80 +#define SSC_8X_EN 0x40 +#define SSC_FIX_FRAC 0x20 +#define SSC_SEL_1M 0x00 +#define SSC_SEL_2M 0x08 +#define SSC_SEL_4M 0x10 +#define SSC_SEL_8M 0x18 + +/* SSC_CTL2 */ +#define SSC_DEPTH_MASK 0x07 +#define SSC_DEPTH_DISALBE 0x00 +#define SSC_DEPTH_4M 0x01 +#define SSC_DEPTH_2M 0x02 +#define SSC_DEPTH_1M 0x03 +#define SSC_DEPTH_500K 0x04 +#define SSC_DEPTH_250K 0x05 + +/* System Clock Control Register */ +#define CLK_LOW_FREQ 0x01 + +/* System Clock Divider Register */ +#define CLK_DIV_1 0x01 +#define CLK_DIV_2 0x02 +#define CLK_DIV_4 0x03 +#define CLK_DIV_8 0x04 + +/* MS_CFG */ +#define SAMPLE_TIME_RISING 0x00 +#define SAMPLE_TIME_FALLING 0x80 +#define PUSH_TIME_DEFAULT 0x00 +#define PUSH_TIME_ODD 0x40 +#define NO_EXTEND_TOGGLE 0x00 +#define EXTEND_TOGGLE_CHK 0x20 +#define MS_BUS_WIDTH_1 0x00 +#define MS_BUS_WIDTH_4 0x10 +#define MS_BUS_WIDTH_8 0x18 +#define MS_2K_SECTOR_MODE 0x04 +#define MS_512_SECTOR_MODE 0x00 +#define MS_TOGGLE_TIMEOUT_EN 0x00 +#define MS_TOGGLE_TIMEOUT_DISEN 0x01 +#define MS_NO_CHECK_INT 0x02 + +/* MS_TRANS_CFG */ +#define WAIT_INT 0x80 +#define NO_WAIT_INT 0x00 +#define NO_AUTO_READ_INT_REG 0x00 +#define AUTO_READ_INT_REG 0x40 +#define MS_CRC16_ERR 0x20 +#define MS_RDY_TIMEOUT 0x10 +#define MS_INT_CMDNK 0x08 +#define MS_INT_BREQ 0x04 +#define MS_INT_ERR 0x02 +#define MS_INT_CED 0x01 + +/* MS_TRANSFER */ +#define MS_TRANSFER_START 0x80 +#define MS_TRANSFER_END 0x40 +#define MS_TRANSFER_ERR 0x20 +#define MS_BS_STATE 0x10 +#define MS_TM_READ_BYTES 0x00 +#define MS_TM_NORMAL_READ 0x01 +#define MS_TM_WRITE_BYTES 0x04 +#define MS_TM_NORMAL_WRITE 0x05 +#define MS_TM_AUTO_READ 0x08 +#define MS_TM_AUTO_WRITE 0x0C + +/* SD Configure 2 Register */ +#define SD_CALCULATE_CRC7 0x00 +#define SD_NO_CALCULATE_CRC7 0x80 +#define SD_CHECK_CRC16 0x00 +#define SD_NO_CHECK_CRC16 0x40 +#define SD_NO_CHECK_WAIT_CRC_TO 0x20 +#define SD_WAIT_BUSY_END 0x08 +#define SD_NO_WAIT_BUSY_END 0x00 +#define SD_CHECK_CRC7 0x00 +#define SD_NO_CHECK_CRC7 0x04 +#define SD_RSP_LEN_0 0x00 +#define SD_RSP_LEN_6 0x01 +#define SD_RSP_LEN_17 0x02 +/* SD/MMC Response Type Definition */ +#define SD_RSP_TYPE_R0 0x04 +#define SD_RSP_TYPE_R1 0x01 +#define SD_RSP_TYPE_R1b 0x09 +#define SD_RSP_TYPE_R2 0x02 +#define SD_RSP_TYPE_R3 0x05 +#define SD_RSP_TYPE_R4 0x05 +#define SD_RSP_TYPE_R5 0x01 +#define SD_RSP_TYPE_R6 0x01 +#define SD_RSP_TYPE_R7 0x01 + +/* SD_CONFIURE3 */ +#define SD_RSP_80CLK_TIMEOUT_EN 0x01 + +/* Card Transfer Reset Register */ +#define SPI_STOP 0x01 +#define XD_STOP 0x02 +#define SD_STOP 0x04 +#define MS_STOP 0x08 +#define SPI_CLR_ERR 0x10 +#define XD_CLR_ERR 0x20 +#define SD_CLR_ERR 0x40 +#define MS_CLR_ERR 0x80 + +/* Card Data Source Register */ +#define PINGPONG_BUFFER 0x01 +#define RING_BUFFER 0x00 + +/* Card Power Control Register */ +#define PMOS_STRG_MASK 0x10 +#define PMOS_STRG_800mA 0x10 +#define PMOS_STRG_400mA 0x00 +#define SD_POWER_OFF 0x03 +#define SD_PARTIAL_POWER_ON 0x01 +#define SD_POWER_ON 0x00 +#define SD_POWER_MASK 0x03 +#define MS_POWER_OFF 0x0C +#define MS_PARTIAL_POWER_ON 0x04 +#define MS_POWER_ON 0x00 +#define MS_POWER_MASK 0x0C +#define BPP_POWER_OFF 0x0F +#define BPP_POWER_5_PERCENT_ON 0x0E +#define BPP_POWER_10_PERCENT_ON 0x0C +#define BPP_POWER_15_PERCENT_ON 0x08 +#define BPP_POWER_ON 0x00 +#define BPP_POWER_MASK 0x0F + +/* PWR_GATE_CTRL */ +#define PWR_GATE_EN 0x01 +#define LDO3318_PWR_MASK 0x06 +#define LDO_ON 0x00 +#define LDO_SUSPEND 0x04 +#define LDO_OFF 0x06 + +/* CARD_CLK_SOURCE */ +#define CRC_FIX_CLK (0x00 << 0) +#define CRC_VAR_CLK0 (0x01 << 0) +#define CRC_VAR_CLK1 (0x02 << 0) +#define SD30_FIX_CLK (0x00 << 2) +#define SD30_VAR_CLK0 (0x01 << 2) +#define SD30_VAR_CLK1 (0x02 << 2) +#define SAMPLE_FIX_CLK (0x00 << 4) +#define SAMPLE_VAR_CLK0 (0x01 << 4) +#define SAMPLE_VAR_CLK1 (0x02 << 4) + +#define MS_CFG 0xFD40 +#define MS_TPC 0xFD41 +#define MS_TRANS_CFG 0xFD42 +#define MS_TRANSFER 0xFD43 +#define MS_INT_REG 0xFD44 +#define MS_BYTE_CNT 0xFD45 +#define MS_SECTOR_CNT_L 0xFD46 +#define MS_SECTOR_CNT_H 0xFD47 +#define MS_DBUS_H 0xFD48 + +#define SD_CFG1 0xFDA0 +#define SD_CFG2 0xFDA1 +#define SD_CFG3 0xFDA2 +#define SD_STAT1 0xFDA3 +#define SD_STAT2 0xFDA4 +#define SD_BUS_STAT 0xFDA5 +#define SD_PAD_CTL 0xFDA6 +#define SD_SAMPLE_POINT_CTL 0xFDA7 +#define SD_PUSH_POINT_CTL 0xFDA8 +#define SD_CMD0 0xFDA9 +#define SD_CMD1 0xFDAA +#define SD_CMD2 0xFDAB +#define SD_CMD3 0xFDAC +#define SD_CMD4 0xFDAD +#define SD_CMD5 0xFDAE +#define SD_BYTE_CNT_L 0xFDAF +#define SD_BYTE_CNT_H 0xFDB0 +#define SD_BLOCK_CNT_L 0xFDB1 +#define SD_BLOCK_CNT_H 0xFDB2 +#define SD_TRANSFER 0xFDB3 +#define SD_CMD_STATE 0xFDB5 +#define SD_DATA_STATE 0xFDB6 + +#define SRCTL 0xFC13 + +#define DCM_DRP_CTL 0xFC23 +#define DCM_DRP_TRIG 0xFC24 +#define DCM_DRP_CFG 0xFC25 +#define DCM_DRP_WR_DATA_L 0xFC26 +#define DCM_DRP_WR_DATA_H 0xFC27 +#define DCM_DRP_RD_DATA_L 0xFC28 +#define DCM_DRP_RD_DATA_H 0xFC29 +#define SD_VPCLK0_CTL 0xFC2A +#define SD_VPCLK1_CTL 0xFC2B +#define SD_DCMPS0_CTL 0xFC2C +#define SD_DCMPS1_CTL 0xFC2D +#define SD_VPTX_CTL SD_VPCLK0_CTL +#define SD_VPRX_CTL SD_VPCLK1_CTL +#define SD_DCMPS_TX_CTL SD_DCMPS0_CTL +#define SD_DCMPS_RX_CTL SD_DCMPS1_CTL +#define CARD_CLK_SOURCE 0xFC2E + +#define CARD_PWR_CTL 0xFD50 +#define CARD_CLK_SWITCH 0xFD51 +#define CARD_SHARE_MODE 0xFD52 +#define CARD_DRIVE_SEL 0xFD53 +#define CARD_STOP 0xFD54 +#define CARD_OE 0xFD55 +#define CARD_AUTO_BLINK 0xFD56 +#define CARD_GPIO_DIR 0xFD57 +#define CARD_GPIO 0xFD58 +#define CARD_DATA_SOURCE 0xFD5B +#define CARD_SELECT 0xFD5C +#define SD30_DRIVE_SEL 0xFD5E +#define CARD_CLK_EN 0xFD69 +#define SDIO_CTRL 0xFD6B +#define CD_PAD_CTL 0xFD73 + +#define FPDCTL 0xFC00 +#define PDINFO 0xFC01 + +#define CLK_CTL 0xFC02 +#define CLK_DIV 0xFC03 +#define CLK_SEL 0xFC04 + +#define SSC_DIV_N_0 0xFC0F +#define SSC_DIV_N_1 0xFC10 +#define SSC_CTL1 0xFC11 +#define SSC_CTL2 0xFC12 + +#define RCCTL 0xFC14 + +#define FPGA_PULL_CTL 0xFC1D +#define OLT_LED_CTL 0xFC1E +#define GPIO_CTL 0xFC1F + +#define LDO_CTL 0xFC1E +#define SYS_VER 0xFC32 + +#define CARD_PULL_CTL1 0xFD60 +#define CARD_PULL_CTL2 0xFD61 +#define CARD_PULL_CTL3 0xFD62 +#define CARD_PULL_CTL4 0xFD63 +#define CARD_PULL_CTL5 0xFD64 +#define CARD_PULL_CTL6 0xFD65 + +/* PCI Express Related Registers */ +#define IRQEN0 0xFE20 +#define IRQSTAT0 0xFE21 +#define IRQEN1 0xFE22 +#define IRQSTAT1 0xFE23 +#define TLPRIEN 0xFE24 +#define TLPRISTAT 0xFE25 +#define TLPTIEN 0xFE26 +#define TLPTISTAT 0xFE27 +#define DMATC0 0xFE28 +#define DMATC1 0xFE29 +#define DMATC2 0xFE2A +#define DMATC3 0xFE2B +#define DMACTL 0xFE2C +#define BCTL 0xFE2D +#define RBBC0 0xFE2E +#define RBBC1 0xFE2F +#define RBDAT 0xFE30 +#define RBCTL 0xFE34 +#define CFGADDR0 0xFE35 +#define CFGADDR1 0xFE36 +#define CFGDATA0 0xFE37 +#define CFGDATA1 0xFE38 +#define CFGDATA2 0xFE39 +#define CFGDATA3 0xFE3A +#define CFGRWCTL 0xFE3B +#define PHYRWCTL 0xFE3C +#define PHYDATA0 0xFE3D +#define PHYDATA1 0xFE3E +#define PHYADDR 0xFE3F +#define MSGRXDATA0 0xFE40 +#define MSGRXDATA1 0xFE41 +#define MSGRXDATA2 0xFE42 +#define MSGRXDATA3 0xFE43 +#define MSGTXDATA0 0xFE44 +#define MSGTXDATA1 0xFE45 +#define MSGTXDATA2 0xFE46 +#define MSGTXDATA3 0xFE47 +#define MSGTXCTL 0xFE48 +#define PETXCFG 0xFE49 + +#define CDRESUMECTL 0xFE52 +#define WAKE_SEL_CTL 0xFE54 +#define PME_FORCE_CTL 0xFE56 +#define ASPM_FORCE_CTL 0xFE57 +#define PM_CLK_FORCE_CTL 0xFE58 +#define PERST_GLITCH_WIDTH 0xFE5C +#define CHANGE_LINK_STATE 0xFE5B +#define RESET_LOAD_REG 0xFE5E +#define EFUSE_CONTENT 0xFE5F +#define HOST_SLEEP_STATE 0xFE60 +#define SDIO_CFG 0xFE70 + +#define NFTS_TX_CTRL 0xFE72 + +#define PWR_GATE_CTRL 0xFE75 +#define PWD_SUSPEND_EN 0xFE76 +#define LDO_PWR_SEL 0xFE78 + +#define DUMMY_REG_RESET_0 0xFE90 + +/* Memory mapping */ +#define SRAM_BASE 0xE600 +#define RBUF_BASE 0xF400 +#define PPBUF_BASE1 0xF800 +#define PPBUF_BASE2 0xFA00 +#define IMAGE_FLAG_ADDR0 0xCE80 +#define IMAGE_FLAG_ADDR1 0xCE81 + +#define rtsx_pci_init_cmd(pcr) ((pcr)->ci = 0) + +struct rtsx_pcr; + +struct pcr_handle { + struct rtsx_pcr *pcr; +}; + +struct pcr_ops { + int (*extra_init_hw)(struct rtsx_pcr *pcr); + int (*optimize_phy)(struct rtsx_pcr *pcr); + int (*turn_on_led)(struct rtsx_pcr *pcr); + int (*turn_off_led)(struct rtsx_pcr *pcr); + int (*enable_auto_blink)(struct rtsx_pcr *pcr); + int (*disable_auto_blink)(struct rtsx_pcr *pcr); + int (*card_power_on)(struct rtsx_pcr *pcr, int card); + int (*card_power_off)(struct rtsx_pcr *pcr, int card); + unsigned int (*cd_deglitch)(struct rtsx_pcr *pcr); +}; + +enum PDEV_STAT {PDEV_STAT_IDLE, PDEV_STAT_RUN}; + +struct rtsx_pcr { + struct pci_dev *pci; + unsigned int id; + + /* pci resources */ + unsigned long addr; + void __iomem *remap_addr; + int irq; + + /* host reserved buffer */ + void *rtsx_resv_buf; + dma_addr_t rtsx_resv_buf_addr; + + void *host_cmds_ptr; + dma_addr_t host_cmds_addr; + int ci; + + void *host_sg_tbl_ptr; + dma_addr_t host_sg_tbl_addr; + int sgi; + + u32 bier; + char trans_result; + + unsigned int card_inserted; + unsigned int card_removed; + + struct delayed_work carddet_work; + struct delayed_work idle_work; + + spinlock_t lock; + struct mutex pcr_mutex; + struct completion *done; + struct completion *finish_me; + + unsigned int cur_clock; + bool ms_pmos; + bool remove_pci; + bool msi_en; + +#define EXTRA_CAPS_SD_SDR50 (1 << 0) +#define EXTRA_CAPS_SD_SDR104 (1 << 1) +#define EXTRA_CAPS_SD_DDR50 (1 << 2) +#define EXTRA_CAPS_MMC_HSDDR (1 << 3) +#define EXTRA_CAPS_MMC_HS200 (1 << 4) +#define EXTRA_CAPS_MMC_8BIT (1 << 5) + u32 extra_caps; + +#define IC_VER_A 0 +#define IC_VER_B 1 +#define IC_VER_C 2 +#define IC_VER_D 3 + u8 ic_version; + + const u32 *sd_pull_ctl_enable_tbl; + const u32 *sd_pull_ctl_disable_tbl; + const u32 *ms_pull_ctl_enable_tbl; + const u32 *ms_pull_ctl_disable_tbl; + + const struct pcr_ops *ops; + enum PDEV_STAT state; + + int num_slots; + struct rtsx_slot *slots; +}; + +#define CHK_PCI_PID(pcr, pid) ((pcr)->pci->device == (pid)) +#define PCI_VID(pcr) ((pcr)->pci->vendor) +#define PCI_PID(pcr) ((pcr)->pci->device) + +void rtsx_pci_start_run(struct rtsx_pcr *pcr); +int rtsx_pci_write_register(struct rtsx_pcr *pcr, u16 addr, u8 mask, u8 data); +int rtsx_pci_read_register(struct rtsx_pcr *pcr, u16 addr, u8 *data); +int rtsx_pci_write_phy_register(struct rtsx_pcr *pcr, u8 addr, u16 val); +int rtsx_pci_read_phy_register(struct rtsx_pcr *pcr, u8 addr, u16 *val); +void rtsx_pci_stop_cmd(struct rtsx_pcr *pcr); +void rtsx_pci_add_cmd(struct rtsx_pcr *pcr, + u8 cmd_type, u16 reg_addr, u8 mask, u8 data); +void rtsx_pci_send_cmd_no_wait(struct rtsx_pcr *pcr); +int rtsx_pci_send_cmd(struct rtsx_pcr *pcr, int timeout); +int rtsx_pci_transfer_data(struct rtsx_pcr *pcr, struct scatterlist *sglist, + int num_sg, bool read, int timeout); +int rtsx_pci_read_ppbuf(struct rtsx_pcr *pcr, u8 *buf, int buf_len); +int rtsx_pci_write_ppbuf(struct rtsx_pcr *pcr, u8 *buf, int buf_len); +int rtsx_pci_card_pull_ctl_enable(struct rtsx_pcr *pcr, int card); +int rtsx_pci_card_pull_ctl_disable(struct rtsx_pcr *pcr, int card); +int rtsx_pci_switch_clock(struct rtsx_pcr *pcr, unsigned int card_clock, + u8 ssc_depth, bool initial_mode, bool double_clk, bool vpclk); +int rtsx_pci_card_power_on(struct rtsx_pcr *pcr, int card); +int rtsx_pci_card_power_off(struct rtsx_pcr *pcr, int card); +unsigned int rtsx_pci_card_exist(struct rtsx_pcr *pcr); +void rtsx_pci_complete_unfinished_transfer(struct rtsx_pcr *pcr); + +static inline u8 *rtsx_pci_get_cmd_data(struct rtsx_pcr *pcr) +{ + return (u8 *)(pcr->host_cmds_ptr); +} + +#endif diff --git a/include/linux/mman.h b/include/linux/mman.h index d09dde1e57fb..9aa863da287f 100644 --- a/include/linux/mman.h +++ b/include/linux/mman.h @@ -11,6 +11,8 @@ extern int sysctl_overcommit_memory; extern int sysctl_overcommit_ratio; extern struct percpu_counter vm_committed_as; +unsigned long vm_memory_committed(void); + static inline void vm_acct_memory(long pages) { percpu_counter_add(&vm_committed_as, pages); diff --git a/include/linux/platform_data/uio_dmem_genirq.h b/include/linux/platform_data/uio_dmem_genirq.h new file mode 100644 index 000000000000..973c1bb32168 --- /dev/null +++ b/include/linux/platform_data/uio_dmem_genirq.h @@ -0,0 +1,26 @@ +/* + * include/linux/platform_data/uio_dmem_genirq.h + * + * Copyright (C) 2012 Damian Hobson-Garcia + * + * 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 _UIO_DMEM_GENIRQ_H +#define _UIO_DMEM_GENIRQ_H + +#include <linux/uio_driver.h> + +struct uio_dmem_genirq_pdata { + struct uio_info uioinfo; + unsigned int *dynamic_region_sizes; + unsigned int num_dynamic_regions; +}; +#endif /* _UIO_DMEM_GENIRQ_H */ diff --git a/mm/mmap.c b/mm/mmap.c index 9a796c41e7d9..7d416055f08c 100644 --- a/mm/mmap.c +++ b/mm/mmap.c @@ -89,6 +89,20 @@ int sysctl_max_map_count __read_mostly = DEFAULT_MAX_MAP_COUNT; struct percpu_counter vm_committed_as ____cacheline_aligned_in_smp; /* + * The global memory commitment made in the system can be a metric + * that can be used to drive ballooning decisions when Linux is hosted + * as a guest. On Hyper-V, the host implements a policy engine for dynamically + * balancing memory across competing virtual machines that are hosted. + * Several metrics drive this policy engine including the guest reported + * memory commitment. + */ +unsigned long vm_memory_committed(void) +{ + return percpu_counter_read_positive(&vm_committed_as); +} +EXPORT_SYMBOL_GPL(vm_memory_committed); + +/* * Check that a process has enough memory to allocate a new virtual * mapping. 0 means there is enough memory for the allocation to * succeed and -ENOMEM implies there is not. diff --git a/mm/nommu.c b/mm/nommu.c index 45131b41bcdb..79c3cac87afa 100644 --- a/mm/nommu.c +++ b/mm/nommu.c @@ -66,6 +66,21 @@ int heap_stack_gap = 0; atomic_long_t mmap_pages_allocated; +/* + * The global memory commitment made in the system can be a metric + * that can be used to drive ballooning decisions when Linux is hosted + * as a guest. On Hyper-V, the host implements a policy engine for dynamically + * balancing memory across competing virtual machines that are hosted. + * Several metrics drive this policy engine including the guest reported + * memory commitment. + */ +unsigned long vm_memory_committed(void) +{ + return percpu_counter_read_positive(&vm_committed_as); +} + +EXPORT_SYMBOL_GPL(vm_memory_committed); + EXPORT_SYMBOL(mem_map); EXPORT_SYMBOL(num_physpages); diff --git a/tools/hv/hv_kvp_daemon.c b/tools/hv/hv_kvp_daemon.c index 5959affd8820..d25a46925e61 100644 --- a/tools/hv/hv_kvp_daemon.c +++ b/tools/hv/hv_kvp_daemon.c @@ -43,6 +43,7 @@ #include <sys/stat.h> #include <fcntl.h> #include <dirent.h> +#include <net/if.h> /* * KVP protocol: The user mode component first registers with the @@ -88,6 +89,7 @@ static char *os_major = ""; static char *os_minor = ""; static char *processor_arch; static char *os_build; +static char *os_version; static char *lic_version = "Unknown version"; static struct utsname uts_buf; @@ -297,7 +299,7 @@ static int kvp_file_init(void) return 0; } -static int kvp_key_delete(int pool, __u8 *key, int key_size) +static int kvp_key_delete(int pool, const char *key, int key_size) { int i; int j, k; @@ -340,7 +342,7 @@ static int kvp_key_delete(int pool, __u8 *key, int key_size) return 1; } -static int kvp_key_add_or_modify(int pool, __u8 *key, int key_size, __u8 *value, +static int kvp_key_add_or_modify(int pool, const char *key, int key_size, const char *value, int value_size) { int i; @@ -394,7 +396,7 @@ static int kvp_key_add_or_modify(int pool, __u8 *key, int key_size, __u8 *value, return 0; } -static int kvp_get_value(int pool, __u8 *key, int key_size, __u8 *value, +static int kvp_get_value(int pool, const char *key, int key_size, char *value, int value_size) { int i; @@ -426,8 +428,8 @@ static int kvp_get_value(int pool, __u8 *key, int key_size, __u8 *value, return 1; } -static int kvp_pool_enumerate(int pool, int index, __u8 *key, int key_size, - __u8 *value, int value_size) +static int kvp_pool_enumerate(int pool, int index, char *key, int key_size, + char *value, int value_size) { struct kvp_record *record; @@ -453,7 +455,9 @@ void kvp_get_os_info(void) char *p, buf[512]; uname(&uts_buf); - os_build = uts_buf.release; + os_version = uts_buf.release; + os_build = strdup(uts_buf.release); + os_name = uts_buf.sysname; processor_arch = uts_buf.machine; @@ -462,7 +466,7 @@ void kvp_get_os_info(void) * string to be of the form: x.y.z * Strip additional information we may have. */ - p = strchr(os_build, '-'); + p = strchr(os_version, '-'); if (p) *p = '\0'; @@ -879,7 +883,7 @@ static int kvp_process_ip_address(void *addrp, addr_length = INET6_ADDRSTRLEN; } - if ((length - *offset) < addr_length + 1) + if ((length - *offset) < addr_length + 2) return HV_E_FAIL; if (str == NULL) { strcpy(buffer, "inet_ntop failed\n"); @@ -887,11 +891,13 @@ static int kvp_process_ip_address(void *addrp, } if (*offset == 0) strcpy(buffer, tmp); - else + else { + strcat(buffer, ";"); strcat(buffer, tmp); - strcat(buffer, ";"); + } *offset += strlen(str) + 1; + return 0; } @@ -953,7 +959,9 @@ kvp_get_ip_info(int family, char *if_name, int op, * supported address families; if not we gather info on * the specified address family. */ - if ((family != 0) && (curp->ifa_addr->sa_family != family)) { + if ((((family != 0) && + (curp->ifa_addr->sa_family != family))) || + (curp->ifa_flags & IFF_LOOPBACK)) { curp = curp->ifa_next; continue; } @@ -1478,13 +1486,19 @@ int main(void) len = recvfrom(fd, kvp_recv_buffer, sizeof(kvp_recv_buffer), 0, addr_p, &addr_l); - if (len < 0 || addr.nl_pid) { + if (len < 0) { syslog(LOG_ERR, "recvfrom failed; pid:%u error:%d %s", addr.nl_pid, errno, strerror(errno)); close(fd); return -1; } + if (addr.nl_pid) { + syslog(LOG_WARNING, "Received packet from untrusted pid:%u", + addr.nl_pid); + continue; + } + incoming_msg = (struct nlmsghdr *)kvp_recv_buffer; incoming_cn_msg = (struct cn_msg *)NLMSG_DATA(incoming_msg); hv_msg = (struct hv_kvp_msg *)incoming_cn_msg->data; @@ -1649,7 +1663,7 @@ int main(void) strcpy(key_name, "OSMinorVersion"); break; case OSVersion: - strcpy(key_value, os_build); + strcpy(key_value, os_version); strcpy(key_name, "OSVersion"); break; case ProcessorArchitecture: |