From 20259849bb1ac1ffb0156eb359810e8b99cb644d Mon Sep 17 00:00:00 2001 From: George Zhang Date: Tue, 8 Jan 2013 15:55:59 -0800 Subject: VMCI: Some header and config files. VMCI head config patch Adds all the necessary files to enable building of the VMCI module with the Linux Makefiles and Kconfig systems. Also adds the header files used for building modules against the driver. Signed-off-by: George Zhang Acked-by: Andy king Acked-by: Dmitry Torokhov Signed-off-by: Greg Kroah-Hartman --- include/linux/vmw_vmci_api.h | 82 ++++ include/linux/vmw_vmci_defs.h | 880 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 962 insertions(+) create mode 100644 include/linux/vmw_vmci_api.h create mode 100644 include/linux/vmw_vmci_defs.h (limited to 'include') diff --git a/include/linux/vmw_vmci_api.h b/include/linux/vmw_vmci_api.h new file mode 100644 index 000000000000..023430e265fe --- /dev/null +++ b/include/linux/vmw_vmci_api.h @@ -0,0 +1,82 @@ +/* + * VMware VMCI Driver + * + * Copyright (C) 2012 VMware, Inc. 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 version 2 and no 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. + */ + +#ifndef __VMW_VMCI_API_H__ +#define __VMW_VMCI_API_H__ + +#include +#include + +#undef VMCI_KERNEL_API_VERSION +#define VMCI_KERNEL_API_VERSION_1 1 +#define VMCI_KERNEL_API_VERSION_2 2 +#define VMCI_KERNEL_API_VERSION VMCI_KERNEL_API_VERSION_2 + +typedef void (vmci_device_shutdown_fn) (void *device_registration, + void *user_data); + +int vmci_datagram_create_handle(u32 resource_id, u32 flags, + vmci_datagram_recv_cb recv_cb, + void *client_data, + struct vmci_handle *out_handle); +int vmci_datagram_create_handle_priv(u32 resource_id, u32 flags, u32 priv_flags, + vmci_datagram_recv_cb recv_cb, + void *client_data, + struct vmci_handle *out_handle); +int vmci_datagram_destroy_handle(struct vmci_handle handle); +int vmci_datagram_send(struct vmci_datagram *msg); +int vmci_doorbell_create(struct vmci_handle *handle, u32 flags, + u32 priv_flags, + vmci_callback notify_cb, void *client_data); +int vmci_doorbell_destroy(struct vmci_handle handle); +int vmci_doorbell_notify(struct vmci_handle handle, u32 priv_flags); +u32 vmci_get_context_id(void); +bool vmci_is_context_owner(u32 context_id, kuid_t uid); + +int vmci_event_subscribe(u32 event, + vmci_event_cb callback, void *callback_data, + u32 *subid); +int vmci_event_unsubscribe(u32 subid); +u32 vmci_context_get_priv_flags(u32 context_id); +int vmci_qpair_alloc(struct vmci_qp **qpair, + struct vmci_handle *handle, + u64 produce_qsize, + u64 consume_qsize, + u32 peer, u32 flags, u32 priv_flags); +int vmci_qpair_detach(struct vmci_qp **qpair); +int vmci_qpair_get_produce_indexes(const struct vmci_qp *qpair, + u64 *producer_tail, + u64 *consumer_head); +int vmci_qpair_get_consume_indexes(const struct vmci_qp *qpair, + u64 *consumer_tail, + u64 *producer_head); +s64 vmci_qpair_produce_free_space(const struct vmci_qp *qpair); +s64 vmci_qpair_produce_buf_ready(const struct vmci_qp *qpair); +s64 vmci_qpair_consume_free_space(const struct vmci_qp *qpair); +s64 vmci_qpair_consume_buf_ready(const struct vmci_qp *qpair); +ssize_t vmci_qpair_enqueue(struct vmci_qp *qpair, + const void *buf, size_t buf_size, int mode); +ssize_t vmci_qpair_dequeue(struct vmci_qp *qpair, + void *buf, size_t buf_size, int mode); +ssize_t vmci_qpair_peek(struct vmci_qp *qpair, void *buf, size_t buf_size, + int mode); +ssize_t vmci_qpair_enquev(struct vmci_qp *qpair, + void *iov, size_t iov_size, int mode); +ssize_t vmci_qpair_dequev(struct vmci_qp *qpair, + void *iov, size_t iov_size, int mode); +ssize_t vmci_qpair_peekv(struct vmci_qp *qpair, void *iov, size_t iov_size, + int mode); + +#endif /* !__VMW_VMCI_API_H__ */ diff --git a/include/linux/vmw_vmci_defs.h b/include/linux/vmw_vmci_defs.h new file mode 100644 index 000000000000..65ac54c61c18 --- /dev/null +++ b/include/linux/vmw_vmci_defs.h @@ -0,0 +1,880 @@ +/* + * VMware VMCI Driver + * + * Copyright (C) 2012 VMware, Inc. 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 version 2 and no 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. + */ + +#ifndef _VMW_VMCI_DEF_H_ +#define _VMW_VMCI_DEF_H_ + +#include + +/* Register offsets. */ +#define VMCI_STATUS_ADDR 0x00 +#define VMCI_CONTROL_ADDR 0x04 +#define VMCI_ICR_ADDR 0x08 +#define VMCI_IMR_ADDR 0x0c +#define VMCI_DATA_OUT_ADDR 0x10 +#define VMCI_DATA_IN_ADDR 0x14 +#define VMCI_CAPS_ADDR 0x18 +#define VMCI_RESULT_LOW_ADDR 0x1c +#define VMCI_RESULT_HIGH_ADDR 0x20 + +/* Max number of devices. */ +#define VMCI_MAX_DEVICES 1 + +/* Status register bits. */ +#define VMCI_STATUS_INT_ON 0x1 + +/* Control register bits. */ +#define VMCI_CONTROL_RESET 0x1 +#define VMCI_CONTROL_INT_ENABLE 0x2 +#define VMCI_CONTROL_INT_DISABLE 0x4 + +/* Capabilities register bits. */ +#define VMCI_CAPS_HYPERCALL 0x1 +#define VMCI_CAPS_GUESTCALL 0x2 +#define VMCI_CAPS_DATAGRAM 0x4 +#define VMCI_CAPS_NOTIFICATIONS 0x8 + +/* Interrupt Cause register bits. */ +#define VMCI_ICR_DATAGRAM 0x1 +#define VMCI_ICR_NOTIFICATION 0x2 + +/* Interrupt Mask register bits. */ +#define VMCI_IMR_DATAGRAM 0x1 +#define VMCI_IMR_NOTIFICATION 0x2 + +/* Interrupt type. */ +enum { + VMCI_INTR_TYPE_INTX = 0, + VMCI_INTR_TYPE_MSI = 1, + VMCI_INTR_TYPE_MSIX = 2, +}; + +/* Maximum MSI/MSI-X interrupt vectors in the device. */ +#define VMCI_MAX_INTRS 2 + +/* + * Supported interrupt vectors. There is one for each ICR value above, + * but here they indicate the position in the vector array/message ID. + */ +enum { + VMCI_INTR_DATAGRAM = 0, + VMCI_INTR_NOTIFICATION = 1, +}; + +/* + * A single VMCI device has an upper limit of 128MB on the amount of + * memory that can be used for queue pairs. + */ +#define VMCI_MAX_GUEST_QP_MEMORY (128 * 1024 * 1024) + +/* + * Queues with pre-mapped data pages must be small, so that we don't pin + * too much kernel memory (especially on vmkernel). We limit a queuepair to + * 32 KB, or 16 KB per queue for symmetrical pairs. + */ +#define VMCI_MAX_PINNED_QP_MEMORY (32 * 1024) + +/* + * We have a fixed set of resource IDs available in the VMX. + * This allows us to have a very simple implementation since we statically + * know how many will create datagram handles. If a new caller arrives and + * we have run out of slots we can manually increment the maximum size of + * available resource IDs. + * + * VMCI reserved hypervisor datagram resource IDs. + */ +enum { + VMCI_RESOURCES_QUERY = 0, + VMCI_GET_CONTEXT_ID = 1, + VMCI_SET_NOTIFY_BITMAP = 2, + VMCI_DOORBELL_LINK = 3, + VMCI_DOORBELL_UNLINK = 4, + VMCI_DOORBELL_NOTIFY = 5, + /* + * VMCI_DATAGRAM_REQUEST_MAP and VMCI_DATAGRAM_REMOVE_MAP are + * obsoleted by the removal of VM to VM communication. + */ + VMCI_DATAGRAM_REQUEST_MAP = 6, + VMCI_DATAGRAM_REMOVE_MAP = 7, + VMCI_EVENT_SUBSCRIBE = 8, + VMCI_EVENT_UNSUBSCRIBE = 9, + VMCI_QUEUEPAIR_ALLOC = 10, + VMCI_QUEUEPAIR_DETACH = 11, + + /* + * VMCI_VSOCK_VMX_LOOKUP was assigned to 12 for Fusion 3.0/3.1, + * WS 7.0/7.1 and ESX 4.1 + */ + VMCI_HGFS_TRANSPORT = 13, + VMCI_UNITY_PBRPC_REGISTER = 14, + VMCI_RPC_PRIVILEGED = 15, + VMCI_RPC_UNPRIVILEGED = 16, + VMCI_RESOURCE_MAX = 17, +}; + +/* + * struct vmci_handle - Ownership information structure + * @context: The VMX context ID. + * @resource: The resource ID (used for locating in resource hash). + * + * The vmci_handle structure is used to track resources used within + * vmw_vmci. + */ +struct vmci_handle { + u32 context; + u32 resource; +}; + +#define vmci_make_handle(_cid, _rid) \ + (struct vmci_handle){ .context = _cid, .resource = _rid } + +static inline bool vmci_handle_is_equal(struct vmci_handle h1, + struct vmci_handle h2) +{ + return h1.context == h2.context && h1.resource == h2.resource; +} + +#define VMCI_INVALID_ID ~0 +static const struct vmci_handle VMCI_INVALID_HANDLE = { + .context = VMCI_INVALID_ID, + .resource = VMCI_INVALID_ID +}; + +static inline bool vmci_handle_is_invalid(struct vmci_handle h) +{ + return vmci_handle_is_equal(h, VMCI_INVALID_HANDLE); +} + +/* + * The below defines can be used to send anonymous requests. + * This also indicates that no response is expected. + */ +#define VMCI_ANON_SRC_CONTEXT_ID VMCI_INVALID_ID +#define VMCI_ANON_SRC_RESOURCE_ID VMCI_INVALID_ID +static const struct vmci_handle VMCI_ANON_SRC_HANDLE = { + .context = VMCI_ANON_SRC_CONTEXT_ID, + .resource = VMCI_ANON_SRC_RESOURCE_ID +}; + +/* The lowest 16 context ids are reserved for internal use. */ +#define VMCI_RESERVED_CID_LIMIT ((u32) 16) + +/* + * Hypervisor context id, used for calling into hypervisor + * supplied services from the VM. + */ +#define VMCI_HYPERVISOR_CONTEXT_ID 0 + +/* + * Well-known context id, a logical context that contains a set of + * well-known services. This context ID is now obsolete. + */ +#define VMCI_WELL_KNOWN_CONTEXT_ID 1 + +/* + * Context ID used by host endpoints. + */ +#define VMCI_HOST_CONTEXT_ID 2 + +#define VMCI_CONTEXT_IS_VM(_cid) (VMCI_INVALID_ID != (_cid) && \ + (_cid) > VMCI_HOST_CONTEXT_ID) + +/* + * The VMCI_CONTEXT_RESOURCE_ID is used together with vmci_make_handle to make + * handles that refer to a specific context. + */ +#define VMCI_CONTEXT_RESOURCE_ID 0 + +/* + * VMCI error codes. + */ +enum { + VMCI_SUCCESS_QUEUEPAIR_ATTACH = 5, + VMCI_SUCCESS_QUEUEPAIR_CREATE = 4, + VMCI_SUCCESS_LAST_DETACH = 3, + VMCI_SUCCESS_ACCESS_GRANTED = 2, + VMCI_SUCCESS_ENTRY_DEAD = 1, + VMCI_SUCCESS = 0, + VMCI_ERROR_INVALID_RESOURCE = (-1), + VMCI_ERROR_INVALID_ARGS = (-2), + VMCI_ERROR_NO_MEM = (-3), + VMCI_ERROR_DATAGRAM_FAILED = (-4), + VMCI_ERROR_MORE_DATA = (-5), + VMCI_ERROR_NO_MORE_DATAGRAMS = (-6), + VMCI_ERROR_NO_ACCESS = (-7), + VMCI_ERROR_NO_HANDLE = (-8), + VMCI_ERROR_DUPLICATE_ENTRY = (-9), + VMCI_ERROR_DST_UNREACHABLE = (-10), + VMCI_ERROR_PAYLOAD_TOO_LARGE = (-11), + VMCI_ERROR_INVALID_PRIV = (-12), + VMCI_ERROR_GENERIC = (-13), + VMCI_ERROR_PAGE_ALREADY_SHARED = (-14), + VMCI_ERROR_CANNOT_SHARE_PAGE = (-15), + VMCI_ERROR_CANNOT_UNSHARE_PAGE = (-16), + VMCI_ERROR_NO_PROCESS = (-17), + VMCI_ERROR_NO_DATAGRAM = (-18), + VMCI_ERROR_NO_RESOURCES = (-19), + VMCI_ERROR_UNAVAILABLE = (-20), + VMCI_ERROR_NOT_FOUND = (-21), + VMCI_ERROR_ALREADY_EXISTS = (-22), + VMCI_ERROR_NOT_PAGE_ALIGNED = (-23), + VMCI_ERROR_INVALID_SIZE = (-24), + VMCI_ERROR_REGION_ALREADY_SHARED = (-25), + VMCI_ERROR_TIMEOUT = (-26), + VMCI_ERROR_DATAGRAM_INCOMPLETE = (-27), + VMCI_ERROR_INCORRECT_IRQL = (-28), + VMCI_ERROR_EVENT_UNKNOWN = (-29), + VMCI_ERROR_OBSOLETE = (-30), + VMCI_ERROR_QUEUEPAIR_MISMATCH = (-31), + VMCI_ERROR_QUEUEPAIR_NOTSET = (-32), + VMCI_ERROR_QUEUEPAIR_NOTOWNER = (-33), + VMCI_ERROR_QUEUEPAIR_NOTATTACHED = (-34), + VMCI_ERROR_QUEUEPAIR_NOSPACE = (-35), + VMCI_ERROR_QUEUEPAIR_NODATA = (-36), + VMCI_ERROR_BUSMEM_INVALIDATION = (-37), + VMCI_ERROR_MODULE_NOT_LOADED = (-38), + VMCI_ERROR_DEVICE_NOT_FOUND = (-39), + VMCI_ERROR_QUEUEPAIR_NOT_READY = (-40), + VMCI_ERROR_WOULD_BLOCK = (-41), + + /* VMCI clients should return error code within this range */ + VMCI_ERROR_CLIENT_MIN = (-500), + VMCI_ERROR_CLIENT_MAX = (-550), + + /* Internal error codes. */ + VMCI_SHAREDMEM_ERROR_BAD_CONTEXT = (-1000), +}; + +/* VMCI reserved events. */ +enum { + /* Only applicable to guest endpoints */ + VMCI_EVENT_CTX_ID_UPDATE = 0, + + /* Applicable to guest and host */ + VMCI_EVENT_CTX_REMOVED = 1, + + /* Only applicable to guest endpoints */ + VMCI_EVENT_QP_RESUMED = 2, + + /* Applicable to guest and host */ + VMCI_EVENT_QP_PEER_ATTACH = 3, + + /* Applicable to guest and host */ + VMCI_EVENT_QP_PEER_DETACH = 4, + + /* + * Applicable to VMX and vmk. On vmk, + * this event has the Context payload type. + */ + VMCI_EVENT_MEM_ACCESS_ON = 5, + + /* + * Applicable to VMX and vmk. Same as + * above for the payload type. + */ + VMCI_EVENT_MEM_ACCESS_OFF = 6, + VMCI_EVENT_MAX = 7, +}; + +/* + * Of the above events, a few are reserved for use in the VMX, and + * other endpoints (guest and host kernel) should not use them. For + * the rest of the events, we allow both host and guest endpoints to + * subscribe to them, to maintain the same API for host and guest + * endpoints. + */ +#define VMCI_EVENT_VALID_VMX(_event) ((_event) == VMCI_EVENT_MEM_ACCESS_ON || \ + (_event) == VMCI_EVENT_MEM_ACCESS_OFF) + +#define VMCI_EVENT_VALID(_event) ((_event) < VMCI_EVENT_MAX && \ + !VMCI_EVENT_VALID_VMX(_event)) + +/* Reserved guest datagram resource ids. */ +#define VMCI_EVENT_HANDLER 0 + +/* + * VMCI coarse-grained privileges (per context or host + * process/endpoint. An entity with the restricted flag is only + * allowed to interact with the hypervisor and trusted entities. + */ +enum { + VMCI_NO_PRIVILEGE_FLAGS = 0, + VMCI_PRIVILEGE_FLAG_RESTRICTED = 1, + VMCI_PRIVILEGE_FLAG_TRUSTED = 2, + VMCI_PRIVILEGE_ALL_FLAGS = (VMCI_PRIVILEGE_FLAG_RESTRICTED | + VMCI_PRIVILEGE_FLAG_TRUSTED), + VMCI_DEFAULT_PROC_PRIVILEGE_FLAGS = VMCI_NO_PRIVILEGE_FLAGS, + VMCI_LEAST_PRIVILEGE_FLAGS = VMCI_PRIVILEGE_FLAG_RESTRICTED, + VMCI_MAX_PRIVILEGE_FLAGS = VMCI_PRIVILEGE_FLAG_TRUSTED, +}; + +/* 0 through VMCI_RESERVED_RESOURCE_ID_MAX are reserved. */ +#define VMCI_RESERVED_RESOURCE_ID_MAX 1023 + +/* + * Driver version. + * + * Increment major version when you make an incompatible change. + * Compatibility goes both ways (old driver with new executable + * as well as new driver with old executable). + */ + +/* Never change VMCI_VERSION_SHIFT_WIDTH */ +#define VMCI_VERSION_SHIFT_WIDTH 16 +#define VMCI_MAKE_VERSION(_major, _minor) \ + ((_major) << VMCI_VERSION_SHIFT_WIDTH | (u16) (_minor)) + +#define VMCI_VERSION_MAJOR(v) ((u32) (v) >> VMCI_VERSION_SHIFT_WIDTH) +#define VMCI_VERSION_MINOR(v) ((u16) (v)) + +/* + * VMCI_VERSION is always the current version. Subsequently listed + * versions are ways of detecting previous versions of the connecting + * application (i.e., VMX). + * + * VMCI_VERSION_NOVMVM: This version removed support for VM to VM + * communication. + * + * VMCI_VERSION_NOTIFY: This version introduced doorbell notification + * support. + * + * VMCI_VERSION_HOSTQP: This version introduced host end point support + * for hosted products. + * + * VMCI_VERSION_PREHOSTQP: This is the version prior to the adoption of + * support for host end-points. + * + * VMCI_VERSION_PREVERS2: This fictional version number is intended to + * represent the version of a VMX which doesn't call into the driver + * with ioctl VERSION2 and thus doesn't establish its version with the + * driver. + */ + +#define VMCI_VERSION VMCI_VERSION_NOVMVM +#define VMCI_VERSION_NOVMVM VMCI_MAKE_VERSION(11, 0) +#define VMCI_VERSION_NOTIFY VMCI_MAKE_VERSION(10, 0) +#define VMCI_VERSION_HOSTQP VMCI_MAKE_VERSION(9, 0) +#define VMCI_VERSION_PREHOSTQP VMCI_MAKE_VERSION(8, 0) +#define VMCI_VERSION_PREVERS2 VMCI_MAKE_VERSION(1, 0) + +#define VMCI_SOCKETS_MAKE_VERSION(_p) \ + ((((_p)[0] & 0xFF) << 24) | (((_p)[1] & 0xFF) << 16) | ((_p)[2])) + +/* + * The VMCI IOCTLs. We use identity code 7, as noted in ioctl-number.h, and + * we start at sequence 9f. This gives us the same values that our shipping + * products use, starting at 1951, provided we leave out the direction and + * structure size. Note that VMMon occupies the block following us, starting + * at 2001. + */ +#define IOCTL_VMCI_VERSION _IO(7, 0x9f) /* 1951 */ +#define IOCTL_VMCI_INIT_CONTEXT _IO(7, 0xa0) +#define IOCTL_VMCI_QUEUEPAIR_SETVA _IO(7, 0xa4) +#define IOCTL_VMCI_NOTIFY_RESOURCE _IO(7, 0xa5) +#define IOCTL_VMCI_NOTIFICATIONS_RECEIVE _IO(7, 0xa6) +#define IOCTL_VMCI_VERSION2 _IO(7, 0xa7) +#define IOCTL_VMCI_QUEUEPAIR_ALLOC _IO(7, 0xa8) +#define IOCTL_VMCI_QUEUEPAIR_SETPAGEFILE _IO(7, 0xa9) +#define IOCTL_VMCI_QUEUEPAIR_DETACH _IO(7, 0xaa) +#define IOCTL_VMCI_DATAGRAM_SEND _IO(7, 0xab) +#define IOCTL_VMCI_DATAGRAM_RECEIVE _IO(7, 0xac) +#define IOCTL_VMCI_CTX_ADD_NOTIFICATION _IO(7, 0xaf) +#define IOCTL_VMCI_CTX_REMOVE_NOTIFICATION _IO(7, 0xb0) +#define IOCTL_VMCI_CTX_GET_CPT_STATE _IO(7, 0xb1) +#define IOCTL_VMCI_CTX_SET_CPT_STATE _IO(7, 0xb2) +#define IOCTL_VMCI_GET_CONTEXT_ID _IO(7, 0xb3) +#define IOCTL_VMCI_SOCKETS_VERSION _IO(7, 0xb4) +#define IOCTL_VMCI_SOCKETS_GET_AF_VALUE _IO(7, 0xb8) +#define IOCTL_VMCI_SOCKETS_GET_LOCAL_CID _IO(7, 0xb9) +#define IOCTL_VMCI_SET_NOTIFY _IO(7, 0xcb) /* 1995 */ +/*IOCTL_VMMON_START _IO(7, 0xd1)*/ /* 2001 */ + +/* + * struct vmci_queue_header - VMCI Queue Header information. + * + * A Queue cannot stand by itself as designed. Each Queue's header + * contains a pointer into itself (the producer_tail) and into its peer + * (consumer_head). The reason for the separation is one of + * accessibility: Each end-point can modify two things: where the next + * location to enqueue is within its produce_q (producer_tail); and + * where the next dequeue location is in its consume_q (consumer_head). + * + * An end-point cannot modify the pointers of its peer (guest to + * guest; NOTE that in the host both queue headers are mapped r/w). + * But, each end-point needs read access to both Queue header + * structures in order to determine how much space is used (or left) + * in the Queue. This is because for an end-point to know how full + * its produce_q is, it needs to use the consumer_head that points into + * the produce_q but -that- consumer_head is in the Queue header for + * that end-points consume_q. + * + * Thoroughly confused? Sorry. + * + * producer_tail: the point to enqueue new entrants. When you approach + * a line in a store, for example, you walk up to the tail. + * + * consumer_head: the point in the queue from which the next element is + * dequeued. In other words, who is next in line is he who is at the + * head of the line. + * + * Also, producer_tail points to an empty byte in the Queue, whereas + * consumer_head points to a valid byte of data (unless producer_tail == + * consumer_head in which case consumer_head does not point to a valid + * byte of data). + * + * For a queue of buffer 'size' bytes, the tail and head pointers will be in + * the range [0, size-1]. + * + * If produce_q_header->producer_tail == consume_q_header->consumer_head + * then the produce_q is empty. + */ +struct vmci_queue_header { + /* All fields are 64bit and aligned. */ + struct vmci_handle handle; /* Identifier. */ + atomic64_t producer_tail; /* Offset in this queue. */ + atomic64_t consumer_head; /* Offset in peer queue. */ +}; + +/* + * struct vmci_datagram - Base struct for vmci datagrams. + * @dst: A vmci_handle that tracks the destination of the datagram. + * @src: A vmci_handle that tracks the source of the datagram. + * @payload_size: The size of the payload. + * + * vmci_datagram structs are used when sending vmci datagrams. They include + * the necessary source and destination information to properly route + * the information along with the size of the package. + */ +struct vmci_datagram { + struct vmci_handle dst; + struct vmci_handle src; + u64 payload_size; +}; + +/* + * Second flag is for creating a well-known handle instead of a per context + * handle. Next flag is for deferring datagram delivery, so that the + * datagram callback is invoked in a delayed context (not interrupt context). + */ +#define VMCI_FLAG_DG_NONE 0 +#define VMCI_FLAG_WELLKNOWN_DG_HND 0x1 +#define VMCI_FLAG_ANYCID_DG_HND 0x2 +#define VMCI_FLAG_DG_DELAYED_CB 0x4 + +/* + * Maximum supported size of a VMCI datagram for routable datagrams. + * Datagrams going to the hypervisor are allowed to be larger. + */ +#define VMCI_MAX_DG_SIZE (17 * 4096) +#define VMCI_MAX_DG_PAYLOAD_SIZE (VMCI_MAX_DG_SIZE - \ + sizeof(struct vmci_datagram)) +#define VMCI_DG_PAYLOAD(_dg) (void *)((char *)(_dg) + \ + sizeof(struct vmci_datagram)) +#define VMCI_DG_HEADERSIZE sizeof(struct vmci_datagram) +#define VMCI_DG_SIZE(_dg) (VMCI_DG_HEADERSIZE + (size_t)(_dg)->payload_size) +#define VMCI_DG_SIZE_ALIGNED(_dg) ((VMCI_DG_SIZE(_dg) + 7) & (~((size_t) 0x7))) +#define VMCI_MAX_DATAGRAM_QUEUE_SIZE (VMCI_MAX_DG_SIZE * 2) + +struct vmci_event_payload_qp { + struct vmci_handle handle; /* queue_pair handle. */ + u32 peer_id; /* Context id of attaching/detaching VM. */ + u32 _pad; +}; + +/* Flags for VMCI queue_pair API. */ +enum { + /* Fail alloc if QP not created by peer. */ + VMCI_QPFLAG_ATTACH_ONLY = 1 << 0, + + /* Only allow attaches from local context. */ + VMCI_QPFLAG_LOCAL = 1 << 1, + + /* Host won't block when guest is quiesced. */ + VMCI_QPFLAG_NONBLOCK = 1 << 2, + + /* Pin data pages in ESX. Used with NONBLOCK */ + VMCI_QPFLAG_PINNED = 1 << 3, + + /* Update the following flag when adding new flags. */ + VMCI_QP_ALL_FLAGS = (VMCI_QPFLAG_ATTACH_ONLY | VMCI_QPFLAG_LOCAL | + VMCI_QPFLAG_NONBLOCK | VMCI_QPFLAG_PINNED), + + /* Convenience flags */ + VMCI_QP_ASYMM = (VMCI_QPFLAG_NONBLOCK | VMCI_QPFLAG_PINNED), + VMCI_QP_ASYMM_PEER = (VMCI_QPFLAG_ATTACH_ONLY | VMCI_QP_ASYMM), +}; + +/* + * We allow at least 1024 more event datagrams from the hypervisor past the + * normally allowed datagrams pending for a given context. We define this + * limit on event datagrams from the hypervisor to guard against DoS attack + * from a malicious VM which could repeatedly attach to and detach from a queue + * pair, causing events to be queued at the destination VM. However, the rate + * at which such events can be generated is small since it requires a VM exit + * and handling of queue pair attach/detach call at the hypervisor. Event + * datagrams may be queued up at the destination VM if it has interrupts + * disabled or if it is not draining events for some other reason. 1024 + * datagrams is a grossly conservative estimate of the time for which + * interrupts may be disabled in the destination VM, but at the same time does + * not exacerbate the memory pressure problem on the host by much (size of each + * event datagram is small). + */ +#define VMCI_MAX_DATAGRAM_AND_EVENT_QUEUE_SIZE \ + (VMCI_MAX_DATAGRAM_QUEUE_SIZE + \ + 1024 * (sizeof(struct vmci_datagram) + \ + sizeof(struct vmci_event_data_max))) + +/* + * Struct used for querying, via VMCI_RESOURCES_QUERY, the availability of + * hypervisor resources. Struct size is 16 bytes. All fields in struct are + * aligned to their natural alignment. + */ +struct vmci_resource_query_hdr { + struct vmci_datagram hdr; + u32 num_resources; + u32 _padding; +}; + +/* + * Convenience struct for negotiating vectors. Must match layout of + * VMCIResourceQueryHdr minus the struct vmci_datagram header. + */ +struct vmci_resource_query_msg { + u32 num_resources; + u32 _padding; + u32 resources[1]; +}; + +/* + * The maximum number of resources that can be queried using + * VMCI_RESOURCE_QUERY is 31, as the result is encoded in the lower 31 + * bits of a positive return value. Negative values are reserved for + * errors. + */ +#define VMCI_RESOURCE_QUERY_MAX_NUM 31 + +/* Maximum size for the VMCI_RESOURCE_QUERY request. */ +#define VMCI_RESOURCE_QUERY_MAX_SIZE \ + (sizeof(struct vmci_resource_query_hdr) + \ + sizeof(u32) * VMCI_RESOURCE_QUERY_MAX_NUM) + +/* + * Struct used for setting the notification bitmap. All fields in + * struct are aligned to their natural alignment. + */ +struct vmci_notify_bm_set_msg { + struct vmci_datagram hdr; + u32 bitmap_ppn; + u32 _pad; +}; + +/* + * Struct used for linking a doorbell handle with an index in the + * notify bitmap. All fields in struct are aligned to their natural + * alignment. + */ +struct vmci_doorbell_link_msg { + struct vmci_datagram hdr; + struct vmci_handle handle; + u64 notify_idx; +}; + +/* + * Struct used for unlinking a doorbell handle from an index in the + * notify bitmap. All fields in struct are aligned to their natural + * alignment. + */ +struct vmci_doorbell_unlink_msg { + struct vmci_datagram hdr; + struct vmci_handle handle; +}; + +/* + * Struct used for generating a notification on a doorbell handle. All + * fields in struct are aligned to their natural alignment. + */ +struct vmci_doorbell_notify_msg { + struct vmci_datagram hdr; + struct vmci_handle handle; +}; + +/* + * This struct is used to contain data for events. Size of this struct is a + * multiple of 8 bytes, and all fields are aligned to their natural alignment. + */ +struct vmci_event_data { + u32 event; /* 4 bytes. */ + u32 _pad; + /* Event payload is put here. */ +}; + +/* + * Define the different VMCI_EVENT payload data types here. All structs must + * be a multiple of 8 bytes, and fields must be aligned to their natural + * alignment. + */ +struct vmci_event_payld_ctx { + u32 context_id; /* 4 bytes. */ + u32 _pad; +}; + +struct vmci_event_payld_qp { + struct vmci_handle handle; /* queue_pair handle. */ + u32 peer_id; /* Context id of attaching/detaching VM. */ + u32 _pad; +}; + +/* + * We define the following struct to get the size of the maximum event + * data the hypervisor may send to the guest. If adding a new event + * payload type above, add it to the following struct too (inside the + * union). + */ +struct vmci_event_data_max { + struct vmci_event_data event_data; + union { + struct vmci_event_payld_ctx context_payload; + struct vmci_event_payld_qp qp_payload; + } ev_data_payload; +}; + +/* + * Struct used for VMCI_EVENT_SUBSCRIBE/UNSUBSCRIBE and + * VMCI_EVENT_HANDLER messages. Struct size is 32 bytes. All fields + * in struct are aligned to their natural alignment. + */ +struct vmci_event_msg { + struct vmci_datagram hdr; + + /* Has event type and payload. */ + struct vmci_event_data event_data; + + /* Payload gets put here. */ +}; + +/* Event with context payload. */ +struct vmci_event_ctx { + struct vmci_event_msg msg; + struct vmci_event_payld_ctx payload; +}; + +/* Event with QP payload. */ +struct vmci_event_qp { + struct vmci_event_msg msg; + struct vmci_event_payld_qp payload; +}; + +/* + * Structs used for queue_pair alloc and detach messages. We align fields of + * these structs to 64bit boundaries. + */ +struct vmci_qp_alloc_msg { + struct vmci_datagram hdr; + struct vmci_handle handle; + u32 peer; + u32 flags; + u64 produce_size; + u64 consume_size; + u64 num_ppns; + + /* List of PPNs placed here. */ +}; + +struct vmci_qp_detach_msg { + struct vmci_datagram hdr; + struct vmci_handle handle; +}; + +/* VMCI Doorbell API. */ +#define VMCI_FLAG_DELAYED_CB 0x01 + +typedef void (*vmci_callback) (void *client_data); + +/* + * struct vmci_qp - A vmw_vmci queue pair handle. + * + * This structure is used as a handle to a queue pair created by + * VMCI. It is intentionally left opaque to clients. + */ +struct vmci_qp; + +/* Callback needed for correctly waiting on events. */ +typedef int (*vmci_datagram_recv_cb) (void *client_data, + struct vmci_datagram *msg); + +/* VMCI Event API. */ +typedef void (*vmci_event_cb) (u32 sub_id, const struct vmci_event_data *ed, + void *client_data); + +/* + * We use the following inline function to access the payload data + * associated with an event data. + */ +static inline const void * +vmci_event_data_const_payload(const struct vmci_event_data *ev_data) +{ + return (const char *)ev_data + sizeof(*ev_data); +} + +static inline void *vmci_event_data_payload(struct vmci_event_data *ev_data) +{ + return (void *)vmci_event_data_const_payload(ev_data); +} + +/* + * Helper to add a given offset to a head or tail pointer. Wraps the + * value of the pointer around the max size of the queue. + */ +static inline void vmci_qp_add_pointer(atomic64_t *var, + size_t add, + u64 size) +{ + u64 new_val = atomic64_read(var); + + if (new_val >= size - add) + new_val -= size; + + new_val += add; + + atomic64_set(var, new_val); +} + +/* + * Helper routine to get the Producer Tail from the supplied queue. + */ +static inline u64 +vmci_q_header_producer_tail(const struct vmci_queue_header *q_header) +{ + struct vmci_queue_header *qh = (struct vmci_queue_header *)q_header; + return atomic64_read(&qh->producer_tail); +} + +/* + * Helper routine to get the Consumer Head from the supplied queue. + */ +static inline u64 +vmci_q_header_consumer_head(const struct vmci_queue_header *q_header) +{ + struct vmci_queue_header *qh = (struct vmci_queue_header *)q_header; + return atomic64_read(&qh->consumer_head); +} + +/* + * Helper routine to increment the Producer Tail. Fundamentally, + * vmci_qp_add_pointer() is used to manipulate the tail itself. + */ +static inline void +vmci_q_header_add_producer_tail(struct vmci_queue_header *q_header, + size_t add, + u64 queue_size) +{ + vmci_qp_add_pointer(&q_header->producer_tail, add, queue_size); +} + +/* + * Helper routine to increment the Consumer Head. Fundamentally, + * vmci_qp_add_pointer() is used to manipulate the head itself. + */ +static inline void +vmci_q_header_add_consumer_head(struct vmci_queue_header *q_header, + size_t add, + u64 queue_size) +{ + vmci_qp_add_pointer(&q_header->consumer_head, add, queue_size); +} + +/* + * Helper routine for getting the head and the tail pointer for a queue. + * Both the VMCIQueues are needed to get both the pointers for one queue. + */ +static inline void +vmci_q_header_get_pointers(const struct vmci_queue_header *produce_q_header, + const struct vmci_queue_header *consume_q_header, + u64 *producer_tail, + u64 *consumer_head) +{ + if (producer_tail) + *producer_tail = vmci_q_header_producer_tail(produce_q_header); + + if (consumer_head) + *consumer_head = vmci_q_header_consumer_head(consume_q_header); +} + +static inline void vmci_q_header_init(struct vmci_queue_header *q_header, + const struct vmci_handle handle) +{ + q_header->handle = handle; + atomic64_set(&q_header->producer_tail, 0); + atomic64_set(&q_header->consumer_head, 0); +} + +/* + * Finds available free space in a produce queue to enqueue more + * data or reports an error if queue pair corruption is detected. + */ +static s64 +vmci_q_header_free_space(const struct vmci_queue_header *produce_q_header, + const struct vmci_queue_header *consume_q_header, + const u64 produce_q_size) +{ + u64 tail; + u64 head; + u64 free_space; + + tail = vmci_q_header_producer_tail(produce_q_header); + head = vmci_q_header_consumer_head(consume_q_header); + + if (tail >= produce_q_size || head >= produce_q_size) + return VMCI_ERROR_INVALID_SIZE; + + /* + * Deduct 1 to avoid tail becoming equal to head which causes + * ambiguity. If head and tail are equal it means that the + * queue is empty. + */ + if (tail >= head) + free_space = produce_q_size - (tail - head) - 1; + else + free_space = head - tail - 1; + + return free_space; +} + +/* + * vmci_q_header_free_space() does all the heavy lifting of + * determing the number of free bytes in a Queue. This routine, + * then subtracts that size from the full size of the Queue so + * the caller knows how many bytes are ready to be dequeued. + * Results: + * On success, available data size in bytes (up to MAX_INT64). + * On failure, appropriate error code. + */ +static inline s64 +vmci_q_header_buf_ready(const struct vmci_queue_header *consume_q_header, + const struct vmci_queue_header *produce_q_header, + const u64 consume_q_size) +{ + s64 free_space; + + free_space = vmci_q_header_free_space(consume_q_header, + produce_q_header, consume_q_size); + if (free_space < VMCI_SUCCESS) + return free_space; + + return consume_q_size - free_space - 1; +} + + +#endif /* _VMW_VMCI_DEF_H_ */ -- cgit v1.2.3 From 154f757fd315270e42bd17f4a68d84bd070e5758 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Tue, 27 Nov 2012 09:40:32 +0900 Subject: extcon: max77693: Remove duplicate code by making function This patch make max77693-muic_get_cable_type() function to remove duplicate code because almost internal function need to read adc/adc1k/adclow/chg_type value of MUIC register. Also, this patch add description of internal function move field constant of muic device from extcon-max77693 driver to max77693 header file because of it is needed for masking some interrupt through platform data. Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-max77693.c | 440 +++++++++++++++++++---------------- include/linux/mfd/max77693-private.h | 86 +++++++ 2 files changed, 327 insertions(+), 199 deletions(-) (limited to 'include') diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index 8c17b65eb74d..e84d5dc06798 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -30,92 +30,6 @@ #define DEV_NAME "max77693-muic" -/* MAX77693 MUIC - STATUS1~3 Register */ -#define STATUS1_ADC_SHIFT (0) -#define STATUS1_ADCLOW_SHIFT (5) -#define STATUS1_ADCERR_SHIFT (6) -#define STATUS1_ADC1K_SHIFT (7) -#define STATUS1_ADC_MASK (0x1f << STATUS1_ADC_SHIFT) -#define STATUS1_ADCLOW_MASK (0x1 << STATUS1_ADCLOW_SHIFT) -#define STATUS1_ADCERR_MASK (0x1 << STATUS1_ADCERR_SHIFT) -#define STATUS1_ADC1K_MASK (0x1 << STATUS1_ADC1K_SHIFT) - -#define STATUS2_CHGTYP_SHIFT (0) -#define STATUS2_CHGDETRUN_SHIFT (3) -#define STATUS2_DCDTMR_SHIFT (4) -#define STATUS2_DXOVP_SHIFT (5) -#define STATUS2_VBVOLT_SHIFT (6) -#define STATUS2_VIDRM_SHIFT (7) -#define STATUS2_CHGTYP_MASK (0x7 << STATUS2_CHGTYP_SHIFT) -#define STATUS2_CHGDETRUN_MASK (0x1 << STATUS2_CHGDETRUN_SHIFT) -#define STATUS2_DCDTMR_MASK (0x1 << STATUS2_DCDTMR_SHIFT) -#define STATUS2_DXOVP_MASK (0x1 << STATUS2_DXOVP_SHIFT) -#define STATUS2_VBVOLT_MASK (0x1 << STATUS2_VBVOLT_SHIFT) -#define STATUS2_VIDRM_MASK (0x1 << STATUS2_VIDRM_SHIFT) - -#define STATUS3_OVP_SHIFT (2) -#define STATUS3_OVP_MASK (0x1 << STATUS3_OVP_SHIFT) - -/* MAX77693 CDETCTRL1~2 register */ -#define CDETCTRL1_CHGDETEN_SHIFT (0) -#define CDETCTRL1_CHGTYPMAN_SHIFT (1) -#define CDETCTRL1_DCDEN_SHIFT (2) -#define CDETCTRL1_DCD2SCT_SHIFT (3) -#define CDETCTRL1_CDDELAY_SHIFT (4) -#define CDETCTRL1_DCDCPL_SHIFT (5) -#define CDETCTRL1_CDPDET_SHIFT (7) -#define CDETCTRL1_CHGDETEN_MASK (0x1 << CDETCTRL1_CHGDETEN_SHIFT) -#define CDETCTRL1_CHGTYPMAN_MASK (0x1 << CDETCTRL1_CHGTYPMAN_SHIFT) -#define CDETCTRL1_DCDEN_MASK (0x1 << CDETCTRL1_DCDEN_SHIFT) -#define CDETCTRL1_DCD2SCT_MASK (0x1 << CDETCTRL1_DCD2SCT_SHIFT) -#define CDETCTRL1_CDDELAY_MASK (0x1 << CDETCTRL1_CDDELAY_SHIFT) -#define CDETCTRL1_DCDCPL_MASK (0x1 << CDETCTRL1_DCDCPL_SHIFT) -#define CDETCTRL1_CDPDET_MASK (0x1 << CDETCTRL1_CDPDET_SHIFT) - -#define CDETCTRL2_VIDRMEN_SHIFT (1) -#define CDETCTRL2_DXOVPEN_SHIFT (3) -#define CDETCTRL2_VIDRMEN_MASK (0x1 << CDETCTRL2_VIDRMEN_SHIFT) -#define CDETCTRL2_DXOVPEN_MASK (0x1 << CDETCTRL2_DXOVPEN_SHIFT) - -/* MAX77693 MUIC - CONTROL1~3 register */ -#define COMN1SW_SHIFT (0) -#define COMP2SW_SHIFT (3) -#define COMN1SW_MASK (0x7 << COMN1SW_SHIFT) -#define COMP2SW_MASK (0x7 << COMP2SW_SHIFT) -#define COMP_SW_MASK (COMP2SW_MASK | COMN1SW_MASK) -#define CONTROL1_SW_USB ((1 << COMP2SW_SHIFT) \ - | (1 << COMN1SW_SHIFT)) -#define CONTROL1_SW_AUDIO ((2 << COMP2SW_SHIFT) \ - | (2 << COMN1SW_SHIFT)) -#define CONTROL1_SW_UART ((3 << COMP2SW_SHIFT) \ - | (3 << COMN1SW_SHIFT)) -#define CONTROL1_SW_OPEN ((0 << COMP2SW_SHIFT) \ - | (0 << COMN1SW_SHIFT)) - -#define CONTROL2_LOWPWR_SHIFT (0) -#define CONTROL2_ADCEN_SHIFT (1) -#define CONTROL2_CPEN_SHIFT (2) -#define CONTROL2_SFOUTASRT_SHIFT (3) -#define CONTROL2_SFOUTORD_SHIFT (4) -#define CONTROL2_ACCDET_SHIFT (5) -#define CONTROL2_USBCPINT_SHIFT (6) -#define CONTROL2_RCPS_SHIFT (7) -#define CONTROL2_LOWPWR_MASK (0x1 << CONTROL2_LOWPWR_SHIFT) -#define CONTROL2_ADCEN_MASK (0x1 << CONTROL2_ADCEN_SHIFT) -#define CONTROL2_CPEN_MASK (0x1 << CONTROL2_CPEN_SHIFT) -#define CONTROL2_SFOUTASRT_MASK (0x1 << CONTROL2_SFOUTASRT_SHIFT) -#define CONTROL2_SFOUTORD_MASK (0x1 << CONTROL2_SFOUTORD_SHIFT) -#define CONTROL2_ACCDET_MASK (0x1 << CONTROL2_ACCDET_SHIFT) -#define CONTROL2_USBCPINT_MASK (0x1 << CONTROL2_USBCPINT_SHIFT) -#define CONTROL2_RCPS_MASK (0x1 << CONTROL2_RCPS_SHIFT) - -#define CONTROL3_JIGSET_SHIFT (0) -#define CONTROL3_BTLDSET_SHIFT (2) -#define CONTROL3_ADCDBSET_SHIFT (4) -#define CONTROL3_JIGSET_MASK (0x3 << CONTROL3_JIGSET_SHIFT) -#define CONTROL3_BTLDSET_MASK (0x3 << CONTROL3_BTLDSET_SHIFT) -#define CONTROL3_ADCDBSET_MASK (0x3 << CONTROL3_ADCDBSET_SHIFT) - enum max77693_muic_adc_debounce_time { ADC_DEBOUNCE_TIME_5MS = 0, ADC_DEBOUNCE_TIME_10MS, @@ -127,8 +41,8 @@ struct max77693_muic_info { struct device *dev; struct max77693_dev *max77693; struct extcon_dev *edev; - int prev_adc; - int prev_adc_gnd; + int prev_cable_type; + int prev_cable_type_gnd; int prev_chg_type; u8 status[2]; @@ -137,6 +51,13 @@ struct max77693_muic_info { struct mutex mutex; }; +enum max77693_muic_cable_group { + MAX77693_CABLE_GROUP_ADC = 0, + MAX77693_CABLE_GROUP_ADC_GND, + MAX77693_CABLE_GROUP_CHG, + MAX77693_CABLE_GROUP_VBVOLT, +}; + enum max77693_muic_charger_type { MAX77693_CHARGER_TYPE_NONE = 0, MAX77693_CHARGER_TYPE_USB, @@ -221,21 +142,40 @@ enum max77693_muic_acc_type { }; /* MAX77693 MUIC device support below list of accessories(external connector) */ -const char *max77693_extcon_cable[] = { - [0] = "USB", - [1] = "USB-Host", - [2] = "TA", - [3] = "Fast-charger", - [4] = "Slow-charger", - [5] = "Charge-downstream", - [6] = "MHL", - [7] = "Audio-video-load", - [8] = "Audio-video-noload", - [9] = "JIG", +enum { + EXTCON_CABLE_USB = 0, + EXTCON_CABLE_USB_HOST, + EXTCON_CABLE_TA, + EXTCON_CABLE_FAST_CHARGER, + EXTCON_CABLE_SLOW_CHARGER, + EXTCON_CABLE_CHARGE_DOWNSTREAM, + EXTCON_CABLE_MHL, + EXTCON_CABLE_AUDIO_VIDEO_LOAD, + EXTCON_CABLE_AUDIO_VIDEO_NOLOAD, + EXTCON_CABLE_JIG, + + _EXTCON_CABLE_NUM, +}; +const char *max77693_extcon_cable[] = { + [EXTCON_CABLE_USB] = "USB", + [EXTCON_CABLE_USB_HOST] = "USB-Host", + [EXTCON_CABLE_TA] = "TA", + [EXTCON_CABLE_FAST_CHARGER] = "Fast-charger", + [EXTCON_CABLE_SLOW_CHARGER] = "Slow-charger", + [EXTCON_CABLE_CHARGE_DOWNSTREAM] = "Charge-downstream", + [EXTCON_CABLE_MHL] = "MHL", + [EXTCON_CABLE_AUDIO_VIDEO_LOAD] = "Audio-video-load", + [EXTCON_CABLE_AUDIO_VIDEO_NOLOAD] = "Audio-video-noload", + [EXTCON_CABLE_JIG] = "JIG", NULL, }; +/* + * max77693_muic_set_debounce_time - Set the debounce time of ADC + * @info: the instance including private data of max77693 MUIC + * @time: the debounce time of ADC + */ static int max77693_muic_set_debounce_time(struct max77693_muic_info *info, enum max77693_muic_adc_debounce_time time) { @@ -262,6 +202,16 @@ static int max77693_muic_set_debounce_time(struct max77693_muic_info *info, return ret; }; +/* + * max77693_muic_set_path - Set hardware line according to attached cable + * @info: the instance including private data of max77693 MUIC + * @value: the path according to attached cable + * @attached: the state of cable (true:attached, false:detached) + * + * The max77693 MUIC device share outside H/W line among a varity of cables + * so, this function set internal path of H/W line according to the type of + * attached cable. + */ static int max77693_muic_set_path(struct max77693_muic_info *info, u8 val, bool attached) { @@ -300,36 +250,169 @@ out: return ret; } -static int max77693_muic_adc_ground_handler(struct max77693_muic_info *info, - bool attached) +/* + * max77693_muic_get_cable_type - Return cable type and check cable state + * @info: the instance including private data of max77693 MUIC + * @group: the path according to attached cable + * @attached: store cable state and return + * + * This function check the cable state either attached or detached, + * and then divide precise type of cable according to cable group. + * - MAX77693_CABLE_GROUP_ADC + * - MAX77693_CABLE_GROUP_ADC_GND + * - MAX77693_CABLE_GROUP_CHG + * - MAX77693_CABLE_GROUP_VBVOLT + */ +static int max77693_muic_get_cable_type(struct max77693_muic_info *info, + enum max77693_muic_cable_group group, bool *attached) { - int ret = 0; - int type; - int adc, adc1k, adclow; + int cable_type = 0; + int adc; + int adc1k; + int adclow; + int vbvolt; + int chg_type; + + switch (group) { + case MAX77693_CABLE_GROUP_ADC: + /* + * Read ADC value to check cable type and decide cable state + * according to cable type + */ + adc = info->status[0] & STATUS1_ADC_MASK; + adc >>= STATUS1_ADC_SHIFT; + + /* + * Check current cable state/cable type and store cable type + * (info->prev_cable_type) for handling cable when cable is + * detached. + */ + if (adc == MAX77693_MUIC_ADC_OPEN) { + *attached = false; + + cable_type = info->prev_cable_type; + info->prev_cable_type = MAX77693_MUIC_ADC_OPEN; + } else { + *attached = true; + + cable_type = info->prev_cable_type = adc; + } + break; + case MAX77693_CABLE_GROUP_ADC_GND: + /* + * Read ADC value to check cable type and decide cable state + * according to cable type + */ + adc = info->status[0] & STATUS1_ADC_MASK; + adc >>= STATUS1_ADC_SHIFT; - if (attached) { + /* + * Check current cable state/cable type and store cable type + * (info->prev_cable_type/_gnd) for handling cable when cable + * is detached. + */ + if (adc == MAX77693_MUIC_ADC_OPEN) { + *attached = false; + + cable_type = info->prev_cable_type_gnd; + info->prev_cable_type_gnd = MAX77693_MUIC_ADC_OPEN; + } else { + *attached = true; + + adclow = info->status[0] & STATUS1_ADCLOW_MASK; + adclow >>= STATUS1_ADCLOW_SHIFT; + adc1k = info->status[0] & STATUS1_ADC1K_MASK; + adc1k >>= STATUS1_ADC1K_SHIFT; + + vbvolt = info->status[1] & STATUS2_VBVOLT_MASK; + vbvolt >>= STATUS2_VBVOLT_SHIFT; + + /** + * [0x1][VBVolt][ADCLow][ADC1K] + * [0x1 0 0 0 ] : USB_OTG + * [0x1 0 1 0 ] : Audio Video Cable with load + * [0x1 0 1 1 ] : MHL without charging connector + * [0x1 1 1 1 ] : MHL with charging connector + */ + cable_type = ((0x1 << 8) + | (vbvolt << 2) + | (adclow << 1) + | adc1k); + + info->prev_cable_type = adc; + info->prev_cable_type_gnd = cable_type; + } + + break; + case MAX77693_CABLE_GROUP_CHG: + /* + * Read charger type to check cable type and decide cable state + * according to type of charger cable. + */ + chg_type = info->status[1] & STATUS2_CHGTYP_MASK; + chg_type >>= STATUS2_CHGTYP_SHIFT; + + if (chg_type == MAX77693_CHARGER_TYPE_NONE) { + *attached = false; + + cable_type = info->prev_chg_type; + info->prev_chg_type = MAX77693_CHARGER_TYPE_NONE; + } else { + *attached = true; + + /* + * Check current cable state/cable type and store cable + * type(info->prev_chg_type) for handling cable when + * charger cable is detached. + */ + cable_type = info->prev_chg_type = chg_type; + } + + break; + case MAX77693_CABLE_GROUP_VBVOLT: + /* + * Read ADC value to check cable type and decide cable state + * according to cable type + */ adc = info->status[0] & STATUS1_ADC_MASK; - adclow = info->status[0] & STATUS1_ADCLOW_MASK; - adclow >>= STATUS1_ADCLOW_SHIFT; - adc1k = info->status[0] & STATUS1_ADC1K_MASK; - adc1k >>= STATUS1_ADC1K_SHIFT; - - /** - * [0x1][ADCLow][ADC1K] - * [0x1 0 0 ] : USB_OTG - * [0x1 1 0 ] : Audio Video Cable with load - * [0x1 1 1 ] : MHL + adc >>= STATUS1_ADC_SHIFT; + chg_type = info->status[1] & STATUS2_CHGTYP_MASK; + chg_type >>= STATUS2_CHGTYP_SHIFT; + + if (adc == MAX77693_MUIC_ADC_OPEN + && chg_type == MAX77693_CHARGER_TYPE_NONE) + *attached = false; + else + *attached = true; + + /* + * Read vbvolt field, if vbvolt is 1, + * this cable is used for charging. */ - type = ((0x1 << 8) | (adclow << 1) | adc1k); + vbvolt = info->status[1] & STATUS2_VBVOLT_MASK; + vbvolt >>= STATUS2_VBVOLT_SHIFT; + + cable_type = vbvolt; + break; + default: + dev_err(info->dev, "Unknown cable group (%d)\n", group); + cable_type = -EINVAL; + break; + } + + return cable_type; +} + +static int max77693_muic_adc_ground_handler(struct max77693_muic_info *info) +{ + int cable_type_gnd; + int ret = 0; + bool attached; - /* Store previous ADC value to handle accessory - when accessory will be detached */ - info->prev_adc = adc; - info->prev_adc_gnd = type; - } else - type = info->prev_adc_gnd; + cable_type_gnd = max77693_muic_get_cable_type(info, + MAX77693_CABLE_GROUP_ADC_GND, &attached); - switch (type) { + switch (cable_type_gnd) { case MAX77693_MUIC_GND_USB_OTG: /* USB_OTG */ ret = max77693_muic_set_path(info, CONTROL1_SW_USB, attached); @@ -352,8 +435,6 @@ static int max77693_muic_adc_ground_handler(struct max77693_muic_info *info, default: dev_err(info->dev, "failed to detect %s accessory\n", attached ? "attached" : "detached"); - dev_err(info->dev, "- adc:0x%x, adclow:0x%x, adc1k:0x%x\n", - adc, adclow, adc1k); ret = -EINVAL; break; } @@ -362,45 +443,36 @@ out: return ret; } -static int max77693_muic_adc_handler(struct max77693_muic_info *info, - int curr_adc, bool attached) +static int max77693_muic_adc_handler(struct max77693_muic_info *info) { + int cable_type; + bool attached; int ret = 0; - int adc; - if (attached) { - /* Store ADC value to handle accessory - when accessory will be detached */ - info->prev_adc = curr_adc; - adc = curr_adc; - } else - adc = info->prev_adc; + /* Check accessory state which is either detached or attached */ + cable_type = max77693_muic_get_cable_type(info, + MAX77693_CABLE_GROUP_ADC, &attached); dev_info(info->dev, "external connector is %s (adc:0x%02x, prev_adc:0x%x)\n", - attached ? "attached" : "detached", curr_adc, info->prev_adc); + attached ? "attached" : "detached", cable_type, + info->prev_cable_type); - switch (adc) { + switch (cable_type) { case MAX77693_MUIC_ADC_GROUND: /* USB_OTG/MHL/Audio */ - max77693_muic_adc_ground_handler(info, attached); + max77693_muic_adc_ground_handler(info); break; case MAX77693_MUIC_ADC_FACTORY_MODE_USB_OFF: case MAX77693_MUIC_ADC_FACTORY_MODE_USB_ON: - /* USB */ - ret = max77693_muic_set_path(info, CONTROL1_SW_USB, attached); - if (ret < 0) - goto out; - extcon_set_cable_state(info->edev, "USB", attached); - break; case MAX77693_MUIC_ADC_FACTORY_MODE_UART_OFF: - case MAX77693_MUIC_ADC_FACTORY_MODE_UART_ON: /* JIG */ ret = max77693_muic_set_path(info, CONTROL1_SW_UART, attached); if (ret < 0) goto out; extcon_set_cable_state(info->edev, "JIG", attached); break; + case MAX77693_MUIC_ADC_FACTORY_MODE_UART_ON: case MAX77693_MUIC_ADC_AUDIO_MODE_REMOTE: /* Audio Video cable with no-load */ ret = max77693_muic_set_path(info, CONTROL1_SW_AUDIO, attached); @@ -439,12 +511,12 @@ static int max77693_muic_adc_handler(struct max77693_muic_info *info, proper operation when this accessory is attached/detached. */ dev_info(info->dev, "accessory is %s but it isn't used (adc:0x%x)\n", - attached ? "attached" : "detached", adc); + attached ? "attached" : "detached", cable_type); goto out; default: dev_err(info->dev, "failed to detect %s accessory (adc:0x%x)\n", - attached ? "attached" : "detached", adc); + attached ? "attached" : "detached", cable_type); ret = -EINVAL; goto out; } @@ -453,24 +525,19 @@ out: return ret; } -static int max77693_muic_chg_handler(struct max77693_muic_info *info, - int curr_chg_type, bool attached) +static int max77693_muic_chg_handler(struct max77693_muic_info *info) { - int ret = 0; int chg_type; + bool attached; + int ret = 0; - if (attached) { - /* Store previous charger type to control - when charger accessory will be detached */ - info->prev_chg_type = curr_chg_type; - chg_type = curr_chg_type; - } else - chg_type = info->prev_chg_type; + chg_type = max77693_muic_get_cable_type(info, + MAX77693_CABLE_GROUP_CHG, &attached); dev_info(info->dev, "external connector is %s(chg_type:0x%x, prev_chg_type:0x%x)\n", attached ? "attached" : "detached", - curr_chg_type, info->prev_chg_type); + chg_type, info->prev_chg_type); switch (chg_type) { case MAX77693_CHARGER_TYPE_USB: @@ -510,10 +577,8 @@ static void max77693_muic_irq_work(struct work_struct *work) { struct max77693_muic_info *info = container_of(work, struct max77693_muic_info, irq_work); - int curr_adc, curr_chg_type; int irq_type = -1; int i, ret = 0; - bool attached = true; if (!info->edev) return; @@ -539,14 +604,7 @@ static void max77693_muic_irq_work(struct work_struct *work) case MAX77693_MUIC_IRQ_INT1_ADC1K: /* Handle all of accessory except for type of charger accessory */ - curr_adc = info->status[0] & STATUS1_ADC_MASK; - curr_adc >>= STATUS1_ADC_SHIFT; - - /* Check accessory state which is either detached or attached */ - if (curr_adc == MAX77693_MUIC_ADC_OPEN) - attached = false; - - ret = max77693_muic_adc_handler(info, curr_adc, attached); + ret = max77693_muic_adc_handler(info); break; case MAX77693_MUIC_IRQ_INT2_CHGTYP: case MAX77693_MUIC_IRQ_INT2_CHGDETREUN: @@ -555,15 +613,7 @@ static void max77693_muic_irq_work(struct work_struct *work) case MAX77693_MUIC_IRQ_INT2_VBVOLT: case MAX77693_MUIC_IRQ_INT2_VIDRM: /* Handle charger accessory */ - curr_chg_type = info->status[1] & STATUS2_CHGTYP_MASK; - curr_chg_type >>= STATUS2_CHGTYP_SHIFT; - - /* Check charger accessory state which - is either detached or attached */ - if (curr_chg_type == MAX77693_CHARGER_TYPE_NONE) - attached = false; - - ret = max77693_muic_chg_handler(info, curr_chg_type, attached); + ret = max77693_muic_chg_handler(info); break; case MAX77693_MUIC_IRQ_INT3_EOC: case MAX77693_MUIC_IRQ_INT3_CGMBC: @@ -604,7 +654,9 @@ static struct regmap_config max77693_muic_regmap_config = { static int max77693_muic_detect_accessory(struct max77693_muic_info *info) { int ret = 0; - int adc, chg_type; + int adc; + int chg_type; + bool attached; mutex_lock(&info->mutex); @@ -617,34 +669,24 @@ static int max77693_muic_detect_accessory(struct max77693_muic_info *info) return -EINVAL; } - adc = info->status[0] & STATUS1_ADC_MASK; - adc >>= STATUS1_ADC_SHIFT; - - if (adc != MAX77693_MUIC_ADC_OPEN) { - dev_info(info->dev, - "external connector is attached (adc:0x%02x)\n", adc); - - ret = max77693_muic_adc_handler(info, adc, true); + adc = max77693_muic_get_cable_type(info, MAX77693_CABLE_GROUP_ADC, + &attached); + if (attached && adc != MAX77693_MUIC_ADC_OPEN) { + ret = max77693_muic_adc_handler(info); if (ret < 0) - dev_err(info->dev, "failed to detect accessory\n"); - goto out; + dev_err(info->dev, "Cannot detect accessory\n"); } - chg_type = info->status[1] & STATUS2_CHGTYP_MASK; - chg_type >>= STATUS2_CHGTYP_SHIFT; - - if (chg_type != MAX77693_CHARGER_TYPE_NONE) { - dev_info(info->dev, - "external connector is attached (chg_type:0x%x)\n", - chg_type); - - max77693_muic_chg_handler(info, chg_type, true); + chg_type = max77693_muic_get_cable_type(info, MAX77693_CABLE_GROUP_CHG, + &attached); + if (attached && chg_type != MAX77693_CHARGER_TYPE_NONE) { + ret = max77693_muic_chg_handler(info); if (ret < 0) - dev_err(info->dev, "failed to detect charger accessory\n"); + dev_err(info->dev, "Cannot detect charger accessory\n"); } -out: mutex_unlock(&info->mutex); + return ret; } diff --git a/include/linux/mfd/max77693-private.h b/include/linux/mfd/max77693-private.h index 1eeae5c07915..5b18ecde69b5 100644 --- a/include/linux/mfd/max77693-private.h +++ b/include/linux/mfd/max77693-private.h @@ -106,6 +106,92 @@ enum max77693_muic_reg { MAX77693_MUIC_REG_END, }; +/* MAX77693 MUIC - STATUS1~3 Register */ +#define STATUS1_ADC_SHIFT (0) +#define STATUS1_ADCLOW_SHIFT (5) +#define STATUS1_ADCERR_SHIFT (6) +#define STATUS1_ADC1K_SHIFT (7) +#define STATUS1_ADC_MASK (0x1f << STATUS1_ADC_SHIFT) +#define STATUS1_ADCLOW_MASK (0x1 << STATUS1_ADCLOW_SHIFT) +#define STATUS1_ADCERR_MASK (0x1 << STATUS1_ADCERR_SHIFT) +#define STATUS1_ADC1K_MASK (0x1 << STATUS1_ADC1K_SHIFT) + +#define STATUS2_CHGTYP_SHIFT (0) +#define STATUS2_CHGDETRUN_SHIFT (3) +#define STATUS2_DCDTMR_SHIFT (4) +#define STATUS2_DXOVP_SHIFT (5) +#define STATUS2_VBVOLT_SHIFT (6) +#define STATUS2_VIDRM_SHIFT (7) +#define STATUS2_CHGTYP_MASK (0x7 << STATUS2_CHGTYP_SHIFT) +#define STATUS2_CHGDETRUN_MASK (0x1 << STATUS2_CHGDETRUN_SHIFT) +#define STATUS2_DCDTMR_MASK (0x1 << STATUS2_DCDTMR_SHIFT) +#define STATUS2_DXOVP_MASK (0x1 << STATUS2_DXOVP_SHIFT) +#define STATUS2_VBVOLT_MASK (0x1 << STATUS2_VBVOLT_SHIFT) +#define STATUS2_VIDRM_MASK (0x1 << STATUS2_VIDRM_SHIFT) + +#define STATUS3_OVP_SHIFT (2) +#define STATUS3_OVP_MASK (0x1 << STATUS3_OVP_SHIFT) + +/* MAX77693 CDETCTRL1~2 register */ +#define CDETCTRL1_CHGDETEN_SHIFT (0) +#define CDETCTRL1_CHGTYPMAN_SHIFT (1) +#define CDETCTRL1_DCDEN_SHIFT (2) +#define CDETCTRL1_DCD2SCT_SHIFT (3) +#define CDETCTRL1_CDDELAY_SHIFT (4) +#define CDETCTRL1_DCDCPL_SHIFT (5) +#define CDETCTRL1_CDPDET_SHIFT (7) +#define CDETCTRL1_CHGDETEN_MASK (0x1 << CDETCTRL1_CHGDETEN_SHIFT) +#define CDETCTRL1_CHGTYPMAN_MASK (0x1 << CDETCTRL1_CHGTYPMAN_SHIFT) +#define CDETCTRL1_DCDEN_MASK (0x1 << CDETCTRL1_DCDEN_SHIFT) +#define CDETCTRL1_DCD2SCT_MASK (0x1 << CDETCTRL1_DCD2SCT_SHIFT) +#define CDETCTRL1_CDDELAY_MASK (0x1 << CDETCTRL1_CDDELAY_SHIFT) +#define CDETCTRL1_DCDCPL_MASK (0x1 << CDETCTRL1_DCDCPL_SHIFT) +#define CDETCTRL1_CDPDET_MASK (0x1 << CDETCTRL1_CDPDET_SHIFT) + +#define CDETCTRL2_VIDRMEN_SHIFT (1) +#define CDETCTRL2_DXOVPEN_SHIFT (3) +#define CDETCTRL2_VIDRMEN_MASK (0x1 << CDETCTRL2_VIDRMEN_SHIFT) +#define CDETCTRL2_DXOVPEN_MASK (0x1 << CDETCTRL2_DXOVPEN_SHIFT) + +/* MAX77693 MUIC - CONTROL1~3 register */ +#define COMN1SW_SHIFT (0) +#define COMP2SW_SHIFT (3) +#define COMN1SW_MASK (0x7 << COMN1SW_SHIFT) +#define COMP2SW_MASK (0x7 << COMP2SW_SHIFT) +#define COMP_SW_MASK (COMP2SW_MASK | COMN1SW_MASK) +#define CONTROL1_SW_USB ((1 << COMP2SW_SHIFT) \ + | (1 << COMN1SW_SHIFT)) +#define CONTROL1_SW_AUDIO ((2 << COMP2SW_SHIFT) \ + | (2 << COMN1SW_SHIFT)) +#define CONTROL1_SW_UART ((3 << COMP2SW_SHIFT) \ + | (3 << COMN1SW_SHIFT)) +#define CONTROL1_SW_OPEN ((0 << COMP2SW_SHIFT) \ + | (0 << COMN1SW_SHIFT)) + +#define CONTROL2_LOWPWR_SHIFT (0) +#define CONTROL2_ADCEN_SHIFT (1) +#define CONTROL2_CPEN_SHIFT (2) +#define CONTROL2_SFOUTASRT_SHIFT (3) +#define CONTROL2_SFOUTORD_SHIFT (4) +#define CONTROL2_ACCDET_SHIFT (5) +#define CONTROL2_USBCPINT_SHIFT (6) +#define CONTROL2_RCPS_SHIFT (7) +#define CONTROL2_LOWPWR_MASK (0x1 << CONTROL2_LOWPWR_SHIFT) +#define CONTROL2_ADCEN_MASK (0x1 << CONTROL2_ADCEN_SHIFT) +#define CONTROL2_CPEN_MASK (0x1 << CONTROL2_CPEN_SHIFT) +#define CONTROL2_SFOUTASRT_MASK (0x1 << CONTROL2_SFOUTASRT_SHIFT) +#define CONTROL2_SFOUTORD_MASK (0x1 << CONTROL2_SFOUTORD_SHIFT) +#define CONTROL2_ACCDET_MASK (0x1 << CONTROL2_ACCDET_SHIFT) +#define CONTROL2_USBCPINT_MASK (0x1 << CONTROL2_USBCPINT_SHIFT) +#define CONTROL2_RCPS_MASK (0x1 << CONTROL2_RCPS_SHIFT) + +#define CONTROL3_JIGSET_SHIFT (0) +#define CONTROL3_BTLDSET_SHIFT (2) +#define CONTROL3_ADCDBSET_SHIFT (4) +#define CONTROL3_JIGSET_MASK (0x3 << CONTROL3_JIGSET_SHIFT) +#define CONTROL3_BTLDSET_MASK (0x3 << CONTROL3_BTLDSET_SHIFT) +#define CONTROL3_ADCDBSET_MASK (0x3 << CONTROL3_ADCDBSET_SHIFT) + /* Slave addr = 0x90: Haptic */ enum max77693_haptic_reg { MAX77693_HAPTIC_REG_STATUS = 0x00, -- cgit v1.2.3 From 297620fd1e14edf5fefa1736f873b9228336eee1 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Wed, 26 Dec 2012 13:10:11 +0900 Subject: extcon: max77693: Check the state/type of cable after boot completed This patch check the state/type of cable after completing the initialization of platform and notify platform of cable state/type through extcon. If extcon provider driver notify the state/type of cable before completing platform boot, this uevent is unused and ignored. Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-max77693.c | 37 ++++++++++++++++++++++++++++++++++--- include/linux/mfd/max77693.h | 2 ++ 2 files changed, 36 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index 07ea96bfd0cb..10f41f3d5be4 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -30,6 +30,7 @@ #include #define DEV_NAME "max77693-muic" +#define DELAY_MS_DEFAULT 20000 /* unit: millisecond */ enum max77693_muic_adc_debounce_time { ADC_DEBOUNCE_TIME_5MS = 0, @@ -52,6 +53,14 @@ struct max77693_muic_info { struct work_struct irq_work; struct mutex mutex; + /* + * Use delayed workqueue to detect cable state and then + * notify cable state to notifiee/platform through uevent. + * After completing the booting of platform, the extcon provider + * driver should notify cable state to upper layer. + */ + struct delayed_work wq_detcable; + /* Button of dock device */ struct input_dev *dock; }; @@ -912,13 +921,23 @@ static int max77693_muic_detect_accessory(struct max77693_muic_info *info) return ret; } +static void max77693_muic_detect_cable_wq(struct work_struct *work) +{ + struct max77693_muic_info *info = container_of(to_delayed_work(work), + struct max77693_muic_info, wq_detcable); + + max77693_muic_detect_accessory(info); +} + static int max77693_muic_probe(struct platform_device *pdev) { struct max77693_dev *max77693 = dev_get_drvdata(pdev->dev.parent); struct max77693_platform_data *pdata = dev_get_platdata(max77693->dev); struct max77693_muic_platform_data *muic_pdata = pdata->muic_data; struct max77693_muic_info *info; - int ret, i; + int delay_jiffies; + int ret; + int i; u8 id; info = devm_kzalloc(&pdev->dev, sizeof(struct max77693_muic_info), @@ -1051,8 +1070,20 @@ static int max77693_muic_probe(struct platform_device *pdev) /* Set ADC debounce time */ max77693_muic_set_debounce_time(info, ADC_DEBOUNCE_TIME_25MS); - /* Detect accessory on boot */ - max77693_muic_detect_accessory(info); + /* + * Detect accessory after completing the initialization of platform + * + * - Use delayed workqueue to detect cable state and then + * notify cable state to notifiee/platform through uevent. + * After completing the booting of platform, the extcon provider + * driver should notify cable state to upper layer. + */ + INIT_DELAYED_WORK(&info->wq_detcable, max77693_muic_detect_cable_wq); + if (muic_pdata->detcable_delay_ms) + delay_jiffies = msecs_to_jiffies(muic_pdata->detcable_delay_ms); + else + delay_jiffies = msecs_to_jiffies(DELAY_MS_DEFAULT); + schedule_delayed_work(&info->wq_detcable, delay_jiffies); return ret; diff --git a/include/linux/mfd/max77693.h b/include/linux/mfd/max77693.h index fe03b2d35d4f..9aa9c203f28b 100644 --- a/include/linux/mfd/max77693.h +++ b/include/linux/mfd/max77693.h @@ -38,6 +38,8 @@ struct max77693_reg_data { struct max77693_muic_platform_data { struct max77693_reg_data *init_data; int num_init_data; + + int detcable_delay_ms; }; struct max77693_platform_data { -- cgit v1.2.3 From 2b75799f5ae9f31ea9778003591fd295d3bc3159 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Thu, 6 Dec 2012 21:27:56 +0900 Subject: extcon: max77693: Set default uart/usb path by using platform data This patch determine default uart/usb path by using platform data. The MAX77693 MUIC device can possibliy set USB/UART/AUDIO/USB_AUX /UART_AUX to internal h/w path of MUIC device. So, drvier should determine default uart/usb path. Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-max77693.c | 24 ++++++++++++++++++++++++ include/linux/mfd/max77693.h | 7 +++++++ 2 files changed, 31 insertions(+) (limited to 'include') diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index 7b7f1a2a0846..abab068adc35 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -63,6 +63,13 @@ struct max77693_muic_info { /* Button of dock device */ struct input_dev *dock; + + /* + * Default usb/uart path whether UART/USB or AUX_UART/AUX_USB + * h/w path of COMP2/COMN1 on CONTROL1 register. + */ + int path_usb; + int path_uart; }; enum max77693_muic_cable_group { @@ -1058,6 +1065,23 @@ static int max77693_muic_probe(struct platform_device *pdev) = muic_pdata->init_data[i].data; } + /* + * Default usb/uart path whether UART/USB or AUX_UART/AUX_USB + * h/w path of COMP2/COMN1 on CONTROL1 register. + */ + if (muic_pdata->path_uart) + info->path_uart = muic_pdata->path_uart; + else + info->path_uart = CONTROL1_SW_UART; + + if (muic_pdata->path_usb) + info->path_usb = muic_pdata->path_usb; + else + info->path_usb = CONTROL1_SW_USB; + + /* Set initial path for UART */ + max77693_muic_set_path(info, info->path_uart, true); + /* Check revision number of MUIC device*/ ret = max77693_read_reg(info->max77693->regmap_muic, MAX77693_MUIC_REG_ID, &id); diff --git a/include/linux/mfd/max77693.h b/include/linux/mfd/max77693.h index 9aa9c203f28b..3109a6c5c948 100644 --- a/include/linux/mfd/max77693.h +++ b/include/linux/mfd/max77693.h @@ -40,6 +40,13 @@ struct max77693_muic_platform_data { int num_init_data; int detcable_delay_ms; + + /* + * Default usb/uart path whether UART/USB or AUX_UART/AUX_USB + * h/w path of COMP2/COMN1 on CONTROL1 register. + */ + int path_usb; + int path_uart; }; struct max77693_platform_data { -- cgit v1.2.3 From b17e54625cff1b06aec9df505b46c9bcdcd4823d Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 11 Jan 2013 08:55:24 +0900 Subject: extcon: arizona: Allow configuration of MICBIAS rise time Allow configuration of the rise time for MICBIAS via platform data, the delay required depends on things like the external component selection. Signed-off-by: Mark Brown Signed-off-by: Chanwoo Choi Acked-by: MyungJoo Ham --- drivers/extcon/extcon-arizona.c | 6 ++++++ include/linux/mfd/arizona/pdata.h | 3 +++ 2 files changed, 9 insertions(+) (limited to 'include') diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 9e1d104803e7..635b7078ce5e 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -417,6 +417,12 @@ static int arizona_extcon_probe(struct platform_device *pdev) } } + if (arizona->pdata.micd_bias_start_time) + regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_1, + ARIZONA_MICD_BIAS_STARTTIME_MASK, + arizona->pdata.micd_bias_start_time + << ARIZONA_MICD_BIAS_STARTTIME_SHIFT); + arizona_extcon_set_mode(info, 0); info->input = devm_input_allocate_device(&pdev->dev); diff --git a/include/linux/mfd/arizona/pdata.h b/include/linux/mfd/arizona/pdata.h index 8b1d1daaae16..5b0508853290 100644 --- a/include/linux/mfd/arizona/pdata.h +++ b/include/linux/mfd/arizona/pdata.h @@ -99,6 +99,9 @@ struct arizona_pdata { /** GPIO for mic detection polarity */ int micd_pol_gpio; + /** Mic detect ramp rate */ + int micd_bias_start_time; + /** Headset polarity configurations */ struct arizona_micd_config *micd_configs; int num_micd_configs; -- cgit v1.2.3 From dab63eb25ced7539a51b8f4218f7b6b56d34df22 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 11 Jan 2013 08:55:36 +0900 Subject: extcon: arizona: Use microphone clamp function if available Newer Arizona devices include a microphone clamp function which is tied to jack detect. Activate this feature when present in order to ensure best performance of the subsystem. Signed-off-by: Mark Brown Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-arizona.c | 19 +++++++++++++++++++ include/linux/mfd/arizona/core.h | 4 +++- include/linux/mfd/arizona/registers.h | 28 ++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 635b7078ce5e..3ef3bee7d1e6 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -45,6 +45,7 @@ struct arizona_extcon_info { int micd_num_modes; bool micd_reva; + bool micd_clamp; bool mic; bool detecting; @@ -375,6 +376,7 @@ static int arizona_extcon_probe(struct platform_device *pdev) info->micd_reva = true; break; default: + info->micd_clamp = true; break; } break; @@ -423,6 +425,19 @@ static int arizona_extcon_probe(struct platform_device *pdev) arizona->pdata.micd_bias_start_time << ARIZONA_MICD_BIAS_STARTTIME_SHIFT); + /* + * If we have a clamp use it. + */ + if (info->micd_clamp) { + regmap_update_bits(arizona->regmap, + ARIZONA_MICD_CLAMP_CONTROL, + ARIZONA_MICD_CLAMP_MODE_MASK, 4); + regmap_update_bits(arizona->regmap, + ARIZONA_JACK_DETECT_DEBOUNCE, + ARIZONA_MICD_CLAMP_DB, + ARIZONA_MICD_CLAMP_DB); + } + arizona_extcon_set_mode(info, 0); info->input = devm_input_allocate_device(&pdev->dev); @@ -529,6 +544,10 @@ static int arizona_extcon_remove(struct platform_device *pdev) pm_runtime_disable(&pdev->dev); + regmap_update_bits(arizona->regmap, + ARIZONA_MICD_CLAMP_CONTROL, + ARIZONA_MICD_CLAMP_MODE_MASK, 0); + arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_RISE, 0); arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_FALL, 0); arizona_free_irq(arizona, ARIZONA_IRQ_MICDET, info); diff --git a/include/linux/mfd/arizona/core.h b/include/linux/mfd/arizona/core.h index a580363a7d29..a710255528d7 100644 --- a/include/linux/mfd/arizona/core.h +++ b/include/linux/mfd/arizona/core.h @@ -75,8 +75,10 @@ enum arizona_type { #define ARIZONA_IRQ_DCS_HP_DONE 47 #define ARIZONA_IRQ_FLL2_CLOCK_OK 48 #define ARIZONA_IRQ_FLL1_CLOCK_OK 49 +#define ARIZONA_IRQ_MICD_CLAMP_RISE 50 +#define ARIZONA_IRQ_MICD_CLAMP_FALL 51 -#define ARIZONA_NUM_IRQ 50 +#define ARIZONA_NUM_IRQ 52 struct snd_soc_dapm_context; diff --git a/include/linux/mfd/arizona/registers.h b/include/linux/mfd/arizona/registers.h index 1f6fe31a4d5c..f3211f06cec6 100644 --- a/include/linux/mfd/arizona/registers.h +++ b/include/linux/mfd/arizona/registers.h @@ -119,6 +119,7 @@ #define ARIZONA_ACCESSORY_DETECT_MODE_1 0x293 #define ARIZONA_HEADPHONE_DETECT_1 0x29B #define ARIZONA_HEADPHONE_DETECT_2 0x29C +#define ARIZONA_MICD_CLAMP_CONTROL 0x2A2 #define ARIZONA_MIC_DETECT_1 0x2A3 #define ARIZONA_MIC_DETECT_2 0x2A4 #define ARIZONA_MIC_DETECT_3 0x2A5 @@ -2069,6 +2070,13 @@ #define ARIZONA_HP_LVL_SHIFT 0 /* HP_LVL - [6:0] */ #define ARIZONA_HP_LVL_WIDTH 7 /* HP_LVL - [6:0] */ +/* + * R674 (0x2A2) - MICD clamp control + */ +#define ARIZONA_MICD_CLAMP_MODE_MASK 0x000F /* MICD_CLAMP_MODE - [3:0] */ +#define ARIZONA_MICD_CLAMP_MODE_SHIFT 0 /* MICD_CLAMP_MODE - [3:0] */ +#define ARIZONA_MICD_CLAMP_MODE_WIDTH 4 /* MICD_CLAMP_MODE - [3:0] */ + /* * R675 (0x2A3) - Mic Detect 1 */ @@ -5267,6 +5275,12 @@ /* * R3409 (0xD51) - AOD IRQ1 */ +#define ARIZONA_MICD_CLAMP_FALL_EINT1 0x0080 /* MICD_CLAMP_FALL_EINT1 */ +#define ARIZONA_MICD_CLAMP_FALL_EINT1_MASK 0x0080 /* MICD_CLAMP_FALL_EINT1 */ +#define ARIZONA_MICD_CLAMP_FALL_EINT1_SHIFT 7 /* MICD_CLAMP_FALL_EINT1 */ +#define ARIZONA_MICD_CLAMP_RISE_EINT1 0x0040 /* MICD_CLAMP_RISE_EINT1 */ +#define ARIZONA_MICD_CLAMP_RISE_EINT1_MASK 0x0040 /* MICD_CLAMP_RISE_EINT1 */ +#define ARIZONA_MICD_CLAMP_RISE_EINT1_SHIFT 6 /* MICD_CLAMP_RISE_EINT1 */ #define ARIZONA_GP5_FALL_EINT1 0x0020 /* GP5_FALL_EINT1 */ #define ARIZONA_GP5_FALL_EINT1_MASK 0x0020 /* GP5_FALL_EINT1 */ #define ARIZONA_GP5_FALL_EINT1_SHIFT 5 /* GP5_FALL_EINT1 */ @@ -5295,6 +5309,12 @@ /* * R3410 (0xD52) - AOD IRQ2 */ +#define ARIZONA_MICD_CLAMP_FALL_EINT2 0x0080 /* MICD_CLAMP_FALL_EINT2 */ +#define ARIZONA_MICD_CLAMP_FALL_EINT2_MASK 0x0080 /* MICD_CLAMP_FALL_EINT2 */ +#define ARIZONA_MICD_CLAMP_FALL_EINT2_SHIFT 7 /* MICD_CLAMP_FALL_EINT2 */ +#define ARIZONA_MICD_CLAMP_RISE_EINT2 0x0040 /* MICD_CLAMP_RISE_EINT2 */ +#define ARIZONA_MICD_CLAMP_RISE_EINT2_MASK 0x0040 /* MICD_CLAMP_RISE_EINT2 */ +#define ARIZONA_MICD_CLAMP_RISE_EINT2_SHIFT 6 /* MICD_CLAMP_RISE_EINT2 */ #define ARIZONA_GP5_FALL_EINT2 0x0020 /* GP5_FALL_EINT2 */ #define ARIZONA_GP5_FALL_EINT2_MASK 0x0020 /* GP5_FALL_EINT2 */ #define ARIZONA_GP5_FALL_EINT2_SHIFT 5 /* GP5_FALL_EINT2 */ @@ -5379,6 +5399,10 @@ /* * R3413 (0xD55) - AOD IRQ Raw Status */ +#define ARIZONA_MICD_CLAMP_STS 0x0008 /* MICD_CLAMP_STS */ +#define ARIZONA_MICD_CLAMP_STS_MASK 0x0008 /* MICD_CLAMP_STS */ +#define ARIZONA_MICD_CLAMP_STS_SHIFT 3 /* MICD_CLAMP_STS */ +#define ARIZONA_MICD_CLAMP_STS_WIDTH 1 /* MICD_CLAMP_STS */ #define ARIZONA_GP5_STS 0x0004 /* GP5_STS */ #define ARIZONA_GP5_STS_MASK 0x0004 /* GP5_STS */ #define ARIZONA_GP5_STS_SHIFT 2 /* GP5_STS */ @@ -5395,6 +5419,10 @@ /* * R3414 (0xD56) - Jack detect debounce */ +#define ARIZONA_MICD_CLAMP_DB 0x0008 /* MICD_CLAMP_DB */ +#define ARIZONA_MICD_CLAMP_DB_MASK 0x0008 /* MICD_CLAMP_DB */ +#define ARIZONA_MICD_CLAMP_DB_SHIFT 3 /* MICD_CLAMP_DB */ +#define ARIZONA_MICD_CLAMP_DB_WIDTH 1 /* MICD_CLAMP_DB */ #define ARIZONA_JD2_DB 0x0002 /* JD2_DB */ #define ARIZONA_JD2_DB_MASK 0x0002 /* JD2_DB */ #define ARIZONA_JD2_DB_SHIFT 1 /* JD2_DB */ -- cgit v1.2.3 From 92a49871b378b1e523a95da569cd38efdd06eee5 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 11 Jan 2013 08:55:39 +0900 Subject: extcon: arizona: Support use of GPIO5 as an input to jack detection Some system designs provide an input on GPIO5 which in conjunction with the jack detection feature indicates the presence of an accessory. Support such systems, using the microphone clamp feature to minimise wakeups of the processor. Signed-off-by: Mark Brown Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-arizona.c | 76 +++++++++++++++++++++++++++++---------- include/linux/mfd/arizona/pdata.h | 3 ++ 2 files changed, 61 insertions(+), 18 deletions(-) (limited to 'include') diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 3ef3bee7d1e6..b5190a811601 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -290,13 +290,21 @@ static irqreturn_t arizona_jackdet(int irq, void *data) { struct arizona_extcon_info *info = data; struct arizona *arizona = info->arizona; - unsigned int val; + unsigned int val, present, mask; int ret, i; pm_runtime_get_sync(info->dev); mutex_lock(&info->lock); + if (arizona->pdata.jd_gpio5) { + mask = ARIZONA_MICD_CLAMP_STS; + present = 0; + } else { + mask = ARIZONA_JD1_STS; + present = ARIZONA_JD1_STS; + } + ret = regmap_read(arizona->regmap, ARIZONA_AOD_IRQ_RAW_STATUS, &val); if (ret != 0) { dev_err(arizona->dev, "Failed to read jackdet status: %d\n", @@ -306,7 +314,7 @@ static irqreturn_t arizona_jackdet(int irq, void *data) return IRQ_NONE; } - if (val & ARIZONA_JD1_STS) { + if ((val & mask) == present) { dev_dbg(arizona->dev, "Detected jack\n"); ret = extcon_set_cable_state_(&info->edev, ARIZONA_CABLE_MECHANICAL, true); @@ -321,6 +329,7 @@ static irqreturn_t arizona_jackdet(int irq, void *data) arizona_stop_mic(info); + for (i = 0; i < ARIZONA_NUM_BUTTONS; i++) input_report_key(info->input, arizona_lvl_to_key[i].report, 0); @@ -345,6 +354,7 @@ static int arizona_extcon_probe(struct platform_device *pdev) struct arizona *arizona = dev_get_drvdata(pdev->dev.parent); struct arizona_pdata *pdata; struct arizona_extcon_info *info; + int jack_irq_fall, jack_irq_rise; int ret, mode, i; pdata = dev_get_platdata(arizona->dev); @@ -426,12 +436,24 @@ static int arizona_extcon_probe(struct platform_device *pdev) << ARIZONA_MICD_BIAS_STARTTIME_SHIFT); /* - * If we have a clamp use it. + * If we have a clamp use it, activating in conjunction with + * GPIO5 if that is connected for jack detect operation. */ if (info->micd_clamp) { - regmap_update_bits(arizona->regmap, - ARIZONA_MICD_CLAMP_CONTROL, - ARIZONA_MICD_CLAMP_MODE_MASK, 4); + if (arizona->pdata.jd_gpio5) { + /* Put the GPIO into input mode */ + regmap_write(arizona->regmap, ARIZONA_GPIO5_CTRL, + 0xc101); + + regmap_update_bits(arizona->regmap, + ARIZONA_MICD_CLAMP_CONTROL, + ARIZONA_MICD_CLAMP_MODE_MASK, 0x9); + } else { + regmap_update_bits(arizona->regmap, + ARIZONA_MICD_CLAMP_CONTROL, + ARIZONA_MICD_CLAMP_MODE_MASK, 0x4); + } + regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_DEBOUNCE, ARIZONA_MICD_CLAMP_DB, @@ -458,7 +480,15 @@ static int arizona_extcon_probe(struct platform_device *pdev) pm_runtime_idle(&pdev->dev); pm_runtime_get_sync(&pdev->dev); - ret = arizona_request_irq(arizona, ARIZONA_IRQ_JD_RISE, + if (arizona->pdata.jd_gpio5) { + jack_irq_rise = ARIZONA_IRQ_MICD_CLAMP_RISE; + jack_irq_fall = ARIZONA_IRQ_MICD_CLAMP_FALL; + } else { + jack_irq_rise = ARIZONA_IRQ_JD_RISE; + jack_irq_fall = ARIZONA_IRQ_JD_FALL; + } + + ret = arizona_request_irq(arizona, jack_irq_rise, "JACKDET rise", arizona_jackdet, info); if (ret != 0) { dev_err(&pdev->dev, "Failed to get JACKDET rise IRQ: %d\n", @@ -466,21 +496,21 @@ static int arizona_extcon_probe(struct platform_device *pdev) goto err_input; } - ret = arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_RISE, 1); + ret = arizona_set_irq_wake(arizona, jack_irq_rise, 1); if (ret != 0) { dev_err(&pdev->dev, "Failed to set JD rise IRQ wake: %d\n", ret); goto err_rise; } - ret = arizona_request_irq(arizona, ARIZONA_IRQ_JD_FALL, + ret = arizona_request_irq(arizona, jack_irq_fall, "JACKDET fall", arizona_jackdet, info); if (ret != 0) { dev_err(&pdev->dev, "Failed to get JD fall IRQ: %d\n", ret); goto err_rise_wake; } - ret = arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_FALL, 1); + ret = arizona_set_irq_wake(arizona, jack_irq_fall, 1); if (ret != 0) { dev_err(&pdev->dev, "Failed to set JD fall IRQ wake: %d\n", ret); @@ -522,13 +552,13 @@ static int arizona_extcon_probe(struct platform_device *pdev) err_micdet: arizona_free_irq(arizona, ARIZONA_IRQ_MICDET, info); err_fall_wake: - arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_FALL, 0); + arizona_set_irq_wake(arizona, jack_irq_fall, 0); err_fall: - arizona_free_irq(arizona, ARIZONA_IRQ_JD_FALL, info); + arizona_free_irq(arizona, jack_irq_fall, info); err_rise_wake: - arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_RISE, 0); + arizona_set_irq_wake(arizona, jack_irq_rise, 0); err_rise: - arizona_free_irq(arizona, ARIZONA_IRQ_JD_RISE, info); + arizona_free_irq(arizona, jack_irq_rise, info); err_input: err_register: pm_runtime_disable(&pdev->dev); @@ -541,6 +571,7 @@ static int arizona_extcon_remove(struct platform_device *pdev) { struct arizona_extcon_info *info = platform_get_drvdata(pdev); struct arizona *arizona = info->arizona; + int jack_irq_rise, jack_irq_fall; pm_runtime_disable(&pdev->dev); @@ -548,11 +579,20 @@ static int arizona_extcon_remove(struct platform_device *pdev) ARIZONA_MICD_CLAMP_CONTROL, ARIZONA_MICD_CLAMP_MODE_MASK, 0); - arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_RISE, 0); - arizona_set_irq_wake(arizona, ARIZONA_IRQ_JD_FALL, 0); + if (arizona->pdata.jd_gpio5) { + jack_irq_rise = ARIZONA_IRQ_MICD_CLAMP_RISE; + jack_irq_fall = ARIZONA_IRQ_MICD_CLAMP_FALL; + } else { + jack_irq_rise = ARIZONA_IRQ_JD_RISE; + jack_irq_fall = ARIZONA_IRQ_JD_FALL; + } + + arizona_set_irq_wake(arizona, jack_irq_rise, 0); + arizona_set_irq_wake(arizona, jack_irq_fall, 0); + arizona_free_irq(arizona, ARIZONA_IRQ_HPDET, info); arizona_free_irq(arizona, ARIZONA_IRQ_MICDET, info); - arizona_free_irq(arizona, ARIZONA_IRQ_JD_RISE, info); - arizona_free_irq(arizona, ARIZONA_IRQ_JD_FALL, info); + arizona_free_irq(arizona, jack_irq_rise, info); + arizona_free_irq(arizona, jack_irq_fall, info); regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_ANALOGUE, ARIZONA_JD1_ENA, 0); arizona_clk32k_disable(arizona); diff --git a/include/linux/mfd/arizona/pdata.h b/include/linux/mfd/arizona/pdata.h index 5b0508853290..0fc26a480be3 100644 --- a/include/linux/mfd/arizona/pdata.h +++ b/include/linux/mfd/arizona/pdata.h @@ -96,6 +96,9 @@ struct arizona_pdata { /** Pin state for GPIO pins */ int gpio_defaults[ARIZONA_MAX_GPIO]; + /** GPIO5 is used for jack detection */ + bool jd_gpio5; + /** GPIO for mic detection polarity */ int micd_pol_gpio; -- cgit v1.2.3 From 4f340333822de79b3439bddccdbf3846f9794e42 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 11 Jan 2013 08:55:43 +0900 Subject: extcon: arizona: Enable basic headphone identification Use the headphone detection to identify if the accessory is a headphone or line load. There are two different revisions of the IP with different register layouts, support both. Signed-off-by: Mark Brown Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-arizona.c | 341 +++++++++++++++++++++++++++++++--- drivers/mfd/wm5102-tables.c | 3 + include/linux/mfd/arizona/registers.h | 12 ++ 3 files changed, 335 insertions(+), 21 deletions(-) (limited to 'include') diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index b5190a811601..cc258a8842ef 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -31,8 +31,14 @@ #include #include +#define ARIZONA_DEFAULT_HP 32 + #define ARIZONA_NUM_BUTTONS 6 +#define ARIZONA_ACCDET_MODE_MIC 0 +#define ARIZONA_ACCDET_MODE_HPL 1 +#define ARIZONA_ACCDET_MODE_HPR 2 + struct arizona_extcon_info { struct device *dev; struct arizona *arizona; @@ -47,10 +53,14 @@ struct arizona_extcon_info { bool micd_reva; bool micd_clamp; + bool hpdet_active; + bool mic; bool detecting; int jack_flips; + int hpdet_ip; + struct extcon_dev edev; }; @@ -74,11 +84,13 @@ static struct { #define ARIZONA_CABLE_MECHANICAL 0 #define ARIZONA_CABLE_MICROPHONE 1 #define ARIZONA_CABLE_HEADPHONE 2 +#define ARIZONA_CABLE_LINEOUT 3 static const char *arizona_cable[] = { "Mechanical", "Microphone", "Headphone", + "Line-out", NULL, }; @@ -100,6 +112,290 @@ static void arizona_extcon_set_mode(struct arizona_extcon_info *info, int mode) dev_dbg(arizona->dev, "Set jack polarity to %d\n", mode); } +static struct { + unsigned int factor_a; + unsigned int factor_b; +} arizona_hpdet_b_ranges[] = { + { 5528, 362464 }, + { 11084, 6186851 }, + { 11065, 65460395 }, +}; + +static struct { + int min; + int max; +} arizona_hpdet_c_ranges[] = { + { 0, 30 }, + { 8, 100 }, + { 100, 1000 }, + { 1000, 10000 }, +}; + +static int arizona_hpdet_read(struct arizona_extcon_info *info) +{ + struct arizona *arizona = info->arizona; + unsigned int val, range; + int ret; + + ret = regmap_read(arizona->regmap, ARIZONA_HEADPHONE_DETECT_2, &val); + if (ret != 0) { + dev_err(arizona->dev, "Failed to read HPDET status: %d\n", + ret); + return ret; + } + + switch (info->hpdet_ip) { + case 0: + if (!(val & ARIZONA_HP_DONE)) { + dev_err(arizona->dev, "HPDET did not complete: %x\n", + val); + val = ARIZONA_DEFAULT_HP; + } + + val &= ARIZONA_HP_LVL_MASK; + break; + + case 1: + if (!(val & ARIZONA_HP_DONE_B)) { + dev_err(arizona->dev, "HPDET did not complete: %x\n", + val); + return ARIZONA_DEFAULT_HP; + } + + ret = regmap_read(arizona->regmap, ARIZONA_HP_DACVAL, &val); + if (ret != 0) { + dev_err(arizona->dev, "Failed to read HP value: %d\n", + ret); + return ARIZONA_DEFAULT_HP; + } + + regmap_read(arizona->regmap, ARIZONA_HEADPHONE_DETECT_1, + &range); + range = (range & ARIZONA_HP_IMPEDANCE_RANGE_MASK) + >> ARIZONA_HP_IMPEDANCE_RANGE_SHIFT; + + if (range < ARRAY_SIZE(arizona_hpdet_b_ranges) - 1 && + (val < 100 || val > 0x3fb)) { + range++; + dev_dbg(arizona->dev, "Moving to HPDET range %d\n", + range); + regmap_update_bits(arizona->regmap, + ARIZONA_HEADPHONE_DETECT_1, + ARIZONA_HP_IMPEDANCE_RANGE_MASK, + range << + ARIZONA_HP_IMPEDANCE_RANGE_SHIFT); + return -EAGAIN; + } + + /* If we go out of range report top of range */ + if (val < 100 || val > 0x3fb) { + dev_dbg(arizona->dev, "Measurement out of range\n"); + return 10000; + } + + dev_dbg(arizona->dev, "HPDET read %d in range %d\n", + val, range); + + val = arizona_hpdet_b_ranges[range].factor_b + / ((val * 100) - + arizona_hpdet_b_ranges[range].factor_a); + break; + + default: + dev_warn(arizona->dev, "Unknown HPDET IP revision %d\n", + info->hpdet_ip); + case 2: + if (!(val & ARIZONA_HP_DONE_B)) { + dev_err(arizona->dev, "HPDET did not complete: %x\n", + val); + return ARIZONA_DEFAULT_HP; + } + + val &= ARIZONA_HP_LVL_B_MASK; + + regmap_read(arizona->regmap, ARIZONA_HEADPHONE_DETECT_1, + &range); + range = (range & ARIZONA_HP_IMPEDANCE_RANGE_MASK) + >> ARIZONA_HP_IMPEDANCE_RANGE_SHIFT; + + /* Skip up or down a range? */ + if (range && (val < arizona_hpdet_c_ranges[range].min)) { + range--; + dev_dbg(arizona->dev, "Moving to HPDET range %d-%d\n", + arizona_hpdet_c_ranges[range].min, + arizona_hpdet_c_ranges[range].max); + regmap_update_bits(arizona->regmap, + ARIZONA_HEADPHONE_DETECT_1, + ARIZONA_HP_IMPEDANCE_RANGE_MASK, + range << + ARIZONA_HP_IMPEDANCE_RANGE_SHIFT); + return -EAGAIN; + } + + if (range < ARRAY_SIZE(arizona_hpdet_c_ranges) - 1 && + (val >= arizona_hpdet_c_ranges[range].max)) { + range++; + dev_dbg(arizona->dev, "Moving to HPDET range %d-%d\n", + arizona_hpdet_c_ranges[range].min, + arizona_hpdet_c_ranges[range].max); + regmap_update_bits(arizona->regmap, + ARIZONA_HEADPHONE_DETECT_1, + ARIZONA_HP_IMPEDANCE_RANGE_MASK, + range << + ARIZONA_HP_IMPEDANCE_RANGE_SHIFT); + return -EAGAIN; + } + } + + dev_dbg(arizona->dev, "HP impedance %d ohms\n", val); + return val; +} + +static irqreturn_t arizona_hpdet_irq(int irq, void *data) +{ + struct arizona_extcon_info *info = data; + struct arizona *arizona = info->arizona; + int report = ARIZONA_CABLE_HEADPHONE; + int ret; + + mutex_lock(&info->lock); + + /* If we got a spurious IRQ for some reason then ignore it */ + if (!info->hpdet_active) { + dev_warn(arizona->dev, "Spurious HPDET IRQ\n"); + mutex_unlock(&info->lock); + return IRQ_NONE; + } + + /* If the cable was removed while measuring ignore the result */ + ret = extcon_get_cable_state_(&info->edev, ARIZONA_CABLE_MECHANICAL); + if (ret < 0) { + dev_err(arizona->dev, "Failed to check cable state: %d\n", + ret); + goto out; + } else if (!ret) { + dev_dbg(arizona->dev, "Ignoring HPDET for removed cable\n"); + goto done; + } + + ret = arizona_hpdet_read(info); + if (ret == -EAGAIN) { + goto out; + } else if (ret < 0) { + goto done; + } + + /* Reset back to starting range */ + regmap_update_bits(arizona->regmap, + ARIZONA_HEADPHONE_DETECT_1, + ARIZONA_HP_IMPEDANCE_RANGE_MASK, 0); + + /* Report high impedence cables as line outputs */ + if (ret >= 5000) + report = ARIZONA_CABLE_LINEOUT; + else + report = ARIZONA_CABLE_HEADPHONE; + + ret = extcon_set_cable_state_(&info->edev, report, true); + if (ret != 0) + dev_err(arizona->dev, "Failed to report HP/line: %d\n", + ret); + + ret = regmap_update_bits(arizona->regmap, 0x225, 0x4000, 0); + if (ret != 0) + dev_warn(arizona->dev, "Failed to undo magic: %d\n", ret); + + ret = regmap_update_bits(arizona->regmap, 0x226, 0x4000, 0); + if (ret != 0) + dev_warn(arizona->dev, "Failed to undo magic: %d\n", ret); + +done: + regmap_update_bits(arizona->regmap, ARIZONA_HEADPHONE_DETECT_1, + ARIZONA_HP_POLL, 0); + + /* Revert back to MICDET mode */ + regmap_update_bits(arizona->regmap, + ARIZONA_ACCESSORY_DETECT_MODE_1, + ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC); + + /* If we have a mic then reenable MICDET */ + if (info->mic) + arizona_start_mic(info); + + if (info->hpdet_active) { + pm_runtime_put_autosuspend(info->dev); + info->hpdet_active = false; + } + +out: + mutex_unlock(&info->lock); + + return IRQ_HANDLED; +} + +static void arizona_identify_headphone(struct arizona_extcon_info *info) +{ + struct arizona *arizona = info->arizona; + int ret; + + dev_dbg(arizona->dev, "Starting HPDET\n"); + + /* Make sure we keep the device enabled during the measurement */ + pm_runtime_get(info->dev); + + info->hpdet_active = true; + + if (info->mic) + arizona_stop_mic(info); + + ret = regmap_update_bits(arizona->regmap, 0x225, 0x4000, 0x4000); + if (ret != 0) + dev_warn(arizona->dev, "Failed to do magic: %d\n", ret); + + ret = regmap_update_bits(arizona->regmap, 0x226, 0x4000, 0x4000); + if (ret != 0) + dev_warn(arizona->dev, "Failed to do magic: %d\n", ret); + + ret = regmap_update_bits(arizona->regmap, + ARIZONA_ACCESSORY_DETECT_MODE_1, + ARIZONA_ACCDET_MODE_MASK, + ARIZONA_ACCDET_MODE_HPL); + if (ret != 0) { + dev_err(arizona->dev, "Failed to set HPDETL mode: %d\n", ret); + goto err; + } + + ret = regmap_update_bits(arizona->regmap, ARIZONA_HEADPHONE_DETECT_1, + ARIZONA_HP_POLL, ARIZONA_HP_POLL); + if (ret != 0) { + dev_err(arizona->dev, "Can't start HPDETL measurement: %d\n", + ret); + goto err; + } + + return; + +err: + regmap_update_bits(arizona->regmap, ARIZONA_ACCESSORY_DETECT_MODE_1, + ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC); + + /* Just report headphone */ + ret = extcon_update_state(&info->edev, + 1 << ARIZONA_CABLE_HEADPHONE, + 1 << ARIZONA_CABLE_HEADPHONE); + if (ret != 0) + dev_err(arizona->dev, "Failed to report headphone: %d\n", ret); + + if (info->mic) + arizona_start_mic(info); + + info->hpdet_active = false; +} + } + + info->hpdet_active = false; +} + static void arizona_start_mic(struct arizona_extcon_info *info) { struct arizona *arizona = info->arizona; @@ -125,6 +421,10 @@ static void arizona_start_mic(struct arizona_extcon_info *info) regmap_write(arizona->regmap, 0x80, 0x0); } + regmap_update_bits(arizona->regmap, + ARIZONA_ACCESSORY_DETECT_MODE_1, + ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC); + regmap_update_bits_check(arizona->regmap, ARIZONA_MIC_DETECT_1, ARIZONA_MICD_ENA, ARIZONA_MICD_ENA, &change); @@ -189,11 +489,11 @@ static irqreturn_t arizona_micdet(int irq, void *data) /* If we got a high impedence we should have a headset, report it. */ if (info->detecting && (val & 0x400)) { + arizona_identify_headphone(info); + ret = extcon_update_state(&info->edev, - 1 << ARIZONA_CABLE_MICROPHONE | - 1 << ARIZONA_CABLE_HEADPHONE, - 1 << ARIZONA_CABLE_MICROPHONE | - 1 << ARIZONA_CABLE_HEADPHONE); + 1 << ARIZONA_CABLE_MICROPHONE, + 1 << ARIZONA_CABLE_MICROPHONE); if (ret != 0) dev_err(arizona->dev, "Headset report failed: %d\n", @@ -214,17 +514,12 @@ static irqreturn_t arizona_micdet(int irq, void *data) info->jack_flips++; if (info->jack_flips >= info->micd_num_modes) { - dev_dbg(arizona->dev, "Detected headphone\n"); + dev_dbg(arizona->dev, "Detected HP/line\n"); + arizona_identify_headphone(info); + info->detecting = false; - arizona_stop_mic(info); - ret = extcon_set_cable_state_(&info->edev, - ARIZONA_CABLE_HEADPHONE, - true); - if (ret != 0) - dev_err(arizona->dev, - "Headphone report failed: %d\n", - ret); + arizona_stop_mic(info); } else { info->micd_mode++; if (info->micd_mode == info->micd_num_modes) @@ -260,13 +555,7 @@ static irqreturn_t arizona_micdet(int irq, void *data) info->detecting = false; arizona_stop_mic(info); - ret = extcon_set_cable_state_(&info->edev, - ARIZONA_CABLE_HEADPHONE, - true); - if (ret != 0) - dev_err(arizona->dev, - "Headphone report failed: %d\n", - ret); + arizona_identify_headphone(info); } else { dev_warn(arizona->dev, "Button with no mic: %x\n", val); @@ -387,6 +676,7 @@ static int arizona_extcon_probe(struct platform_device *pdev) break; default: info->micd_clamp = true; + info->hpdet_ip = 1; break; } break; @@ -524,6 +814,13 @@ static int arizona_extcon_probe(struct platform_device *pdev) goto err_fall_wake; } + ret = arizona_request_irq(arizona, ARIZONA_IRQ_HPDET, + "HPDET", arizona_hpdet_irq, info); + if (ret != 0) { + dev_err(&pdev->dev, "Failed to get HPDET IRQ: %d\n", ret); + goto err_micdet; + } + regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_1, ARIZONA_MICD_RATE_MASK, 8 << ARIZONA_MICD_RATE_SHIFT); @@ -544,11 +841,13 @@ static int arizona_extcon_probe(struct platform_device *pdev) ret = input_register_device(info->input); if (ret) { dev_err(&pdev->dev, "Can't register input device: %d\n", ret); - goto err_micdet; + goto err_hpdet; } return 0; +err_hpdet: + arizona_free_irq(arizona, ARIZONA_IRQ_HPDET, info); err_micdet: arizona_free_irq(arizona, ARIZONA_IRQ_MICDET, info); err_fall_wake: diff --git a/drivers/mfd/wm5102-tables.c b/drivers/mfd/wm5102-tables.c index 088872ab6338..311cc3657b0b 100644 --- a/drivers/mfd/wm5102-tables.c +++ b/drivers/mfd/wm5102-tables.c @@ -1106,6 +1106,8 @@ static bool wm5102_readable_register(struct device *dev, unsigned int reg) case ARIZONA_ACCESSORY_DETECT_MODE_1: case ARIZONA_HEADPHONE_DETECT_1: case ARIZONA_HEADPHONE_DETECT_2: + case ARIZONA_HP_DACVAL: + case ARIZONA_MICD_CLAMP_CONTROL: case ARIZONA_MIC_DETECT_1: case ARIZONA_MIC_DETECT_2: case ARIZONA_MIC_DETECT_3: @@ -1875,6 +1877,7 @@ static bool wm5102_volatile_register(struct device *dev, unsigned int reg) case ARIZONA_DSP1_STATUS_2: case ARIZONA_DSP1_STATUS_3: case ARIZONA_HEADPHONE_DETECT_2: + case ARIZONA_HP_DACVAL: case ARIZONA_MIC_DETECT_3: return true; default: diff --git a/include/linux/mfd/arizona/registers.h b/include/linux/mfd/arizona/registers.h index f3211f06cec6..e9150533e701 100644 --- a/include/linux/mfd/arizona/registers.h +++ b/include/linux/mfd/arizona/registers.h @@ -119,6 +119,7 @@ #define ARIZONA_ACCESSORY_DETECT_MODE_1 0x293 #define ARIZONA_HEADPHONE_DETECT_1 0x29B #define ARIZONA_HEADPHONE_DETECT_2 0x29C +#define ARIZONA_HP_DACVAL 0x29F #define ARIZONA_MICD_CLAMP_CONTROL 0x2A2 #define ARIZONA_MIC_DETECT_1 0x2A3 #define ARIZONA_MIC_DETECT_2 0x2A4 @@ -2036,6 +2037,9 @@ /* * R667 (0x29B) - Headphone Detect 1 */ +#define ARIZONA_HP_IMPEDANCE_RANGE_MASK 0x0600 /* HP_IMPEDANCE_RANGE - [10:9] */ +#define ARIZONA_HP_IMPEDANCE_RANGE_SHIFT 9 /* HP_IMPEDANCE_RANGE - [10:9] */ +#define ARIZONA_HP_IMPEDANCE_RANGE_WIDTH 2 /* HP_IMPEDANCE_RANGE - [10:9] */ #define ARIZONA_HP_STEP_SIZE 0x0100 /* HP_STEP_SIZE */ #define ARIZONA_HP_STEP_SIZE_MASK 0x0100 /* HP_STEP_SIZE */ #define ARIZONA_HP_STEP_SIZE_SHIFT 8 /* HP_STEP_SIZE */ @@ -2070,6 +2074,14 @@ #define ARIZONA_HP_LVL_SHIFT 0 /* HP_LVL - [6:0] */ #define ARIZONA_HP_LVL_WIDTH 7 /* HP_LVL - [6:0] */ +#define ARIZONA_HP_DONE_B 0x8000 /* HP_DONE */ +#define ARIZONA_HP_DONE_B_MASK 0x8000 /* HP_DONE */ +#define ARIZONA_HP_DONE_B_SHIFT 15 /* HP_DONE */ +#define ARIZONA_HP_DONE_B_WIDTH 1 /* HP_DONE */ +#define ARIZONA_HP_LVL_B_MASK 0x7FFF /* HP_LVL - [14:0] */ +#define ARIZONA_HP_LVL_B_SHIFT 0 /* HP_LVL - [14:0] */ +#define ARIZONA_HP_LVL_B_WIDTH 15 /* HP_LVL - [14:0] */ + /* * R674 (0x2A2) - MICD clamp control */ -- cgit v1.2.3 From dd235eea4ed75b1599dd9a53bb618fe5befeb731 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 11 Jan 2013 08:55:51 +0900 Subject: extcon: arizona: Support HPDET based accessory identification The accessory detection functionality in Arizona devices is flexible and supports several system designs in addition to the default one implemented by the existing driver. One such design uses the HPDET feature to determine what kind of accessory is present by comparing measurements taken with the two headphone grounds available on the device, implement that if selected by platform data. Signed-off-by: Mark Brown Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-arizona.c | 159 +++++++++++++++++++++++++++++++++++--- include/linux/mfd/arizona/pdata.h | 3 + 2 files changed, 150 insertions(+), 12 deletions(-) (limited to 'include') diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index dab4584a4ad5..ba9525ee4706 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -55,6 +55,9 @@ struct arizona_extcon_info { bool hpdet_active; + int num_hpdet_res; + unsigned int hpdet_res[2]; + bool mic; bool detecting; int jack_flips; @@ -65,8 +68,8 @@ struct arizona_extcon_info { }; static const struct arizona_micd_config micd_default_modes[] = { - { ARIZONA_ACCDET_SRC, 1 << ARIZONA_MICD_BIAS_SRC_SHIFT, 0 }, { 0, 2 << ARIZONA_MICD_BIAS_SRC_SHIFT, 1 }, + { ARIZONA_ACCDET_SRC, 1 << ARIZONA_MICD_BIAS_SRC_SHIFT, 0 }, }; static struct { @@ -118,10 +121,6 @@ static void arizona_start_mic(struct arizona_extcon_info *info) bool change; int ret; - info->detecting = true; - info->mic = false; - info->jack_flips = 0; - /* Microphone detection can't use idle mode */ pm_runtime_get(info->dev); @@ -311,12 +310,80 @@ static int arizona_hpdet_read(struct arizona_extcon_info *info) return val; } +static int arizona_hpdet_do_id(struct arizona_extcon_info *info, int *reading) +{ + struct arizona *arizona = info->arizona; + int ret; + + /* + * If we're using HPDET for accessory identification we need + * to take multiple measurements, step through them in sequence. + */ + if (arizona->pdata.hpdet_acc_id) { + info->hpdet_res[info->num_hpdet_res++] = *reading; + + /* + * If the impedence is too high don't measure the + * second ground. + */ + if (info->num_hpdet_res == 1 && *reading >= 45) { + dev_dbg(arizona->dev, "Skipping ground flip\n"); + info->hpdet_res[info->num_hpdet_res++] = *reading; + } + + if (info->num_hpdet_res == 1) { + dev_dbg(arizona->dev, "Flipping ground\n"); + + regmap_update_bits(arizona->regmap, + ARIZONA_ACCESSORY_DETECT_MODE_1, + ARIZONA_ACCDET_SRC, + ~info->micd_modes[0].src); + regmap_update_bits(arizona->regmap, + ARIZONA_HEADPHONE_DETECT_1, + ARIZONA_HP_POLL, 0); + regmap_update_bits(arizona->regmap, + ARIZONA_HEADPHONE_DETECT_1, + ARIZONA_HP_POLL, ARIZONA_HP_POLL); + return -EAGAIN; + } + + /* OK, got both. Now, compare... */ + dev_dbg(arizona->dev, "HPDET measured %d %d\n", + info->hpdet_res[0], info->hpdet_res[1]); + + if (info->hpdet_res[0] > info->hpdet_res[1] * 2) { + dev_dbg(arizona->dev, "Detected mic\n"); + info->mic = true; + ret = extcon_set_cable_state_(&info->edev, + ARIZONA_CABLE_MICROPHONE, + true); + if (ret != 0) { + dev_err(arizona->dev, + "Failed to report mic: %d\n", ret); + } + + /* Take the headphone impedance for the main report */ + *reading = info->hpdet_res[1]; + } else { + dev_dbg(arizona->dev, "Detected headphone\n"); + } + + /* Make sure everything is reset back to the real polarity */ + regmap_update_bits(arizona->regmap, + ARIZONA_ACCESSORY_DETECT_MODE_1, + ARIZONA_ACCDET_SRC, + info->micd_modes[0].src); + } + + return 0; +} + static irqreturn_t arizona_hpdet_irq(int irq, void *data) { struct arizona_extcon_info *info = data; struct arizona *arizona = info->arizona; int report = ARIZONA_CABLE_HEADPHONE; - int ret; + int ret, reading; mutex_lock(&info->lock); @@ -344,14 +411,23 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data) } else if (ret < 0) { goto done; } + reading = ret; /* Reset back to starting range */ regmap_update_bits(arizona->regmap, ARIZONA_HEADPHONE_DETECT_1, - ARIZONA_HP_IMPEDANCE_RANGE_MASK, 0); + ARIZONA_HP_IMPEDANCE_RANGE_MASK | ARIZONA_HP_POLL, + 0); + + ret = arizona_hpdet_do_id(info, &reading); + if (ret == -EAGAIN) { + goto out; + } else if (ret < 0) { + goto done; + } /* Report high impedence cables as line outputs */ - if (ret >= 5000) + if (reading >= 5000) report = ARIZONA_CABLE_LINEOUT; else report = ARIZONA_CABLE_HEADPHONE; @@ -370,8 +446,6 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data) dev_warn(arizona->dev, "Failed to undo magic: %d\n", ret); done: - regmap_update_bits(arizona->regmap, ARIZONA_HEADPHONE_DETECT_1, - ARIZONA_HP_POLL, 0); /* Revert back to MICDET mode */ regmap_update_bits(arizona->regmap, @@ -451,8 +525,58 @@ err: info->hpdet_active = false; } + +static void arizona_start_hpdet_acc_id(struct arizona_extcon_info *info) +{ + struct arizona *arizona = info->arizona; + int ret; + + dev_dbg(arizona->dev, "Starting identification via HPDET\n"); + + /* Make sure we keep the device enabled during the measurement */ + pm_runtime_get(info->dev); + + info->hpdet_active = true; + + ret = regmap_update_bits(arizona->regmap, 0x225, 0x4000, 0x4000); + if (ret != 0) + dev_warn(arizona->dev, "Failed to do magic: %d\n", ret); + + ret = regmap_update_bits(arizona->regmap, 0x226, 0x4000, 0x4000); + if (ret != 0) + dev_warn(arizona->dev, "Failed to do magic: %d\n", ret); + + ret = regmap_update_bits(arizona->regmap, + ARIZONA_ACCESSORY_DETECT_MODE_1, + ARIZONA_ACCDET_SRC | ARIZONA_ACCDET_MODE_MASK, + info->micd_modes[0].src | + ARIZONA_ACCDET_MODE_HPL); + if (ret != 0) { + dev_err(arizona->dev, "Failed to set HPDETL mode: %d\n", ret); + goto err; } + ret = regmap_update_bits(arizona->regmap, ARIZONA_HEADPHONE_DETECT_1, + ARIZONA_HP_POLL, ARIZONA_HP_POLL); + if (ret != 0) { + dev_err(arizona->dev, "Can't start HPDETL measurement: %d\n", + ret); + goto err; + } + + return; + +err: + regmap_update_bits(arizona->regmap, ARIZONA_ACCESSORY_DETECT_MODE_1, + ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC); + + /* Just report headphone */ + ret = extcon_update_state(&info->edev, + 1 << ARIZONA_CABLE_HEADPHONE, + 1 << ARIZONA_CABLE_HEADPHONE); + if (ret != 0) + dev_err(arizona->dev, "Failed to report headphone: %d\n", ret); + info->hpdet_active = false; } @@ -612,12 +736,24 @@ static irqreturn_t arizona_jackdet(int irq, void *data) dev_err(arizona->dev, "Mechanical report failed: %d\n", ret); - arizona_start_mic(info); + if (!arizona->pdata.hpdet_acc_id) { + info->detecting = true; + info->mic = false; + info->jack_flips = 0; + + arizona_start_mic(info); + } else { + arizona_start_hpdet_acc_id(info); + } } else { dev_dbg(arizona->dev, "Detected jack removal\n"); arizona_stop_mic(info); + info->num_hpdet_res = 0; + for (i = 0; i < ARRAY_SIZE(info->hpdet_res); i++) + info->hpdet_res[i] = 0; + info->mic = false; for (i = 0; i < ARIZONA_NUM_BUTTONS; i++) input_report_key(info->input, @@ -665,7 +801,6 @@ static int arizona_extcon_probe(struct platform_device *pdev) mutex_init(&info->lock); info->arizona = arizona; info->dev = &pdev->dev; - info->detecting = true; platform_set_drvdata(pdev, info); switch (arizona->type) { diff --git a/include/linux/mfd/arizona/pdata.h b/include/linux/mfd/arizona/pdata.h index 0fc26a480be3..7c0878778357 100644 --- a/include/linux/mfd/arizona/pdata.h +++ b/include/linux/mfd/arizona/pdata.h @@ -99,6 +99,9 @@ struct arizona_pdata { /** GPIO5 is used for jack detection */ bool jd_gpio5; + /** Use the headphone detect circuit to identify the accessory */ + bool hpdet_acc_id; + /** GPIO for mic detection polarity */ int micd_pol_gpio; -- cgit v1.2.3 From 1eda6aa7ce101b59dfd91abef2c8b0e51e96e199 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 11 Jan 2013 08:55:54 +0900 Subject: extcon: arizona: Support direct microphone measurement via HPDET With some GPIO control it is possible to detect microphones in a wider range of configurations by directly measuring the microphone impedance when the HPDET method cannot distinguish between the behaviour of the two grounds. Allow a GPIO to be provided in platform data and use it to implement this behaviour. Signed-off-by: Mark Brown Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-arizona.c | 50 +++++++++++++++++++++++++++++++++++---- include/linux/mfd/arizona/pdata.h | 3 +++ 2 files changed, 48 insertions(+), 5 deletions(-) (limited to 'include') diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index ba9525ee4706..de141f72c8e5 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -56,7 +56,7 @@ struct arizona_extcon_info { bool hpdet_active; int num_hpdet_res; - unsigned int hpdet_res[2]; + unsigned int hpdet_res[3]; bool mic; bool detecting; @@ -313,6 +313,7 @@ static int arizona_hpdet_read(struct arizona_extcon_info *info) static int arizona_hpdet_do_id(struct arizona_extcon_info *info, int *reading) { struct arizona *arizona = info->arizona; + int id_gpio = arizona->pdata.hpdet_id_gpio; int ret; /* @@ -338,9 +339,27 @@ static int arizona_hpdet_do_id(struct arizona_extcon_info *info, int *reading) ARIZONA_ACCESSORY_DETECT_MODE_1, ARIZONA_ACCDET_SRC, ~info->micd_modes[0].src); + regmap_update_bits(arizona->regmap, ARIZONA_HEADPHONE_DETECT_1, - ARIZONA_HP_POLL, 0); + ARIZONA_HP_POLL, ARIZONA_HP_POLL); + return -EAGAIN; + } + + /* Only check the mic directly if we didn't already ID it */ + if (id_gpio && info->num_hpdet_res == 2 && + !((info->hpdet_res[0] > info->hpdet_res[1] * 2))) { + dev_dbg(arizona->dev, "Measuring mic\n"); + + regmap_update_bits(arizona->regmap, + ARIZONA_ACCESSORY_DETECT_MODE_1, + ARIZONA_ACCDET_MODE_MASK | + ARIZONA_ACCDET_SRC, + ARIZONA_ACCDET_MODE_HPR | + info->micd_modes[0].src); + + gpio_set_value_cansleep(id_gpio, 1); + regmap_update_bits(arizona->regmap, ARIZONA_HEADPHONE_DETECT_1, ARIZONA_HP_POLL, ARIZONA_HP_POLL); @@ -348,10 +367,16 @@ static int arizona_hpdet_do_id(struct arizona_extcon_info *info, int *reading) } /* OK, got both. Now, compare... */ - dev_dbg(arizona->dev, "HPDET measured %d %d\n", - info->hpdet_res[0], info->hpdet_res[1]); + dev_dbg(arizona->dev, "HPDET measured %d %d %d\n", + info->hpdet_res[0], info->hpdet_res[1], + info->hpdet_res[2]); - if (info->hpdet_res[0] > info->hpdet_res[1] * 2) { + /* + * Either the two grounds measure differently or we + * measure the mic as high impedance. + */ + if ((info->hpdet_res[0] > info->hpdet_res[1] * 2) || + (id_gpio && info->hpdet_res[2] > 10)) { dev_dbg(arizona->dev, "Detected mic\n"); info->mic = true; ret = extcon_set_cable_state_(&info->edev, @@ -382,6 +407,7 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data) { struct arizona_extcon_info *info = data; struct arizona *arizona = info->arizona; + int id_gpio = arizona->pdata.hpdet_id_gpio; int report = ARIZONA_CABLE_HEADPHONE; int ret, reading; @@ -446,6 +472,8 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data) dev_warn(arizona->dev, "Failed to undo magic: %d\n", ret); done: + if (id_gpio) + gpio_set_value_cansleep(id_gpio, 0); /* Revert back to MICDET mode */ regmap_update_bits(arizona->regmap, @@ -854,6 +882,18 @@ static int arizona_extcon_probe(struct platform_device *pdev) } } + if (arizona->pdata.hpdet_id_gpio > 0) { + ret = devm_gpio_request_one(&pdev->dev, + arizona->pdata.hpdet_id_gpio, + GPIOF_OUT_INIT_LOW, + "HPDET"); + if (ret != 0) { + dev_err(arizona->dev, "Failed to request GPIO%d: %d\n", + arizona->pdata.hpdet_id_gpio, ret); + goto err_register; + } + } + if (arizona->pdata.micd_bias_start_time) regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_1, ARIZONA_MICD_BIAS_STARTTIME_MASK, diff --git a/include/linux/mfd/arizona/pdata.h b/include/linux/mfd/arizona/pdata.h index 7c0878778357..bcbe4fda87cb 100644 --- a/include/linux/mfd/arizona/pdata.h +++ b/include/linux/mfd/arizona/pdata.h @@ -102,6 +102,9 @@ struct arizona_pdata { /** Use the headphone detect circuit to identify the accessory */ bool hpdet_acc_id; + /** GPIO used for mic isolation with HPDET */ + int hpdet_id_gpio; + /** GPIO for mic detection polarity */ int micd_pol_gpio; -- cgit v1.2.3 From 689557d3c7045320958d5bc4141088f7b4dff7ba Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 11 Jan 2013 08:55:57 +0900 Subject: mfd: wm5102: Add microphone clamp control registers Signed-off-by: Mark Brown Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/mfd/wm5102-tables.c | 7 +++++++ include/linux/mfd/arizona/registers.h | 8 ++++++++ 2 files changed, 15 insertions(+) (limited to 'include') diff --git a/drivers/mfd/wm5102-tables.c b/drivers/mfd/wm5102-tables.c index 311cc3657b0b..a396a3ae2045 100644 --- a/drivers/mfd/wm5102-tables.c +++ b/drivers/mfd/wm5102-tables.c @@ -84,6 +84,12 @@ int wm5102_patch(struct arizona *arizona) } static const struct regmap_irq wm5102_aod_irqs[ARIZONA_NUM_IRQ] = { + [ARIZONA_IRQ_MICD_CLAMP_FALL] = { + .mask = ARIZONA_MICD_CLAMP_FALL_EINT1 + }, + [ARIZONA_IRQ_MICD_CLAMP_RISE] = { + .mask = ARIZONA_MICD_CLAMP_RISE_EINT1 + }, [ARIZONA_IRQ_GP5_FALL] = { .mask = ARIZONA_GP5_FALL_EINT1 }, [ARIZONA_IRQ_GP5_RISE] = { .mask = ARIZONA_GP5_RISE_EINT1 }, [ARIZONA_IRQ_JD_FALL] = { .mask = ARIZONA_JD1_FALL_EINT1 }, @@ -312,6 +318,7 @@ static const struct reg_default wm5102_reg_default[] = { { 0x0000021A, 0x01A6 }, /* R538 - Mic Bias Ctrl 3 */ { 0x00000293, 0x0000 }, /* R659 - Accessory Detect Mode 1 */ { 0x0000029B, 0x0020 }, /* R667 - Headphone Detect 1 */ + { 0x000002A2, 0x0000 }, /* R674 - Micd clamp control */ { 0x000002A3, 0x1102 }, /* R675 - Mic Detect 1 */ { 0x000002A4, 0x009F }, /* R676 - Mic Detect 2 */ { 0x000002A5, 0x0000 }, /* R677 - Mic Detect 3 */ diff --git a/include/linux/mfd/arizona/registers.h b/include/linux/mfd/arizona/registers.h index e9150533e701..79e9dd4073d8 100644 --- a/include/linux/mfd/arizona/registers.h +++ b/include/linux/mfd/arizona/registers.h @@ -1196,6 +1196,14 @@ /* * R64 (0x40) - Wake control */ +#define ARIZONA_WKUP_MICD_CLAMP_FALL 0x0080 /* WKUP_MICD_CLAMP_FALL */ +#define ARIZONA_WKUP_MICD_CLAMP_FALL_MASK 0x0080 /* WKUP_MICD_CLAMP_FALL */ +#define ARIZONA_WKUP_MICD_CLAMP_FALL_SHIFT 7 /* WKUP_MICD_CLAMP_FALL */ +#define ARIZONA_WKUP_MICD_CLAMP_FALL_WIDTH 1 /* WKUP_MICD_CLAMP_FALL */ +#define ARIZONA_WKUP_MICD_CLAMP_RISE 0x0040 /* WKUP_MICD_CLAMP_RISE */ +#define ARIZONA_WKUP_MICD_CLAMP_RISE_MASK 0x0040 /* WKUP_MICD_CLAMP_RISE */ +#define ARIZONA_WKUP_MICD_CLAMP_RISE_SHIFT 6 /* WKUP_MICD_CLAMP_RISE */ +#define ARIZONA_WKUP_MICD_CLAMP_RISE_WIDTH 1 /* WKUP_MICD_CLAMP_RISE */ #define ARIZONA_WKUP_GP5_FALL 0x0020 /* WKUP_GP5_FALL */ #define ARIZONA_WKUP_GP5_FALL_MASK 0x0020 /* WKUP_GP5_FALL */ #define ARIZONA_WKUP_GP5_FALL_SHIFT 5 /* WKUP_GP5_FALL */ -- cgit v1.2.3 From 132368bd0b286457f83f56d0bbdecd85999562dc Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:33 -0800 Subject: Drivers: hv: Add state to manage batched reading For the "read" side signaling optimization, the reader has to completely drain the queue before exiting. Add state to manage this "batched" reading. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/channel_mgmt.c | 7 +++++++ include/linux/hyperv.h | 20 ++++++++++++++++++++ 2 files changed, 27 insertions(+) (limited to 'include') diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index 2f84c5cff8d4..7bf59177aaf8 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -275,6 +275,13 @@ static void vmbus_onoffer(struct vmbus_channel_message_header *hdr) return; } + /* + * By default we setup state to enable batched + * reading. A specific service can choose to + * disable this prior to opening the channel. + */ + newchannel->batched_reading = true; + memcpy(&newchannel->offermsg, offer, sizeof(struct vmbus_channel_offer_channel)); newchannel->monitor_grp = (u8)offer->monitorid / 32; diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index e73b852156b1..1ffe84de6c55 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -882,8 +882,28 @@ struct vmbus_channel { void (*onchannel_callback)(void *context); void *channel_callback_context; + + /* + * A channel can be marked for efficient (batched) + * reading: + * If batched_reading is set to "true", we read until the + * channel is empty and hold off interrupts from the host + * during the entire read process. + * If batched_reading is set to "false", the client is not + * going to perform batched reading. + * + * By default we will enable batched reading; specific + * drivers that don't want this behavior can turn it off. + */ + + bool batched_reading; }; +static inline void set_channel_read_state(struct vmbus_channel *c, bool state) +{ + c->batched_reading = state; +} + void vmbus_onmessage(void *context); int vmbus_request_offers(void); -- cgit v1.2.3 From 610071c38463998d5a66388ff9956aaeb24b49a8 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:38 -0800 Subject: Drivers: hv: Support handling multiple VMBUS versions The current code hard coded the vmbus version independent of the host it was running on. Add code to dynamically negotiate the most appropriate version. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/connection.c | 165 ++++++++++++++++++++++++++++++++---------------- include/linux/hyperv.h | 6 -- 2 files changed, 111 insertions(+), 60 deletions(-) (limited to 'include') diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c index d1019a770ad7..2b56a3f47b30 100644 --- a/drivers/hv/connection.c +++ b/drivers/hv/connection.c @@ -39,16 +39,112 @@ struct vmbus_connection vmbus_connection = { .next_gpadl_handle = ATOMIC_INIT(0xE1E10), }; +/* + * VMBUS version is 32 bit entity broken up into + * two 16 bit quantities: major_number. minor_number. + * + * 0 . 13 (Windows Server 2008) + * 1 . 1 (Windows 7) + * 2 . 4 (Windows 8) + */ + +#define VERSION_WS2008 ((0 << 16) | (13)) +#define VERSION_WIN7 ((1 << 16) | (1)) +#define VERSION_WIN8 ((2 << 16) | (4)) + +#define VERSION_INVAL -1 + +static __u32 vmbus_get_next_version(__u32 current_version) +{ + switch (current_version) { + case (VERSION_WIN7): + return VERSION_WS2008; + + case (VERSION_WIN8): + return VERSION_WIN7; + + case (VERSION_WS2008): + default: + return VERSION_INVAL; + } +} + +static int vmbus_negotiate_version(struct vmbus_channel_msginfo *msginfo, + __u32 version) +{ + int ret = 0; + struct vmbus_channel_initiate_contact *msg; + unsigned long flags; + int t; + + init_completion(&msginfo->waitevent); + + msg = (struct vmbus_channel_initiate_contact *)msginfo->msg; + + msg->header.msgtype = CHANNELMSG_INITIATE_CONTACT; + msg->vmbus_version_requested = version; + msg->interrupt_page = virt_to_phys(vmbus_connection.int_page); + msg->monitor_page1 = virt_to_phys(vmbus_connection.monitor_pages); + msg->monitor_page2 = virt_to_phys( + (void *)((unsigned long)vmbus_connection.monitor_pages + + PAGE_SIZE)); + + /* + * Add to list before we send the request since we may + * receive the response before returning from this routine + */ + spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags); + list_add_tail(&msginfo->msglistentry, + &vmbus_connection.chn_msg_list); + + spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags); + + ret = vmbus_post_msg(msg, + sizeof(struct vmbus_channel_initiate_contact)); + if (ret != 0) { + spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags); + list_del(&msginfo->msglistentry); + spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, + flags); + return ret; + } + + /* Wait for the connection response */ + t = wait_for_completion_timeout(&msginfo->waitevent, 5*HZ); + if (t == 0) { + spin_lock_irqsave(&vmbus_connection.channelmsg_lock, + flags); + list_del(&msginfo->msglistentry); + spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, + flags); + return -ETIMEDOUT; + } + + spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags); + list_del(&msginfo->msglistentry); + spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags); + + /* Check if successful */ + if (msginfo->response.version_response.version_supported) { + vmbus_connection.conn_state = CONNECTED; + } else { + pr_err("Unable to connect, " + "Version %d not supported by Hyper-V\n", + version); + return -ECONNREFUSED; + } + + return ret; +} + /* * vmbus_connect - Sends a connect request on the partition service connection */ int vmbus_connect(void) { int ret = 0; - int t; struct vmbus_channel_msginfo *msginfo = NULL; - struct vmbus_channel_initiate_contact *msg; - unsigned long flags; + __u32 version; /* Initialize the vmbus connection */ vmbus_connection.conn_state = CONNECTING; @@ -99,64 +195,25 @@ int vmbus_connect(void) goto cleanup; } - init_completion(&msginfo->waitevent); - - msg = (struct vmbus_channel_initiate_contact *)msginfo->msg; - - msg->header.msgtype = CHANNELMSG_INITIATE_CONTACT; - msg->vmbus_version_requested = VMBUS_REVISION_NUMBER; - msg->interrupt_page = virt_to_phys(vmbus_connection.int_page); - msg->monitor_page1 = virt_to_phys(vmbus_connection.monitor_pages); - msg->monitor_page2 = virt_to_phys( - (void *)((unsigned long)vmbus_connection.monitor_pages + - PAGE_SIZE)); - /* - * Add to list before we send the request since we may - * receive the response before returning from this routine + * Negotiate a compatible VMBUS version number with the + * host. We start with the highest number we can support + * and work our way down until we negotiate a compatible + * version. */ - spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags); - list_add_tail(&msginfo->msglistentry, - &vmbus_connection.chn_msg_list); - - spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags); - ret = vmbus_post_msg(msg, - sizeof(struct vmbus_channel_initiate_contact)); - if (ret != 0) { - spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags); - list_del(&msginfo->msglistentry); - spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, - flags); - goto cleanup; - } + version = VERSION_WS2008; - /* Wait for the connection response */ - t = wait_for_completion_timeout(&msginfo->waitevent, 5*HZ); - if (t == 0) { - spin_lock_irqsave(&vmbus_connection.channelmsg_lock, - flags); - list_del(&msginfo->msglistentry); - spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, - flags); - ret = -ETIMEDOUT; - goto cleanup; - } + do { + ret = vmbus_negotiate_version(msginfo, version); + if (ret == 0) + break; - spin_lock_irqsave(&vmbus_connection.channelmsg_lock, flags); - list_del(&msginfo->msglistentry); - spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags); + version = vmbus_get_next_version(version); + } while (version != VERSION_INVAL); - /* Check if successful */ - if (msginfo->response.version_response.version_supported) { - vmbus_connection.conn_state = CONNECTED; - } else { - pr_err("Unable to connect, " - "Version %d not supported by Hyper-V\n", - VMBUS_REVISION_NUMBER); - ret = -ECONNREFUSED; + if (version == VERSION_INVAL) goto cleanup; - } kfree(msginfo); return 0; diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 1ffe84de6c55..b097bf9d9328 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -406,12 +406,6 @@ hv_get_ringbuffer_availbytes(struct hv_ring_buffer_info *rbi, #define HV_DRV_VERSION "3.1" -/* - * A revision number of vmbus that is used for ensuring both ends on a - * partition are using compatible versions. - */ -#define VMBUS_REVISION_NUMBER 13 - /* Make maximum size of pipe payload of 16K */ #define MAX_PIPE_DATA_PAYLOAD (sizeof(u8) * 16384) -- cgit v1.2.3 From 2416603ef1e12955850ade0d0cdecbef1fc59fa3 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:39 -0800 Subject: Drivers: hv: Update the ring buffer structure to match win8 functionality Update the ringbuffer structure to support win8 functionality. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- include/linux/hyperv.h | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) (limited to 'include') diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index b097bf9d9328..2b5480126394 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -325,14 +325,28 @@ struct hv_ring_buffer { u32 interrupt_mask; - /* Pad it to PAGE_SIZE so that data starts on page boundary */ - u8 reserved[4084]; - - /* NOTE: - * The interrupt_mask field is used only for channels but since our - * vmbus connection also uses this data structure and its data starts - * here, we commented out this field. + /* + * Win8 uses some of the reserved bits to implement + * interrupt driven flow management. On the send side + * we can request that the receiver interrupt the sender + * when the ring transitions from being full to being able + * to handle a message of size "pending_send_sz". + * + * Add necessary state for this enhancement. */ + u32 pending_send_sz; + + u32 reserved1[12]; + + union { + struct { + u32 feat_pending_send_sz:1; + }; + u32 value; + } feature_bits; + + /* Pad it to PAGE_SIZE so that data starts on page boundary */ + u8 reserved2[4028]; /* * Ring data starts here + RingDataStartOffset -- cgit v1.2.3 From 29423b7e51a8ea4d687cf98c3a50f33c554be194 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:40 -0800 Subject: Drivers: hv: Extend/modify vmbus_channel_offer_channel for win7 and beyond The "offfer" message sent by the host has been extended in win7 (ws2008 R2). Add/modify state to reflect this extension. All these changes are backward compatible. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- include/linux/hyperv.h | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) (limited to 'include') diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 2b5480126394..bee559ada3bb 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -440,9 +440,13 @@ hv_get_ringbuffer_availbytes(struct hv_ring_buffer_info *rbi, struct vmbus_channel_offer { uuid_le if_type; uuid_le if_instance; - u64 int_latency; /* in 100ns units */ - u32 if_revision; - u32 server_ctx_size; /* in bytes */ + + /* + * These two fields are not currently used. + */ + u64 reserved1; + u64 reserved2; + u16 chn_flags; u16 mmio_megabytes; /* in bytes * 1024 * 1024 */ @@ -464,7 +468,11 @@ struct vmbus_channel_offer { unsigned char user_def[MAX_PIPE_USER_DEFINED_BYTES]; } pipe; } u; - u32 padding; + /* + * The sub_channel_index is defined in win8. + */ + u16 sub_channel_index; + u16 reserved3; } __packed; /* Server Flags */ @@ -660,7 +668,25 @@ struct vmbus_channel_offer_channel { struct vmbus_channel_offer offer; u32 child_relid; u8 monitorid; - u8 monitor_allocated; + /* + * win7 and beyond splits this field into a bit field. + */ + u8 monitor_allocated:1; + u8 reserved:7; + /* + * These are new fields added in win7 and later. + * Do not access these fields without checking the + * negotiated protocol. + * + * If "is_dedicated_interrupt" is set, we must not set the + * associated bit in the channel bitmap while sending the + * interrupt to the host. + * + * connection_id is to be used in signaling the host. + */ + u16 is_dedicated_interrupt:1; + u16 reserved1:15; + u32 connection_id; } __packed; /* Rescind Offer parameters */ -- cgit v1.2.3 From 37f7278b81a9ab9b76cf4d716ba420444ca4a616 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:41 -0800 Subject: Drivers: hv: Save and export negotiated vmbus version Export the negotiated vmbus version as this may be useful for individual drivers. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/connection.c | 9 +++++++++ include/linux/hyperv.h | 6 ++++++ 2 files changed, 15 insertions(+) (limited to 'include') diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c index 2b56a3f47b30..70ea5d1fc16c 100644 --- a/drivers/hv/connection.c +++ b/drivers/hv/connection.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include "hyperv_vmbus.h" @@ -54,6 +55,12 @@ struct vmbus_connection vmbus_connection = { #define VERSION_INVAL -1 +/* + * Negotiated protocol version with the host. + */ +__u32 vmbus_proto_version; +EXPORT_SYMBOL_GPL(vmbus_proto_version); + static __u32 vmbus_get_next_version(__u32 current_version) { switch (current_version) { @@ -215,6 +222,8 @@ int vmbus_connect(void) if (version == VERSION_INVAL) goto cleanup; + vmbus_proto_version = version; + pr_info("Negotiated host information %d\n", version); kfree(msginfo); return 0; diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index bee559ada3bb..134a2022a7a3 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -1204,5 +1204,11 @@ int hv_kvp_init(struct hv_util_service *); void hv_kvp_deinit(void); void hv_kvp_onchannelcallback(void *); +/* + * Negotiated version with the Host. + */ + +extern __u32 vmbus_proto_version; + #endif /* __KERNEL__ */ #endif /* _HYPERV_H */ -- cgit v1.2.3 From eafa7072e7cd806dff42b705284ca26189e527a4 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:44 -0800 Subject: Drivers: hv: Move vmbus version definitions to hyperv.h To support version specific optimization in various vmbus drivers, move the vmbus definitions to the public header file. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/connection.c | 15 --------------- include/linux/hyperv.h | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 15 deletions(-) (limited to 'include') diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c index 0d8b13290e93..56b14e57bdd8 100644 --- a/drivers/hv/connection.c +++ b/drivers/hv/connection.c @@ -40,21 +40,6 @@ struct vmbus_connection vmbus_connection = { .next_gpadl_handle = ATOMIC_INIT(0xE1E10), }; -/* - * VMBUS version is 32 bit entity broken up into - * two 16 bit quantities: major_number. minor_number. - * - * 0 . 13 (Windows Server 2008) - * 1 . 1 (Windows 7) - * 2 . 4 (Windows 8) - */ - -#define VERSION_WS2008 ((0 << 16) | (13)) -#define VERSION_WIN7 ((1 << 16) | (1)) -#define VERSION_WIN8 ((2 << 16) | (4)) - -#define VERSION_INVAL -1 - /* * Negotiated protocol version with the host. */ diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 134a2022a7a3..e72502689cdc 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -419,6 +419,21 @@ hv_get_ringbuffer_availbytes(struct hv_ring_buffer_info *rbi, */ #define HV_DRV_VERSION "3.1" +/* + * VMBUS version is 32 bit entity broken up into + * two 16 bit quantities: major_number. minor_number. + * + * 0 . 13 (Windows Server 2008) + * 1 . 1 (Windows 7) + * 2 . 4 (Windows 8) + */ + +#define VERSION_WS2008 ((0 << 16) | (13)) +#define VERSION_WIN7 ((1 << 16) | (1)) +#define VERSION_WIN8 ((2 << 16) | (4)) + +#define VERSION_INVAL -1 + /* Make maximum size of pipe payload of 16K */ #define MAX_PIPE_DATA_PAYLOAD (sizeof(u8) * 16384) -- cgit v1.2.3 From b3bf60c7b4665d40b8eae2217b54c4745f49f470 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:45 -0800 Subject: Drivers: hv: Manage signaling state on a per-connection basis The current code has a global handle for supporting signaling of the host from guest. Make this a per-channel attribute as on some versions of the host we can signal on per-channel handle. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/channel_mgmt.c | 20 ++++++++++++++++++++ drivers/hv/hyperv_vmbus.h | 21 --------------------- include/linux/hyperv.h | 25 +++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 21 deletions(-) (limited to 'include') diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index 7bf59177aaf8..f4d990285d99 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -282,6 +282,26 @@ static void vmbus_onoffer(struct vmbus_channel_message_header *hdr) */ newchannel->batched_reading = true; + /* + * Setup state for signalling the host. + */ + newchannel->sig_event = (struct hv_input_signal_event *) + (ALIGN((unsigned long) + &newchannel->sig_buf, + HV_HYPERCALL_PARAM_ALIGN)); + + newchannel->sig_event->connectionid.asu32 = 0; + newchannel->sig_event->connectionid.u.id = VMBUS_EVENT_CONNECTION_ID; + newchannel->sig_event->flag_number = 0; + newchannel->sig_event->rsvdz = 0; + + if (vmbus_proto_version != VERSION_WS2008) { + newchannel->is_dedicated_interrupt = + (offer->is_dedicated_interrupt != 0); + newchannel->sig_event->connectionid.u.id = + offer->connection_id; + } + memcpy(&newchannel->offermsg, offer, sizeof(struct vmbus_channel_offer_channel)); newchannel->monitor_grp = (u8)offer->monitorid / 32; diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index cd48ac331708..1bc7500fb84c 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -101,15 +101,6 @@ enum hv_message_type { /* Define invalid partition identifier. */ #define HV_PARTITION_ID_INVALID ((u64)0x0) -/* Define connection identifier type. */ -union hv_connection_id { - u32 asu32; - struct { - u32 id:24; - u32 reserved:8; - } u; -}; - /* Define port identifier type. */ union hv_port_id { u32 asu32; @@ -338,13 +329,6 @@ struct hv_input_post_message { u64 payload[HV_MESSAGE_PAYLOAD_QWORD_COUNT]; }; -/* Definition of the hv_signal_event hypercall input structure. */ -struct hv_input_signal_event { - union hv_connection_id connectionid; - u16 flag_number; - u16 rsvdz; -}; - /* * Versioning definitions used for guests reporting themselves to the * hypervisor, and visa versa. @@ -498,11 +482,6 @@ static const uuid_le VMBUS_SERVICE_ID = { -struct hv_input_signal_event_buffer { - u64 align8; - struct hv_input_signal_event event; -}; - struct hv_context { /* We only support running on top of Hyper-V * So at this point this really can only contain the Hyper-V ID diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index e72502689cdc..c6e2c44a1be9 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -897,6 +897,27 @@ struct vmbus_close_msg { struct vmbus_channel_close_channel msg; }; +/* Define connection identifier type. */ +union hv_connection_id { + u32 asu32; + struct { + u32 id:24; + u32 reserved:8; + } u; +}; + +/* Definition of the hv_signal_event hypercall input structure. */ +struct hv_input_signal_event { + union hv_connection_id connectionid; + u16 flag_number; + u16 rsvdz; +}; + +struct hv_input_signal_event_buffer { + u64 align8; + struct hv_input_signal_event event; +}; + struct vmbus_channel { struct list_head listentry; @@ -946,6 +967,10 @@ struct vmbus_channel { */ bool batched_reading; + + bool is_dedicated_interrupt; + struct hv_input_signal_event_buffer sig_buf; + struct hv_input_signal_event *sig_event; }; static inline void set_channel_read_state(struct vmbus_channel *c, bool state) -- cgit v1.2.3 From abbf3b2aa090b4a6bf22c935924b6467990266da Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:48 -0800 Subject: Drivers: hv: Add state to manage incoming channel interrupt load Add state to bind a channel to a specific VCPU. This will help us better distribute incoming interrupt load. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/channel.c | 2 +- drivers/hv/channel_mgmt.c | 2 ++ include/linux/hyperv.h | 21 +++++++++++++++++++-- 3 files changed, 22 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index 70a34daa04c1..9303252b2e19 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -181,7 +181,7 @@ int vmbus_open(struct vmbus_channel *newchannel, u32 send_ringbuffer_size, open_msg->ringbuffer_gpadlhandle = newchannel->ringbuffer_gpadlhandle; open_msg->downstream_ringbuffer_pageoffset = send_ringbuffer_size >> PAGE_SHIFT; - open_msg->server_contextarea_gpadlhandle = 0; + open_msg->target_vp = newchannel->target_vp; if (userdatalen > MAX_USER_DEFINED_BYTES) { err = -EINVAL; diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index f4d990285d99..56ed45c74d01 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -302,6 +302,8 @@ static void vmbus_onoffer(struct vmbus_channel_message_header *hdr) offer->connection_id; } + newchannel->target_vp = 0; + memcpy(&newchannel->offermsg, offer, sizeof(struct vmbus_channel_offer_channel)); newchannel->monitor_grp = (u8)offer->monitorid / 32; diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index c6e2c44a1be9..8c3cb1fc34d4 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -732,8 +732,15 @@ struct vmbus_channel_open_channel { /* GPADL for the channel's ring buffer. */ u32 ringbuffer_gpadlhandle; - /* GPADL for the channel's server context save area. */ - u32 server_contextarea_gpadlhandle; + /* + * Starting with win8, this field will be used to specify + * the target virtual processor on which to deliver the interrupt for + * the host to guest communication. + * Prior to win8, incoming channel interrupts would only + * be delivered on cpu 0. Setting this value to 0 would + * preserve the earlier behavior. + */ + u32 target_vp; /* * The upstream ring buffer begins at offset zero in the memory @@ -971,6 +978,16 @@ struct vmbus_channel { bool is_dedicated_interrupt; struct hv_input_signal_event_buffer sig_buf; struct hv_input_signal_event *sig_event; + + /* + * Starting with win8, this field will be used to specify + * the target virtual processor on which to deliver the interrupt for + * the host to guest communication. + * Prior to win8, incoming channel interrupts would only + * be delivered on cpu 0. Setting this value to 0 would + * preserve the earlier behavior. + */ + u32 target_vp; }; static inline void set_channel_read_state(struct vmbus_channel *c, bool state) -- cgit v1.2.3 From 2a5c43a821b3b26e6af1cdb987b4daeba6f13a6f Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Sat, 1 Dec 2012 06:46:56 -0800 Subject: Drivers: hv: Enable protocol negotiation with win8 hosts Now that we have implemented all of the Win8 (WS2012) functionality, negotiate Win8 protocol with the host. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/connection.c | 2 +- include/linux/hyperv.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c index ac71653abb77..3965537a659d 100644 --- a/drivers/hv/connection.c +++ b/drivers/hv/connection.c @@ -194,7 +194,7 @@ int vmbus_connect(void) * version. */ - version = VERSION_WS2008; + version = VERSION_CURRENT; do { ret = vmbus_negotiate_version(msginfo, version); diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 8c3cb1fc34d4..5095b066df94 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -434,6 +434,7 @@ hv_get_ringbuffer_availbytes(struct hv_ring_buffer_info *rbi, #define VERSION_INVAL -1 +#define VERSION_CURRENT VERSION_WIN8 /* Make maximum size of pipe payload of 16K */ #define MAX_PIPE_DATA_PAYLOAD (sizeof(u8) * 16384) -- cgit v1.2.3 From fce8a7bb5b4bfb8a27324703fd5b002ee9247e90 Mon Sep 17 00:00:00 2001 From: Jon Mason Date: Fri, 16 Nov 2012 19:27:12 -0700 Subject: PCI-Express Non-Transparent Bridge Support A PCI-Express non-transparent bridge (NTB) is a point-to-point PCIe bus connecting 2 systems, providing electrical isolation between the two subsystems. A non-transparent bridge is functionally similar to a transparent bridge except that both sides of the bridge have their own independent address domains. The host on one side of the bridge will not have the visibility of the complete memory or I/O space on the other side of the bridge. To communicate across the non-transparent bridge, each NTB endpoint has one (or more) apertures exposed to the local system. Writes to these apertures are mirrored to memory on the remote system. Communications can also occur through the use of doorbell registers that initiate interrupts to the alternate domain, and scratch-pad registers accessible from both sides. The NTB device driver is needed to configure these memory windows, doorbell, and scratch-pad registers as well as use them in such a way as they can be turned into a viable communication channel to the remote system. ntb_hw.[ch] determines the usage model (NTB to NTB or NTB to Root Port) and abstracts away the underlying hardware to provide access and a common interface to the doorbell registers, scratch pads, and memory windows. These hardware interfaces are exported so that other, non-mainlined kernel drivers can access these. ntb_transport.[ch] also uses the exported interfaces in ntb_hw.[ch] to setup a communication channel(s) and provide a reliable way of transferring data from one side to the other, which it then exports so that "client" drivers can access them. These client drivers are used to provide a standard kernel interface (i.e., Ethernet device) to NTB, such that Linux can transfer data from one system to the other in a standard way. Signed-off-by: Jon Mason Reviewed-by: Nicholas Bellinger Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 6 + drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/ntb/Kconfig | 13 + drivers/ntb/Makefile | 3 + drivers/ntb/ntb_hw.c | 1157 +++++++++++++++++++++++++++++++++++ drivers/ntb/ntb_hw.h | 181 ++++++ drivers/ntb/ntb_regs.h | 139 +++++ drivers/ntb/ntb_transport.c | 1427 +++++++++++++++++++++++++++++++++++++++++++ include/linux/ntb.h | 83 +++ 10 files changed, 3012 insertions(+) create mode 100644 drivers/ntb/Kconfig create mode 100644 drivers/ntb/Makefile create mode 100644 drivers/ntb/ntb_hw.c create mode 100644 drivers/ntb/ntb_hw.h create mode 100644 drivers/ntb/ntb_regs.h create mode 100644 drivers/ntb/ntb_transport.c create mode 100644 include/linux/ntb.h (limited to 'include') diff --git a/MAINTAINERS b/MAINTAINERS index 915564eda145..c5d675a8c53d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5394,6 +5394,12 @@ S: Maintained F: Documentation/scsi/NinjaSCSI.txt F: drivers/scsi/nsp32* +NTB DRIVER +M: Jon Mason +S: Supported +F: drivers/ntb/ +F: include/linux/ntb.h + NTFS FILESYSTEM M: Anton Altaparmakov L: linux-ntfs-dev@lists.sourceforge.net diff --git a/drivers/Kconfig b/drivers/Kconfig index f5fb0722a63a..c13044cb8aa9 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -150,6 +150,8 @@ source "drivers/memory/Kconfig" source "drivers/iio/Kconfig" +source "drivers/ntb/Kconfig" + source "drivers/vme/Kconfig" source "drivers/pwm/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index 7863b9fee50b..3d92e1269672 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -146,3 +146,4 @@ obj-$(CONFIG_MEMORY) += memory/ obj-$(CONFIG_IIO) += iio/ obj-$(CONFIG_VME_BUS) += vme/ obj-$(CONFIG_IPACK_BUS) += ipack/ +obj-$(CONFIG_NTB) += ntb/ diff --git a/drivers/ntb/Kconfig b/drivers/ntb/Kconfig new file mode 100644 index 000000000000..f69df793dbe2 --- /dev/null +++ b/drivers/ntb/Kconfig @@ -0,0 +1,13 @@ +config NTB + tristate "Intel Non-Transparent Bridge support" + depends on PCI + depends on X86 + help + The PCI-E Non-transparent bridge hardware is a point-to-point PCI-E bus + connecting 2 systems. When configured, writes to the device's PCI + mapped memory will be mirrored to a buffer on the remote system. The + ntb Linux driver uses this point-to-point communication as a method to + transfer data from one system to the other. + + If unsure, say N. + diff --git a/drivers/ntb/Makefile b/drivers/ntb/Makefile new file mode 100644 index 000000000000..15cb59fd354e --- /dev/null +++ b/drivers/ntb/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_NTB) += ntb.o + +ntb-objs := ntb_hw.o ntb_transport.o diff --git a/drivers/ntb/ntb_hw.c b/drivers/ntb/ntb_hw.c new file mode 100644 index 000000000000..facad51fbc7a --- /dev/null +++ b/drivers/ntb/ntb_hw.c @@ -0,0 +1,1157 @@ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * BSD LICENSE + * + * Copyright(c) 2012 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copy + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Intel PCIe NTB Linux driver + * + * Contact Information: + * Jon Mason + */ +#include +#include +#include +#include +#include +#include +#include "ntb_hw.h" +#include "ntb_regs.h" + +#define NTB_NAME "Intel(R) PCI-E Non-Transparent Bridge Driver" +#define NTB_VER "0.24" + +MODULE_DESCRIPTION(NTB_NAME); +MODULE_VERSION(NTB_VER); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_AUTHOR("Intel Corporation"); + +enum { + NTB_CONN_CLASSIC = 0, + NTB_CONN_B2B, + NTB_CONN_RP, +}; + +enum { + NTB_DEV_USD = 0, + NTB_DEV_DSD, +}; + +enum { + SNB_HW = 0, + BWD_HW, +}; + +/* Translate memory window 0,1 to BAR 2,4 */ +#define MW_TO_BAR(mw) (mw * 2 + 2) + +static DEFINE_PCI_DEVICE_TABLE(ntb_pci_tbl) = { + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_NTB_B2B_BWD)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_NTB_B2B_JSF)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_NTB_CLASSIC_JSF)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_NTB_RP_JSF)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_NTB_RP_SNB)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_NTB_B2B_SNB)}, + {PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_NTB_CLASSIC_SNB)}, + {0} +}; +MODULE_DEVICE_TABLE(pci, ntb_pci_tbl); + +/** + * ntb_register_event_callback() - register event callback + * @ndev: pointer to ntb_device instance + * @func: callback function to register + * + * This function registers a callback for any HW driver events such as link + * up/down, power management notices and etc. + * + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +int ntb_register_event_callback(struct ntb_device *ndev, + void (*func)(void *handle, unsigned int event)) +{ + if (ndev->event_cb) + return -EINVAL; + + ndev->event_cb = func; + + return 0; +} + +/** + * ntb_unregister_event_callback() - unregisters the event callback + * @ndev: pointer to ntb_device instance + * + * This function unregisters the existing callback from transport + */ +void ntb_unregister_event_callback(struct ntb_device *ndev) +{ + ndev->event_cb = NULL; +} + +/** + * ntb_register_db_callback() - register a callback for doorbell interrupt + * @ndev: pointer to ntb_device instance + * @idx: doorbell index to register callback, zero based + * @func: callback function to register + * + * This function registers a callback function for the doorbell interrupt + * on the primary side. The function will unmask the doorbell as well to + * allow interrupt. + * + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +int ntb_register_db_callback(struct ntb_device *ndev, unsigned int idx, + void *data, void (*func)(void *data, int db_num)) +{ + unsigned long mask; + + if (idx >= ndev->max_cbs || ndev->db_cb[idx].callback) { + dev_warn(&ndev->pdev->dev, "Invalid Index.\n"); + return -EINVAL; + } + + ndev->db_cb[idx].callback = func; + ndev->db_cb[idx].data = data; + + /* unmask interrupt */ + mask = readw(ndev->reg_ofs.pdb_mask); + clear_bit(idx * ndev->bits_per_vector, &mask); + writew(mask, ndev->reg_ofs.pdb_mask); + + return 0; +} + +/** + * ntb_unregister_db_callback() - unregister a callback for doorbell interrupt + * @ndev: pointer to ntb_device instance + * @idx: doorbell index to register callback, zero based + * + * This function unregisters a callback function for the doorbell interrupt + * on the primary side. The function will also mask the said doorbell. + */ +void ntb_unregister_db_callback(struct ntb_device *ndev, unsigned int idx) +{ + unsigned long mask; + + if (idx >= ndev->max_cbs || !ndev->db_cb[idx].callback) + return; + + mask = readw(ndev->reg_ofs.pdb_mask); + set_bit(idx * ndev->bits_per_vector, &mask); + writew(mask, ndev->reg_ofs.pdb_mask); + + ndev->db_cb[idx].callback = NULL; +} + +/** + * ntb_find_transport() - find the transport pointer + * @transport: pointer to pci device + * + * Given the pci device pointer, return the transport pointer passed in when + * the transport attached when it was inited. + * + * RETURNS: pointer to transport. + */ +void *ntb_find_transport(struct pci_dev *pdev) +{ + struct ntb_device *ndev = pci_get_drvdata(pdev); + return ndev->ntb_transport; +} + +/** + * ntb_register_transport() - Register NTB transport with NTB HW driver + * @transport: transport identifier + * + * This function allows a transport to reserve the hardware driver for + * NTB usage. + * + * RETURNS: pointer to ntb_device, NULL on error. + */ +struct ntb_device *ntb_register_transport(struct pci_dev *pdev, void *transport) +{ + struct ntb_device *ndev = pci_get_drvdata(pdev); + + if (ndev->ntb_transport) + return NULL; + + ndev->ntb_transport = transport; + return ndev; +} + +/** + * ntb_unregister_transport() - Unregister the transport with the NTB HW driver + * @ndev - ntb_device of the transport to be freed + * + * This function unregisters the transport from the HW driver and performs any + * necessary cleanups. + */ +void ntb_unregister_transport(struct ntb_device *ndev) +{ + int i; + + if (!ndev->ntb_transport) + return; + + for (i = 0; i < ndev->max_cbs; i++) + ntb_unregister_db_callback(ndev, i); + + ntb_unregister_event_callback(ndev); + ndev->ntb_transport = NULL; +} + +/** + * ntb_get_max_spads() - get the total scratch regs usable + * @ndev: pointer to ntb_device instance + * + * This function returns the max 32bit scratchpad registers usable by the + * upper layer. + * + * RETURNS: total number of scratch pad registers available + */ +int ntb_get_max_spads(struct ntb_device *ndev) +{ + return ndev->limits.max_spads; +} + +/** + * ntb_write_local_spad() - write to the secondary scratchpad register + * @ndev: pointer to ntb_device instance + * @idx: index to the scratchpad register, 0 based + * @val: the data value to put into the register + * + * This function allows writing of a 32bit value to the indexed scratchpad + * register. This writes over the data mirrored to the local scratchpad register + * by the remote system. + * + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +int ntb_write_local_spad(struct ntb_device *ndev, unsigned int idx, u32 val) +{ + if (idx >= ndev->limits.max_spads) + return -EINVAL; + + dev_dbg(&ndev->pdev->dev, "Writing %x to local scratch pad index %d\n", + val, idx); + writel(val, ndev->reg_ofs.spad_read + idx * 4); + + return 0; +} + +/** + * ntb_read_local_spad() - read from the primary scratchpad register + * @ndev: pointer to ntb_device instance + * @idx: index to scratchpad register, 0 based + * @val: pointer to 32bit integer for storing the register value + * + * This function allows reading of the 32bit scratchpad register on + * the primary (internal) side. This allows the local system to read data + * written and mirrored to the scratchpad register by the remote system. + * + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +int ntb_read_local_spad(struct ntb_device *ndev, unsigned int idx, u32 *val) +{ + if (idx >= ndev->limits.max_spads) + return -EINVAL; + + *val = readl(ndev->reg_ofs.spad_write + idx * 4); + dev_dbg(&ndev->pdev->dev, + "Reading %x from local scratch pad index %d\n", *val, idx); + + return 0; +} + +/** + * ntb_write_remote_spad() - write to the secondary scratchpad register + * @ndev: pointer to ntb_device instance + * @idx: index to the scratchpad register, 0 based + * @val: the data value to put into the register + * + * This function allows writing of a 32bit value to the indexed scratchpad + * register. The register resides on the secondary (external) side. This allows + * the local system to write data to be mirrored to the remote systems + * scratchpad register. + * + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +int ntb_write_remote_spad(struct ntb_device *ndev, unsigned int idx, u32 val) +{ + if (idx >= ndev->limits.max_spads) + return -EINVAL; + + dev_dbg(&ndev->pdev->dev, "Writing %x to remote scratch pad index %d\n", + val, idx); + writel(val, ndev->reg_ofs.spad_write + idx * 4); + + return 0; +} + +/** + * ntb_read_remote_spad() - read from the primary scratchpad register + * @ndev: pointer to ntb_device instance + * @idx: index to scratchpad register, 0 based + * @val: pointer to 32bit integer for storing the register value + * + * This function allows reading of the 32bit scratchpad register on + * the primary (internal) side. This alloows the local system to read the data + * it wrote to be mirrored on the remote system. + * + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +int ntb_read_remote_spad(struct ntb_device *ndev, unsigned int idx, u32 *val) +{ + if (idx >= ndev->limits.max_spads) + return -EINVAL; + + *val = readl(ndev->reg_ofs.spad_read + idx * 4); + dev_dbg(&ndev->pdev->dev, + "Reading %x from remote scratch pad index %d\n", *val, idx); + + return 0; +} + +/** + * ntb_get_mw_vbase() - get virtual addr for the NTB memory window + * @ndev: pointer to ntb_device instance + * @mw: memory window number + * + * This function provides the base virtual address of the memory window + * specified. + * + * RETURNS: pointer to virtual address, or NULL on error. + */ +void *ntb_get_mw_vbase(struct ntb_device *ndev, unsigned int mw) +{ + if (mw > NTB_NUM_MW) + return NULL; + + return ndev->mw[mw].vbase; +} + +/** + * ntb_get_mw_size() - return size of NTB memory window + * @ndev: pointer to ntb_device instance + * @mw: memory window number + * + * This function provides the physical size of the memory window specified + * + * RETURNS: the size of the memory window or zero on error + */ +resource_size_t ntb_get_mw_size(struct ntb_device *ndev, unsigned int mw) +{ + if (mw > NTB_NUM_MW) + return 0; + + return ndev->mw[mw].bar_sz; +} + +/** + * ntb_set_mw_addr - set the memory window address + * @ndev: pointer to ntb_device instance + * @mw: memory window number + * @addr: base address for data + * + * This function sets the base physical address of the memory window. This + * memory address is where data from the remote system will be transfered into + * or out of depending on how the transport is configured. + */ +void ntb_set_mw_addr(struct ntb_device *ndev, unsigned int mw, u64 addr) +{ + if (mw > NTB_NUM_MW) + return; + + dev_dbg(&ndev->pdev->dev, "Writing addr %Lx to BAR %d\n", addr, + MW_TO_BAR(mw)); + + ndev->mw[mw].phys_addr = addr; + + switch (MW_TO_BAR(mw)) { + case NTB_BAR_23: + writeq(addr, ndev->reg_ofs.sbar2_xlat); + break; + case NTB_BAR_45: + writeq(addr, ndev->reg_ofs.sbar4_xlat); + break; + } +} + +/** + * ntb_ring_sdb() - Set the doorbell on the secondary/external side + * @ndev: pointer to ntb_device instance + * @db: doorbell to ring + * + * This function allows triggering of a doorbell on the secondary/external + * side that will initiate an interrupt on the remote host + * + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +void ntb_ring_sdb(struct ntb_device *ndev, unsigned int db) +{ + dev_dbg(&ndev->pdev->dev, "%s: ringing doorbell %d\n", __func__, db); + + if (ndev->hw_type == BWD_HW) + writeq((u64) 1 << db, ndev->reg_ofs.sdb); + else + writew(((1 << ndev->bits_per_vector) - 1) << + (db * ndev->bits_per_vector), ndev->reg_ofs.sdb); +} + +static void ntb_link_event(struct ntb_device *ndev, int link_state) +{ + unsigned int event; + + if (ndev->link_status == link_state) + return; + + if (link_state == NTB_LINK_UP) { + u16 status; + + dev_info(&ndev->pdev->dev, "Link Up\n"); + ndev->link_status = NTB_LINK_UP; + event = NTB_EVENT_HW_LINK_UP; + + if (ndev->hw_type == BWD_HW) + status = readw(ndev->reg_ofs.lnk_stat); + else { + int rc = pci_read_config_word(ndev->pdev, + SNB_LINK_STATUS_OFFSET, + &status); + if (rc) + return; + } + dev_info(&ndev->pdev->dev, "Link Width %d, Link Speed %d\n", + (status & NTB_LINK_WIDTH_MASK) >> 4, + (status & NTB_LINK_SPEED_MASK)); + } else { + dev_info(&ndev->pdev->dev, "Link Down\n"); + ndev->link_status = NTB_LINK_DOWN; + event = NTB_EVENT_HW_LINK_DOWN; + } + + /* notify the upper layer if we have an event change */ + if (ndev->event_cb) + ndev->event_cb(ndev->ntb_transport, event); +} + +static int ntb_link_status(struct ntb_device *ndev) +{ + int link_state; + + if (ndev->hw_type == BWD_HW) { + u32 ntb_cntl; + + ntb_cntl = readl(ndev->reg_ofs.lnk_cntl); + if (ntb_cntl & BWD_CNTL_LINK_DOWN) + link_state = NTB_LINK_DOWN; + else + link_state = NTB_LINK_UP; + } else { + u16 status; + int rc; + + rc = pci_read_config_word(ndev->pdev, SNB_LINK_STATUS_OFFSET, + &status); + if (rc) + return rc; + + if (status & NTB_LINK_STATUS_ACTIVE) + link_state = NTB_LINK_UP; + else + link_state = NTB_LINK_DOWN; + } + + ntb_link_event(ndev, link_state); + + return 0; +} + +/* BWD doesn't have link status interrupt, poll on that platform */ +static void bwd_link_poll(struct work_struct *work) +{ + struct ntb_device *ndev = container_of(work, struct ntb_device, + hb_timer.work); + unsigned long ts = jiffies; + + /* If we haven't gotten an interrupt in a while, check the BWD link + * status bit + */ + if (ts > ndev->last_ts + NTB_HB_TIMEOUT) { + int rc = ntb_link_status(ndev); + if (rc) + dev_err(&ndev->pdev->dev, + "Error determining link status\n"); + } + + schedule_delayed_work(&ndev->hb_timer, NTB_HB_TIMEOUT); +} + +static int ntb_xeon_setup(struct ntb_device *ndev) +{ + int rc; + u8 val; + + ndev->hw_type = SNB_HW; + + rc = pci_read_config_byte(ndev->pdev, NTB_PPD_OFFSET, &val); + if (rc) + return rc; + + switch (val & SNB_PPD_CONN_TYPE) { + case NTB_CONN_B2B: + ndev->conn_type = NTB_CONN_B2B; + break; + case NTB_CONN_CLASSIC: + case NTB_CONN_RP: + default: + dev_err(&ndev->pdev->dev, "Only B2B supported at this time\n"); + return -EINVAL; + } + + if (val & SNB_PPD_DEV_TYPE) + ndev->dev_type = NTB_DEV_DSD; + else + ndev->dev_type = NTB_DEV_USD; + + ndev->reg_ofs.pdb = ndev->reg_base + SNB_PDOORBELL_OFFSET; + ndev->reg_ofs.pdb_mask = ndev->reg_base + SNB_PDBMSK_OFFSET; + ndev->reg_ofs.sbar2_xlat = ndev->reg_base + SNB_SBAR2XLAT_OFFSET; + ndev->reg_ofs.sbar4_xlat = ndev->reg_base + SNB_SBAR4XLAT_OFFSET; + ndev->reg_ofs.lnk_cntl = ndev->reg_base + SNB_NTBCNTL_OFFSET; + ndev->reg_ofs.lnk_stat = ndev->reg_base + SNB_LINK_STATUS_OFFSET; + ndev->reg_ofs.spad_read = ndev->reg_base + SNB_SPAD_OFFSET; + ndev->reg_ofs.spci_cmd = ndev->reg_base + SNB_PCICMD_OFFSET; + + if (ndev->conn_type == NTB_CONN_B2B) { + ndev->reg_ofs.sdb = ndev->reg_base + SNB_B2B_DOORBELL_OFFSET; + ndev->reg_ofs.spad_write = ndev->reg_base + SNB_B2B_SPAD_OFFSET; + ndev->limits.max_spads = SNB_MAX_SPADS; + } else { + ndev->reg_ofs.sdb = ndev->reg_base + SNB_SDOORBELL_OFFSET; + ndev->reg_ofs.spad_write = ndev->reg_base + SNB_SPAD_OFFSET; + ndev->limits.max_spads = SNB_MAX_COMPAT_SPADS; + } + + ndev->limits.max_db_bits = SNB_MAX_DB_BITS; + ndev->limits.msix_cnt = SNB_MSIX_CNT; + ndev->bits_per_vector = SNB_DB_BITS_PER_VEC; + + return 0; +} + +static int ntb_bwd_setup(struct ntb_device *ndev) +{ + int rc; + u32 val; + + ndev->hw_type = BWD_HW; + + rc = pci_read_config_dword(ndev->pdev, NTB_PPD_OFFSET, &val); + if (rc) + return rc; + + switch ((val & BWD_PPD_CONN_TYPE) >> 8) { + case NTB_CONN_B2B: + ndev->conn_type = NTB_CONN_B2B; + break; + case NTB_CONN_RP: + default: + dev_err(&ndev->pdev->dev, "Only B2B supported at this time\n"); + return -EINVAL; + } + + if (val & BWD_PPD_DEV_TYPE) + ndev->dev_type = NTB_DEV_DSD; + else + ndev->dev_type = NTB_DEV_USD; + + /* Initiate PCI-E link training */ + rc = pci_write_config_dword(ndev->pdev, NTB_PPD_OFFSET, + val | BWD_PPD_INIT_LINK); + if (rc) + return rc; + + ndev->reg_ofs.pdb = ndev->reg_base + BWD_PDOORBELL_OFFSET; + ndev->reg_ofs.pdb_mask = ndev->reg_base + BWD_PDBMSK_OFFSET; + ndev->reg_ofs.sbar2_xlat = ndev->reg_base + BWD_SBAR2XLAT_OFFSET; + ndev->reg_ofs.sbar4_xlat = ndev->reg_base + BWD_SBAR4XLAT_OFFSET; + ndev->reg_ofs.lnk_cntl = ndev->reg_base + BWD_NTBCNTL_OFFSET; + ndev->reg_ofs.lnk_stat = ndev->reg_base + BWD_LINK_STATUS_OFFSET; + ndev->reg_ofs.spad_read = ndev->reg_base + BWD_SPAD_OFFSET; + ndev->reg_ofs.spci_cmd = ndev->reg_base + BWD_PCICMD_OFFSET; + + if (ndev->conn_type == NTB_CONN_B2B) { + ndev->reg_ofs.sdb = ndev->reg_base + BWD_B2B_DOORBELL_OFFSET; + ndev->reg_ofs.spad_write = ndev->reg_base + BWD_B2B_SPAD_OFFSET; + ndev->limits.max_spads = BWD_MAX_SPADS; + } else { + ndev->reg_ofs.sdb = ndev->reg_base + BWD_PDOORBELL_OFFSET; + ndev->reg_ofs.spad_write = ndev->reg_base + BWD_SPAD_OFFSET; + ndev->limits.max_spads = BWD_MAX_COMPAT_SPADS; + } + + ndev->limits.max_db_bits = BWD_MAX_DB_BITS; + ndev->limits.msix_cnt = BWD_MSIX_CNT; + ndev->bits_per_vector = BWD_DB_BITS_PER_VEC; + + /* Since bwd doesn't have a link interrupt, setup a poll timer */ + INIT_DELAYED_WORK(&ndev->hb_timer, bwd_link_poll); + schedule_delayed_work(&ndev->hb_timer, NTB_HB_TIMEOUT); + + return 0; +} + +static int __devinit ntb_device_setup(struct ntb_device *ndev) +{ + int rc; + + switch (ndev->pdev->device) { + case PCI_DEVICE_ID_INTEL_NTB_2ND_SNB: + case PCI_DEVICE_ID_INTEL_NTB_RP_JSF: + case PCI_DEVICE_ID_INTEL_NTB_RP_SNB: + case PCI_DEVICE_ID_INTEL_NTB_CLASSIC_JSF: + case PCI_DEVICE_ID_INTEL_NTB_CLASSIC_SNB: + case PCI_DEVICE_ID_INTEL_NTB_B2B_JSF: + case PCI_DEVICE_ID_INTEL_NTB_B2B_SNB: + rc = ntb_xeon_setup(ndev); + break; + case PCI_DEVICE_ID_INTEL_NTB_B2B_BWD: + rc = ntb_bwd_setup(ndev); + break; + default: + rc = -ENODEV; + } + + /* Enable Bus Master and Memory Space on the secondary side */ + writew(PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER, ndev->reg_ofs.spci_cmd); + + return rc; +} + +static void ntb_device_free(struct ntb_device *ndev) +{ + if (ndev->hw_type == BWD_HW) + cancel_delayed_work_sync(&ndev->hb_timer); +} + +static irqreturn_t bwd_callback_msix_irq(int irq, void *data) +{ + struct ntb_db_cb *db_cb = data; + struct ntb_device *ndev = db_cb->ndev; + + dev_dbg(&ndev->pdev->dev, "MSI-X irq %d received for DB %d\n", irq, + db_cb->db_num); + + if (db_cb->callback) + db_cb->callback(db_cb->data, db_cb->db_num); + + /* No need to check for the specific HB irq, any interrupt means + * we're connected. + */ + ndev->last_ts = jiffies; + + writeq((u64) 1 << db_cb->db_num, ndev->reg_ofs.pdb); + + return IRQ_HANDLED; +} + +static irqreturn_t xeon_callback_msix_irq(int irq, void *data) +{ + struct ntb_db_cb *db_cb = data; + struct ntb_device *ndev = db_cb->ndev; + + dev_dbg(&ndev->pdev->dev, "MSI-X irq %d received for DB %d\n", irq, + db_cb->db_num); + + if (db_cb->callback) + db_cb->callback(db_cb->data, db_cb->db_num); + + /* On Sandybridge, there are 16 bits in the interrupt register + * but only 4 vectors. So, 5 bits are assigned to the first 3 + * vectors, with the 4th having a single bit for link + * interrupts. + */ + writew(((1 << ndev->bits_per_vector) - 1) << + (db_cb->db_num * ndev->bits_per_vector), ndev->reg_ofs.pdb); + + return IRQ_HANDLED; +} + +/* Since we do not have a HW doorbell in BWD, this is only used in JF/JT */ +static irqreturn_t xeon_event_msix_irq(int irq, void *dev) +{ + struct ntb_device *ndev = dev; + int rc; + + dev_dbg(&ndev->pdev->dev, "MSI-X irq %d received for Events\n", irq); + + rc = ntb_link_status(ndev); + if (rc) + dev_err(&ndev->pdev->dev, "Error determining link status\n"); + + /* bit 15 is always the link bit */ + writew(1 << ndev->limits.max_db_bits, ndev->reg_ofs.pdb); + + return IRQ_HANDLED; +} + +static irqreturn_t ntb_interrupt(int irq, void *dev) +{ + struct ntb_device *ndev = dev; + unsigned int i = 0; + + if (ndev->hw_type == BWD_HW) { + u64 pdb = readq(ndev->reg_ofs.pdb); + + dev_dbg(&ndev->pdev->dev, "irq %d - pdb = %Lx\n", irq, pdb); + + while (pdb) { + i = __ffs(pdb); + pdb &= pdb - 1; + bwd_callback_msix_irq(irq, &ndev->db_cb[i]); + } + } else { + u16 pdb = readw(ndev->reg_ofs.pdb); + + dev_dbg(&ndev->pdev->dev, "irq %d - pdb = %x sdb %x\n", irq, + pdb, readw(ndev->reg_ofs.sdb)); + + if (pdb & SNB_DB_HW_LINK) { + xeon_event_msix_irq(irq, dev); + pdb &= ~SNB_DB_HW_LINK; + } + + while (pdb) { + i = __ffs(pdb); + pdb &= pdb - 1; + xeon_callback_msix_irq(irq, &ndev->db_cb[i]); + } + } + + return IRQ_HANDLED; +} + +static int ntb_setup_msix(struct ntb_device *ndev) +{ + struct pci_dev *pdev = ndev->pdev; + struct msix_entry *msix; + int msix_entries; + int rc, i, pos; + u16 val; + + pos = pci_find_capability(pdev, PCI_CAP_ID_MSIX); + if (!pos) { + rc = -EIO; + goto err; + } + + rc = pci_read_config_word(pdev, pos + PCI_MSIX_FLAGS, &val); + if (rc) + goto err; + + msix_entries = msix_table_size(val); + if (msix_entries > ndev->limits.msix_cnt) { + rc = -EINVAL; + goto err; + } + + ndev->msix_entries = kmalloc(sizeof(struct msix_entry) * msix_entries, + GFP_KERNEL); + if (!ndev->msix_entries) { + rc = -ENOMEM; + goto err; + } + + for (i = 0; i < msix_entries; i++) + ndev->msix_entries[i].entry = i; + + rc = pci_enable_msix(pdev, ndev->msix_entries, msix_entries); + if (rc < 0) + goto err1; + if (rc > 0) { + /* On SNB, the link interrupt is always tied to 4th vector. If + * we can't get all 4, then we can't use MSI-X. + */ + if (ndev->hw_type != BWD_HW) { + rc = -EIO; + goto err1; + } + + dev_warn(&pdev->dev, + "Only %d MSI-X vectors. Limiting the number of queues to that number.\n", + rc); + msix_entries = rc; + } + + for (i = 0; i < msix_entries; i++) { + msix = &ndev->msix_entries[i]; + WARN_ON(!msix->vector); + + /* Use the last MSI-X vector for Link status */ + if (ndev->hw_type == BWD_HW) { + rc = request_irq(msix->vector, bwd_callback_msix_irq, 0, + "ntb-callback-msix", &ndev->db_cb[i]); + if (rc) + goto err2; + } else { + if (i == msix_entries - 1) { + rc = request_irq(msix->vector, + xeon_event_msix_irq, 0, + "ntb-event-msix", ndev); + if (rc) + goto err2; + } else { + rc = request_irq(msix->vector, + xeon_callback_msix_irq, 0, + "ntb-callback-msix", + &ndev->db_cb[i]); + if (rc) + goto err2; + } + } + } + + ndev->num_msix = msix_entries; + if (ndev->hw_type == BWD_HW) + ndev->max_cbs = msix_entries; + else + ndev->max_cbs = msix_entries - 1; + + return 0; + +err2: + while (--i >= 0) { + msix = &ndev->msix_entries[i]; + if (ndev->hw_type != BWD_HW && i == ndev->num_msix - 1) + free_irq(msix->vector, ndev); + else + free_irq(msix->vector, &ndev->db_cb[i]); + } + pci_disable_msix(pdev); +err1: + kfree(ndev->msix_entries); + dev_err(&pdev->dev, "Error allocating MSI-X interrupt\n"); +err: + ndev->num_msix = 0; + return rc; +} + +static int ntb_setup_msi(struct ntb_device *ndev) +{ + struct pci_dev *pdev = ndev->pdev; + int rc; + + rc = pci_enable_msi(pdev); + if (rc) + return rc; + + rc = request_irq(pdev->irq, ntb_interrupt, 0, "ntb-msi", ndev); + if (rc) { + pci_disable_msi(pdev); + dev_err(&pdev->dev, "Error allocating MSI interrupt\n"); + return rc; + } + + return 0; +} + +static int ntb_setup_intx(struct ntb_device *ndev) +{ + struct pci_dev *pdev = ndev->pdev; + int rc; + + pci_msi_off(pdev); + + /* Verify intx is enabled */ + pci_intx(pdev, 1); + + rc = request_irq(pdev->irq, ntb_interrupt, IRQF_SHARED, "ntb-intx", + ndev); + if (rc) + return rc; + + return 0; +} + +static int __devinit ntb_setup_interrupts(struct ntb_device *ndev) +{ + int rc; + + /* On BWD, disable all interrupts. On SNB, disable all but Link + * Interrupt. The rest will be unmasked as callbacks are registered. + */ + if (ndev->hw_type == BWD_HW) + writeq(~0, ndev->reg_ofs.pdb_mask); + else + writew(~(1 << ndev->limits.max_db_bits), + ndev->reg_ofs.pdb_mask); + + rc = ntb_setup_msix(ndev); + if (!rc) + goto done; + + ndev->bits_per_vector = 1; + ndev->max_cbs = ndev->limits.max_db_bits; + + rc = ntb_setup_msi(ndev); + if (!rc) + goto done; + + rc = ntb_setup_intx(ndev); + if (rc) { + dev_err(&ndev->pdev->dev, "no usable interrupts\n"); + return rc; + } + +done: + return 0; +} + +static void __devexit ntb_free_interrupts(struct ntb_device *ndev) +{ + struct pci_dev *pdev = ndev->pdev; + + /* mask interrupts */ + if (ndev->hw_type == BWD_HW) + writeq(~0, ndev->reg_ofs.pdb_mask); + else + writew(~0, ndev->reg_ofs.pdb_mask); + + if (ndev->num_msix) { + struct msix_entry *msix; + u32 i; + + for (i = 0; i < ndev->num_msix; i++) { + msix = &ndev->msix_entries[i]; + if (ndev->hw_type != BWD_HW && i == ndev->num_msix - 1) + free_irq(msix->vector, ndev); + else + free_irq(msix->vector, &ndev->db_cb[i]); + } + pci_disable_msix(pdev); + } else { + free_irq(pdev->irq, ndev); + + if (pci_dev_msi_enabled(pdev)) + pci_disable_msi(pdev); + } +} + +static int __devinit ntb_create_callbacks(struct ntb_device *ndev) +{ + int i; + + /* Checken-egg issue. We won't know how many callbacks are necessary + * until we see how many MSI-X vectors we get, but these pointers need + * to be passed into the MSI-X register fucntion. So, we allocate the + * max, knowing that they might not all be used, to work around this. + */ + ndev->db_cb = kcalloc(ndev->limits.max_db_bits, + sizeof(struct ntb_db_cb), + GFP_KERNEL); + if (!ndev->db_cb) + return -ENOMEM; + + for (i = 0; i < ndev->limits.max_db_bits; i++) { + ndev->db_cb[i].db_num = i; + ndev->db_cb[i].ndev = ndev; + } + + return 0; +} + +static void ntb_free_callbacks(struct ntb_device *ndev) +{ + int i; + + for (i = 0; i < ndev->limits.max_db_bits; i++) + ntb_unregister_db_callback(ndev, i); + + kfree(ndev->db_cb); +} + +static int __devinit +ntb_pci_probe(struct pci_dev *pdev, + __attribute__((unused)) const struct pci_device_id *id) +{ + struct ntb_device *ndev; + int rc, i; + + ndev = kzalloc(sizeof(struct ntb_device), GFP_KERNEL); + if (!ndev) + return -ENOMEM; + + ndev->pdev = pdev; + ndev->link_status = NTB_LINK_DOWN; + pci_set_drvdata(pdev, ndev); + + rc = pci_enable_device(pdev); + if (rc) + goto err; + + pci_set_master(ndev->pdev); + + rc = pci_request_selected_regions(pdev, NTB_BAR_MASK, KBUILD_MODNAME); + if (rc) + goto err1; + + ndev->reg_base = pci_ioremap_bar(pdev, NTB_BAR_MMIO); + if (!ndev->reg_base) { + dev_warn(&pdev->dev, "Cannot remap BAR 0\n"); + rc = -EIO; + goto err2; + } + + for (i = 0; i < NTB_NUM_MW; i++) { + ndev->mw[i].bar_sz = pci_resource_len(pdev, MW_TO_BAR(i)); + ndev->mw[i].vbase = + ioremap_wc(pci_resource_start(pdev, MW_TO_BAR(i)), + ndev->mw[i].bar_sz); + dev_info(&pdev->dev, "MW %d size %d\n", i, + (u32) pci_resource_len(pdev, MW_TO_BAR(i))); + if (!ndev->mw[i].vbase) { + dev_warn(&pdev->dev, "Cannot remap BAR %d\n", + MW_TO_BAR(i)); + rc = -EIO; + goto err3; + } + } + + rc = pci_set_dma_mask(pdev, DMA_BIT_MASK(64)); + if (rc) { + rc = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); + if (rc) + goto err3; + + dev_warn(&pdev->dev, "Cannot DMA highmem\n"); + } + + rc = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64)); + if (rc) { + rc = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32)); + if (rc) + goto err3; + + dev_warn(&pdev->dev, "Cannot DMA consistent highmem\n"); + } + + rc = ntb_device_setup(ndev); + if (rc) + goto err3; + + rc = ntb_create_callbacks(ndev); + if (rc) + goto err4; + + rc = ntb_setup_interrupts(ndev); + if (rc) + goto err5; + + /* The scratchpad registers keep the values between rmmod/insmod, + * blast them now + */ + for (i = 0; i < ndev->limits.max_spads; i++) { + ntb_write_local_spad(ndev, i, 0); + ntb_write_remote_spad(ndev, i, 0); + } + + rc = ntb_transport_init(pdev); + if (rc) + goto err6; + + /* Let's bring the NTB link up */ + writel(NTB_CNTL_BAR23_SNOOP | NTB_CNTL_BAR45_SNOOP, + ndev->reg_ofs.lnk_cntl); + + return 0; + +err6: + ntb_free_interrupts(ndev); +err5: + ntb_free_callbacks(ndev); +err4: + ntb_device_free(ndev); +err3: + for (i--; i >= 0; i--) + iounmap(ndev->mw[i].vbase); + iounmap(ndev->reg_base); +err2: + pci_release_selected_regions(pdev, NTB_BAR_MASK); +err1: + pci_disable_device(pdev); +err: + kfree(ndev); + + dev_err(&pdev->dev, "Error loading %s module\n", KBUILD_MODNAME); + return rc; +} + +static void __devexit ntb_pci_remove(struct pci_dev *pdev) +{ + struct ntb_device *ndev = pci_get_drvdata(pdev); + int i; + u32 ntb_cntl; + + /* Bring NTB link down */ + ntb_cntl = readl(ndev->reg_ofs.lnk_cntl); + ntb_cntl |= NTB_LINK_DISABLE; + writel(ntb_cntl, ndev->reg_ofs.lnk_cntl); + + ntb_transport_free(ndev->ntb_transport); + + ntb_free_interrupts(ndev); + ntb_free_callbacks(ndev); + ntb_device_free(ndev); + + for (i = 0; i < NTB_NUM_MW; i++) + iounmap(ndev->mw[i].vbase); + + iounmap(ndev->reg_base); + pci_release_selected_regions(pdev, NTB_BAR_MASK); + pci_disable_device(pdev); + kfree(ndev); +} + +static struct pci_driver ntb_pci_driver = { + .name = KBUILD_MODNAME, + .id_table = ntb_pci_tbl, + .probe = ntb_pci_probe, + .remove = __devexit_p(ntb_pci_remove), +}; +module_pci_driver(ntb_pci_driver); diff --git a/drivers/ntb/ntb_hw.h b/drivers/ntb/ntb_hw.h new file mode 100644 index 000000000000..5e009519c64f --- /dev/null +++ b/drivers/ntb/ntb_hw.h @@ -0,0 +1,181 @@ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * BSD LICENSE + * + * Copyright(c) 2012 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copy + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Intel PCIe NTB Linux driver + * + * Contact Information: + * Jon Mason + */ + +#define PCI_DEVICE_ID_INTEL_NTB_B2B_JSF 0x3725 +#define PCI_DEVICE_ID_INTEL_NTB_CLASSIC_JSF 0x3726 +#define PCI_DEVICE_ID_INTEL_NTB_RP_JSF 0x3727 +#define PCI_DEVICE_ID_INTEL_NTB_RP_SNB 0x3C08 +#define PCI_DEVICE_ID_INTEL_NTB_B2B_SNB 0x3C0D +#define PCI_DEVICE_ID_INTEL_NTB_CLASSIC_SNB 0x3C0E +#define PCI_DEVICE_ID_INTEL_NTB_2ND_SNB 0x3C0F +#define PCI_DEVICE_ID_INTEL_NTB_B2B_BWD 0x0C4E + +#define msix_table_size(control) ((control & PCI_MSIX_FLAGS_QSIZE)+1) + +#define NTB_BAR_MMIO 0 +#define NTB_BAR_23 2 +#define NTB_BAR_45 4 +#define NTB_BAR_MASK ((1 << NTB_BAR_MMIO) | (1 << NTB_BAR_23) |\ + (1 << NTB_BAR_45)) + +#define NTB_LINK_DOWN 0 +#define NTB_LINK_UP 1 + +#define NTB_HB_TIMEOUT msecs_to_jiffies(1000) + +#define NTB_NUM_MW 2 + +enum ntb_hw_event { + NTB_EVENT_SW_EVENT0 = 0, + NTB_EVENT_SW_EVENT1, + NTB_EVENT_SW_EVENT2, + NTB_EVENT_HW_ERROR, + NTB_EVENT_HW_LINK_UP, + NTB_EVENT_HW_LINK_DOWN, +}; + +struct ntb_mw { + dma_addr_t phys_addr; + void __iomem *vbase; + resource_size_t bar_sz; +}; + +struct ntb_db_cb { + void (*callback) (void *data, int db_num); + unsigned int db_num; + void *data; + struct ntb_device *ndev; +}; + +struct ntb_device { + struct pci_dev *pdev; + struct msix_entry *msix_entries; + void __iomem *reg_base; + struct ntb_mw mw[NTB_NUM_MW]; + struct { + unsigned int max_spads; + unsigned int max_db_bits; + unsigned int msix_cnt; + } limits; + struct { + void __iomem *pdb; + void __iomem *pdb_mask; + void __iomem *sdb; + void __iomem *sbar2_xlat; + void __iomem *sbar4_xlat; + void __iomem *spad_write; + void __iomem *spad_read; + void __iomem *lnk_cntl; + void __iomem *lnk_stat; + void __iomem *spci_cmd; + } reg_ofs; + struct ntb_transport *ntb_transport; + void (*event_cb)(void *handle, enum ntb_hw_event event); + + struct ntb_db_cb *db_cb; + unsigned char hw_type; + unsigned char conn_type; + unsigned char dev_type; + unsigned char num_msix; + unsigned char bits_per_vector; + unsigned char max_cbs; + unsigned char link_status; + struct delayed_work hb_timer; + unsigned long last_ts; +}; + +/** + * ntb_hw_link_status() - return the hardware link status + * @ndev: pointer to ntb_device instance + * + * Returns true if the hardware is connected to the remote system + * + * RETURNS: true or false based on the hardware link state + */ +static inline bool ntb_hw_link_status(struct ntb_device *ndev) +{ + return ndev->link_status == NTB_LINK_UP; +} + +/** + * ntb_query_pdev() - return the pci_dev pointer + * @ndev: pointer to ntb_device instance + * + * Given the ntb pointer return the pci_dev pointerfor the NTB hardware device + * + * RETURNS: a pointer to the ntb pci_dev + */ +static inline struct pci_dev *ntb_query_pdev(struct ntb_device *ndev) +{ + return ndev->pdev; +} + +struct ntb_device *ntb_register_transport(struct pci_dev *pdev, + void *transport); +void ntb_unregister_transport(struct ntb_device *ndev); +void ntb_set_mw_addr(struct ntb_device *ndev, unsigned int mw, u64 addr); +int ntb_register_db_callback(struct ntb_device *ndev, unsigned int idx, + void *data, void (*db_cb_func) (void *data, + int db_num)); +void ntb_unregister_db_callback(struct ntb_device *ndev, unsigned int idx); +int ntb_register_event_callback(struct ntb_device *ndev, + void (*event_cb_func) (void *handle, + unsigned int event)); +void ntb_unregister_event_callback(struct ntb_device *ndev); +int ntb_get_max_spads(struct ntb_device *ndev); +int ntb_write_local_spad(struct ntb_device *ndev, unsigned int idx, u32 val); +int ntb_read_local_spad(struct ntb_device *ndev, unsigned int idx, u32 *val); +int ntb_write_remote_spad(struct ntb_device *ndev, unsigned int idx, u32 val); +int ntb_read_remote_spad(struct ntb_device *ndev, unsigned int idx, u32 *val); +void *ntb_get_mw_vbase(struct ntb_device *ndev, unsigned int mw); +resource_size_t ntb_get_mw_size(struct ntb_device *ndev, unsigned int mw); +void ntb_ring_sdb(struct ntb_device *ndev, unsigned int idx); +void *ntb_find_transport(struct pci_dev *pdev); + +int ntb_transport_init(struct pci_dev *pdev); +void ntb_transport_free(void *transport); diff --git a/drivers/ntb/ntb_regs.h b/drivers/ntb/ntb_regs.h new file mode 100644 index 000000000000..5bfa8c06c059 --- /dev/null +++ b/drivers/ntb/ntb_regs.h @@ -0,0 +1,139 @@ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * BSD LICENSE + * + * Copyright(c) 2012 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copy + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Intel PCIe NTB Linux driver + * + * Contact Information: + * Jon Mason + */ + +#define NTB_LINK_ENABLE 0x0000 +#define NTB_LINK_DISABLE 0x0002 +#define NTB_LINK_STATUS_ACTIVE 0x2000 +#define NTB_LINK_SPEED_MASK 0x000f +#define NTB_LINK_WIDTH_MASK 0x03f0 + +#define SNB_MSIX_CNT 4 +#define SNB_MAX_SPADS 16 +#define SNB_MAX_COMPAT_SPADS 8 +/* Reserve the uppermost bit for link interrupt */ +#define SNB_MAX_DB_BITS 15 +#define SNB_DB_BITS_PER_VEC 5 + +#define SNB_DB_HW_LINK 0x8000 + +#define SNB_PCICMD_OFFSET 0x0504 +#define SNB_DEVCTRL_OFFSET 0x0598 +#define SNB_LINK_STATUS_OFFSET 0x01A2 + +#define SNB_PBAR2LMT_OFFSET 0x0000 +#define SNB_PBAR4LMT_OFFSET 0x0008 +#define SNB_PBAR2XLAT_OFFSET 0x0010 +#define SNB_PBAR4XLAT_OFFSET 0x0018 +#define SNB_SBAR2LMT_OFFSET 0x0020 +#define SNB_SBAR4LMT_OFFSET 0x0028 +#define SNB_SBAR2XLAT_OFFSET 0x0030 +#define SNB_SBAR4XLAT_OFFSET 0x0038 +#define SNB_SBAR0BASE_OFFSET 0x0040 +#define SNB_SBAR2BASE_OFFSET 0x0048 +#define SNB_SBAR4BASE_OFFSET 0x0050 +#define SNB_NTBCNTL_OFFSET 0x0058 +#define SNB_SBDF_OFFSET 0x005C +#define SNB_PDOORBELL_OFFSET 0x0060 +#define SNB_PDBMSK_OFFSET 0x0062 +#define SNB_SDOORBELL_OFFSET 0x0064 +#define SNB_SDBMSK_OFFSET 0x0066 +#define SNB_USMEMMISS 0x0070 +#define SNB_SPAD_OFFSET 0x0080 +#define SNB_SPADSEMA4_OFFSET 0x00c0 +#define SNB_WCCNTRL_OFFSET 0x00e0 +#define SNB_B2B_SPAD_OFFSET 0x0100 +#define SNB_B2B_DOORBELL_OFFSET 0x0140 +#define SNB_B2B_XLAT_OFFSET 0x0144 + +#define BWD_MSIX_CNT 34 +#define BWD_MAX_SPADS 16 +#define BWD_MAX_COMPAT_SPADS 16 +#define BWD_MAX_DB_BITS 34 +#define BWD_DB_BITS_PER_VEC 1 + +#define BWD_PCICMD_OFFSET 0xb004 +#define BWD_MBAR23_OFFSET 0xb018 +#define BWD_MBAR45_OFFSET 0xb020 +#define BWD_DEVCTRL_OFFSET 0xb048 +#define BWD_LINK_STATUS_OFFSET 0xb052 + +#define BWD_SBAR2XLAT_OFFSET 0x0008 +#define BWD_SBAR4XLAT_OFFSET 0x0010 +#define BWD_PDOORBELL_OFFSET 0x0020 +#define BWD_PDBMSK_OFFSET 0x0028 +#define BWD_NTBCNTL_OFFSET 0x0060 +#define BWD_EBDF_OFFSET 0x0064 +#define BWD_SPAD_OFFSET 0x0080 +#define BWD_SPADSEMA_OFFSET 0x00c0 +#define BWD_STKYSPAD_OFFSET 0x00c4 +#define BWD_PBAR2XLAT_OFFSET 0x8008 +#define BWD_PBAR4XLAT_OFFSET 0x8010 +#define BWD_B2B_DOORBELL_OFFSET 0x8020 +#define BWD_B2B_SPAD_OFFSET 0x8080 +#define BWD_B2B_SPADSEMA_OFFSET 0x80c0 +#define BWD_B2B_STKYSPAD_OFFSET 0x80c4 + +#define NTB_CNTL_BAR23_SNOOP (1 << 2) +#define NTB_CNTL_BAR45_SNOOP (1 << 6) +#define BWD_CNTL_LINK_DOWN (1 << 16) + +#define NTB_PPD_OFFSET 0x00D4 +#define SNB_PPD_CONN_TYPE 0x0003 +#define SNB_PPD_DEV_TYPE 0x0010 +#define BWD_PPD_INIT_LINK 0x0008 +#define BWD_PPD_CONN_TYPE 0x0300 +#define BWD_PPD_DEV_TYPE 0x1000 + +#define BWD_PBAR2XLAT_USD_ADDR 0x0000004000000000 +#define BWD_PBAR4XLAT_USD_ADDR 0x0000008000000000 +#define BWD_MBAR23_USD_ADDR 0x000000410000000C +#define BWD_MBAR45_USD_ADDR 0x000000810000000C +#define BWD_PBAR2XLAT_DSD_ADDR 0x0000004100000000 +#define BWD_PBAR4XLAT_DSD_ADDR 0x0000008100000000 +#define BWD_MBAR23_DSD_ADDR 0x000000400000000C +#define BWD_MBAR45_DSD_ADDR 0x000000800000000C diff --git a/drivers/ntb/ntb_transport.c b/drivers/ntb/ntb_transport.c new file mode 100644 index 000000000000..c907e0773532 --- /dev/null +++ b/drivers/ntb/ntb_transport.c @@ -0,0 +1,1427 @@ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * BSD LICENSE + * + * Copyright(c) 2012 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copy + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Intel PCIe NTB Linux driver + * + * Contact Information: + * Jon Mason + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ntb_hw.h" + +#define NTB_TRANSPORT_VERSION 1 + +static int transport_mtu = 0x401E; +module_param(transport_mtu, uint, 0644); +MODULE_PARM_DESC(transport_mtu, "Maximum size of NTB transport packets"); + +static unsigned char max_num_clients = 2; +module_param(max_num_clients, byte, 0644); +MODULE_PARM_DESC(max_num_clients, "Maximum number of NTB transport clients"); + +struct ntb_queue_entry { + /* ntb_queue list reference */ + struct list_head entry; + /* pointers to data to be transfered */ + void *cb_data; + void *buf; + unsigned int len; + unsigned int flags; +}; + +struct ntb_transport_qp { + struct ntb_transport *transport; + struct ntb_device *ndev; + void *cb_data; + + bool client_ready; + bool qp_link; + u8 qp_num; /* Only 64 QP's are allowed. 0-63 */ + + void (*tx_handler) (struct ntb_transport_qp *qp, void *qp_data, + void *data, int len); + struct list_head tx_free_q; + spinlock_t ntb_tx_free_q_lock; + void *tx_mw_begin; + void *tx_mw_end; + void *tx_offset; + + void (*rx_handler) (struct ntb_transport_qp *qp, void *qp_data, + void *data, int len); + struct tasklet_struct rx_work; + struct list_head rx_pend_q; + struct list_head rx_free_q; + spinlock_t ntb_rx_pend_q_lock; + spinlock_t ntb_rx_free_q_lock; + void *rx_buff_begin; + void *rx_buff_end; + void *rx_offset; + + void (*event_handler) (void *data, int status); + struct delayed_work link_work; + + struct dentry *debugfs_dir; + struct dentry *debugfs_stats; + + /* Stats */ + u64 rx_bytes; + u64 rx_pkts; + u64 rx_ring_empty; + u64 rx_err_no_buf; + u64 rx_err_oflow; + u64 rx_err_ver; + u64 tx_bytes; + u64 tx_pkts; + u64 tx_ring_full; +}; + +struct ntb_transport_mw { + size_t size; + void *virt_addr; + dma_addr_t dma_addr; +}; + +struct ntb_transport_client_dev { + struct list_head entry; + struct device dev; +}; + +struct ntb_transport { + struct list_head entry; + struct list_head client_devs; + + struct ntb_device *ndev; + struct ntb_transport_mw mw[NTB_NUM_MW]; + struct ntb_transport_qp *qps; + unsigned int max_qps; + unsigned long qp_bitmap; + bool transport_link; + struct delayed_work link_work; + struct dentry *debugfs_dir; +}; + +enum { + DESC_DONE_FLAG = 1 << 0, + LINK_DOWN_FLAG = 1 << 1, +}; + +struct ntb_payload_header { + u64 ver; + unsigned int len; + unsigned int flags; +}; + +enum { + VERSION = 0, + MW0_SZ, + MW1_SZ, + NUM_QPS, + QP_LINKS, + MAX_SPAD, +}; + +#define QP_TO_MW(qp) ((qp) % NTB_NUM_MW) +#define NTB_QP_DEF_NUM_ENTRIES 100 +#define NTB_LINK_DOWN_TIMEOUT 10 + +static int ntb_match_bus(struct device *dev, struct device_driver *drv) +{ + return !strncmp(dev_name(dev), drv->name, strlen(drv->name)); +} + +static int ntb_client_probe(struct device *dev) +{ + const struct ntb_client *drv = container_of(dev->driver, + struct ntb_client, driver); + struct pci_dev *pdev = container_of(dev->parent, struct pci_dev, dev); + int rc = -EINVAL; + + get_device(dev); + if (drv && drv->probe) + rc = drv->probe(pdev); + if (rc) + put_device(dev); + + return rc; +} + +static int ntb_client_remove(struct device *dev) +{ + const struct ntb_client *drv = container_of(dev->driver, + struct ntb_client, driver); + struct pci_dev *pdev = container_of(dev->parent, struct pci_dev, dev); + + if (drv && drv->remove) + drv->remove(pdev); + + put_device(dev); + + return 0; +} + +struct bus_type ntb_bus_type = { + .name = "ntb_bus", + .match = ntb_match_bus, + .probe = ntb_client_probe, + .remove = ntb_client_remove, +}; + +static LIST_HEAD(ntb_transport_list); + +static int __devinit ntb_bus_init(struct ntb_transport *nt) +{ + if (list_empty(&ntb_transport_list)) { + int rc = bus_register(&ntb_bus_type); + if (rc) + return rc; + } + + list_add(&nt->entry, &ntb_transport_list); + + return 0; +} + +static void __devexit ntb_bus_remove(struct ntb_transport *nt) +{ + struct ntb_transport_client_dev *client_dev, *cd; + + list_for_each_entry_safe(client_dev, cd, &nt->client_devs, entry) { + dev_err(client_dev->dev.parent, "%s still attached to bus, removing\n", + dev_name(&client_dev->dev)); + list_del(&client_dev->entry); + device_unregister(&client_dev->dev); + } + + list_del(&nt->entry); + + if (list_empty(&ntb_transport_list)) + bus_unregister(&ntb_bus_type); +} + +static void ntb_client_release(struct device *dev) +{ + struct ntb_transport_client_dev *client_dev; + client_dev = container_of(dev, struct ntb_transport_client_dev, dev); + + kfree(client_dev); +} + +/** + * ntb_unregister_client_dev - Unregister NTB client device + * @device_name: Name of NTB client device + * + * Unregister an NTB client device with the NTB transport layer + */ +void ntb_unregister_client_dev(char *device_name) +{ + struct ntb_transport_client_dev *client, *cd; + struct ntb_transport *nt; + + list_for_each_entry(nt, &ntb_transport_list, entry) + list_for_each_entry_safe(client, cd, &nt->client_devs, entry) + if (!strncmp(dev_name(&client->dev), device_name, + strlen(device_name))) { + list_del(&client->entry); + device_unregister(&client->dev); + } +} +EXPORT_SYMBOL_GPL(ntb_unregister_client_dev); + +/** + * ntb_register_client_dev - Register NTB client device + * @device_name: Name of NTB client device + * + * Register an NTB client device with the NTB transport layer + */ +int ntb_register_client_dev(char *device_name) +{ + struct ntb_transport_client_dev *client_dev; + struct ntb_transport *nt; + int rc; + + list_for_each_entry(nt, &ntb_transport_list, entry) { + struct device *dev; + + client_dev = kzalloc(sizeof(struct ntb_transport_client_dev), + GFP_KERNEL); + if (!client_dev) { + rc = -ENOMEM; + goto err; + } + + dev = &client_dev->dev; + + /* setup and register client devices */ + dev_set_name(dev, "%s", device_name); + dev->bus = &ntb_bus_type; + dev->release = ntb_client_release; + dev->parent = &ntb_query_pdev(nt->ndev)->dev; + + rc = device_register(dev); + if (rc) { + kfree(client_dev); + goto err; + } + + list_add_tail(&client_dev->entry, &nt->client_devs); + } + + return 0; + +err: + ntb_unregister_client_dev(device_name); + + return rc; +} +EXPORT_SYMBOL_GPL(ntb_register_client_dev); + +/** + * ntb_register_client - Register NTB client driver + * @drv: NTB client driver to be registered + * + * Register an NTB client driver with the NTB transport layer + * + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +int ntb_register_client(struct ntb_client *drv) +{ + drv->driver.bus = &ntb_bus_type; + + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(ntb_register_client); + +/** + * ntb_unregister_client - Unregister NTB client driver + * @drv: NTB client driver to be unregistered + * + * Unregister an NTB client driver with the NTB transport layer + * + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +void ntb_unregister_client(struct ntb_client *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(ntb_unregister_client); + +static int debugfs_open(struct inode *inode, struct file *filp) +{ + filp->private_data = inode->i_private; + return 0; +} + +static ssize_t debugfs_read(struct file *filp, char __user *ubuf, size_t count, + loff_t *offp) +{ + struct ntb_transport_qp *qp; + char buf[1024]; + ssize_t ret, out_offset, out_count; + + out_count = 1024; + + qp = filp->private_data; + out_offset = 0; + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "NTB QP stats\n"); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "rx_bytes - \t%llu\n", qp->rx_bytes); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "rx_pkts - \t%llu\n", qp->rx_pkts); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "rx_ring_empty - %llu\n", qp->rx_ring_empty); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "rx_err_no_buf - %llu\n", qp->rx_err_no_buf); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "rx_err_oflow - \t%llu\n", qp->rx_err_oflow); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "rx_err_ver - \t%llu\n", qp->rx_err_ver); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "rx_buff_begin - %p\n", qp->rx_buff_begin); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "rx_offset - \t%p\n", qp->rx_offset); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "rx_buff_end - \t%p\n", qp->rx_buff_end); + + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "tx_bytes - \t%llu\n", qp->tx_bytes); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "tx_pkts - \t%llu\n", qp->tx_pkts); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "tx_ring_full - \t%llu\n", qp->tx_ring_full); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "tx_mw_begin - \t%p\n", qp->tx_mw_begin); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "tx_offset - \t%p\n", qp->tx_offset); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "tx_mw_end - \t%p\n", qp->tx_mw_end); + + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "QP Link %s\n", (qp->qp_link == NTB_LINK_UP) ? + "Up" : "Down"); + + ret = simple_read_from_buffer(ubuf, count, offp, buf, out_offset); + return ret; +} + +static const struct file_operations ntb_qp_debugfs_stats = { + .owner = THIS_MODULE, + .open = debugfs_open, + .read = debugfs_read, +}; + +static void ntb_list_add(spinlock_t *lock, struct list_head *entry, + struct list_head *list) +{ + unsigned long flags; + + spin_lock_irqsave(lock, flags); + list_add_tail(entry, list); + spin_unlock_irqrestore(lock, flags); +} + +static struct ntb_queue_entry *ntb_list_rm(spinlock_t *lock, + struct list_head *list) +{ + struct ntb_queue_entry *entry; + unsigned long flags; + + spin_lock_irqsave(lock, flags); + if (list_empty(list)) { + entry = NULL; + goto out; + } + entry = list_first_entry(list, struct ntb_queue_entry, entry); + list_del(&entry->entry); +out: + spin_unlock_irqrestore(lock, flags); + + return entry; +} + +static void ntb_transport_setup_qp_mw(struct ntb_transport *nt, + unsigned int qp_num) +{ + struct ntb_transport_qp *qp = &nt->qps[qp_num]; + unsigned int size, num_qps_mw; + u8 mw_num = QP_TO_MW(qp_num); + + WARN_ON(nt->mw[mw_num].virt_addr == 0); + + if (nt->max_qps % NTB_NUM_MW && !mw_num) + num_qps_mw = nt->max_qps / NTB_NUM_MW + + (nt->max_qps % NTB_NUM_MW - mw_num); + else + num_qps_mw = nt->max_qps / NTB_NUM_MW; + + size = nt->mw[mw_num].size / num_qps_mw; + + qp->rx_buff_begin = nt->mw[mw_num].virt_addr + + (qp_num / NTB_NUM_MW * size); + qp->rx_buff_end = qp->rx_buff_begin + size; + qp->rx_offset = qp->rx_buff_begin; + + qp->tx_mw_begin = ntb_get_mw_vbase(nt->ndev, mw_num) + + (qp_num / NTB_NUM_MW * size); + qp->tx_mw_end = qp->tx_mw_begin + size; + qp->tx_offset = qp->tx_mw_begin; + + qp->rx_pkts = 0; + qp->tx_pkts = 0; +} + +static int ntb_set_mw(struct ntb_transport *nt, int num_mw, unsigned int size) +{ + struct ntb_transport_mw *mw = &nt->mw[num_mw]; + struct pci_dev *pdev = ntb_query_pdev(nt->ndev); + void *offset; + + /* Alloc memory for receiving data. Must be 4k aligned */ + mw->size = ALIGN(size, 4096); + + mw->virt_addr = dma_alloc_coherent(&pdev->dev, mw->size, &mw->dma_addr, + GFP_KERNEL); + if (!mw->virt_addr) { + dev_err(&pdev->dev, "Unable to allocate MW buffer of size %d\n", + (int) mw->size); + return -ENOMEM; + } + + /* setup the hdr offsets with 0's */ + for (offset = mw->virt_addr + transport_mtu - + sizeof(struct ntb_payload_header); + offset < mw->virt_addr + size; offset += transport_mtu) + memset(offset, 0, sizeof(struct ntb_payload_header)); + + /* Notify HW the memory location of the receive buffer */ + ntb_set_mw_addr(nt->ndev, num_mw, mw->dma_addr); + + return 0; +} + +static void ntb_qp_link_down(struct ntb_transport_qp *qp) +{ + struct ntb_transport *nt = qp->transport; + struct pci_dev *pdev = ntb_query_pdev(nt->ndev); + + if (qp->qp_link == NTB_LINK_DOWN) { + cancel_delayed_work_sync(&qp->link_work); + return; + } + + if (qp->event_handler) + qp->event_handler(qp->cb_data, NTB_LINK_DOWN); + + dev_info(&pdev->dev, "qp %d: Link Down\n", qp->qp_num); + qp->qp_link = NTB_LINK_DOWN; + + if (nt->transport_link == NTB_LINK_UP) + schedule_delayed_work(&qp->link_work, + msecs_to_jiffies(NTB_LINK_DOWN_TIMEOUT)); +} + +static void ntb_transport_conn_down(struct ntb_transport *nt) +{ + int i; + + if (nt->transport_link == NTB_LINK_DOWN) + cancel_delayed_work_sync(&nt->link_work); + else + nt->transport_link = NTB_LINK_DOWN; + + /* Pass along the info to any clients */ + for (i = 0; i < nt->max_qps; i++) + if (!test_bit(i, &nt->qp_bitmap)) + ntb_qp_link_down(&nt->qps[i]); + + /* The scratchpad registers keep the values if the remote side + * goes down, blast them now to give them a sane value the next + * time they are accessed + */ + for (i = 0; i < MAX_SPAD; i++) + ntb_write_local_spad(nt->ndev, i, 0); +} + +static void ntb_transport_event_callback(void *data, enum ntb_hw_event event) +{ + struct ntb_transport *nt = data; + + switch (event) { + case NTB_EVENT_HW_LINK_UP: + schedule_delayed_work(&nt->link_work, 0); + break; + case NTB_EVENT_HW_LINK_DOWN: + ntb_transport_conn_down(nt); + break; + default: + BUG(); + } +} + +static void ntb_transport_link_work(struct work_struct *work) +{ + struct ntb_transport *nt = container_of(work, struct ntb_transport, + link_work.work); + struct ntb_device *ndev = nt->ndev; + struct pci_dev *pdev = ntb_query_pdev(ndev); + u32 val; + int rc, i; + + /* send the local info */ + rc = ntb_write_remote_spad(ndev, VERSION, NTB_TRANSPORT_VERSION); + if (rc) { + dev_err(&pdev->dev, "Error writing %x to remote spad %d\n", + 0, VERSION); + goto out; + } + + rc = ntb_write_remote_spad(ndev, MW0_SZ, ntb_get_mw_size(ndev, 0)); + if (rc) { + dev_err(&pdev->dev, "Error writing %x to remote spad %d\n", + (u32) ntb_get_mw_size(ndev, 0), MW0_SZ); + goto out; + } + + rc = ntb_write_remote_spad(ndev, MW1_SZ, ntb_get_mw_size(ndev, 1)); + if (rc) { + dev_err(&pdev->dev, "Error writing %x to remote spad %d\n", + (u32) ntb_get_mw_size(ndev, 1), MW1_SZ); + goto out; + } + + rc = ntb_write_remote_spad(ndev, NUM_QPS, nt->max_qps); + if (rc) { + dev_err(&pdev->dev, "Error writing %x to remote spad %d\n", + nt->max_qps, NUM_QPS); + goto out; + } + + rc = ntb_read_local_spad(nt->ndev, QP_LINKS, &val); + if (rc) { + dev_err(&pdev->dev, "Error reading spad %d\n", QP_LINKS); + goto out; + } + + rc = ntb_write_remote_spad(ndev, QP_LINKS, val); + if (rc) { + dev_err(&pdev->dev, "Error writing %x to remote spad %d\n", + val, QP_LINKS); + goto out; + } + + /* Query the remote side for its info */ + rc = ntb_read_remote_spad(ndev, VERSION, &val); + if (rc) { + dev_err(&pdev->dev, "Error reading remote spad %d\n", VERSION); + goto out; + } + + if (val != NTB_TRANSPORT_VERSION) + goto out; + dev_dbg(&pdev->dev, "Remote version = %d\n", val); + + rc = ntb_read_remote_spad(ndev, NUM_QPS, &val); + if (rc) { + dev_err(&pdev->dev, "Error reading remote spad %d\n", NUM_QPS); + goto out; + } + + if (val != nt->max_qps) + goto out; + dev_dbg(&pdev->dev, "Remote max number of qps = %d\n", val); + + rc = ntb_read_remote_spad(ndev, MW0_SZ, &val); + if (rc) { + dev_err(&pdev->dev, "Error reading remote spad %d\n", MW0_SZ); + goto out; + } + + if (!val) + goto out; + dev_dbg(&pdev->dev, "Remote MW0 size = %d\n", val); + + rc = ntb_set_mw(nt, 0, val); + if (rc) + goto out; + + rc = ntb_read_remote_spad(ndev, MW1_SZ, &val); + if (rc) { + dev_err(&pdev->dev, "Error reading remote spad %d\n", MW1_SZ); + goto out; + } + + if (!val) + goto out; + dev_dbg(&pdev->dev, "Remote MW1 size = %d\n", val); + + rc = ntb_set_mw(nt, 1, val); + if (rc) + goto out; + + nt->transport_link = NTB_LINK_UP; + + for (i = 0; i < nt->max_qps; i++) { + struct ntb_transport_qp *qp = &nt->qps[i]; + + ntb_transport_setup_qp_mw(nt, i); + + if (qp->client_ready == NTB_LINK_UP) + schedule_delayed_work(&qp->link_work, 0); + } + + return; + +out: + if (ntb_hw_link_status(ndev)) + schedule_delayed_work(&nt->link_work, + msecs_to_jiffies(NTB_LINK_DOWN_TIMEOUT)); +} + +static void ntb_qp_link_work(struct work_struct *work) +{ + struct ntb_transport_qp *qp = container_of(work, + struct ntb_transport_qp, + link_work.work); + struct pci_dev *pdev = ntb_query_pdev(qp->ndev); + struct ntb_transport *nt = qp->transport; + int rc, val; + + WARN_ON(nt->transport_link != NTB_LINK_UP); + + rc = ntb_read_local_spad(nt->ndev, QP_LINKS, &val); + if (rc) { + dev_err(&pdev->dev, "Error reading spad %d\n", QP_LINKS); + return; + } + + rc = ntb_write_remote_spad(nt->ndev, QP_LINKS, val | 1 << qp->qp_num); + if (rc) + dev_err(&pdev->dev, "Error writing %x to remote spad %d\n", + val | 1 << qp->qp_num, QP_LINKS); + + /* query remote spad for qp ready bits */ + rc = ntb_read_remote_spad(nt->ndev, QP_LINKS, &val); + if (rc) + dev_err(&pdev->dev, "Error reading remote spad %d\n", QP_LINKS); + + dev_dbg(&pdev->dev, "Remote QP link status = %x\n", val); + + /* See if the remote side is up */ + if (1 << qp->qp_num & val) { + qp->qp_link = NTB_LINK_UP; + + dev_info(&pdev->dev, "qp %d: Link Up\n", qp->qp_num); + if (qp->event_handler) + qp->event_handler(qp->cb_data, NTB_LINK_UP); + } else if (nt->transport_link == NTB_LINK_UP) + schedule_delayed_work(&qp->link_work, + msecs_to_jiffies(NTB_LINK_DOWN_TIMEOUT)); +} + +static void ntb_transport_init_queue(struct ntb_transport *nt, + unsigned int qp_num) +{ + struct ntb_transport_qp *qp; + + qp = &nt->qps[qp_num]; + qp->qp_num = qp_num; + qp->transport = nt; + qp->ndev = nt->ndev; + qp->qp_link = NTB_LINK_DOWN; + qp->client_ready = NTB_LINK_DOWN; + qp->event_handler = NULL; + + if (nt->debugfs_dir) { + char debugfs_name[4]; + + snprintf(debugfs_name, 4, "qp%d", qp_num); + qp->debugfs_dir = debugfs_create_dir(debugfs_name, + nt->debugfs_dir); + + qp->debugfs_stats = debugfs_create_file("stats", S_IRUSR, + qp->debugfs_dir, qp, + &ntb_qp_debugfs_stats); + } + + INIT_DELAYED_WORK(&qp->link_work, ntb_qp_link_work); + + spin_lock_init(&qp->ntb_rx_pend_q_lock); + spin_lock_init(&qp->ntb_rx_free_q_lock); + spin_lock_init(&qp->ntb_tx_free_q_lock); + + INIT_LIST_HEAD(&qp->rx_pend_q); + INIT_LIST_HEAD(&qp->rx_free_q); + INIT_LIST_HEAD(&qp->tx_free_q); +} + +int ntb_transport_init(struct pci_dev *pdev) +{ + struct ntb_transport *nt; + int rc, i; + + nt = kzalloc(sizeof(struct ntb_transport), GFP_KERNEL); + if (!nt) + return -ENOMEM; + + if (debugfs_initialized()) + nt->debugfs_dir = debugfs_create_dir(KBUILD_MODNAME, NULL); + else + nt->debugfs_dir = NULL; + + nt->ndev = ntb_register_transport(pdev, nt); + if (!nt->ndev) { + rc = -EIO; + goto err; + } + + nt->max_qps = min(nt->ndev->max_cbs, max_num_clients); + + nt->qps = kcalloc(nt->max_qps, sizeof(struct ntb_transport_qp), + GFP_KERNEL); + if (!nt->qps) { + rc = -ENOMEM; + goto err1; + } + + nt->qp_bitmap = ((u64) 1 << nt->max_qps) - 1; + + for (i = 0; i < nt->max_qps; i++) + ntb_transport_init_queue(nt, i); + + INIT_DELAYED_WORK(&nt->link_work, ntb_transport_link_work); + + rc = ntb_register_event_callback(nt->ndev, + ntb_transport_event_callback); + if (rc) + goto err2; + + INIT_LIST_HEAD(&nt->client_devs); + rc = ntb_bus_init(nt); + if (rc) + goto err3; + + if (ntb_hw_link_status(nt->ndev)) + schedule_delayed_work(&nt->link_work, 0); + + return 0; + +err3: + ntb_unregister_event_callback(nt->ndev); +err2: + kfree(nt->qps); +err1: + ntb_unregister_transport(nt->ndev); +err: + debugfs_remove_recursive(nt->debugfs_dir); + kfree(nt); + return rc; +} + +void ntb_transport_free(void *transport) +{ + struct ntb_transport *nt = transport; + struct pci_dev *pdev; + int i; + + nt->transport_link = NTB_LINK_DOWN; + + /* verify that all the qp's are freed */ + for (i = 0; i < nt->max_qps; i++) + if (!test_bit(i, &nt->qp_bitmap)) + ntb_transport_free_queue(&nt->qps[i]); + + ntb_bus_remove(nt); + + cancel_delayed_work_sync(&nt->link_work); + + debugfs_remove_recursive(nt->debugfs_dir); + + ntb_unregister_event_callback(nt->ndev); + + pdev = ntb_query_pdev(nt->ndev); + + for (i = 0; i < NTB_NUM_MW; i++) + if (nt->mw[i].virt_addr) + dma_free_coherent(&pdev->dev, nt->mw[i].size, + nt->mw[i].virt_addr, + nt->mw[i].dma_addr); + + kfree(nt->qps); + ntb_unregister_transport(nt->ndev); + kfree(nt); +} + +static void ntb_rx_copy_task(struct ntb_transport_qp *qp, + struct ntb_queue_entry *entry, void *offset) +{ + + struct ntb_payload_header *hdr; + + BUG_ON(offset < qp->rx_buff_begin || + offset + transport_mtu >= qp->rx_buff_end); + + hdr = offset + transport_mtu - sizeof(struct ntb_payload_header); + entry->len = hdr->len; + + memcpy(entry->buf, offset, entry->len); + + /* Ensure that the data is fully copied out before clearing the flag */ + wmb(); + hdr->flags = 0; + + if (qp->rx_handler && qp->client_ready == NTB_LINK_UP) + qp->rx_handler(qp, qp->cb_data, entry->cb_data, entry->len); + + ntb_list_add(&qp->ntb_rx_free_q_lock, &entry->entry, &qp->rx_free_q); +} + +static int ntb_process_rxc(struct ntb_transport_qp *qp) +{ + struct ntb_payload_header *hdr; + struct ntb_queue_entry *entry; + void *offset; + + entry = ntb_list_rm(&qp->ntb_rx_pend_q_lock, &qp->rx_pend_q); + if (!entry) { + hdr = offset + transport_mtu - + sizeof(struct ntb_payload_header); + dev_dbg(&ntb_query_pdev(qp->ndev)->dev, + "no buffer - HDR ver %llu, len %d, flags %x\n", + hdr->ver, hdr->len, hdr->flags); + qp->rx_err_no_buf++; + return -ENOMEM; + } + + offset = qp->rx_offset; + hdr = offset + transport_mtu - sizeof(struct ntb_payload_header); + + if (!(hdr->flags & DESC_DONE_FLAG)) { + ntb_list_add(&qp->ntb_rx_pend_q_lock, &entry->entry, + &qp->rx_pend_q); + qp->rx_ring_empty++; + return -EAGAIN; + } + + if (hdr->ver != qp->rx_pkts) { + dev_dbg(&ntb_query_pdev(qp->ndev)->dev, + "qp %d: version mismatch, expected %llu - got %llu\n", + qp->qp_num, qp->rx_pkts, hdr->ver); + ntb_list_add(&qp->ntb_rx_pend_q_lock, &entry->entry, + &qp->rx_pend_q); + qp->rx_err_ver++; + return -EIO; + } + + if (hdr->flags & LINK_DOWN_FLAG) { + ntb_qp_link_down(qp); + + ntb_list_add(&qp->ntb_rx_pend_q_lock, &entry->entry, + &qp->rx_pend_q); + + /* Ensure that the data is fully copied out before clearing the + * done flag + */ + wmb(); + hdr->flags = 0; + goto out; + } + + dev_dbg(&ntb_query_pdev(qp->ndev)->dev, + "rx offset %p, ver %llu - %d payload received, buf size %d\n", + qp->rx_offset, hdr->ver, hdr->len, entry->len); + + if (hdr->len <= entry->len) + ntb_rx_copy_task(qp, entry, offset); + else { + ntb_list_add(&qp->ntb_rx_pend_q_lock, &entry->entry, + &qp->rx_pend_q); + + /* Ensure that the data is fully copied out before clearing the + * done flag + */ + wmb(); + hdr->flags = 0; + qp->rx_err_oflow++; + dev_dbg(&ntb_query_pdev(qp->ndev)->dev, + "RX overflow! Wanted %d got %d\n", + hdr->len, entry->len); + } + + qp->rx_bytes += hdr->len; + qp->rx_pkts++; + +out: + qp->rx_offset += transport_mtu; + if (qp->rx_offset + transport_mtu >= qp->rx_buff_end) + qp->rx_offset = qp->rx_buff_begin; + + return 0; +} + +static void ntb_transport_rx(unsigned long data) +{ + struct ntb_transport_qp *qp = (struct ntb_transport_qp *)data; + int rc; + + do { + rc = ntb_process_rxc(qp); + } while (!rc); +} + +static void ntb_transport_rxc_db(void *data, int db_num) +{ + struct ntb_transport_qp *qp = data; + + dev_dbg(&ntb_query_pdev(qp->ndev)->dev, "%s: doorbell %d received\n", + __func__, db_num); + + tasklet_schedule(&qp->rx_work); +} + +static void ntb_tx_copy_task(struct ntb_transport_qp *qp, + struct ntb_queue_entry *entry, + void *offset) +{ + struct ntb_payload_header *hdr; + + BUG_ON(offset < qp->tx_mw_begin || + offset + transport_mtu >= qp->tx_mw_end); + + memcpy_toio(offset, entry->buf, entry->len); + + hdr = offset + transport_mtu - sizeof(struct ntb_payload_header); + hdr->len = entry->len; + hdr->ver = qp->tx_pkts; + + /* Ensure that the data is fully copied out before setting the flag */ + mmiowb(); + hdr->flags = entry->flags | DESC_DONE_FLAG; + + ntb_ring_sdb(qp->ndev, qp->qp_num); + + /* The entry length can only be zero if the packet is intended to be a + * "link down" or similar. Since no payload is being sent in these + * cases, there is nothing to add to the completion queue. + */ + if (entry->len > 0) { + qp->tx_bytes += entry->len; + + if (qp->tx_handler) + qp->tx_handler(qp, qp->cb_data, entry->cb_data, + entry->len); + } + + ntb_list_add(&qp->ntb_tx_free_q_lock, &entry->entry, &qp->tx_free_q); +} + +static int ntb_process_tx(struct ntb_transport_qp *qp, + struct ntb_queue_entry *entry) +{ + struct ntb_payload_header *hdr; + void *offset; + + offset = qp->tx_offset; + hdr = offset + transport_mtu - sizeof(struct ntb_payload_header); + + dev_dbg(&ntb_query_pdev(qp->ndev)->dev, "%lld - offset %p, tx %p, entry len %d flags %x buff %p\n", + qp->tx_pkts, offset, qp->tx_offset, entry->len, entry->flags, + entry->buf); + if (hdr->flags) { + qp->tx_ring_full++; + return -EAGAIN; + } + + if (entry->len > transport_mtu - sizeof(struct ntb_payload_header)) { + if (qp->tx_handler) + qp->tx_handler(qp->cb_data, qp, NULL, -EIO); + + ntb_list_add(&qp->ntb_tx_free_q_lock, &entry->entry, + &qp->tx_free_q); + return 0; + } + + ntb_tx_copy_task(qp, entry, offset); + + qp->tx_offset += transport_mtu; + if (qp->tx_offset + transport_mtu >= qp->tx_mw_end) + qp->tx_offset = qp->tx_mw_begin; + + qp->tx_pkts++; + + return 0; +} + +static void ntb_send_link_down(struct ntb_transport_qp *qp) +{ + struct pci_dev *pdev = ntb_query_pdev(qp->ndev); + struct ntb_queue_entry *entry; + int i, rc; + + if (qp->qp_link == NTB_LINK_DOWN) + return; + + qp->qp_link = NTB_LINK_DOWN; + dev_info(&pdev->dev, "qp %d: Link Down\n", qp->qp_num); + + for (i = 0; i < NTB_LINK_DOWN_TIMEOUT; i++) { + entry = ntb_list_rm(&qp->ntb_tx_free_q_lock, + &qp->tx_free_q); + if (entry) + break; + msleep(100); + } + + if (!entry) + return; + + entry->cb_data = NULL; + entry->buf = NULL; + entry->len = 0; + entry->flags = LINK_DOWN_FLAG; + + rc = ntb_process_tx(qp, entry); + if (rc) + dev_err(&pdev->dev, "ntb: QP%d unable to send linkdown msg\n", + qp->qp_num); +} + +/** + * ntb_transport_create_queue - Create a new NTB transport layer queue + * @rx_handler: receive callback function + * @tx_handler: transmit callback function + * @event_handler: event callback function + * + * Create a new NTB transport layer queue and provide the queue with a callback + * routine for both transmit and receive. The receive callback routine will be + * used to pass up data when the transport has received it on the queue. The + * transmit callback routine will be called when the transport has completed the + * transmission of the data on the queue and the data is ready to be freed. + * + * RETURNS: pointer to newly created ntb_queue, NULL on error. + */ +struct ntb_transport_qp * +ntb_transport_create_queue(void *data, struct pci_dev *pdev, + const struct ntb_queue_handlers *handlers) +{ + struct ntb_queue_entry *entry; + struct ntb_transport_qp *qp; + struct ntb_transport *nt; + unsigned int free_queue; + int rc, i; + + nt = ntb_find_transport(pdev); + if (!nt) + goto err; + + free_queue = ffs(nt->qp_bitmap); + if (!free_queue) + goto err; + + /* decrement free_queue to make it zero based */ + free_queue--; + + clear_bit(free_queue, &nt->qp_bitmap); + + qp = &nt->qps[free_queue]; + qp->cb_data = data; + qp->rx_handler = handlers->rx_handler; + qp->tx_handler = handlers->tx_handler; + qp->event_handler = handlers->event_handler; + + for (i = 0; i < NTB_QP_DEF_NUM_ENTRIES; i++) { + entry = kzalloc(sizeof(struct ntb_queue_entry), GFP_ATOMIC); + if (!entry) + goto err1; + + ntb_list_add(&qp->ntb_rx_free_q_lock, &entry->entry, + &qp->rx_free_q); + } + + for (i = 0; i < NTB_QP_DEF_NUM_ENTRIES; i++) { + entry = kzalloc(sizeof(struct ntb_queue_entry), GFP_ATOMIC); + if (!entry) + goto err2; + + ntb_list_add(&qp->ntb_tx_free_q_lock, &entry->entry, + &qp->tx_free_q); + } + + tasklet_init(&qp->rx_work, ntb_transport_rx, (unsigned long) qp); + + rc = ntb_register_db_callback(qp->ndev, free_queue, qp, + ntb_transport_rxc_db); + if (rc) + goto err3; + + dev_info(&pdev->dev, "NTB Transport QP %d created\n", qp->qp_num); + + return qp; + +err3: + tasklet_disable(&qp->rx_work); +err2: + while ((entry = + ntb_list_rm(&qp->ntb_tx_free_q_lock, &qp->tx_free_q))) + kfree(entry); +err1: + while ((entry = + ntb_list_rm(&qp->ntb_rx_free_q_lock, &qp->rx_free_q))) + kfree(entry); + set_bit(free_queue, &nt->qp_bitmap); +err: + return NULL; +} +EXPORT_SYMBOL_GPL(ntb_transport_create_queue); + +/** + * ntb_transport_free_queue - Frees NTB transport queue + * @qp: NTB queue to be freed + * + * Frees NTB transport queue + */ +void ntb_transport_free_queue(struct ntb_transport_qp *qp) +{ + struct pci_dev *pdev = ntb_query_pdev(qp->ndev); + struct ntb_queue_entry *entry; + + if (!qp) + return; + + cancel_delayed_work_sync(&qp->link_work); + + ntb_unregister_db_callback(qp->ndev, qp->qp_num); + tasklet_disable(&qp->rx_work); + + while ((entry = + ntb_list_rm(&qp->ntb_rx_free_q_lock, &qp->rx_free_q))) + kfree(entry); + + while ((entry = + ntb_list_rm(&qp->ntb_rx_pend_q_lock, &qp->rx_pend_q))) { + dev_warn(&pdev->dev, "Freeing item from a non-empty queue\n"); + kfree(entry); + } + + while ((entry = + ntb_list_rm(&qp->ntb_tx_free_q_lock, &qp->tx_free_q))) + kfree(entry); + + set_bit(qp->qp_num, &qp->transport->qp_bitmap); + + dev_info(&pdev->dev, "NTB Transport QP %d freed\n", qp->qp_num); +} +EXPORT_SYMBOL_GPL(ntb_transport_free_queue); + +/** + * ntb_transport_rx_remove - Dequeues enqueued rx packet + * @qp: NTB queue to be freed + * @len: pointer to variable to write enqueued buffers length + * + * Dequeues unused buffers from receive queue. Should only be used during + * shutdown of qp. + * + * RETURNS: NULL error value on error, or void* for success. + */ +void *ntb_transport_rx_remove(struct ntb_transport_qp *qp, unsigned int *len) +{ + struct ntb_queue_entry *entry; + void *buf; + + if (!qp || qp->client_ready == NTB_LINK_UP) + return NULL; + + entry = ntb_list_rm(&qp->ntb_rx_pend_q_lock, &qp->rx_pend_q); + if (!entry) + return NULL; + + buf = entry->cb_data; + *len = entry->len; + + ntb_list_add(&qp->ntb_rx_free_q_lock, &entry->entry, + &qp->rx_free_q); + + return buf; +} +EXPORT_SYMBOL_GPL(ntb_transport_rx_remove); + +/** + * ntb_transport_rx_enqueue - Enqueue a new NTB queue entry + * @qp: NTB transport layer queue the entry is to be enqueued on + * @cb: per buffer pointer for callback function to use + * @data: pointer to data buffer that incoming packets will be copied into + * @len: length of the data buffer + * + * Enqueue a new receive buffer onto the transport queue into which a NTB + * payload can be received into. + * + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +int ntb_transport_rx_enqueue(struct ntb_transport_qp *qp, void *cb, void *data, + unsigned int len) +{ + struct ntb_queue_entry *entry; + + if (!qp) + return -EINVAL; + + entry = ntb_list_rm(&qp->ntb_rx_free_q_lock, &qp->rx_free_q); + if (!entry) + return -ENOMEM; + + entry->cb_data = cb; + entry->buf = data; + entry->len = len; + + ntb_list_add(&qp->ntb_rx_pend_q_lock, &entry->entry, + &qp->rx_pend_q); + + return 0; +} +EXPORT_SYMBOL_GPL(ntb_transport_rx_enqueue); + +/** + * ntb_transport_tx_enqueue - Enqueue a new NTB queue entry + * @qp: NTB transport layer queue the entry is to be enqueued on + * @cb: per buffer pointer for callback function to use + * @data: pointer to data buffer that will be sent + * @len: length of the data buffer + * + * Enqueue a new transmit buffer onto the transport queue from which a NTB + * payload will be transmitted. This assumes that a lock is behing held to + * serialize access to the qp. + * + * RETURNS: An appropriate -ERRNO error value on error, or zero for success. + */ +int ntb_transport_tx_enqueue(struct ntb_transport_qp *qp, void *cb, void *data, + unsigned int len) +{ + struct ntb_queue_entry *entry; + int rc; + + if (!qp || qp->qp_link != NTB_LINK_UP || !len) + return -EINVAL; + + entry = ntb_list_rm(&qp->ntb_tx_free_q_lock, &qp->tx_free_q); + if (!entry) + return -ENOMEM; + + entry->cb_data = cb; + entry->buf = data; + entry->len = len; + entry->flags = 0; + + rc = ntb_process_tx(qp, entry); + if (rc) + ntb_list_add(&qp->ntb_tx_free_q_lock, &entry->entry, + &qp->tx_free_q); + + return rc; +} +EXPORT_SYMBOL_GPL(ntb_transport_tx_enqueue); + +/** + * ntb_transport_link_up - Notify NTB transport of client readiness to use queue + * @qp: NTB transport layer queue to be enabled + * + * Notify NTB transport layer of client readiness to use queue + */ +void ntb_transport_link_up(struct ntb_transport_qp *qp) +{ + if (!qp) + return; + + qp->client_ready = NTB_LINK_UP; + + if (qp->transport->transport_link == NTB_LINK_UP) + schedule_delayed_work(&qp->link_work, 0); +} +EXPORT_SYMBOL_GPL(ntb_transport_link_up); + +/** + * ntb_transport_link_down - Notify NTB transport to no longer enqueue data + * @qp: NTB transport layer queue to be disabled + * + * Notify NTB transport layer of client's desire to no longer receive data on + * transport queue specified. It is the client's responsibility to ensure all + * entries on queue are purged or otherwise handled appropraitely. + */ +void ntb_transport_link_down(struct ntb_transport_qp *qp) +{ + struct pci_dev *pdev = ntb_query_pdev(qp->ndev); + int rc, val; + + if (!qp) + return; + + qp->client_ready = NTB_LINK_DOWN; + + rc = ntb_read_local_spad(qp->ndev, QP_LINKS, &val); + if (rc) { + dev_err(&pdev->dev, "Error reading spad %d\n", QP_LINKS); + return; + } + + rc = ntb_write_remote_spad(qp->ndev, QP_LINKS, + val & ~(1 << qp->qp_num)); + if (rc) + dev_err(&pdev->dev, "Error writing %x to remote spad %d\n", + val & ~(1 << qp->qp_num), QP_LINKS); + + if (qp->qp_link == NTB_LINK_UP) + ntb_send_link_down(qp); + else + cancel_delayed_work_sync(&qp->link_work); +} +EXPORT_SYMBOL_GPL(ntb_transport_link_down); + +/** + * ntb_transport_link_query - Query transport link state + * @qp: NTB transport layer queue to be queried + * + * Query connectivity to the remote system of the NTB transport queue + * + * RETURNS: true for link up or false for link down + */ +bool ntb_transport_link_query(struct ntb_transport_qp *qp) +{ + return qp->qp_link == NTB_LINK_UP; +} +EXPORT_SYMBOL_GPL(ntb_transport_link_query); + +/** + * ntb_transport_qp_num - Query the qp number + * @qp: NTB transport layer queue to be queried + * + * Query qp number of the NTB transport queue + * + * RETURNS: a zero based number specifying the qp number + */ +unsigned char ntb_transport_qp_num(struct ntb_transport_qp *qp) +{ + return qp->qp_num; +} +EXPORT_SYMBOL_GPL(ntb_transport_qp_num); + +/** + * ntb_transport_max_size - Query the max payload size of a qp + * @qp: NTB transport layer queue to be queried + * + * Query the maximum payload size permissible on the given qp + * + * RETURNS: the max payload size of a qp + */ +unsigned int +ntb_transport_max_size(__attribute__((unused)) struct ntb_transport_qp *qp) +{ + return transport_mtu - sizeof(struct ntb_payload_header); +} +EXPORT_SYMBOL_GPL(ntb_transport_max_size); diff --git a/include/linux/ntb.h b/include/linux/ntb.h new file mode 100644 index 000000000000..f6a15205853b --- /dev/null +++ b/include/linux/ntb.h @@ -0,0 +1,83 @@ +/* + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2012 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * BSD LICENSE + * + * Copyright(c) 2012 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copy + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Intel PCIe NTB Linux driver + * + * Contact Information: + * Jon Mason + */ + +struct ntb_transport_qp; + +struct ntb_client { + struct device_driver driver; + int (*probe) (struct pci_dev *pdev); + void (*remove) (struct pci_dev *pdev); +}; + +int ntb_register_client(struct ntb_client *drvr); +void ntb_unregister_client(struct ntb_client *drvr); +int ntb_register_client_dev(char *device_name); +void ntb_unregister_client_dev(char *device_name); + +struct ntb_queue_handlers { + void (*rx_handler) (struct ntb_transport_qp *qp, void *qp_data, + void *data, int len); + void (*tx_handler) (struct ntb_transport_qp *qp, void *qp_data, + void *data, int len); + void (*event_handler) (void *data, int status); +}; + +unsigned char ntb_transport_qp_num(struct ntb_transport_qp *qp); +unsigned int ntb_transport_max_size(struct ntb_transport_qp *qp); +struct ntb_transport_qp * +ntb_transport_create_queue(void *data, struct pci_dev *pdev, + const struct ntb_queue_handlers *handlers); +void ntb_transport_free_queue(struct ntb_transport_qp *qp); +int ntb_transport_rx_enqueue(struct ntb_transport_qp *qp, void *cb, void *data, + unsigned int len); +int ntb_transport_tx_enqueue(struct ntb_transport_qp *qp, void *cb, void *data, + unsigned int len); +void *ntb_transport_rx_remove(struct ntb_transport_qp *qp, unsigned int *len); +void ntb_transport_link_up(struct ntb_transport_qp *qp); +void ntb_transport_link_down(struct ntb_transport_qp *qp); +bool ntb_transport_link_query(struct ntb_transport_qp *qp); -- cgit v1.2.3 From 7fb96565e3e18ad41857ca6ffdaa9a26ae92df5a Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Wed, 23 Jan 2013 17:42:40 -0800 Subject: Drivers: hv: vmbus: Consolidate all offer GUID definitions in hyperv.h Consolidate all GUID definitions in hyperv.h and use these definitions in implementing channel bindings (as far as interrupt delivery goes). Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/channel_mgmt.c | 31 +++------------- include/linux/hyperv.h | 94 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 25 deletions(-) (limited to 'include') diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index 7478ef914b9a..53a8600162a5 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -265,36 +265,17 @@ enum { }; /* - * This is an array of channels (devices) that are performance critical. + * This is an array of device_ids (device types) that are performance critical. * We attempt to distribute the interrupt load for these devices across * all available CPUs. */ -static const uuid_le hp_devs[] = { - /* {32412632-86cb-44a2-9b5c-50d1417354f5} */ +static const struct hv_vmbus_device_id hp_devs[] = { /* IDE */ - { - .b = { - 0x32, 0x26, 0x41, 0x32, 0xcb, 0x86, 0xa2, 0x44, - 0x9b, 0x5c, 0x50, 0xd1, 0x41, 0x73, 0x54, 0xf5 - } - }, - /* {ba6163d9-04a1-4d29-b605-72e2ffb1dc7f} */ + { HV_IDE_GUID, }, /* Storage - SCSI */ - { - .b = { - 0xd9, 0x63, 0x61, 0xba, 0xa1, 0x04, 0x29, 0x4d, - 0xb6, 0x05, 0x72, 0xe2, 0xff, 0xb1, 0xdc, 0x7f - } - }, - /* {F8615163-DF3E-46c5-913F-F2D2F965ED0E} */ + { HV_SCSI_GUID, }, /* Network */ - { - .b = { - 0x63, 0x51, 0x61, 0xF8, 0x3E, 0xDF, 0xc5, 0x46, - 0x91, 0x3F, 0xF2, 0xD2, 0xF9, 0x65, 0xED, 0x0E - } - }, - + { HV_NIC_GUID, }, }; @@ -320,7 +301,7 @@ static u32 get_vp_index(uuid_le *type_guid) u32 max_cpus = num_online_cpus(); for (i = IDE; i < MAX_PERF_CHN; i++) { - if (!memcmp(type_guid->b, hp_devs[i].b, + if (!memcmp(type_guid->b, hp_devs[i].guid, sizeof(uuid_le))) { perf_chn = true; break; diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 5095b066df94..df77ba9a8166 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -1158,6 +1158,100 @@ void vmbus_driver_unregister(struct hv_driver *hv_driver); .guid = { g0, g1, g2, g3, g4, g5, g6, g7, \ g8, g9, ga, gb, gc, gd, ge, gf }, +/* + * GUID definitions of various offer types - services offered to the guest. + */ + +/* + * Network GUID + * {f8615163-df3e-46c5-913f-f2d2f965ed0e} + */ +#define HV_NIC_GUID \ + .guid = { \ + 0x63, 0x51, 0x61, 0xf8, 0x3e, 0xdf, 0xc5, 0x46, \ + 0x91, 0x3f, 0xf2, 0xd2, 0xf9, 0x65, 0xed, 0x0e \ + } + +/* + * IDE GUID + * {32412632-86cb-44a2-9b5c-50d1417354f5} + */ +#define HV_IDE_GUID \ + .guid = { \ + 0x32, 0x26, 0x41, 0x32, 0xcb, 0x86, 0xa2, 0x44, \ + 0x9b, 0x5c, 0x50, 0xd1, 0x41, 0x73, 0x54, 0xf5 \ + } + +/* + * SCSI GUID + * {ba6163d9-04a1-4d29-b605-72e2ffb1dc7f} + */ +#define HV_SCSI_GUID \ + .guid = { \ + 0xd9, 0x63, 0x61, 0xba, 0xa1, 0x04, 0x29, 0x4d, \ + 0xb6, 0x05, 0x72, 0xe2, 0xff, 0xb1, 0xdc, 0x7f \ + } + +/* + * Shutdown GUID + * {0e0b6031-5213-4934-818b-38d90ced39db} + */ +#define HV_SHUTDOWN_GUID \ + .guid = { \ + 0x31, 0x60, 0x0b, 0x0e, 0x13, 0x52, 0x34, 0x49, \ + 0x81, 0x8b, 0x38, 0xd9, 0x0c, 0xed, 0x39, 0xdb \ + } + +/* + * Time Synch GUID + * {9527E630-D0AE-497b-ADCE-E80AB0175CAF} + */ +#define HV_TS_GUID \ + .guid = { \ + 0x30, 0xe6, 0x27, 0x95, 0xae, 0xd0, 0x7b, 0x49, \ + 0xad, 0xce, 0xe8, 0x0a, 0xb0, 0x17, 0x5c, 0xaf \ + } + +/* + * Heartbeat GUID + * {57164f39-9115-4e78-ab55-382f3bd5422d} + */ +#define HV_HEART_BEAT_GUID \ + .guid = { \ + 0x39, 0x4f, 0x16, 0x57, 0x15, 0x91, 0x78, 0x4e, \ + 0xab, 0x55, 0x38, 0x2f, 0x3b, 0xd5, 0x42, 0x2d \ + } + +/* + * KVP GUID + * {a9a0f4e7-5a45-4d96-b827-8a841e8c03e6} + */ +#define HV_KVP_GUID \ + .guid = { \ + 0xe7, 0xf4, 0xa0, 0xa9, 0x45, 0x5a, 0x96, 0x4d, \ + 0xb8, 0x27, 0x8a, 0x84, 0x1e, 0x8c, 0x3, 0xe6 \ + } + +/* + * Dynamic memory GUID + * {525074dc-8985-46e2-8057-a307dc18a502} + */ +#define HV_DM_GUID \ + .guid = { \ + 0xdc, 0x74, 0x50, 0X52, 0x85, 0x89, 0xe2, 0x46, \ + 0x80, 0x57, 0xa3, 0x07, 0xdc, 0x18, 0xa5, 0x02 \ + } + +/* + * Mouse GUID + * {cfa8b69e-5b4a-4cc0-b98b-8ba1a1f3f95a} + */ +#define HV_MOUSE_GUID \ + .guid = { \ + 0x9e, 0xb6, 0xa8, 0xcf, 0x4a, 0x5b, 0xc0, 0x4c, \ + 0xb9, 0x8b, 0x8b, 0xa1, 0xa1, 0xf3, 0xf9, 0x5a \ + } + /* * Common header for Hyper-V ICs */ -- cgit v1.2.3 From 2e033db5ddf299de2ae568919d78b0258a5a6423 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 21 Jan 2013 17:36:33 +0900 Subject: extcon: arizona: Support additional configuration of microphone detection Allow systems to tune detection rate and debounce suitably for their mechanical parameters. Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 12 ++++++++++++ include/linux/mfd/arizona/pdata.h | 6 ++++++ 2 files changed, 18 insertions(+) (limited to 'include') diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index ab8b9c7359fb..d7e1047ad68e 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -907,6 +907,18 @@ static int arizona_extcon_probe(struct platform_device *pdev) arizona->pdata.micd_bias_start_time << ARIZONA_MICD_BIAS_STARTTIME_SHIFT); + if (arizona->pdata.micd_rate) + regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_1, + ARIZONA_MICD_RATE_MASK, + arizona->pdata.micd_rate + << ARIZONA_MICD_RATE_SHIFT); + + if (arizona->pdata.micd_dbtime) + regmap_update_bits(arizona->regmap, ARIZONA_MIC_DETECT_1, + ARIZONA_MICD_DBTIME_MASK, + arizona->pdata.micd_dbtime + << ARIZONA_MICD_DBTIME_SHIFT); + /* * If we have a clamp use it, activating in conjunction with * GPIO5 if that is connected for jack detect operation. diff --git a/include/linux/mfd/arizona/pdata.h b/include/linux/mfd/arizona/pdata.h index bcbe4fda87cb..2f5f08e10b79 100644 --- a/include/linux/mfd/arizona/pdata.h +++ b/include/linux/mfd/arizona/pdata.h @@ -111,6 +111,12 @@ struct arizona_pdata { /** Mic detect ramp rate */ int micd_bias_start_time; + /** Mic detect sample rate */ + int micd_rate; + + /** Mic detect debounce level */ + int micd_dbtime; + /** Headset polarity configurations */ struct arizona_micd_config *micd_configs; int num_micd_configs; -- cgit v1.2.3 From bbbd46e3d7fcdf1c8362bf1c83bcc08a93676cc9 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 10 Jan 2013 19:38:43 +0000 Subject: extcon: arizona: Use regulated mode for microphone supply when detecting When starting microphone detection some headsets should be exposed to the fully regulated microphone bias in order to ensure that they behave in an optimal fashion. Signed-off-by: Mark Brown --- drivers/extcon/Kconfig | 2 +- drivers/extcon/extcon-arizona.c | 93 +++++++++++++++++++++++++++++++++++++++ include/linux/mfd/arizona/pdata.h | 3 ++ 3 files changed, 97 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index 07122a9ef36e..9377050e5335 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -47,7 +47,7 @@ config EXTCON_MAX8997 config EXTCON_ARIZONA tristate "Wolfson Arizona EXTCON support" - depends on MFD_ARIZONA && INPUT + depends on MFD_ARIZONA && INPUT && SND_SOC help Say Y here to enable support for external accessory detection with Wolfson Arizona devices. These are audio CODECs with diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index d7e1047ad68e..aa724314677a 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -27,6 +27,8 @@ #include #include +#include + #include #include #include @@ -113,6 +115,52 @@ static void arizona_extcon_set_mode(struct arizona_extcon_info *info, int mode) dev_dbg(arizona->dev, "Set jack polarity to %d\n", mode); } +static const char *arizona_extcon_get_micbias(struct arizona_extcon_info *info) +{ + switch (info->micd_modes[0].bias >> ARIZONA_MICD_BIAS_SRC_SHIFT) { + case 1: + return "MICBIAS1"; + case 2: + return "MICBIAS2"; + case 3: + return "MICBIAS3"; + default: + return "MICVDD"; + } +} + +static void arizona_extcon_pulse_micbias(struct arizona_extcon_info *info) +{ + struct arizona *arizona = info->arizona; + const char *widget = arizona_extcon_get_micbias(info); + struct snd_soc_dapm_context *dapm = arizona->dapm; + int ret; + + mutex_lock(&dapm->card->dapm_mutex); + + ret = snd_soc_dapm_force_enable_pin(dapm, widget); + if (ret != 0) + dev_warn(arizona->dev, "Failed to enable %s: %d\n", + widget, ret); + + mutex_unlock(&dapm->card->dapm_mutex); + + snd_soc_dapm_sync(dapm); + + if (!arizona->pdata.micd_force_micbias) { + mutex_lock(&dapm->card->dapm_mutex); + + ret = snd_soc_dapm_disable_pin(arizona->dapm, widget); + if (ret != 0) + dev_warn(arizona->dev, "Failed to disable %s: %d\n", + widget, ret); + + mutex_unlock(&dapm->card->dapm_mutex); + + snd_soc_dapm_sync(dapm); + } +} + static void arizona_start_mic(struct arizona_extcon_info *info) { struct arizona *arizona = info->arizona; @@ -122,6 +170,15 @@ static void arizona_start_mic(struct arizona_extcon_info *info) /* Microphone detection can't use idle mode */ pm_runtime_get(info->dev); + if (info->detecting) { + ret = regulator_allow_bypass(info->micvdd, false); + if (ret != 0) { + dev_err(arizona->dev, + "Failed to regulate MICVDD: %d\n", + ret); + } + } + ret = regulator_enable(info->micvdd); if (ret != 0) { dev_err(arizona->dev, "Failed to enable MICVDD: %d\n", @@ -138,6 +195,8 @@ static void arizona_start_mic(struct arizona_extcon_info *info) ARIZONA_ACCESSORY_DETECT_MODE_1, ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC); + arizona_extcon_pulse_micbias(info); + regmap_update_bits_check(arizona->regmap, ARIZONA_MIC_DETECT_1, ARIZONA_MICD_ENA, ARIZONA_MICD_ENA, &change); @@ -150,18 +209,39 @@ static void arizona_start_mic(struct arizona_extcon_info *info) static void arizona_stop_mic(struct arizona_extcon_info *info) { struct arizona *arizona = info->arizona; + const char *widget = arizona_extcon_get_micbias(info); + struct snd_soc_dapm_context *dapm = arizona->dapm; bool change; + int ret; regmap_update_bits_check(arizona->regmap, ARIZONA_MIC_DETECT_1, ARIZONA_MICD_ENA, 0, &change); + mutex_lock(&dapm->card->dapm_mutex); + + ret = snd_soc_dapm_disable_pin(dapm, widget); + if (ret != 0) + dev_warn(arizona->dev, + "Failed to disable %s: %d\n", + widget, ret); + + mutex_unlock(&dapm->card->dapm_mutex); + + snd_soc_dapm_sync(dapm); + if (info->micd_reva) { regmap_write(arizona->regmap, 0x80, 0x3); regmap_write(arizona->regmap, 0x294, 2); regmap_write(arizona->regmap, 0x80, 0x0); } + ret = regulator_allow_bypass(info->micvdd, true); + if (ret != 0) { + dev_err(arizona->dev, "Failed to bypass MICVDD: %d\n", + ret); + } + if (change) { regulator_disable(info->micvdd); pm_runtime_mark_last_busy(info->dev); @@ -564,6 +644,8 @@ static void arizona_start_hpdet_acc_id(struct arizona_extcon_info *info) info->hpdet_active = true; + arizona_extcon_pulse_micbias(info); + ret = regmap_update_bits(arizona->regmap, 0x225, 0x4000, 0x4000); if (ret != 0) dev_warn(arizona->dev, "Failed to do magic: %d\n", ret); @@ -649,6 +731,13 @@ static irqreturn_t arizona_micdet(int irq, void *data) dev_err(arizona->dev, "Headset report failed: %d\n", ret); + /* Don't need to regulate for button detection */ + ret = regulator_allow_bypass(info->micvdd, false); + if (ret != 0) { + dev_err(arizona->dev, "Failed to bypass MICVDD: %d\n", + ret); + } + info->mic = true; info->detecting = false; goto handled; @@ -716,6 +805,7 @@ static irqreturn_t arizona_micdet(int irq, void *data) input_report_key(info->input, arizona_lvl_to_key[i].report, 0); input_sync(info->input); + arizona_extcon_pulse_micbias(info); } handled: @@ -817,6 +907,9 @@ static int arizona_extcon_probe(struct platform_device *pdev) int jack_irq_fall, jack_irq_rise; int ret, mode, i; + if (!arizona->dapm || !arizona->dapm->card) + return -EPROBE_DEFER; + pdata = dev_get_platdata(arizona->dev); info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); diff --git a/include/linux/mfd/arizona/pdata.h b/include/linux/mfd/arizona/pdata.h index 2f5f08e10b79..f8241753415c 100644 --- a/include/linux/mfd/arizona/pdata.h +++ b/include/linux/mfd/arizona/pdata.h @@ -117,6 +117,9 @@ struct arizona_pdata { /** Mic detect debounce level */ int micd_dbtime; + /** Force MICBIAS on for mic detect */ + bool micd_force_micbias; + /** Headset polarity configurations */ struct arizona_micd_config *micd_configs; int num_micd_configs; -- cgit v1.2.3 From 5d9ab708200fefc3ec6e4454c65584d14ce716b0 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Tue, 5 Feb 2013 10:13:38 +0000 Subject: extcon: arizona: Clear _trig_sts bits after jack detection It is important to clear the wake trigger status bits otherwise DCVDD will be held high independent of the state of the LDOENA line. Signed-off-by: Charles Keepax Signed-off-by: Mark Brown --- drivers/extcon/extcon-arizona.c | 7 +++++++ include/linux/mfd/arizona/registers.h | 8 ++++++++ 2 files changed, 15 insertions(+) (limited to 'include') diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index cfd206c4797c..aeaf217a05ee 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -939,6 +939,13 @@ static irqreturn_t arizona_jackdet(int irq, void *data) ARIZONA_MICD_CLAMP_DB | ARIZONA_JD1_DB); } + /* Clear trig_sts to make sure DCVDD is not forced up */ + regmap_write(arizona->regmap, ARIZONA_AOD_WKUP_AND_TRIG, + ARIZONA_MICD_CLAMP_FALL_TRIG_STS | + ARIZONA_MICD_CLAMP_RISE_TRIG_STS | + ARIZONA_JD1_FALL_TRIG_STS | + ARIZONA_JD1_RISE_TRIG_STS); + mutex_unlock(&info->lock); pm_runtime_mark_last_busy(info->dev); diff --git a/include/linux/mfd/arizona/registers.h b/include/linux/mfd/arizona/registers.h index 79e9dd4073d8..188d89abd963 100644 --- a/include/linux/mfd/arizona/registers.h +++ b/include/linux/mfd/arizona/registers.h @@ -5267,6 +5267,14 @@ /* * R3408 (0xD50) - AOD wkup and trig */ +#define ARIZONA_MICD_CLAMP_FALL_TRIG_STS 0x0080 /* MICD_CLAMP_FALL_TRIG_STS */ +#define ARIZONA_MICD_CLAMP_FALL_TRIG_STS_MASK 0x0080 /* MICD_CLAMP_FALL_TRIG_STS */ +#define ARIZONA_MICD_CLAMP_FALL_TRIG_STS_SHIFT 7 /* MICD_CLAMP_FALL_TRIG_STS */ +#define ARIZONA_MICD_CLAMP_FALL_TRIG_STS_WIDTH 1 /* MICD_CLAMP_FALL_TRIG_STS */ +#define ARIZONA_MICD_CLAMP_RISE_TRIG_STS 0x0040 /* MICD_CLAMP_RISE_TRIG_STS */ +#define ARIZONA_MICD_CLAMP_RISE_TRIG_STS_MASK 0x0040 /* MICD_CLAMP_RISE_TRIG_STS */ +#define ARIZONA_MICD_CLAMP_RISE_TRIG_STS_SHIFT 6 /* MICD_CLAMP_RISE_TRIG_STS */ +#define ARIZONA_MICD_CLAMP_RISE_TRIG_STS_WIDTH 1 /* MICD_CLAMP_RISE_TRIG_STS */ #define ARIZONA_GP5_FALL_TRIG_STS 0x0020 /* GP5_FALL_TRIG_STS */ #define ARIZONA_GP5_FALL_TRIG_STS_MASK 0x0020 /* GP5_FALL_TRIG_STS */ #define ARIZONA_GP5_FALL_TRIG_STS_SHIFT 5 /* GP5_FALL_TRIG_STS */ -- cgit v1.2.3 From fd116066d966549211b41d929feea5474d57461b Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Wed, 13 Feb 2013 18:35:00 +0900 Subject: extcon: gpio: Rename filename of extcon-gpio.c according to kernel naming style Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham Signed-off-by: Greg Kroah-Hartman --- drivers/extcon/extcon-gpio.c | 2 +- include/linux/extcon/extcon-gpio.h | 52 ++++++++++++++++++++++++++++++++++++++ include/linux/extcon/extcon_gpio.h | 52 -------------------------------------- 3 files changed, 53 insertions(+), 53 deletions(-) create mode 100644 include/linux/extcon/extcon-gpio.h delete mode 100644 include/linux/extcon/extcon_gpio.h (limited to 'include') diff --git a/drivers/extcon/extcon-gpio.c b/drivers/extcon/extcon-gpio.c index 1b14bfcdc176..02bec32adde4 100644 --- a/drivers/extcon/extcon-gpio.c +++ b/drivers/extcon/extcon-gpio.c @@ -29,7 +29,7 @@ #include #include #include -#include +#include struct gpio_extcon_data { struct extcon_dev edev; diff --git a/include/linux/extcon/extcon-gpio.h b/include/linux/extcon/extcon-gpio.h new file mode 100644 index 000000000000..2d8307f7d67d --- /dev/null +++ b/include/linux/extcon/extcon-gpio.h @@ -0,0 +1,52 @@ +/* + * External connector (extcon) class generic GPIO driver + * + * Copyright (C) 2012 Samsung Electronics + * Author: MyungJoo Ham + * + * based on switch class driver + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * +*/ +#ifndef __EXTCON_GPIO_H__ +#define __EXTCON_GPIO_H__ __FILE__ + +#include + +/** + * struct gpio_extcon_platform_data - A simple GPIO-controlled extcon device. + * @name The name of this GPIO extcon device. + * @gpio Corresponding GPIO. + * @debounce Debounce time for GPIO IRQ in ms. + * @irq_flags IRQ Flags (e.g., IRQF_TRIGGER_LOW). + * @state_on print_state is overriden with state_on if attached. If Null, + * default method of extcon class is used. + * @state_off print_state is overriden with state_on if detached. If Null, + * default method of extcon class is used. + * + * Note that in order for state_on or state_off to be valid, both state_on + * and state_off should be not NULL. If at least one of them is NULL, + * the print_state is not overriden. + */ +struct gpio_extcon_platform_data { + const char *name; + unsigned gpio; + unsigned long debounce; + unsigned long irq_flags; + + /* if NULL, "0" or "1" will be printed */ + const char *state_on; + const char *state_off; +}; + +#endif /* __EXTCON_GPIO_H__ */ diff --git a/include/linux/extcon/extcon_gpio.h b/include/linux/extcon/extcon_gpio.h deleted file mode 100644 index 2d8307f7d67d..000000000000 --- a/include/linux/extcon/extcon_gpio.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * External connector (extcon) class generic GPIO driver - * - * Copyright (C) 2012 Samsung Electronics - * Author: MyungJoo Ham - * - * based on switch class driver - * Copyright (C) 2008 Google, Inc. - * Author: Mike Lockwood - * - * This software is licensed under the terms of the GNU General Public - * License version 2, as published by the Free Software Foundation, and - * may be copied, distributed, and modified under those terms. - * - * 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. - * -*/ -#ifndef __EXTCON_GPIO_H__ -#define __EXTCON_GPIO_H__ __FILE__ - -#include - -/** - * struct gpio_extcon_platform_data - A simple GPIO-controlled extcon device. - * @name The name of this GPIO extcon device. - * @gpio Corresponding GPIO. - * @debounce Debounce time for GPIO IRQ in ms. - * @irq_flags IRQ Flags (e.g., IRQF_TRIGGER_LOW). - * @state_on print_state is overriden with state_on if attached. If Null, - * default method of extcon class is used. - * @state_off print_state is overriden with state_on if detached. If Null, - * default method of extcon class is used. - * - * Note that in order for state_on or state_off to be valid, both state_on - * and state_off should be not NULL. If at least one of them is NULL, - * the print_state is not overriden. - */ -struct gpio_extcon_platform_data { - const char *name; - unsigned gpio; - unsigned long debounce; - unsigned long irq_flags; - - /* if NULL, "0" or "1" will be printed */ - const char *state_on; - const char *state_off; -}; - -#endif /* __EXTCON_GPIO_H__ */ -- cgit v1.2.3 From e3e5bc02d2365d3e09164fd9a559011399ca2a4f Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Tue, 12 Feb 2013 20:44:19 +0900 Subject: extcon: max8997: Move defined constant to header file This patch move defined constants to header file(max77693-private.h) because of mask/unmask selectively interrupt of MUIC device according to attribute of H/W board. Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-max8997.c | 92 +++++++++++++------------------------ include/linux/mfd/max8997-private.h | 49 ++++++++++++++++++++ 2 files changed, 80 insertions(+), 61 deletions(-) (limited to 'include') diff --git a/drivers/extcon/extcon-max8997.c b/drivers/extcon/extcon-max8997.c index d16090ddd65a..0fb1d48b0741 100644 --- a/drivers/extcon/extcon-max8997.c +++ b/drivers/extcon/extcon-max8997.c @@ -30,51 +30,6 @@ #define DEV_NAME "max8997-muic" -/* MAX8997-MUIC STATUS1 register */ -#define STATUS1_ADC_SHIFT 0 -#define STATUS1_ADCLOW_SHIFT 5 -#define STATUS1_ADCERR_SHIFT 6 -#define STATUS1_ADC_MASK (0x1f << STATUS1_ADC_SHIFT) -#define STATUS1_ADCLOW_MASK (0x1 << STATUS1_ADCLOW_SHIFT) -#define STATUS1_ADCERR_MASK (0x1 << STATUS1_ADCERR_SHIFT) - -/* MAX8997-MUIC STATUS2 register */ -#define STATUS2_CHGTYP_SHIFT 0 -#define STATUS2_CHGDETRUN_SHIFT 3 -#define STATUS2_DCDTMR_SHIFT 4 -#define STATUS2_DBCHG_SHIFT 5 -#define STATUS2_VBVOLT_SHIFT 6 -#define STATUS2_CHGTYP_MASK (0x7 << STATUS2_CHGTYP_SHIFT) -#define STATUS2_CHGDETRUN_MASK (0x1 << STATUS2_CHGDETRUN_SHIFT) -#define STATUS2_DCDTMR_MASK (0x1 << STATUS2_DCDTMR_SHIFT) -#define STATUS2_DBCHG_MASK (0x1 << STATUS2_DBCHG_SHIFT) -#define STATUS2_VBVOLT_MASK (0x1 << STATUS2_VBVOLT_SHIFT) - -/* MAX8997-MUIC STATUS3 register */ -#define STATUS3_OVP_SHIFT 2 -#define STATUS3_OVP_MASK (0x1 << STATUS3_OVP_SHIFT) - -/* MAX8997-MUIC CONTROL1 register */ -#define COMN1SW_SHIFT 0 -#define COMP2SW_SHIFT 3 -#define COMN1SW_MASK (0x7 << COMN1SW_SHIFT) -#define COMP2SW_MASK (0x7 << COMP2SW_SHIFT) -#define SW_MASK (COMP2SW_MASK | COMN1SW_MASK) - -#define MAX8997_SW_USB ((1 << COMP2SW_SHIFT) | (1 << COMN1SW_SHIFT)) -#define MAX8997_SW_AUDIO ((2 << COMP2SW_SHIFT) | (2 << COMN1SW_SHIFT)) -#define MAX8997_SW_UART ((3 << COMP2SW_SHIFT) | (3 << COMN1SW_SHIFT)) -#define MAX8997_SW_OPEN ((0 << COMP2SW_SHIFT) | (0 << COMN1SW_SHIFT)) - -#define MAX8997_ADC_GROUND 0x00 -#define MAX8997_ADC_MHL 0x01 -#define MAX8997_ADC_JIG_USB_1 0x18 -#define MAX8997_ADC_JIG_USB_2 0x19 -#define MAX8997_ADC_DESKDOCK 0x1a -#define MAX8997_ADC_JIG_UART 0x1c -#define MAX8997_ADC_CARDOCK 0x1d -#define MAX8997_ADC_OPEN 0x1f - struct max8997_muic_irq { unsigned int irq; const char *name; @@ -109,17 +64,32 @@ struct max8997_muic_info { struct extcon_dev *edev; }; +enum { + EXTCON_CABLE_USB = 0, + EXTCON_CABLE_USB_HOST, + EXTCON_CABLE_TA, + EXTCON_CABLE_FAST_CHARGER, + EXTCON_CABLE_SLOW_CHARGER, + EXTCON_CABLE_CHARGE_DOWNSTREAM, + EXTCON_CABLE_MHL, + EXTCON_CABLE_DOCK_DESK, + EXTCON_CABLE_DOCK_CARD, + EXTCON_CABLE_JIG, + + _EXTCON_CABLE_NUM, +}; + static const char *max8997_extcon_cable[] = { - [0] = "USB", - [1] = "USB-Host", - [2] = "TA", - [3] = "Fast-charger", - [4] = "Slow-charger", - [5] = "Charge-downstream", - [6] = "MHL", - [7] = "Dock-desk", - [8] = "Dock-card", - [9] = "JIG", + [EXTCON_CABLE_USB] = "USB", + [EXTCON_CABLE_USB_HOST] = "USB-Host", + [EXTCON_CABLE_TA] = "TA", + [EXTCON_CABLE_FAST_CHARGER] = "Fast-charger", + [EXTCON_CABLE_SLOW_CHARGER] = "Slow-charger", + [EXTCON_CABLE_CHARGE_DOWNSTREAM] = "Charge-downstream", + [EXTCON_CABLE_MHL] = "MHL", + [EXTCON_CABLE_DOCK_DESK] = "Dock-Desk", + [EXTCON_CABLE_DOCK_CARD] = "Dock-Card", + [EXTCON_CABLE_JIG] = "JIG", NULL, }; @@ -132,8 +102,8 @@ static int max8997_muic_handle_usb(struct max8997_muic_info *info, if (usb_type == MAX8997_USB_HOST) { /* switch to USB */ ret = max8997_update_reg(info->muic, MAX8997_MUIC_REG_CONTROL1, - attached ? MAX8997_SW_USB : MAX8997_SW_OPEN, - SW_MASK); + attached ? CONTROL1_SW_USB : CONTROL1_SW_OPEN, + CONTROL1_SW_MASK); if (ret) { dev_err(info->dev, "failed to update muic register\n"); goto out; @@ -163,8 +133,8 @@ static int max8997_muic_handle_dock(struct max8997_muic_info *info, /* switch to AUDIO */ ret = max8997_update_reg(info->muic, MAX8997_MUIC_REG_CONTROL1, - attached ? MAX8997_SW_AUDIO : MAX8997_SW_OPEN, - SW_MASK); + attached ? CONTROL1_SW_AUDIO : CONTROL1_SW_OPEN, + CONTROL1_SW_MASK); if (ret) { dev_err(info->dev, "failed to update muic register\n"); goto out; @@ -192,8 +162,8 @@ static int max8997_muic_handle_jig_uart(struct max8997_muic_info *info, /* switch to UART */ ret = max8997_update_reg(info->muic, MAX8997_MUIC_REG_CONTROL1, - attached ? MAX8997_SW_UART : MAX8997_SW_OPEN, - SW_MASK); + attached ? CONTROL1_SW_UART : CONTROL1_SW_OPEN, + CONTROL1_SW_MASK); if (ret) { dev_err(info->dev, "failed to update muic register\n"); goto out; diff --git a/include/linux/mfd/max8997-private.h b/include/linux/mfd/max8997-private.h index 6ae21bf47d64..acf42e960320 100644 --- a/include/linux/mfd/max8997-private.h +++ b/include/linux/mfd/max8997-private.h @@ -194,6 +194,55 @@ enum max8997_muic_reg { MAX8997_MUIC_REG_END = 0xf, }; +/* MAX8997-MUIC STATUS1 register */ +#define STATUS1_ADC_SHIFT 0 +#define STATUS1_ADCLOW_SHIFT 5 +#define STATUS1_ADCERR_SHIFT 6 +#define STATUS1_ADC_MASK (0x1f << STATUS1_ADC_SHIFT) +#define STATUS1_ADCLOW_MASK (0x1 << STATUS1_ADCLOW_SHIFT) +#define STATUS1_ADCERR_MASK (0x1 << STATUS1_ADCERR_SHIFT) + +/* MAX8997-MUIC STATUS2 register */ +#define STATUS2_CHGTYP_SHIFT 0 +#define STATUS2_CHGDETRUN_SHIFT 3 +#define STATUS2_DCDTMR_SHIFT 4 +#define STATUS2_DBCHG_SHIFT 5 +#define STATUS2_VBVOLT_SHIFT 6 +#define STATUS2_CHGTYP_MASK (0x7 << STATUS2_CHGTYP_SHIFT) +#define STATUS2_CHGDETRUN_MASK (0x1 << STATUS2_CHGDETRUN_SHIFT) +#define STATUS2_DCDTMR_MASK (0x1 << STATUS2_DCDTMR_SHIFT) +#define STATUS2_DBCHG_MASK (0x1 << STATUS2_DBCHG_SHIFT) +#define STATUS2_VBVOLT_MASK (0x1 << STATUS2_VBVOLT_SHIFT) + +/* MAX8997-MUIC STATUS3 register */ +#define STATUS3_OVP_SHIFT 2 +#define STATUS3_OVP_MASK (0x1 << STATUS3_OVP_SHIFT) + +/* MAX8997-MUIC CONTROL1 register */ +#define COMN1SW_SHIFT 0 +#define COMP2SW_SHIFT 3 +#define COMN1SW_MASK (0x7 << COMN1SW_SHIFT) +#define COMP2SW_MASK (0x7 << COMP2SW_SHIFT) +#define CONTROL1_SW_MASK (COMP2SW_MASK | COMN1SW_MASK) + +#define CONTROL1_SW_USB ((1 << COMP2SW_SHIFT) \ + | (1 << COMN1SW_SHIFT)) +#define CONTROL1_SW_AUDIO ((2 << COMP2SW_SHIFT) \ + | (2 << COMN1SW_SHIFT)) +#define CONTROL1_SW_UART ((3 << COMP2SW_SHIFT) \ + | (3 << COMN1SW_SHIFT)) +#define CONTROL1_SW_OPEN ((0 << COMP2SW_SHIFT) \ + | (0 << COMN1SW_SHIFT)) + +#define MAX8997_ADC_GROUND 0x00 +#define MAX8997_ADC_MHL 0x01 +#define MAX8997_ADC_JIG_USB_1 0x18 +#define MAX8997_ADC_JIG_USB_2 0x19 +#define MAX8997_ADC_DESKDOCK 0x1a +#define MAX8997_ADC_JIG_UART 0x1c +#define MAX8997_ADC_CARDOCK 0x1d +#define MAX8997_ADC_OPEN 0x1f + enum max8997_haptic_reg { MAX8997_HAPTIC_REG_GENERAL = 0x00, MAX8997_HAPTIC_REG_CONF1 = 0x01, -- cgit v1.2.3 From 07c70503a420d48402b3859e2c1c4c847a130a8b Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Wed, 13 Feb 2013 08:42:37 +0900 Subject: extcon: max8997: Remove duplicate code related to set H/W line path Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-max8997.c | 62 ++++++++++++++++++++++++++++++------- include/linux/mfd/max8997-private.h | 19 +++++++++++- 2 files changed, 69 insertions(+), 12 deletions(-) (limited to 'include') diff --git a/drivers/extcon/extcon-max8997.c b/drivers/extcon/extcon-max8997.c index 0fb1d48b0741..8739b50c2b36 100644 --- a/drivers/extcon/extcon-max8997.c +++ b/drivers/extcon/extcon-max8997.c @@ -94,16 +94,61 @@ static const char *max8997_extcon_cable[] = { NULL, }; +/* + * max8997_muic_set_path - Set hardware line according to attached cable + * @info: the instance including private data of max8997 MUIC + * @value: the path according to attached cable + * @attached: the state of cable (true:attached, false:detached) + * + * The max8997 MUIC device share outside H/W line among a varity of cables, + * so this function set internal path of H/W line according to the type of + * attached cable. + */ +static int max8997_muic_set_path(struct max8997_muic_info *info, + u8 val, bool attached) +{ + int ret = 0; + u8 ctrl1, ctrl2 = 0; + + if (attached) + ctrl1 = val; + else + ctrl1 = CONTROL1_SW_OPEN; + + ret = max8997_update_reg(info->muic, + MAX8997_MUIC_REG_CONTROL1, ctrl1, COMP_SW_MASK); + if (ret < 0) { + dev_err(info->dev, "failed to update MUIC register\n"); + return -EAGAIN; + } + + if (attached) + ctrl2 |= CONTROL2_CPEN_MASK; /* LowPwr=0, CPEn=1 */ + else + ctrl2 |= CONTROL2_LOWPWR_MASK; /* LowPwr=1, CPEn=0 */ + + ret = max8997_update_reg(info->muic, + MAX8997_MUIC_REG_CONTROL2, ctrl2, + CONTROL2_LOWPWR_MASK | CONTROL2_CPEN_MASK); + if (ret < 0) { + dev_err(info->dev, "failed to update MUIC register\n"); + return -EAGAIN; + } + + dev_info(info->dev, + "CONTROL1 : 0x%02x, CONTROL2 : 0x%02x, state : %s\n", + ctrl1, ctrl2, attached ? "attached" : "detached"); + + return 0; +} + static int max8997_muic_handle_usb(struct max8997_muic_info *info, enum max8997_muic_usb_type usb_type, bool attached) { int ret = 0; if (usb_type == MAX8997_USB_HOST) { - /* switch to USB */ - ret = max8997_update_reg(info->muic, MAX8997_MUIC_REG_CONTROL1, - attached ? CONTROL1_SW_USB : CONTROL1_SW_OPEN, - CONTROL1_SW_MASK); + ret = max8997_muic_set_path(info, CONTROL1_SW_USB, attached); if (ret) { dev_err(info->dev, "failed to update muic register\n"); goto out; @@ -131,10 +176,7 @@ static int max8997_muic_handle_dock(struct max8997_muic_info *info, { int ret = 0; - /* switch to AUDIO */ - ret = max8997_update_reg(info->muic, MAX8997_MUIC_REG_CONTROL1, - attached ? CONTROL1_SW_AUDIO : CONTROL1_SW_OPEN, - CONTROL1_SW_MASK); + ret = max8997_muic_set_path(info, CONTROL1_SW_AUDIO, attached); if (ret) { dev_err(info->dev, "failed to update muic register\n"); goto out; @@ -161,9 +203,7 @@ static int max8997_muic_handle_jig_uart(struct max8997_muic_info *info, int ret = 0; /* switch to UART */ - ret = max8997_update_reg(info->muic, MAX8997_MUIC_REG_CONTROL1, - attached ? CONTROL1_SW_UART : CONTROL1_SW_OPEN, - CONTROL1_SW_MASK); + ret = max8997_muic_set_path(info, CONTROL1_SW_UART, attached); if (ret) { dev_err(info->dev, "failed to update muic register\n"); goto out; diff --git a/include/linux/mfd/max8997-private.h b/include/linux/mfd/max8997-private.h index acf42e960320..010173a92274 100644 --- a/include/linux/mfd/max8997-private.h +++ b/include/linux/mfd/max8997-private.h @@ -223,7 +223,7 @@ enum max8997_muic_reg { #define COMP2SW_SHIFT 3 #define COMN1SW_MASK (0x7 << COMN1SW_SHIFT) #define COMP2SW_MASK (0x7 << COMP2SW_SHIFT) -#define CONTROL1_SW_MASK (COMP2SW_MASK | COMN1SW_MASK) +#define COMP_SW_MASK (COMP2SW_MASK | COMN1SW_MASK) #define CONTROL1_SW_USB ((1 << COMP2SW_SHIFT) \ | (1 << COMN1SW_SHIFT)) @@ -234,6 +234,23 @@ enum max8997_muic_reg { #define CONTROL1_SW_OPEN ((0 << COMP2SW_SHIFT) \ | (0 << COMN1SW_SHIFT)) +#define CONTROL2_LOWPWR_SHIFT (0) +#define CONTROL2_ADCEN_SHIFT (1) +#define CONTROL2_CPEN_SHIFT (2) +#define CONTROL2_SFOUTASRT_SHIFT (3) +#define CONTROL2_SFOUTORD_SHIFT (4) +#define CONTROL2_ACCDET_SHIFT (5) +#define CONTROL2_USBCPINT_SHIFT (6) +#define CONTROL2_RCPS_SHIFT (7) +#define CONTROL2_LOWPWR_MASK (0x1 << CONTROL2_LOWPWR_SHIFT) +#define CONTROL2_ADCEN_MASK (0x1 << CONTROL2_ADCEN_SHIFT) +#define CONTROL2_CPEN_MASK (0x1 << CONTROL2_CPEN_SHIFT) +#define CONTROL2_SFOUTASRT_MASK (0x1 << CONTROL2_SFOUTASRT_SHIFT) +#define CONTROL2_SFOUTORD_MASK (0x1 << CONTROL2_SFOUTORD_SHIFT) +#define CONTROL2_ACCDET_MASK (0x1 << CONTROL2_ACCDET_SHIFT) +#define CONTROL2_USBCPINT_MASK (0x1 << CONTROL2_USBCPINT_SHIFT) +#define CONTROL2_RCPS_MASK (0x1 << CONTROL2_RCPS_SHIFT) + #define MAX8997_ADC_GROUND 0x00 #define MAX8997_ADC_MHL 0x01 #define MAX8997_ADC_JIG_USB_1 0x18 -- cgit v1.2.3 From 027fcd50500fd87847891d5c2f341c1f002de3e8 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Wed, 13 Feb 2013 08:50:00 +0900 Subject: extcon: max8997: Set default of ADC debounce time during initialization This patch set default of ADC Debounce Time(25ms) during probe step. Also, can possible change ADC Debounce Time according to H/W situation by using max8997_set_adc_debounce_time() Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-max8997.c | 42 +++++++++++++++++++++++++++++++++++++ include/linux/mfd/max8997-private.h | 7 +++++++ 2 files changed, 49 insertions(+) (limited to 'include') diff --git a/drivers/extcon/extcon-max8997.c b/drivers/extcon/extcon-max8997.c index 8739b50c2b36..3206daaf8e08 100644 --- a/drivers/extcon/extcon-max8997.c +++ b/drivers/extcon/extcon-max8997.c @@ -30,6 +30,13 @@ #define DEV_NAME "max8997-muic" +enum max8997_muic_adc_debounce_time { + ADC_DEBOUNCE_TIME_0_5MS = 0, /* 0.5ms */ + ADC_DEBOUNCE_TIME_10MS, /* 10ms */ + ADC_DEBOUNCE_TIME_25MS, /* 25ms */ + ADC_DEBOUNCE_TIME_38_62MS, /* 38.62ms */ +}; + struct max8997_muic_irq { unsigned int irq; const char *name; @@ -94,6 +101,38 @@ static const char *max8997_extcon_cable[] = { NULL, }; +/* + * max8997_muic_set_debounce_time - Set the debounce time of ADC + * @info: the instance including private data of max8997 MUIC + * @time: the debounce time of ADC + */ +static int max8997_muic_set_debounce_time(struct max8997_muic_info *info, + enum max8997_muic_adc_debounce_time time) +{ + int ret; + + switch (time) { + case ADC_DEBOUNCE_TIME_0_5MS: + case ADC_DEBOUNCE_TIME_10MS: + case ADC_DEBOUNCE_TIME_25MS: + case ADC_DEBOUNCE_TIME_38_62MS: + ret = max8997_update_reg(info->muic, + MAX8997_MUIC_REG_CONTROL3, + time << CONTROL3_ADCDBSET_SHIFT, + CONTROL3_ADCDBSET_MASK); + if (ret) { + dev_err(info->dev, "failed to set ADC debounce time\n"); + return -EAGAIN; + } + break; + default: + dev_err(info->dev, "invalid ADC debounce time\n"); + return -EINVAL; + } + + return 0; +}; + /* * max8997_muic_set_path - Set hardware line according to attached cable * @info: the instance including private data of max8997 MUIC @@ -507,6 +546,9 @@ static int max8997_muic_probe(struct platform_device *pdev) } } + /* Set ADC debounce time */ + max8997_muic_set_debounce_time(info, ADC_DEBOUNCE_TIME_25MS); + /* Initial device detection */ max8997_muic_detect_dev(info); diff --git a/include/linux/mfd/max8997-private.h b/include/linux/mfd/max8997-private.h index 010173a92274..cd37a92ccba9 100644 --- a/include/linux/mfd/max8997-private.h +++ b/include/linux/mfd/max8997-private.h @@ -251,6 +251,13 @@ enum max8997_muic_reg { #define CONTROL2_USBCPINT_MASK (0x1 << CONTROL2_USBCPINT_SHIFT) #define CONTROL2_RCPS_MASK (0x1 << CONTROL2_RCPS_SHIFT) +#define CONTROL3_JIGSET_SHIFT (0) +#define CONTROL3_BTLDSET_SHIFT (2) +#define CONTROL3_ADCDBSET_SHIFT (4) +#define CONTROL3_JIGSET_MASK (0x3 << CONTROL3_JIGSET_SHIFT) +#define CONTROL3_BTLDSET_MASK (0x3 << CONTROL3_BTLDSET_SHIFT) +#define CONTROL3_ADCDBSET_MASK (0x3 << CONTROL3_ADCDBSET_SHIFT) + #define MAX8997_ADC_GROUND 0x00 #define MAX8997_ADC_MHL 0x01 #define MAX8997_ADC_JIG_USB_1 0x18 -- cgit v1.2.3 From f73f6232af9131f7b6fc6e377267e4a441727eb3 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Wed, 13 Feb 2013 12:05:42 +0900 Subject: extcon: max8997: Consolidate duplicate code for checking ADC/CHG cable type This patch make max8997_muic_get_cable_type() function to remove duplicate code for checking ADC/Charger cable type because almost internal function need to read adc/chg_type value of MUIC register. Also, remove *_detach() function, extcon-max8997 driver treat attach/detach operation of cable in max8997_*_handler() function. Lastly, this patch move defined constant in header file(include/ linux/mfd/max8997.h, max8997-private.h) because defined constant is only used in the 'extcon-max8997.c'. Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-max8997.c | 492 +++++++++++++++++++++++------------- include/linux/mfd/max8997-private.h | 9 - include/linux/mfd/max8997.h | 15 -- 3 files changed, 323 insertions(+), 193 deletions(-) (limited to 'include') diff --git a/drivers/extcon/extcon-max8997.c b/drivers/extcon/extcon-max8997.c index 3206daaf8e08..35338a090c06 100644 --- a/drivers/extcon/extcon-max8997.c +++ b/drivers/extcon/extcon-max8997.c @@ -44,31 +44,89 @@ struct max8997_muic_irq { }; static struct max8997_muic_irq muic_irqs[] = { - { MAX8997_MUICIRQ_ADCError, "muic-ADC_error" }, - { MAX8997_MUICIRQ_ADCLow, "muic-ADC_low" }, - { MAX8997_MUICIRQ_ADC, "muic-ADC" }, - { MAX8997_MUICIRQ_VBVolt, "muic-VB_voltage" }, - { MAX8997_MUICIRQ_DBChg, "muic-DB_charger" }, - { MAX8997_MUICIRQ_DCDTmr, "muic-DCD_timer" }, - { MAX8997_MUICIRQ_ChgDetRun, "muic-CDR_status" }, - { MAX8997_MUICIRQ_ChgTyp, "muic-charger_type" }, - { MAX8997_MUICIRQ_OVP, "muic-over_voltage" }, + { MAX8997_MUICIRQ_ADCError, "muic-ADCERROR" }, + { MAX8997_MUICIRQ_ADCLow, "muic-ADCLOW" }, + { MAX8997_MUICIRQ_ADC, "muic-ADC" }, + { MAX8997_MUICIRQ_VBVolt, "muic-VBVOLT" }, + { MAX8997_MUICIRQ_DBChg, "muic-DBCHG" }, + { MAX8997_MUICIRQ_DCDTmr, "muic-DCDTMR" }, + { MAX8997_MUICIRQ_ChgDetRun, "muic-CHGDETRUN" }, + { MAX8997_MUICIRQ_ChgTyp, "muic-CHGTYP" }, + { MAX8997_MUICIRQ_OVP, "muic-OVP" }, +}; + +/* Define supported cable type */ +enum max8997_muic_acc_type { + MAX8997_MUIC_ADC_GROUND = 0x0, + MAX8997_MUIC_ADC_MHL, /* MHL*/ + MAX8997_MUIC_ADC_REMOTE_S1_BUTTON, + MAX8997_MUIC_ADC_REMOTE_S2_BUTTON, + MAX8997_MUIC_ADC_REMOTE_S3_BUTTON, + MAX8997_MUIC_ADC_REMOTE_S4_BUTTON, + MAX8997_MUIC_ADC_REMOTE_S5_BUTTON, + MAX8997_MUIC_ADC_REMOTE_S6_BUTTON, + MAX8997_MUIC_ADC_REMOTE_S7_BUTTON, + MAX8997_MUIC_ADC_REMOTE_S8_BUTTON, + MAX8997_MUIC_ADC_REMOTE_S9_BUTTON, + MAX8997_MUIC_ADC_REMOTE_S10_BUTTON, + MAX8997_MUIC_ADC_REMOTE_S11_BUTTON, + MAX8997_MUIC_ADC_REMOTE_S12_BUTTON, + MAX8997_MUIC_ADC_RESERVED_ACC_1, + MAX8997_MUIC_ADC_RESERVED_ACC_2, + MAX8997_MUIC_ADC_RESERVED_ACC_3, + MAX8997_MUIC_ADC_RESERVED_ACC_4, + MAX8997_MUIC_ADC_RESERVED_ACC_5, + MAX8997_MUIC_ADC_CEA936_AUDIO, + MAX8997_MUIC_ADC_PHONE_POWERED_DEV, + MAX8997_MUIC_ADC_TTY_CONVERTER, + MAX8997_MUIC_ADC_UART_CABLE, + MAX8997_MUIC_ADC_CEA936A_TYPE1_CHG, + MAX8997_MUIC_ADC_FACTORY_MODE_USB_OFF, /* JIG-USB-OFF */ + MAX8997_MUIC_ADC_FACTORY_MODE_USB_ON, /* JIG-USB-ON */ + MAX8997_MUIC_ADC_AV_CABLE_NOLOAD, /* DESKDOCK */ + MAX8997_MUIC_ADC_CEA936A_TYPE2_CHG, + MAX8997_MUIC_ADC_FACTORY_MODE_UART_OFF, /* JIG-UART */ + MAX8997_MUIC_ADC_FACTORY_MODE_UART_ON, /* CARDOCK */ + MAX8997_MUIC_ADC_AUDIO_MODE_REMOTE, + MAX8997_MUIC_ADC_OPEN, /* OPEN */ +}; + +enum max8997_muic_cable_group { + MAX8997_CABLE_GROUP_ADC = 0, + MAX8997_CABLE_GROUP_ADC_GND, + MAX8997_CABLE_GROUP_CHG, + MAX8997_CABLE_GROUP_VBVOLT, +}; + +enum max8997_muic_usb_type { + MAX8997_USB_HOST, + MAX8997_USB_DEVICE, +}; + +enum max8997_muic_charger_type { + MAX8997_CHARGER_TYPE_NONE = 0, + MAX8997_CHARGER_TYPE_USB, + MAX8997_CHARGER_TYPE_DOWNSTREAM_PORT, + MAX8997_CHARGER_TYPE_DEDICATED_CHG, + MAX8997_CHARGER_TYPE_500MA, + MAX8997_CHARGER_TYPE_1A, + MAX8997_CHARGER_TYPE_DEAD_BATTERY = 7, }; struct max8997_muic_info { struct device *dev; struct i2c_client *muic; - struct max8997_muic_platform_data *muic_pdata; + struct extcon_dev *edev; + int prev_cable_type; + int prev_chg_type; + u8 status[2]; int irq; struct work_struct irq_work; - - enum max8997_muic_charger_type pre_charger_type; - int pre_adc; - struct mutex mutex; - struct extcon_dev *edev; + struct max8997_muic_platform_data *muic_pdata; + enum max8997_muic_charger_type pre_charger_type; }; enum { @@ -181,6 +239,83 @@ static int max8997_muic_set_path(struct max8997_muic_info *info, return 0; } +/* + * max8997_muic_get_cable_type - Return cable type and check cable state + * @info: the instance including private data of max8997 MUIC + * @group: the path according to attached cable + * @attached: store cable state and return + * + * This function check the cable state either attached or detached, + * and then divide precise type of cable according to cable group. + * - MAX8997_CABLE_GROUP_ADC + * - MAX8997_CABLE_GROUP_CHG + */ +static int max8997_muic_get_cable_type(struct max8997_muic_info *info, + enum max8997_muic_cable_group group, bool *attached) +{ + int cable_type = 0; + int adc; + int chg_type; + + switch (group) { + case MAX8997_CABLE_GROUP_ADC: + /* + * Read ADC value to check cable type and decide cable state + * according to cable type + */ + adc = info->status[0] & STATUS1_ADC_MASK; + adc >>= STATUS1_ADC_SHIFT; + + /* + * Check current cable state/cable type and store cable type + * (info->prev_cable_type) for handling cable when cable is + * detached. + */ + if (adc == MAX8997_MUIC_ADC_OPEN) { + *attached = false; + + cable_type = info->prev_cable_type; + info->prev_cable_type = MAX8997_MUIC_ADC_OPEN; + } else { + *attached = true; + + cable_type = info->prev_cable_type = adc; + } + break; + case MAX8997_CABLE_GROUP_CHG: + /* + * Read charger type to check cable type and decide cable state + * according to type of charger cable. + */ + chg_type = info->status[1] & STATUS2_CHGTYP_MASK; + chg_type >>= STATUS2_CHGTYP_SHIFT; + + if (chg_type == MAX8997_CHARGER_TYPE_NONE) { + *attached = false; + + cable_type = info->prev_chg_type; + info->prev_chg_type = MAX8997_CHARGER_TYPE_NONE; + } else { + *attached = true; + + /* + * Check current cable state/cable type and store cable + * type(info->prev_chg_type) for handling cable when + * charger cable is detached. + */ + cable_type = info->prev_chg_type = chg_type; + } + + break; + default: + dev_err(info->dev, "Unknown cable group (%d)\n", group); + cable_type = -EINVAL; + break; + } + + return cable_type; +} + static int max8997_muic_handle_usb(struct max8997_muic_info *info, enum max8997_muic_usb_type usb_type, bool attached) { @@ -188,9 +323,9 @@ static int max8997_muic_handle_usb(struct max8997_muic_info *info, if (usb_type == MAX8997_USB_HOST) { ret = max8997_muic_set_path(info, CONTROL1_SW_USB, attached); - if (ret) { + if (ret < 0) { dev_err(info->dev, "failed to update muic register\n"); - goto out; + return ret; } } @@ -202,38 +337,39 @@ static int max8997_muic_handle_usb(struct max8997_muic_info *info, extcon_set_cable_state(info->edev, "USB", attached); break; default: - ret = -EINVAL; - break; + dev_err(info->dev, "failed to detect %s usb cable\n", + attached ? "attached" : "detached"); + return -EINVAL; } -out: - return ret; + return 0; } static int max8997_muic_handle_dock(struct max8997_muic_info *info, - int adc, bool attached) + int cable_type, bool attached) { int ret = 0; ret = max8997_muic_set_path(info, CONTROL1_SW_AUDIO, attached); if (ret) { dev_err(info->dev, "failed to update muic register\n"); - goto out; + return ret; } - switch (adc) { - case MAX8997_ADC_DESKDOCK: + switch (cable_type) { + case MAX8997_MUIC_ADC_AV_CABLE_NOLOAD: extcon_set_cable_state(info->edev, "Dock-desk", attached); break; - case MAX8997_ADC_CARDOCK: + case MAX8997_MUIC_ADC_FACTORY_MODE_UART_ON: extcon_set_cable_state(info->edev, "Dock-card", attached); break; default: - ret = -EINVAL; - break; + dev_err(info->dev, "failed to detect %s dock device\n", + attached ? "attached" : "detached"); + return -EINVAL; } -out: - return ret; + + return 0; } static int max8997_muic_handle_jig_uart(struct max8997_muic_info *info, @@ -245,193 +381,185 @@ static int max8997_muic_handle_jig_uart(struct max8997_muic_info *info, ret = max8997_muic_set_path(info, CONTROL1_SW_UART, attached); if (ret) { dev_err(info->dev, "failed to update muic register\n"); - goto out; + return -EINVAL; } extcon_set_cable_state(info->edev, "JIG", attached); -out: - return ret; -} -static int max8997_muic_handle_adc_detach(struct max8997_muic_info *info) -{ - int ret = 0; - - switch (info->pre_adc) { - case MAX8997_ADC_GROUND: - ret = max8997_muic_handle_usb(info, MAX8997_USB_HOST, false); - break; - case MAX8997_ADC_MHL: - extcon_set_cable_state(info->edev, "MHL", false); - break; - case MAX8997_ADC_JIG_USB_1: - case MAX8997_ADC_JIG_USB_2: - ret = max8997_muic_handle_usb(info, MAX8997_USB_DEVICE, false); - break; - case MAX8997_ADC_DESKDOCK: - case MAX8997_ADC_CARDOCK: - ret = max8997_muic_handle_dock(info, info->pre_adc, false); - break; - case MAX8997_ADC_JIG_UART: - ret = max8997_muic_handle_jig_uart(info, false); - break; - default: - break; - } - - return ret; + return 0; } -static int max8997_muic_handle_adc(struct max8997_muic_info *info, int adc) +static int max8997_muic_adc_handler(struct max8997_muic_info *info) { + int cable_type; + bool attached; int ret = 0; - switch (adc) { - case MAX8997_ADC_GROUND: - ret = max8997_muic_handle_usb(info, MAX8997_USB_HOST, true); - break; - case MAX8997_ADC_MHL: - extcon_set_cable_state(info->edev, "MHL", true); - break; - case MAX8997_ADC_JIG_USB_1: - case MAX8997_ADC_JIG_USB_2: - ret = max8997_muic_handle_usb(info, MAX8997_USB_DEVICE, true); - break; - case MAX8997_ADC_DESKDOCK: - case MAX8997_ADC_CARDOCK: - ret = max8997_muic_handle_dock(info, adc, true); - break; - case MAX8997_ADC_JIG_UART: - ret = max8997_muic_handle_jig_uart(info, true); - break; - case MAX8997_ADC_OPEN: - ret = max8997_muic_handle_adc_detach(info); - break; - default: - ret = -EINVAL; - goto out; - } - - info->pre_adc = adc; -out: - return ret; -} - -static int max8997_muic_handle_charger_type_detach( - struct max8997_muic_info *info) -{ - switch (info->pre_charger_type) { - case MAX8997_CHARGER_TYPE_USB: - extcon_set_cable_state(info->edev, "USB", false); - break; - case MAX8997_CHARGER_TYPE_DOWNSTREAM_PORT: - extcon_set_cable_state(info->edev, "Charge-downstream", false); - break; - case MAX8997_CHARGER_TYPE_DEDICATED_CHG: - extcon_set_cable_state(info->edev, "TA", false); - break; - case MAX8997_CHARGER_TYPE_500MA: - extcon_set_cable_state(info->edev, "Slow-charger", false); - break; - case MAX8997_CHARGER_TYPE_1A: - extcon_set_cable_state(info->edev, "Fast-charger", false); - break; + /* Check cable state which is either detached or attached */ + cable_type = max8997_muic_get_cable_type(info, + MAX8997_CABLE_GROUP_ADC, &attached); + + switch (cable_type) { + case MAX8997_MUIC_ADC_GROUND: + ret = max8997_muic_handle_usb(info, MAX8997_USB_HOST, attached); + if (ret < 0) + return ret; + break; + case MAX8997_MUIC_ADC_MHL: + extcon_set_cable_state(info->edev, "MHL", attached); + break; + case MAX8997_MUIC_ADC_FACTORY_MODE_USB_OFF: + case MAX8997_MUIC_ADC_FACTORY_MODE_USB_ON: + ret = max8997_muic_handle_usb(info, MAX8997_USB_DEVICE, attached); + if (ret < 0) + return ret; + break; + case MAX8997_MUIC_ADC_AV_CABLE_NOLOAD: + case MAX8997_MUIC_ADC_FACTORY_MODE_UART_ON: + ret = max8997_muic_handle_dock(info, cable_type, attached); + if (ret < 0) + return ret; + break; + case MAX8997_MUIC_ADC_FACTORY_MODE_UART_OFF: + ret = max8997_muic_handle_jig_uart(info, attached); + break; + case MAX8997_MUIC_ADC_REMOTE_S1_BUTTON: + case MAX8997_MUIC_ADC_REMOTE_S2_BUTTON: + case MAX8997_MUIC_ADC_REMOTE_S3_BUTTON: + case MAX8997_MUIC_ADC_REMOTE_S4_BUTTON: + case MAX8997_MUIC_ADC_REMOTE_S5_BUTTON: + case MAX8997_MUIC_ADC_REMOTE_S6_BUTTON: + case MAX8997_MUIC_ADC_REMOTE_S7_BUTTON: + case MAX8997_MUIC_ADC_REMOTE_S8_BUTTON: + case MAX8997_MUIC_ADC_REMOTE_S9_BUTTON: + case MAX8997_MUIC_ADC_REMOTE_S10_BUTTON: + case MAX8997_MUIC_ADC_REMOTE_S11_BUTTON: + case MAX8997_MUIC_ADC_REMOTE_S12_BUTTON: + case MAX8997_MUIC_ADC_RESERVED_ACC_1: + case MAX8997_MUIC_ADC_RESERVED_ACC_2: + case MAX8997_MUIC_ADC_RESERVED_ACC_3: + case MAX8997_MUIC_ADC_RESERVED_ACC_4: + case MAX8997_MUIC_ADC_RESERVED_ACC_5: + case MAX8997_MUIC_ADC_CEA936_AUDIO: + case MAX8997_MUIC_ADC_PHONE_POWERED_DEV: + case MAX8997_MUIC_ADC_TTY_CONVERTER: + case MAX8997_MUIC_ADC_UART_CABLE: + case MAX8997_MUIC_ADC_CEA936A_TYPE1_CHG: + case MAX8997_MUIC_ADC_CEA936A_TYPE2_CHG: + case MAX8997_MUIC_ADC_AUDIO_MODE_REMOTE: + /* + * This cable isn't used in general case if it is specially + * needed to detect additional cable, should implement + * proper operation when this cable is attached/detached. + */ + dev_info(info->dev, + "cable is %s but it isn't used (type:0x%x)\n", + attached ? "attached" : "detached", cable_type); + return -EAGAIN; default: + dev_err(info->dev, + "failed to detect %s unknown cable (type:0x%x)\n", + attached ? "attached" : "detached", cable_type); return -EINVAL; } return 0; } -static int max8997_muic_handle_charger_type(struct max8997_muic_info *info, - enum max8997_muic_charger_type charger_type) +static int max8997_muic_chg_handler(struct max8997_muic_info *info) { - u8 adc; - int ret; + int chg_type; + bool attached; + int adc; - ret = max8997_read_reg(info->muic, MAX8997_MUIC_REG_STATUS1, &adc); - if (ret) { - dev_err(info->dev, "failed to read muic register\n"); - goto out; - } + chg_type = max8997_muic_get_cable_type(info, + MAX8997_CABLE_GROUP_CHG, &attached); - switch (charger_type) { + switch (chg_type) { case MAX8997_CHARGER_TYPE_NONE: - ret = max8997_muic_handle_charger_type_detach(info); break; case MAX8997_CHARGER_TYPE_USB: - if ((adc & STATUS1_ADC_MASK) == MAX8997_ADC_OPEN) { + adc = info->status[0] & STATUS1_ADC_MASK; + adc >>= STATUS1_ADC_SHIFT; + + if ((adc & STATUS1_ADC_MASK) == MAX8997_MUIC_ADC_OPEN) { max8997_muic_handle_usb(info, - MAX8997_USB_DEVICE, true); + MAX8997_USB_DEVICE, attached); } break; case MAX8997_CHARGER_TYPE_DOWNSTREAM_PORT: - extcon_set_cable_state(info->edev, "Charge-downstream", true); + extcon_set_cable_state(info->edev, "Charge-downstream", attached); break; case MAX8997_CHARGER_TYPE_DEDICATED_CHG: - extcon_set_cable_state(info->edev, "TA", true); + extcon_set_cable_state(info->edev, "TA", attached); break; case MAX8997_CHARGER_TYPE_500MA: - extcon_set_cable_state(info->edev, "Slow-charger", true); + extcon_set_cable_state(info->edev, "Slow-charger", attached); break; case MAX8997_CHARGER_TYPE_1A: - extcon_set_cable_state(info->edev, "Fast-charger", true); + extcon_set_cable_state(info->edev, "Fast-charger", attached); break; default: - ret = -EINVAL; - goto out; + dev_err(info->dev, + "failed to detect %s unknown chg cable (type:0x%x)\n", + attached ? "attached" : "detached", chg_type); + return -EINVAL; } - info->pre_charger_type = charger_type; -out: - return ret; + return 0; } static void max8997_muic_irq_work(struct work_struct *work) { struct max8997_muic_info *info = container_of(work, struct max8997_muic_info, irq_work); - u8 status[2]; - u8 adc, chg_type; int irq_type = 0; int i, ret; + if (!info->edev) + return; + mutex_lock(&info->mutex); + for (i = 0 ; i < ARRAY_SIZE(muic_irqs) ; i++) + if (info->irq == muic_irqs[i].virq) + irq_type = muic_irqs[i].irq; + ret = max8997_bulk_read(info->muic, MAX8997_MUIC_REG_STATUS1, - 2, status); + 2, info->status); if (ret) { dev_err(info->dev, "failed to read muic register\n"); mutex_unlock(&info->mutex); return; } - dev_dbg(info->dev, "%s: STATUS1:0x%x, 2:0x%x\n", __func__, - status[0], status[1]); - - for (i = 0 ; i < ARRAY_SIZE(muic_irqs) ; i++) - if (info->irq == muic_irqs[i].virq) - irq_type = muic_irqs[i].irq; - switch (irq_type) { + case MAX8997_MUICIRQ_ADCError: + case MAX8997_MUICIRQ_ADCLow: case MAX8997_MUICIRQ_ADC: - adc = status[0] & STATUS1_ADC_MASK; - adc >>= STATUS1_ADC_SHIFT; - - max8997_muic_handle_adc(info, adc); + /* Handle all of cable except for charger cable */ + ret = max8997_muic_adc_handler(info); break; + case MAX8997_MUICIRQ_VBVolt: + case MAX8997_MUICIRQ_DBChg: + case MAX8997_MUICIRQ_DCDTmr: + case MAX8997_MUICIRQ_ChgDetRun: case MAX8997_MUICIRQ_ChgTyp: - chg_type = status[1] & STATUS2_CHGTYP_MASK; - chg_type >>= STATUS2_CHGTYP_SHIFT; - - max8997_muic_handle_charger_type(info, chg_type); + /* Handle charger cable */ + ret = max8997_muic_chg_handler(info); + break; + case MAX8997_MUICIRQ_OVP: break; default: dev_info(info->dev, "misc interrupt: irq %d occurred\n", irq_type); - break; + mutex_unlock(&info->mutex); + return; } + if (ret < 0) + dev_err(info->dev, "failed to handle MUIC interrupt\n"); + mutex_unlock(&info->mutex); return; @@ -449,29 +577,49 @@ static irqreturn_t max8997_muic_irq_handler(int irq, void *data) return IRQ_HANDLED; } -static void max8997_muic_detect_dev(struct max8997_muic_info *info) +static int max8997_muic_detect_dev(struct max8997_muic_info *info) { - int ret; - u8 status[2], adc, chg_type; + int ret = 0; + int adc; + int chg_type; + bool attached; - ret = max8997_bulk_read(info->muic, MAX8997_MUIC_REG_STATUS1, - 2, status); + mutex_lock(&info->mutex); + + /* Read STATUSx register to detect accessory */ + ret = max8997_bulk_read(info->muic, + MAX8997_MUIC_REG_STATUS1, 2, info->status); if (ret) { - dev_err(info->dev, "failed to read muic register\n"); - return; + dev_err(info->dev, "failed to read MUIC register\n"); + mutex_unlock(&info->mutex); + return -EINVAL; } - dev_info(info->dev, "STATUS1:0x%x, STATUS2:0x%x\n", - status[0], status[1]); + adc = max8997_muic_get_cable_type(info, MAX8997_CABLE_GROUP_ADC, + &attached); + if (attached && adc != MAX8997_MUIC_ADC_OPEN) { + ret = max8997_muic_adc_handler(info); + if (ret < 0) { + dev_err(info->dev, "Cannot detect ADC cable\n"); + mutex_unlock(&info->mutex); + return ret; + } + } - adc = status[0] & STATUS1_ADC_MASK; - adc >>= STATUS1_ADC_SHIFT; + chg_type = max8997_muic_get_cable_type(info, MAX8997_CABLE_GROUP_CHG, + &attached); + if (attached && chg_type != MAX8997_CHARGER_TYPE_NONE) { + ret = max8997_muic_chg_handler(info); + if (ret < 0) { + dev_err(info->dev, "Cannot detect charger cable\n"); + mutex_unlock(&info->mutex); + return ret; + } + } - chg_type = status[1] & STATUS2_CHGTYP_MASK; - chg_type >>= STATUS2_CHGTYP_SHIFT; + mutex_unlock(&info->mutex); - max8997_muic_handle_adc(info, adc); - max8997_muic_handle_charger_type(info, chg_type); + return 0; } static int max8997_muic_probe(struct platform_device *pdev) @@ -550,10 +698,16 @@ static int max8997_muic_probe(struct platform_device *pdev) max8997_muic_set_debounce_time(info, ADC_DEBOUNCE_TIME_25MS); /* Initial device detection */ - max8997_muic_detect_dev(info); + ret = max8997_muic_detect_dev(info); + if (ret < 0) { + dev_err(&pdev->dev, "failed to detect cable type\n"); + goto err_extcon; + } return ret; +err_extcon: + extcon_dev_unregister(info->edev); err_irq: while (--i >= 0) free_irq(muic_irqs[i].virq, info); diff --git a/include/linux/mfd/max8997-private.h b/include/linux/mfd/max8997-private.h index cd37a92ccba9..fb465dfbb59e 100644 --- a/include/linux/mfd/max8997-private.h +++ b/include/linux/mfd/max8997-private.h @@ -258,15 +258,6 @@ enum max8997_muic_reg { #define CONTROL3_BTLDSET_MASK (0x3 << CONTROL3_BTLDSET_SHIFT) #define CONTROL3_ADCDBSET_MASK (0x3 << CONTROL3_ADCDBSET_SHIFT) -#define MAX8997_ADC_GROUND 0x00 -#define MAX8997_ADC_MHL 0x01 -#define MAX8997_ADC_JIG_USB_1 0x18 -#define MAX8997_ADC_JIG_USB_2 0x19 -#define MAX8997_ADC_DESKDOCK 0x1a -#define MAX8997_ADC_JIG_UART 0x1c -#define MAX8997_ADC_CARDOCK 0x1d -#define MAX8997_ADC_OPEN 0x1f - enum max8997_haptic_reg { MAX8997_HAPTIC_REG_GENERAL = 0x00, MAX8997_HAPTIC_REG_CONF1 = 0x01, diff --git a/include/linux/mfd/max8997.h b/include/linux/mfd/max8997.h index 1d4a4fe6ac33..65f8d6a3a608 100644 --- a/include/linux/mfd/max8997.h +++ b/include/linux/mfd/max8997.h @@ -78,21 +78,6 @@ struct max8997_regulator_data { struct device_node *reg_node; }; -enum max8997_muic_usb_type { - MAX8997_USB_HOST, - MAX8997_USB_DEVICE, -}; - -enum max8997_muic_charger_type { - MAX8997_CHARGER_TYPE_NONE = 0, - MAX8997_CHARGER_TYPE_USB, - MAX8997_CHARGER_TYPE_DOWNSTREAM_PORT, - MAX8997_CHARGER_TYPE_DEDICATED_CHG, - MAX8997_CHARGER_TYPE_500MA, - MAX8997_CHARGER_TYPE_1A, - MAX8997_CHARGER_TYPE_DEAD_BATTERY = 7, -}; - struct max8997_muic_reg_data { u8 addr; u8 data; -- cgit v1.2.3 From 685dc9a7dbfede28cc4a0fe4e65804194ec04fa5 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Wed, 13 Feb 2013 15:04:15 +0900 Subject: extcon: max8997: Set default UART/USB path on probe This patch set default H/W line path according to platfomr data. The MAX8997 MUIC device can possibly set UART/USB or UART_AUX /USB_AUX to internal H/W line path of MUIC device. Namely, only one H/W line is used for two operation. For example, if H/W line path of MAX8997 device set UART/USB, micro usb cable is connected to AP(Application Processor) and if H/W line path set UART_AUX/USB_AUX, micro usb cable is connected to CP(Coprocessor). Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-max8997.c | 28 ++++++++++++++++++++++++++-- include/linux/mfd/max8997.h | 7 +++++++ 2 files changed, 33 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/drivers/extcon/extcon-max8997.c b/drivers/extcon/extcon-max8997.c index 35338a090c06..349f65f6a4a4 100644 --- a/drivers/extcon/extcon-max8997.c +++ b/drivers/extcon/extcon-max8997.c @@ -127,6 +127,13 @@ struct max8997_muic_info { struct max8997_muic_platform_data *muic_pdata; enum max8997_muic_charger_type pre_charger_type; + + /* + * Default usb/uart path whether UART/USB or AUX_UART/AUX_USB + * h/w path of COMP2/COMN1 on CONTROL1 register. + */ + int path_usb; + int path_uart; }; enum { @@ -322,7 +329,7 @@ static int max8997_muic_handle_usb(struct max8997_muic_info *info, int ret = 0; if (usb_type == MAX8997_USB_HOST) { - ret = max8997_muic_set_path(info, CONTROL1_SW_USB, attached); + ret = max8997_muic_set_path(info, info->path_usb, attached); if (ret < 0) { dev_err(info->dev, "failed to update muic register\n"); return ret; @@ -378,7 +385,7 @@ static int max8997_muic_handle_jig_uart(struct max8997_muic_info *info, int ret = 0; /* switch to UART */ - ret = max8997_muic_set_path(info, CONTROL1_SW_UART, attached); + ret = max8997_muic_set_path(info, info->path_uart, attached); if (ret) { dev_err(info->dev, "failed to update muic register\n"); return -EINVAL; @@ -694,6 +701,23 @@ static int max8997_muic_probe(struct platform_device *pdev) } } + /* + * Default usb/uart path whether UART/USB or AUX_UART/AUX_USB + * h/w path of COMP2/COMN1 on CONTROL1 register. + */ + if (pdata->muic_pdata->path_uart) + info->path_uart = pdata->muic_pdata->path_uart; + else + info->path_uart = CONTROL1_SW_UART; + + if (pdata->muic_pdata->path_usb) + info->path_usb = pdata->muic_pdata->path_usb; + else + info->path_usb = CONTROL1_SW_USB; + + /* Set initial path for UART */ + max8997_muic_set_path(info, info->path_uart, true); + /* Set ADC debounce time */ max8997_muic_set_debounce_time(info, ADC_DEBOUNCE_TIME_25MS); diff --git a/include/linux/mfd/max8997.h b/include/linux/mfd/max8997.h index 65f8d6a3a608..2d2d67b6a393 100644 --- a/include/linux/mfd/max8997.h +++ b/include/linux/mfd/max8997.h @@ -92,6 +92,13 @@ struct max8997_muic_reg_data { struct max8997_muic_platform_data { struct max8997_muic_reg_data *init_data; int num_init_data; + + /* + * Default usb/uart path whether UART/USB or AUX_UART/AUX_USB + * h/w path of COMP2/COMN1 on CONTROL1 register. + */ + int path_usb; + int path_uart; }; enum max8997_haptic_motor_type { -- cgit v1.2.3 From af5eb1a13273447c5708cd5425696f3b6f79dd9b Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Wed, 13 Feb 2013 15:10:00 +0900 Subject: extcon: max8997: Use workqueue to check cable state after completing boot of platform This patch use delayed workqueue to check cable state after a certain time. If extcon-max8997 driver check cable state during booting of platform, this couldn't send the correct notification of cable state to extcon consumer. Alwasys, this driver should check cable state after the completion of platform initialization Signed-off-by: Chanwoo Choi Signed-off-by: Myungjoo Ham --- drivers/extcon/extcon-max8997.c | 45 ++++++++++++++++++++++++++++++++--------- include/linux/mfd/max8997.h | 3 +++ 2 files changed, 39 insertions(+), 9 deletions(-) (limited to 'include') diff --git a/drivers/extcon/extcon-max8997.c b/drivers/extcon/extcon-max8997.c index 349f65f6a4a4..e636d950ad6c 100644 --- a/drivers/extcon/extcon-max8997.c +++ b/drivers/extcon/extcon-max8997.c @@ -29,6 +29,7 @@ #include #define DEV_NAME "max8997-muic" +#define DELAY_MS_DEFAULT 20000 /* unit: millisecond */ enum max8997_muic_adc_debounce_time { ADC_DEBOUNCE_TIME_0_5MS = 0, /* 0.5ms */ @@ -128,6 +129,14 @@ struct max8997_muic_info { struct max8997_muic_platform_data *muic_pdata; enum max8997_muic_charger_type pre_charger_type; + /* + * Use delayed workqueue to detect cable state and then + * notify cable state to notifiee/platform through uevent. + * After completing the booting of platform, the extcon provider + * driver should notify cable state to upper layer. + */ + struct delayed_work wq_detcable; + /* * Default usb/uart path whether UART/USB or AUX_UART/AUX_USB * h/w path of COMP2/COMN1 on CONTROL1 register. @@ -629,11 +638,23 @@ static int max8997_muic_detect_dev(struct max8997_muic_info *info) return 0; } +static void max8997_muic_detect_cable_wq(struct work_struct *work) +{ + struct max8997_muic_info *info = container_of(to_delayed_work(work), + struct max8997_muic_info, wq_detcable); + int ret; + + ret = max8997_muic_detect_dev(info); + if (ret < 0) + pr_err("failed to detect cable type\n"); +} + static int max8997_muic_probe(struct platform_device *pdev) { struct max8997_dev *max8997 = dev_get_drvdata(pdev->dev.parent); struct max8997_platform_data *pdata = dev_get_platdata(max8997->dev); struct max8997_muic_info *info; + int delay_jiffies; int ret, i; info = devm_kzalloc(&pdev->dev, sizeof(struct max8997_muic_info), @@ -721,17 +742,23 @@ static int max8997_muic_probe(struct platform_device *pdev) /* Set ADC debounce time */ max8997_muic_set_debounce_time(info, ADC_DEBOUNCE_TIME_25MS); - /* Initial device detection */ - ret = max8997_muic_detect_dev(info); - if (ret < 0) { - dev_err(&pdev->dev, "failed to detect cable type\n"); - goto err_extcon; - } + /* + * Detect accessory after completing the initialization of platform + * + * - Use delayed workqueue to detect cable state and then + * notify cable state to notifiee/platform through uevent. + * After completing the booting of platform, the extcon provider + * driver should notify cable state to upper layer. + */ + INIT_DELAYED_WORK(&info->wq_detcable, max8997_muic_detect_cable_wq); + if (pdata->muic_pdata->detcable_delay_ms) + delay_jiffies = msecs_to_jiffies(pdata->muic_pdata->detcable_delay_ms); + else + delay_jiffies = msecs_to_jiffies(DELAY_MS_DEFAULT); + schedule_delayed_work(&info->wq_detcable, delay_jiffies); - return ret; + return 0; -err_extcon: - extcon_dev_unregister(info->edev); err_irq: while (--i >= 0) free_irq(muic_irqs[i].virq, info); diff --git a/include/linux/mfd/max8997.h b/include/linux/mfd/max8997.h index 2d2d67b6a393..cf815577bd68 100644 --- a/include/linux/mfd/max8997.h +++ b/include/linux/mfd/max8997.h @@ -93,6 +93,9 @@ struct max8997_muic_platform_data { struct max8997_muic_reg_data *init_data; int num_init_data; + /* Check cable state after certain delay */ + int detcable_delay_ms; + /* * Default usb/uart path whether UART/USB or AUX_UART/AUX_USB * h/w path of COMP2/COMN1 on CONTROL1 register. -- cgit v1.2.3