This is the initial skeleton driver for mhi bus stack. MHI Host Interface is a communication protocol to be used by the host to control and communcate with modem over a high speed peripheral bus. This module will allow host to communicate with external devices that support MHI protocol. Signed-off-by: Sujeev Dias <sdias@xxxxxxxxxxxxxx> Reviewed-by: Tony Truong <truong@xxxxxxxxxxxxxx> Signed-off-by: Siddartha Mohanadoss <smohanad@xxxxxxxxxxxxxx> --- Documentation/00-INDEX | 2 + Documentation/devicetree/bindings/bus/mhi.txt | 258 ++++++++++++ Documentation/mhi.txt | 235 +++++++++++ drivers/bus/Kconfig | 8 + drivers/bus/Makefile | 1 + drivers/bus/mhi/Makefile | 6 + drivers/bus/mhi/core/Makefile | 1 + drivers/bus/mhi/core/mhi_init.c | 538 ++++++++++++++++++++++++++ drivers/bus/mhi/core/mhi_internal.h | 238 ++++++++++++ drivers/bus/mhi/core/mhi_main.c | 122 ++++++ include/linux/mhi.h | 341 ++++++++++++++++ include/linux/mod_devicetable.h | 12 + 12 files changed, 1762 insertions(+) create mode 100644 Documentation/devicetree/bindings/bus/mhi.txt create mode 100644 Documentation/mhi.txt create mode 100644 drivers/bus/mhi/Makefile create mode 100644 drivers/bus/mhi/core/Makefile create mode 100644 drivers/bus/mhi/core/mhi_init.c create mode 100644 drivers/bus/mhi/core/mhi_internal.h create mode 100644 drivers/bus/mhi/core/mhi_main.c create mode 100644 include/linux/mhi.h diff --git a/Documentation/00-INDEX b/Documentation/00-INDEX index 708dc4c..44e2c6b 100644 --- a/Documentation/00-INDEX +++ b/Documentation/00-INDEX @@ -270,6 +270,8 @@ memory-hotplug.txt - Hotpluggable memory support, how to use and current status. men-chameleon-bus.txt - info on MEN chameleon bus. +mhi.txt + - Modem Host Interface mic/ - Intel Many Integrated Core (MIC) architecture device driver. mips/ diff --git a/Documentation/devicetree/bindings/bus/mhi.txt b/Documentation/devicetree/bindings/bus/mhi.txt new file mode 100644 index 0000000..19deb84 --- /dev/null +++ b/Documentation/devicetree/bindings/bus/mhi.txt @@ -0,0 +1,258 @@ +MHI Host Interface + +MHI used by the host to control and communicate with modem over +high speed peripheral bus. + +============== +Node Structure +============== + +Main node properties: + +- mhi,max-channels + Usage: required + Value type: <u32> + Definition: Maximum number of channels supported by this controller + +- mhi,timeout + Usage: optional + Value type: <u32> + Definition: Maximum timeout in ms wait for state and cmd completion + +- mhi,use-bb + Usage: optional + Value type: <bool> + Definition: Set true, if PCIe controller does not have full access to host + DDR, and we're using a dedicated memory pool like cma, or + carveout pool. Pool must support atomic allocation. + +- mhi,buffer-len + Usage: optional + Value type: <bool> + Definition: MHI automatically pre-allocate buffers for some channel. + Set the length of buffer size to allocate. If not default + size MHI_MAX_MTU will be used. + +============================ +mhi channel node properties: +============================ + +- reg + Usage: required + Value type: <u32> + Definition: physical channel number + +- label + Usage: required + Value type: <string> + Definition: given name for the channel + +- mhi,num-elements + Usage: optional + Value type: <u32> + Definition: Number of elements transfer ring support + +- mhi,event-ring + Usage: required + Value type: <u32> + Definition: Event ring index associated with this channel + +- mhi,chan-dir + Usage: required + Value type: <u32> + Definition: Channel direction as defined by enum dma_data_direction + 0 = Bidirectional data transfer + 1 = UL data transfer + 2 = DL data transfer + 3 = No direction, not a regular data transfer channel + +- mhi,ee + Usage: required + Value type: <u32> + Definition: Channel execution enviornment as defined by enum MHI_EE + 1 = Bootloader stage + 2 = AMSS mode + +- mhi,pollcfg + Usage: optional + Value type: <u32> + Definition: MHI poll configuration, valid only when burst mode is enabled + 0 = Use default (device specific) polling configuration + For UL channels, value specifies the timer to poll MHI context in + milliseconds. + For DL channels, the threshold to poll the MHI context in multiple of + eight ring element. + +- mhi,data-type + Usage: required + Value type: <u32> + Definition: Data transfer type accepted as defined by enum MHI_XFER_TYPE + 0 = accept cpu address for buffer + 1 = accept skb + 2 = accept scatterlist + 3 = offload channel, does not accept any transfer type + +- mhi,doorbell-mode + Usage: required + Value type: <u32> + Definition: Channel doorbell mode configuration as defined by enum + MHI_BRSTMODE + 2 = burst mode disabled + 3 = burst mode enabled + +- mhi,lpm-notify + Usage: optional + Value type: <bool> + Definition: This channel master require low power mode enter and exit + notifications from mhi bus master. + +- mhi,offload-chan + Usage: optional + Value type: <bool> + Definition: Client managed channel, MHI host only involved in setting up + the data path, not involved in active data path. + +- mhi,db-mode-switch + Usage: optional + Value type: <bool> + Definition: Must switch to doorbell mode whenever MHI M0 state transition + happens. + +- mhi,auto-queue + Usage: optional + Value type: <bool> + Definition: MHI bus driver will pre-allocate buffers for this channel and + queue to hardware. If set, client not allowed to queue buffers. Valid + only for downlink direction. + +- mhi,auto-start + Usage: optional + Value type: <bool> + Definition: MHI host driver to automatically start channels once mhi device + driver probe is complete. This should be only set true if initial + handshake iniaitead by external modem. + +========================== +mhi event node properties: +========================== + +- mhi,num-elements + Usage: required + Value type: <u32> + Definition: Number of elements event ring support + +- mhi,intmod + Usage: required + Value type: <u32> + Definition: interrupt moderation time in ms + +- mhi,msi + Usage: required + Value type: <u32> + Definition: MSI associated with this event ring + +- mhi,chan + Usage: optional + Value type: <u32> + Definition: Dedicated channel number, if it's a dedicated event ring + +- mhi,priority + Usage: required + Value type: <u32> + Definition: Event ring priority, set to 1 for now + +- mhi,brstmode + Usage: required + Value type: <u32> + Definition: Event doorbell mode configuration as defined by + enum MHI_BRSTMODE + 2 = burst mode disabled + 3 = burst mode enabled + +- mhi,data-type + Usage: optional + Value type: <u32> + Definition: Type of data this event ring will process as defined + by enum mhi_er_data_type + 0 = process data packets (default) + 1 = process mhi control packets + +- mhi,hw-ev + Usage: optional + Value type: <bool> + Definition: Event ring associated with hardware channels + +- mhi,client-manage + Usage: optional + Value type: <bool> + Definition: Client manages the event ring (use by napi_poll) + +- mhi,offload + Usage: optional + Value type: <bool> + Definition: Event ring associated with offload channel + + +Children node properties: + +MHI drivers that require DT can add driver specific information as a child node. + +- mhi,chan + Usage: Required + Value type: <string> + Definition: Channel name + +======== +Example: +======== +mhi_controller { + mhi,max-channels = <105>; + + mhi_chan@0 { + reg = <0>; + label = "LOOPBACK"; + mhi,num-elements = <64>; + mhi,event-ring = <2>; + mhi,chan-dir = <1>; + mhi,data-type = <0>; + mhi,doorbell-mode = <2>; + mhi,ee = <2>; + }; + + mhi_chan@1 { + reg = <1>; + label = "LOOPBACK"; + mhi,num-elements = <64>; + mhi,event-ring = <2>; + mhi,chan-dir = <2>; + mhi,data-type = <0>; + mhi,doorbell-mode = <2>; + mhi,ee = <2>; + }; + + mhi_event@0 { + mhi,num-elements = <32>; + mhi,intmod = <1>; + mhi,msi = <1>; + mhi,chan = <0>; + mhi,priority = <1>; + mhi,bstmode = <2>; + mhi,data-type = <1>; + }; + + mhi_event@1 { + mhi,num-elements = <256>; + mhi,intmod = <1>; + mhi,msi = <2>; + mhi,chan = <0>; + mhi,priority = <1>; + mhi,bstmode = <2>; + }; + + mhi,timeout = <500>; + + children_node { + mhi,chan = "LOOPBACK" + <driver specific properties> + }; +}; diff --git a/Documentation/mhi.txt b/Documentation/mhi.txt new file mode 100644 index 0000000..1c501f1 --- /dev/null +++ b/Documentation/mhi.txt @@ -0,0 +1,235 @@ +Overview of Linux kernel MHI support +==================================== + +Modem-Host Interface (MHI) +========================= +MHI used by the host to control and communicate with modem over high speed +peripheral bus. Even though MHI can be easily adapt to any peripheral buses, +primarily used with PCIe based devices. The host has one or more PCIe root +ports connected to modem device. The host has limited access to device memory +space, including register configuration and control the device operation. +Data transfers are invoked from the device. + +All data structures used by MHI are in the host system memory. Using PCIe +interface, the device accesses those data structures. MHI data structures and +data buffers in the host system memory regions are mapped for device. + +Memory spaces +------------- +PCIe Configurations : Used for enumeration and resource management, such as +interrupt and base addresses. This is done by mhi control driver. + +MMIO +---- +MHI MMIO : Memory mapped IO consists of set of registers in the device hardware, +which are mapped to the host memory space through PCIe base address register +(BAR) + +MHI control registers : Access to MHI configurations registers +(struct mhi_controller.regs). + +MHI BHI register: Boot host interface registers (struct mhi_controller.bhi) used +for firmware download before MHI initialization. + +Channel db array : Doorbell registers (struct mhi_chan.tre_ring.db_addr) used by +host to notify device there is new work to do. + +Event db array : Associated with event context array +(struct mhi_event.ring.db_addr), host uses to notify device free events are +available. + +Data structures +--------------- +Host memory : Directly accessed by the host to manage the MHI data structures +and buffers. The device accesses the host memory over the PCIe interface. + +Channel context array : All channel configurations are organized in channel +context data array. + +struct __packed mhi_chan_ctxt; +struct mhi_ctxt.chan_ctxt; + +Transfer rings : Used by host to schedule work items for a channel and organized +as a circular queue of transfer descriptors (TD). + +struct __packed mhi_tre; +struct mhi_chan.tre_ring; + +Event context array : All event configurations are organized in event context +data array. + +struct mhi_ctxt.er_ctxt; +struct __packed mhi_event_ctxt; + +Event rings: Used by device to send completion and state transition messages to +host + +struct mhi_event.ring; +struct __packed mhi_tre; + +Command context array: All command configurations are organized in command +context data array. + +struct __packed mhi_cmd_ctxt; +struct mhi_ctxt.cmd_ctxt; + +Command rings: Used by host to send MHI commands to device + +struct __packed mhi_tre; +struct mhi_cmd.ring; + +Transfer rings +-------------- +MHI channels are logical, unidirectional data pipes between host and device. +Each channel associated with a single transfer ring. The data direction can be +either inbound (device to host) or outbound (host to device). Transfer +descriptors are managed by using transfer rings, which are defined for each +channel between device and host and resides in the host memory. + +Transfer ring Pointer: Transfer Ring Array +[Read Pointer (RP)] ----------->[Ring Element] } TD +[Write Pointer (WP)]- [Ring Element] + - [Ring Element] + --------->[Ring Element] + [Ring Element] + +1. Host allocate memory for transfer ring +2. Host sets base, read pointer, write pointer in corresponding channel context +3. Ring is considered empty when RP == WP +4. Ring is considered full when WP + 1 == RP +4. RP indicates the next element to be serviced by device +4. When host new buffer to send, host update the Ring element with buffer + information +5. Host increment the WP to next element +6. Ring the associated channel DB. + +Event rings +----------- +Events from the device to host are organized in event rings and defined in event +descriptors. Event rings are array of EDs that resides in the host memory. + +Transfer ring Pointer: Event Ring Array +[Read Pointer (RP)] ----------->[Ring Element] } ED +[Write Pointer (WP)]- [Ring Element] + - [Ring Element] + --------->[Ring Element] + [Ring Element] + +1. Host allocate memory for event ring +2. Host sets base, read pointer, write pointer in corresponding channel context +3. Both host and device has local copy of RP, WP +3. Ring is considered empty (no events to service) when WP + 1 == RP +4. Ring is full of events when RP == WP +4. RP - 1 = last event device programmed +4. When there is a new event device need to send, device update ED pointed by RP +5. Device increment RP to next element +6. Device trigger and interrupt + +Example Operation for data transfer: + +1. Host prepare TD with buffer information +2. Host increment Chan[id].ctxt.WP +3. Host ring channel DB register +4. Device wakes up process the TD +5. Device generate a completion event for that TD by updating ED +6. Device increment Event[id].ctxt.RP +7. Device trigger MSI to wake host +8. Host wakes up and check event ring for completion event +9. Host update the Event[i].ctxt.WP to indicate processed of completion event. + +MHI States +---------- + +enum MHI_STATE { +MHI_STATE_RESET : MHI is in reset state, POR state. Host is not allowed to + access device MMIO register space. +MHI_STATE_READY : Device is ready for initialization. Host can start MHI + initialization by programming MMIO +MHI_STATE_M0 : MHI is in fully active state, data transfer is active +MHI_STATE_M1 : Device in a suspended state +MHI_STATE_M2 : MHI in low power mode, device may enter lower power mode. +MHI_STATE_M3 : Both host and device in suspended state. PCIe link is not + accessible to device. + +MHI Initialization +------------------ + +1. After system boots, the device is enumerated over PCIe interface +2. Host allocate MHI context for event, channel and command arrays +3. Initialize context array, and prepare interrupts +3. Host waits until device enter READY state +4. Program MHI MMIO registers and set device into MHI_M0 state +5. Wait for device to enter M0 state + +Linux Software Architecture +=========================== + +MHI Controller +-------------- +MHI controller is also the MHI bus master. In charge of managing the physical +link between host and device. Not involved in actual data transfer. At least +for PCIe based buses, for other type of bus, we can expand to add support. + +Roles: +1. Turn on PCIe bus and configure the link +2. Configure MSI, SMMU, and IOMEM +3. Allocate struct mhi_controller and register with MHI bus framework +2. Initiate power on and shutdown sequence +3. Initiate suspend and resume + +Usage +----- + +1. Allocate control data structure by calling mhi_alloc_controller() +2. Initialize mhi_controller with all the known information such as: + - Device Topology + - IOMMU window + - IOMEM mapping + - Device to use for memory allocation, and of_node with DT configuration + - Configure asynchronous callback functions +3. Register MHI controller with MHI bus framework by calling + of_register_mhi_controller() + +After successfully registering controller can initiate any of these power modes: + +1. Power up sequence + - mhi_prepare_for_power_up() + - mhi_async_power_up() + - mhi_sync_power_up() +2. Power down sequence + - mhi_power_down() + - mhi_unprepare_after_power_down() +3. Initiate suspend + - mhi_pm_suspend() +4. Initiate resume + - mhi_pm_resume() + +MHI Devices +----------- +Logical device that bind to maximum of two physical MHI channels. Once MHI is in +powered on state, each supported channel by controller will be allocated as a +mhi_device. + +Each supported device would be enumerated under +/sys/bus/mhi/devices/ + +struct mhi_device; + +MHI Driver +---------- +Each MHI driver can bind to one or more MHI devices. MHI host driver will bind +mhi_device to mhi_driver. + +All registered drivers are visible under +/sys/bus/mhi/drivers/ + +struct mhi_driver; + +Usage +----- + +1. Register driver using mhi_driver_register +2. Before sending data, prepare device for transfer by calling + mhi_prepare_for_transfer +3. Initiate data transfer by calling mhi_queue_transfer +4. After finish, call mhi_unprepare_from_transfer to end data transfer diff --git a/drivers/bus/Kconfig b/drivers/bus/Kconfig index d1c0b60..080d3c2 100644 --- a/drivers/bus/Kconfig +++ b/drivers/bus/Kconfig @@ -171,6 +171,14 @@ config DA8XX_MSTPRI configuration. Allows to adjust the priorities of all master peripherals. +config MHI_BUS + tristate "Modem Host Interface" + help + MHI Host Interface is a communication protocol to be used by the host + to control and communcate with modem over a high speed peripheral bus. + Enabling this module will allow host to communicate with external + devices that support MHI protocol. + source "drivers/bus/fsl-mc/Kconfig" endmenu diff --git a/drivers/bus/Makefile b/drivers/bus/Makefile index b8f036c..8fc0b3b 100644 --- a/drivers/bus/Makefile +++ b/drivers/bus/Makefile @@ -31,3 +31,4 @@ obj-$(CONFIG_UNIPHIER_SYSTEM_BUS) += uniphier-system-bus.o obj-$(CONFIG_VEXPRESS_CONFIG) += vexpress-config.o obj-$(CONFIG_DA8XX_MSTPRI) += da8xx-mstpri.o +obj-$(CONFIG_MHI_BUS) += mhi/ diff --git a/drivers/bus/mhi/Makefile b/drivers/bus/mhi/Makefile new file mode 100644 index 0000000..f8f14f7 --- /dev/null +++ b/drivers/bus/mhi/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for the MHI stack +# + +# core layer +obj-y += core/ diff --git a/drivers/bus/mhi/core/Makefile b/drivers/bus/mhi/core/Makefile new file mode 100644 index 0000000..a015809 --- /dev/null +++ b/drivers/bus/mhi/core/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_MHI_BUS) +=mhi_init.o mhi_main.o diff --git a/drivers/bus/mhi/core/mhi_init.c b/drivers/bus/mhi/core/mhi_init.c new file mode 100644 index 0000000..b8c30f8 --- /dev/null +++ b/drivers/bus/mhi/core/mhi_init.c @@ -0,0 +1,538 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018, The Linux Foundation. All rights reserved. + * + */ + +#include <linux/debugfs.h> +#include <linux/device.h> +#include <linux/dma-direction.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/list.h> +#include <linux/of.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/wait.h> +#include <linux/mhi.h> +#include "mhi_internal.h" + +static int of_parse_ev_cfg(struct mhi_controller *mhi_cntrl, + struct device_node *of_node) +{ + int i, ret, num = 0; + struct mhi_event *mhi_event; + struct device_node *child; + + for_each_available_child_of_node(of_node, child) { + if (!strcmp(child->name, "mhi_event")) + num++; + } + + if (!num) + return -EINVAL; + + mhi_cntrl->total_ev_rings = num; + mhi_cntrl->mhi_event = kcalloc(num, sizeof(*mhi_cntrl->mhi_event), + GFP_KERNEL); + if (!mhi_cntrl->mhi_event) + return -ENOMEM; + + /* populate ev ring */ + mhi_event = mhi_cntrl->mhi_event; + i = 0; + for_each_available_child_of_node(of_node, child) { + if (strcmp(child->name, "mhi_event")) + continue; + + mhi_event->er_index = i++; + ret = of_property_read_u32(child, "mhi,num-elements", + (u32 *)&mhi_event->ring.elements); + if (ret) + goto error_ev_cfg; + + ret = of_property_read_u32(child, "mhi,intmod", + &mhi_event->intmod); + if (ret) + goto error_ev_cfg; + + ret = of_property_read_u32(child, "mhi,msi", + &mhi_event->msi); + if (ret) + goto error_ev_cfg; + + ret = of_property_read_u32(child, "mhi,chan", + &mhi_event->chan); + if (!ret) { + if (mhi_event->chan >= mhi_cntrl->max_chan) + goto error_ev_cfg; + /* this event ring has a dedicated channel */ + mhi_event->mhi_chan = + &mhi_cntrl->mhi_chan[mhi_event->chan]; + } + + ret = of_property_read_u32(child, "mhi,priority", + &mhi_event->priority); + if (ret) + goto error_ev_cfg; + + ret = of_property_read_u32(child, "mhi,brstmode", + &mhi_event->db_cfg.brstmode); + if (ret || MHI_INVALID_BRSTMODE(mhi_event->db_cfg.brstmode)) + goto error_ev_cfg; + + mhi_event->db_cfg.process_db = + (mhi_event->db_cfg.brstmode == MHI_BRSTMODE_ENABLE) ? + mhi_db_brstmode : mhi_db_brstmode_disable; + + ret = of_property_read_u32(child, "mhi,data-type", + &mhi_event->data_type); + if (ret) + mhi_event->data_type = MHI_ER_DATA_ELEMENT_TYPE; + + if (mhi_event->data_type > MHI_ER_DATA_TYPE_MAX) + goto error_ev_cfg; + + switch (mhi_event->data_type) { + case MHI_ER_DATA_ELEMENT_TYPE: + mhi_event->process_event = mhi_process_data_event_ring; + break; + case MHI_ER_CTRL_ELEMENT_TYPE: + mhi_event->process_event = mhi_process_ctrl_ev_ring; + break; + } + + mhi_event->hw_ring = of_property_read_bool(child, "mhi,hw-ev"); + if (mhi_event->hw_ring) + mhi_cntrl->hw_ev_rings++; + else + mhi_cntrl->sw_ev_rings++; + mhi_event->cl_manage = of_property_read_bool(child, + "mhi,client-manage"); + mhi_event->offload_ev = of_property_read_bool(child, + "mhi,offload"); + mhi_event++; + } + + /* we need msi for each event ring + additional one for BHI */ + mhi_cntrl->msi_required = mhi_cntrl->total_ev_rings + 1; + + return 0; + +error_ev_cfg: + + kfree(mhi_cntrl->mhi_event); + return -EINVAL; +} +static int of_parse_ch_cfg(struct mhi_controller *mhi_cntrl, + struct device_node *of_node) +{ + int ret; + struct device_node *child; + u32 chan; + + ret = of_property_read_u32(of_node, "mhi,max-channels", + &mhi_cntrl->max_chan); + if (ret) + return ret; + + mhi_cntrl->mhi_chan = kcalloc(mhi_cntrl->max_chan, + sizeof(*mhi_cntrl->mhi_chan), GFP_KERNEL); + if (!mhi_cntrl->mhi_chan) + return -ENOMEM; + + INIT_LIST_HEAD(&mhi_cntrl->lpm_chans); + + /* populate channel configurations */ + for_each_available_child_of_node(of_node, child) { + struct mhi_chan *mhi_chan; + + if (strcmp(child->name, "mhi_chan")) + continue; + + ret = of_property_read_u32(child, "reg", &chan); + if (ret || chan >= mhi_cntrl->max_chan) + goto error_chan_cfg; + + mhi_chan = &mhi_cntrl->mhi_chan[chan]; + + ret = of_property_read_string(child, "label", + &mhi_chan->name); + if (ret) + goto error_chan_cfg; + + mhi_chan->chan = chan; + + ret = of_property_read_u32(child, "mhi,num-elements", + (u32 *)&mhi_chan->buf_ring.elements); + if (!ret && !mhi_chan->buf_ring.elements) + goto error_chan_cfg; + + mhi_chan->tre_ring.elements = mhi_chan->buf_ring.elements; + + ret = of_property_read_u32(child, "mhi,event-ring", + &mhi_chan->er_index); + if (ret) + goto error_chan_cfg; + + ret = of_property_read_u32(child, "mhi,chan-dir", + &mhi_chan->dir); + if (ret) + goto error_chan_cfg; + + ret = of_property_read_u32(child, "mhi,ee", &mhi_chan->ee); + if (ret || mhi_chan->ee >= MHI_EE_MAX_SUPPORTED) + goto error_chan_cfg; + + of_property_read_u32(child, "mhi,pollcfg", + &mhi_chan->db_cfg.pollcfg); + + ret = of_property_read_u32(child, "mhi,data-type", + &mhi_chan->xfer_type); + if (ret) + goto error_chan_cfg; + + switch (mhi_chan->xfer_type) { + case MHI_XFER_BUFFER: + mhi_chan->gen_tre = mhi_gen_tre; + mhi_chan->queue_xfer = mhi_queue_buf; + break; + case MHI_XFER_SKB: + mhi_chan->queue_xfer = mhi_queue_skb; + break; + case MHI_XFER_SCLIST: + mhi_chan->gen_tre = mhi_gen_tre; + mhi_chan->queue_xfer = mhi_queue_sclist; + break; + case MHI_XFER_NOP: + mhi_chan->queue_xfer = mhi_queue_nop; + break; + default: + goto error_chan_cfg; + } + + mhi_chan->lpm_notify = of_property_read_bool(child, + "mhi,lpm-notify"); + mhi_chan->offload_ch = of_property_read_bool(child, + "mhi,offload-chan"); + mhi_chan->db_cfg.reset_req = of_property_read_bool(child, + "mhi,db-mode-switch"); + mhi_chan->pre_alloc = of_property_read_bool(child, + "mhi,auto-queue"); + mhi_chan->auto_start = of_property_read_bool(child, + "mhi,auto-start"); + + if (mhi_chan->pre_alloc && + (mhi_chan->dir != DMA_FROM_DEVICE || + mhi_chan->xfer_type != MHI_XFER_BUFFER)) + goto error_chan_cfg; + + /* bi-dir and dirctionless channels must be a offload chan */ + if ((mhi_chan->dir == DMA_BIDIRECTIONAL || + mhi_chan->dir == DMA_NONE) && !mhi_chan->offload_ch) + goto error_chan_cfg; + + /* if mhi host allocate the buffers then client cannot queue */ + if (mhi_chan->pre_alloc) + mhi_chan->queue_xfer = mhi_queue_nop; + + if (!mhi_chan->offload_ch) { + ret = of_property_read_u32(child, "mhi,doorbell-mode", + &mhi_chan->db_cfg.brstmode); + if (ret || + MHI_INVALID_BRSTMODE(mhi_chan->db_cfg.brstmode)) + goto error_chan_cfg; + + mhi_chan->db_cfg.process_db = + (mhi_chan->db_cfg.brstmode == + MHI_BRSTMODE_ENABLE) ? + mhi_db_brstmode : mhi_db_brstmode_disable; + } + + mhi_chan->configured = true; + + if (mhi_chan->lpm_notify) + list_add_tail(&mhi_chan->node, &mhi_cntrl->lpm_chans); + } + + return 0; + +error_chan_cfg: + kfree(mhi_cntrl->mhi_chan); + + return -EINVAL; +} + +static int of_parse_dt(struct mhi_controller *mhi_cntrl, + struct device_node *of_node) +{ + int ret; + + /* parse MHI channel configuration */ + ret = of_parse_ch_cfg(mhi_cntrl, of_node); + if (ret) + return ret; + + /* parse MHI event configuration */ + ret = of_parse_ev_cfg(mhi_cntrl, of_node); + if (ret) + goto error_ev_cfg; + + ret = of_property_read_u32(of_node, "mhi,timeout", + &mhi_cntrl->timeout_ms); + if (ret) + mhi_cntrl->timeout_ms = MHI_TIMEOUT_MS; + + mhi_cntrl->bounce_buf = of_property_read_bool(of_node, "mhi,use-bb"); + ret = of_property_read_u32(of_node, "mhi,buffer-len", + (u32 *)&mhi_cntrl->buffer_len); + if (ret) + mhi_cntrl->buffer_len = MHI_MAX_MTU; + + return 0; + +error_ev_cfg: + kfree(mhi_cntrl->mhi_chan); + + return ret; +} + +int of_register_mhi_controller(struct mhi_controller *mhi_cntrl) +{ + int ret; + int i; + struct mhi_event *mhi_event; + struct mhi_chan *mhi_chan; + struct mhi_cmd *mhi_cmd; + struct mhi_device *mhi_dev; + + if (!mhi_cntrl->of_node) + return -EINVAL; + + if (!mhi_cntrl->runtime_get || !mhi_cntrl->runtime_put) + return -EINVAL; + + if (!mhi_cntrl->status_cb || !mhi_cntrl->link_status) + return -EINVAL; + + ret = of_parse_dt(mhi_cntrl, mhi_cntrl->of_node); + if (ret) + return -EINVAL; + + mhi_cntrl->mhi_cmd = kcalloc(NR_OF_CMD_RINGS, + sizeof(*mhi_cntrl->mhi_cmd), GFP_KERNEL); + if (!mhi_cntrl->mhi_cmd) { + ret = -ENOMEM; + goto error_alloc_cmd; + } + + INIT_LIST_HEAD(&mhi_cntrl->transition_list); + mutex_init(&mhi_cntrl->pm_mutex); + rwlock_init(&mhi_cntrl->pm_lock); + spin_lock_init(&mhi_cntrl->transition_lock); + spin_lock_init(&mhi_cntrl->wlock); + init_waitqueue_head(&mhi_cntrl->state_event); + + mhi_cmd = mhi_cntrl->mhi_cmd; + for (i = 0; i < NR_OF_CMD_RINGS; i++, mhi_cmd++) + spin_lock_init(&mhi_cmd->lock); + + mhi_event = mhi_cntrl->mhi_event; + for (i = 0; i < mhi_cntrl->total_ev_rings; i++, mhi_event++) { + if (mhi_event->offload_ev) + continue; + + mhi_event->mhi_cntrl = mhi_cntrl; + spin_lock_init(&mhi_event->lock); + if (mhi_event->data_type == MHI_ER_CTRL_ELEMENT_TYPE) + tasklet_init(&mhi_event->task, mhi_ctrl_ev_task, + (ulong)mhi_event); + else + tasklet_init(&mhi_event->task, mhi_ev_task, + (ulong)mhi_event); + } + + mhi_chan = mhi_cntrl->mhi_chan; + for (i = 0; i < mhi_cntrl->max_chan; i++, mhi_chan++) { + mutex_init(&mhi_chan->mutex); + init_completion(&mhi_chan->completion); + rwlock_init(&mhi_chan->lock); + } + + if (mhi_cntrl->bounce_buf) { + mhi_cntrl->map_single = mhi_map_single_use_bb; + mhi_cntrl->unmap_single = mhi_unmap_single_use_bb; + } else { + mhi_cntrl->map_single = mhi_map_single_no_bb; + mhi_cntrl->unmap_single = mhi_unmap_single_no_bb; + } + + /* register controller with mhi_bus */ + mhi_dev = mhi_alloc_device(mhi_cntrl); + if (!mhi_dev) { + ret = -ENOMEM; + goto error_alloc_dev; + } + + mhi_dev->dev_type = MHI_CONTROLLER_TYPE; + mhi_dev->mhi_cntrl = mhi_cntrl; + dev_set_name(&mhi_dev->dev, "%04x_%02u.%02u.%02u", mhi_dev->dev_id, + mhi_dev->domain, mhi_dev->bus, mhi_dev->slot); + ret = device_add(&mhi_dev->dev); + if (ret) + goto error_add_dev; + + mhi_cntrl->mhi_dev = mhi_dev; + + mhi_cntrl->parent = debugfs_lookup(mhi_bus_type.name, NULL); + + return 0; + +error_add_dev: + mhi_dealloc_device(mhi_cntrl, mhi_dev); + +error_alloc_dev: + kfree(mhi_cntrl->mhi_cmd); + +error_alloc_cmd: + kfree(mhi_cntrl->mhi_chan); + kfree(mhi_cntrl->mhi_event); + + return ret; +}; +EXPORT_SYMBOL(of_register_mhi_controller); + +void mhi_unregister_mhi_controller(struct mhi_controller *mhi_cntrl) +{ + struct mhi_device *mhi_dev = mhi_cntrl->mhi_dev; + + kfree(mhi_cntrl->mhi_cmd); + kfree(mhi_cntrl->mhi_event); + kfree(mhi_cntrl->mhi_chan); + + device_del(&mhi_dev->dev); + put_device(&mhi_dev->dev); +} + +/* set ptr to control private data */ +static inline void mhi_controller_set_devdata(struct mhi_controller *mhi_cntrl, + void *priv) +{ + mhi_cntrl->priv_data = priv; +} + + +/* allocate mhi controller to register */ +struct mhi_controller *mhi_alloc_controller(size_t size) +{ + struct mhi_controller *mhi_cntrl; + + mhi_cntrl = kzalloc(size + sizeof(*mhi_cntrl), GFP_KERNEL); + + if (mhi_cntrl && size) + mhi_controller_set_devdata(mhi_cntrl, mhi_cntrl + 1); + + return mhi_cntrl; +} +EXPORT_SYMBOL(mhi_alloc_controller); + +/* match dev to drv */ +static int mhi_match(struct device *dev, struct device_driver *drv) +{ + struct mhi_device *mhi_dev = to_mhi_device(dev); + struct mhi_driver *mhi_drv = to_mhi_driver(drv); + const struct mhi_device_id *id; + + /* if controller type there is no client driver associated with it */ + if (mhi_dev->dev_type == MHI_CONTROLLER_TYPE) + return 0; + + for (id = mhi_drv->id_table; id->chan[0]; id++) + if (!strcmp(mhi_dev->chan_name, id->chan)) { + mhi_dev->id = id; + return 1; + } + + return 0; +}; + +static void mhi_release_device(struct device *dev) +{ + struct mhi_device *mhi_dev = to_mhi_device(dev); + + kfree(mhi_dev); +} + +struct bus_type mhi_bus_type = { + .name = "mhi", + .dev_name = "mhi", + .match = mhi_match, +}; + +static int mhi_driver_probe(struct device *dev) +{ + return -EINVAL; +} + +static int mhi_driver_remove(struct device *dev) +{ + return 0; +} + +int mhi_driver_register(struct mhi_driver *mhi_drv) +{ + struct device_driver *driver = &mhi_drv->driver; + + if (!mhi_drv->probe || !mhi_drv->remove) + return -EINVAL; + + driver->bus = &mhi_bus_type; + driver->probe = mhi_driver_probe; + driver->remove = mhi_driver_remove; + + return driver_register(driver); +} +EXPORT_SYMBOL(mhi_driver_register); + +void mhi_driver_unregister(struct mhi_driver *mhi_drv) +{ + driver_unregister(&mhi_drv->driver); +} +EXPORT_SYMBOL(mhi_driver_unregister); + +struct mhi_device *mhi_alloc_device(struct mhi_controller *mhi_cntrl) +{ + struct mhi_device *mhi_dev = kzalloc(sizeof(*mhi_dev), GFP_KERNEL); + struct device *dev; + + if (!mhi_dev) + return NULL; + + dev = &mhi_dev->dev; + device_initialize(dev); + dev->bus = &mhi_bus_type; + dev->release = mhi_release_device; + dev->parent = mhi_cntrl->dev; + mhi_dev->mhi_cntrl = mhi_cntrl; + mhi_dev->dev_id = mhi_cntrl->dev_id; + mhi_dev->domain = mhi_cntrl->domain; + mhi_dev->bus = mhi_cntrl->bus; + mhi_dev->slot = mhi_cntrl->slot; + mhi_dev->mtu = MHI_MAX_MTU; + atomic_set(&mhi_dev->dev_wake, 0); + + return mhi_dev; +} + +static int __init mhi_init(void) +{ + /* parent directory */ + debugfs_create_dir(mhi_bus_type.name, NULL); + + return bus_register(&mhi_bus_type); +} +postcore_initcall(mhi_init); + +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("MHI_CORE"); +MODULE_DESCRIPTION("MHI Host Interface"); diff --git a/drivers/bus/mhi/core/mhi_internal.h b/drivers/bus/mhi/core/mhi_internal.h new file mode 100644 index 0000000..90c40de --- /dev/null +++ b/drivers/bus/mhi/core/mhi_internal.h @@ -0,0 +1,238 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2018, The Linux Foundation. All rights reserved. + * + */ + +#ifndef _MHI_INT_H +#define _MHI_INT_H + +extern struct bus_type mhi_bus_type; + +/* MHI transfer completion events */ +enum MHI_EV_CCS { + MHI_EV_CC_INVALID = 0x0, + MHI_EV_CC_SUCCESS = 0x1, + MHI_EV_CC_EOT = 0x2, + MHI_EV_CC_OVERFLOW = 0x3, + MHI_EV_CC_EOB = 0x4, + MHI_EV_CC_OOB = 0x5, + MHI_EV_CC_DB_MODE = 0x6, + MHI_EV_CC_UNDEFINED_ERR = 0x10, + MHI_EV_CC_BAD_TRE = 0x11, +}; + +enum MHI_CH_STATE { + MHI_CH_STATE_DISABLED = 0x0, + MHI_CH_STATE_ENABLED = 0x1, + MHI_CH_STATE_RUNNING = 0x2, + MHI_CH_STATE_SUSPENDED = 0x3, + MHI_CH_STATE_STOP = 0x4, + MHI_CH_STATE_ERROR = 0x5, +}; + +enum MHI_BRSTMODE { + MHI_BRSTMODE_DISABLE = 0x2, + MHI_BRSTMODE_ENABLE = 0x3, +}; + +#define MHI_INVALID_BRSTMODE(mode) (mode != MHI_BRSTMODE_DISABLE && \ + mode != MHI_BRSTMODE_ENABLE) + +enum MHI_EE { + MHI_EE_PBL = 0x0, + MHI_EE_SBL = 0x1, + MHI_EE_AMSS = 0x2, + MHI_EE_BHIE = 0x3, + MHI_EE_RDDM = 0x4, + MHI_EE_PTHRU = 0x5, + MHI_EE_EDL = 0x6, + MHI_EE_MAX_SUPPORTED = MHI_EE_EDL, + MHI_EE_DISABLE_TRANSITION, /* local EE, not related to mhi spec */ + MHI_EE_MAX, +}; + +/* accepted buffer type for the channel */ +enum MHI_XFER_TYPE { + MHI_XFER_BUFFER, + MHI_XFER_SKB, + MHI_XFER_SCLIST, + MHI_XFER_NOP, /* CPU offload channel, host does not accept transfer */ +}; + +#define NR_OF_CMD_RINGS (1) +#define CMD_EL_PER_RING (128) +#define PRIMARY_CMD_RING (0) +#define MHI_MAX_MTU (0xffff) + +enum MHI_ER_TYPE { + MHI_ER_TYPE_INVALID = 0x0, + MHI_ER_TYPE_VALID = 0x1, +}; + +enum mhi_er_data_type { + MHI_ER_DATA_ELEMENT_TYPE, + MHI_ER_CTRL_ELEMENT_TYPE, + MHI_ER_DATA_TYPE_MAX = MHI_ER_CTRL_ELEMENT_TYPE, +}; + +struct db_cfg { + bool reset_req; + bool db_mode; + u32 pollcfg; + enum MHI_BRSTMODE brstmode; + dma_addr_t db_val; + void (*process_db)(struct mhi_controller *mhi_cntrl, + struct db_cfg *db_cfg, void __iomem *io_addr, + dma_addr_t db_val); +}; + +struct mhi_ring { + dma_addr_t dma_handle; + dma_addr_t iommu_base; + u64 *ctxt_wp; /* point to ctxt wp */ + void *pre_aligned; + void *base; + void *rp; + void *wp; + size_t el_size; + size_t len; + size_t elements; + size_t alloc_size; + void __iomem *db_addr; +}; + +struct mhi_cmd { + struct mhi_ring ring; + spinlock_t lock; +}; + +struct mhi_buf_info { + dma_addr_t p_addr; + void *v_addr; + void *bb_addr; + void *wp; + size_t len; + void *cb_buf; + enum dma_data_direction dir; +}; + +struct mhi_event { + u32 er_index; + u32 intmod; + u32 msi; + int chan; /* this event ring is dedicated to a channel */ + u32 priority; + enum mhi_er_data_type data_type; + struct mhi_ring ring; + struct db_cfg db_cfg; + bool hw_ring; + bool cl_manage; + bool offload_ev; /* managed by a device driver */ + spinlock_t lock; + struct mhi_chan *mhi_chan; /* dedicated to channel */ + struct tasklet_struct task; + int (*process_event)(struct mhi_controller *mhi_cntrl, + struct mhi_event *mhi_event, + u32 event_quota); + struct mhi_controller *mhi_cntrl; +}; + +struct mhi_chan { + u32 chan; + const char *name; + /* + * important, when consuming increment tre_ring first, when releasing + * decrement buf_ring first. If tre_ring has space, buf_ring + * guranteed to have space so we do not need to check both rings. + */ + struct mhi_ring buf_ring; + struct mhi_ring tre_ring; + u32 er_index; + u32 intmod; + enum dma_data_direction dir; + struct db_cfg db_cfg; + enum MHI_EE ee; + enum MHI_XFER_TYPE xfer_type; + enum MHI_CH_STATE ch_state; + enum MHI_EV_CCS ccs; + bool lpm_notify; + bool configured; + bool offload_ch; + bool pre_alloc; + bool auto_start; + /* functions that generate the transfer ring elements */ + int (*gen_tre)(struct mhi_controller *mhi_cntrl, + struct mhi_chan *mhi_chan, void *buf, void *cb, + size_t len, enum MHI_FLAGS flags); + int (*queue_xfer)(struct mhi_device *mhi_dev, struct mhi_chan *mhi_chan, + void *buf, size_t len, enum MHI_FLAGS mflags); + /* xfer call back */ + struct mhi_device *mhi_dev; + void (*xfer_cb)(struct mhi_device *mhi_dev, struct mhi_result *result); + struct mutex mutex; + struct completion completion; + rwlock_t lock; + struct list_head node; +}; + +/* default MHI timeout */ +#define MHI_TIMEOUT_MS (1000) + +/* power management apis */ +void mhi_pm_st_worker(struct work_struct *work); +void mhi_fw_load_worker(struct work_struct *work); +void mhi_pm_sys_err_worker(struct work_struct *work); + +/* queue transfer buffer */ +int mhi_gen_tre(struct mhi_controller *mhi_cntrl, struct mhi_chan *mhi_chan, + void *buf, void *cb, size_t buf_len, enum MHI_FLAGS flags); +int mhi_queue_buf(struct mhi_device *mhi_dev, struct mhi_chan *mhi_chan, + void *buf, size_t len, enum MHI_FLAGS mflags); +int mhi_queue_skb(struct mhi_device *mhi_dev, struct mhi_chan *mhi_chan, + void *buf, size_t len, enum MHI_FLAGS mflags); +int mhi_queue_sclist(struct mhi_device *mhi_dev, struct mhi_chan *mhi_chan, + void *buf, size_t len, enum MHI_FLAGS mflags); +int mhi_queue_nop(struct mhi_device *mhi_dev, struct mhi_chan *mhi_chan, + void *buf, size_t len, enum MHI_FLAGS mflags); + +/* register access methods */ +void mhi_db_brstmode(struct mhi_controller *mhi_cntrl, struct db_cfg *db_cfg, + void __iomem *db_addr, dma_addr_t wp); +void mhi_db_brstmode_disable(struct mhi_controller *mhi_cntrl, + struct db_cfg *db_mode, void __iomem *db_addr, + dma_addr_t wp); + +struct mhi_device *mhi_alloc_device(struct mhi_controller *mhi_cntrl); +static inline void mhi_dealloc_device(struct mhi_controller *mhi_cntrl, + struct mhi_device *mhi_dev) +{ + kfree(mhi_dev); +} +int mhi_destroy_device(struct device *dev, void *data); +void mhi_create_devices(struct mhi_controller *mhi_cntrl); + +int mhi_map_single_no_bb(struct mhi_controller *mhi_cntrl, + struct mhi_buf_info *buf_info); +int mhi_map_single_use_bb(struct mhi_controller *mhi_cntrl, + struct mhi_buf_info *buf_info); +void mhi_unmap_single_no_bb(struct mhi_controller *mhi_cntrl, + struct mhi_buf_info *buf_info); +void mhi_unmap_single_use_bb(struct mhi_controller *mhi_cntrl, + struct mhi_buf_info *buf_info); +void mhi_ctrl_ev_task(unsigned long data); +int mhi_process_data_event_ring(struct mhi_controller *mhi_cntrl, + struct mhi_event *mhi_event, u32 event_quota); +int mhi_process_ctrl_ev_ring(struct mhi_controller *mhi_cntrl, + struct mhi_event *mhi_event, u32 event_quota); + +/* initialization methods */ +int mhi_dtr_init(void); + +/* isr handlers */ +irqreturn_t mhi_msi_handlr(int irq_number, void *dev); +irqreturn_t mhi_intvec_threaded_handlr(int irq_number, void *dev); +irqreturn_t mhi_intvec_handlr(int irq_number, void *dev); +void mhi_ev_task(unsigned long data); + +#endif /* _MHI_INT_H */ diff --git a/drivers/bus/mhi/core/mhi_main.c b/drivers/bus/mhi/core/mhi_main.c new file mode 100644 index 0000000..4df4cd0 --- /dev/null +++ b/drivers/bus/mhi/core/mhi_main.c @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018, The Linux Foundation. All rights reserved. + * + */ + +#include <linux/debugfs.h> +#include <linux/device.h> +#include <linux/dma-direction.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/list.h> +#include <linux/of.h> +#include <linux/module.h> +#include <linux/skbuff.h> +#include <linux/slab.h> +#include <linux/mhi.h> +#include "mhi_internal.h" + +void mhi_db_brstmode(struct mhi_controller *mhi_cntrl, + struct db_cfg *db_cfg, + void __iomem *db_addr, + dma_addr_t wp) +{ +} + +void mhi_db_brstmode_disable(struct mhi_controller *mhi_cntrl, + struct db_cfg *db_cfg, + void __iomem *db_addr, + dma_addr_t wp) +{ +} + +int mhi_map_single_no_bb(struct mhi_controller *mhi_cntrl, + struct mhi_buf_info *buf_info) +{ + return -ENOMEM; +} + +int mhi_map_single_use_bb(struct mhi_controller *mhi_cntrl, + struct mhi_buf_info *buf_info) +{ + return -ENOMEM; +} + +void mhi_unmap_single_no_bb(struct mhi_controller *mhi_cntrl, + struct mhi_buf_info *buf_info) +{ +} + +void mhi_unmap_single_use_bb(struct mhi_controller *mhi_cntrl, + struct mhi_buf_info *buf_info) +{ +} + +int mhi_queue_sclist(struct mhi_device *mhi_dev, + struct mhi_chan *mhi_chan, + void *buf, + size_t len, + enum MHI_FLAGS mflags) +{ + return -EINVAL; +} + +int mhi_queue_nop(struct mhi_device *mhi_dev, + struct mhi_chan *mhi_chan, + void *buf, + size_t len, + enum MHI_FLAGS mflags) +{ + return -EINVAL; +} + +int mhi_queue_skb(struct mhi_device *mhi_dev, + struct mhi_chan *mhi_chan, + void *buf, + size_t len, + enum MHI_FLAGS mflags) +{ + return -EINVAL; +} + +int mhi_gen_tre(struct mhi_controller *mhi_cntrl, + struct mhi_chan *mhi_chan, + void *buf, + void *cb, + size_t buf_len, + enum MHI_FLAGS flags) +{ + return -EINVAL; +} + +int mhi_queue_buf(struct mhi_device *mhi_dev, + struct mhi_chan *mhi_chan, + void *buf, + size_t len, + enum MHI_FLAGS mflags) +{ + return -EINVAL; +} + +int mhi_process_ctrl_ev_ring(struct mhi_controller *mhi_cntrl, + struct mhi_event *mhi_event, + u32 event_quota) +{ + return -EIO; +} + +int mhi_process_data_event_ring(struct mhi_controller *mhi_cntrl, + struct mhi_event *mhi_event, + u32 event_quota) +{ + return -EIO; +} + +void mhi_ev_task(unsigned long data) +{ +} + +void mhi_ctrl_ev_task(unsigned long data) +{ +} diff --git a/include/linux/mhi.h b/include/linux/mhi.h new file mode 100644 index 0000000..c80685e9 --- /dev/null +++ b/include/linux/mhi.h @@ -0,0 +1,341 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2018, The Linux Foundation. All rights reserved. + * + */ +#ifndef _MHI_H_ +#define _MHI_H_ + +struct mhi_chan; +struct mhi_event; +struct mhi_ctxt; +struct mhi_cmd; +struct mhi_buf_info; + +/** + * enum MHI_CB - MHI callback + * @MHI_CB_IDLE: MHI entered idle state + * @MHI_CB_PENDING_DATA: New data available for client to process + * @MHI_CB_LPM_ENTER: MHI host entered low power mode + * @MHI_CB_LPM_EXIT: MHI host about to exit low power mode + * @MHI_CB_EE_RDDM: MHI device entered RDDM execution enviornment + */ +enum MHI_CB { + MHI_CB_IDLE, + MHI_CB_PENDING_DATA, + MHI_CB_LPM_ENTER, + MHI_CB_LPM_EXIT, + MHI_CB_EE_RDDM, +}; + +/** + * enum MHI_FLAGS - Transfer flags + * @MHI_EOB: End of buffer for bulk transfer + * @MHI_EOT: End of transfer + * @MHI_CHAIN: Linked transfer + */ +enum MHI_FLAGS { + MHI_EOB, + MHI_EOT, + MHI_CHAIN, +}; + +/** + * enum mhi_device_type - Device types + * @MHI_XFER_TYPE: Handles data transfer + * @MHI_TIMESYNC_TYPE: Use for timesync feature + * @MHI_CONTROLLER_TYPE: Control device + */ +enum mhi_device_type { + MHI_XFER_TYPE, + MHI_TIMESYNC_TYPE, + MHI_CONTROLLER_TYPE, +}; + +/** + * struct mhi_controller - Master controller structure for external modem + * @dev: Device associated with this controller + * @of_node: DT that has MHI configuration information + * @regs: Points to base of MHI MMIO register space + * @dev_id: PCIe device id of the external device + * @domain: PCIe domain the device connected to + * @bus: PCIe bus the device assigned to + * @slot: PCIe slot for the modem + * @iova_start: IOMMU starting address for data + * @iova_stop: IOMMU stop address for data + * @fw_image: Firmware image name for normal booting + * @edl_image: Firmware image name for emergency download mode + * @fbc_download: MHI host needs to do complete image transfer + * @rddm_size: RAM dump size that host should allocate for debugging purpose + * @sbl_size: SBL image size + * @seg_len: BHIe vector size + * @fbc_image: Points to firmware image buffer + * @rddm_image: Points to RAM dump buffer + * @max_chan: Maximum number of channels controller support + * @mhi_chan: Points to channel configuration table + * @lpm_chans: List of channels that require LPM notifications + * @total_ev_rings: Total # of event rings allocated + * @hw_ev_rings: Number of hardware event rings + * @sw_ev_rings: Number of software event rings + * @msi_required: Number of msi required to operate + * @msi_allocated: Number of msi allocated by bus master + * @irq: base irq # to request + * @mhi_event: MHI event ring configurations table + * @mhi_cmd: MHI command ring configurations table + * @mhi_ctxt: MHI device context, shared memory between host and device + * @timeout_ms: Timeout in ms for state transitions + * @pm_state: Power management state + * @ee: MHI device execution environment + * @dev_state: MHI STATE + * @status_cb: CB function to notify various power states to but master + * @link_status: Query link status in case of abnormal value read from device + * @runtime_get: Async runtime resume function + * @runtimet_put: Release votes + * @time_get: Return host time in us + * @lpm_disable: Request controller to disable link level low power modes + * @lpm_enable: Controller may enable link level low power modes again + * @priv_data: Points to bus master's private data + */ +struct mhi_controller { + struct list_head node; + struct mhi_device *mhi_dev; + + /* device node for iommu ops */ + struct device *dev; + struct device_node *of_node; + + /* mmio base */ + void __iomem *regs; + + /* device topology */ + u32 dev_id; + u32 domain; + u32 bus; + u32 slot; + + /* addressing window */ + dma_addr_t iova_start; + dma_addr_t iova_stop; + + /* fw images */ + const char *fw_image; + const char *edl_image; + + /* mhi host manages downloading entire fbc images */ + bool fbc_download; + size_t rddm_size; + size_t sbl_size; + size_t seg_len; + u32 session_id; + u32 sequence_id; + + /* physical channel config data */ + u32 max_chan; + struct mhi_chan *mhi_chan; + struct list_head lpm_chans; /* these chan require lpm notification */ + + /* physical event config data */ + u32 total_ev_rings; + u32 hw_ev_rings; + u32 sw_ev_rings; + u32 msi_required; + u32 msi_allocated; + int *irq; /* interrupt table */ + struct mhi_event *mhi_event; + + /* cmd rings */ + struct mhi_cmd *mhi_cmd; + + /* mhi context (shared with device) */ + struct mhi_ctxt *mhi_ctxt; + + u32 timeout_ms; + + /* caller should grab pm_mutex for suspend/resume operations */ + struct mutex pm_mutex; + bool pre_init; + rwlock_t pm_lock; + u32 pm_state; + u32 ee; + u32 dev_state; + bool wake_set; + atomic_t dev_wake; + atomic_t alloc_size; + struct list_head transition_list; + spinlock_t transition_lock; + spinlock_t wlock; + + /* debug counters */ + u32 M0, M2, M3; + + /* worker for different state transitions */ + struct work_struct st_worker; + struct work_struct fw_worker; + struct work_struct syserr_worker; + wait_queue_head_t state_event; + + /* shadow functions */ + void (*status_cb)(struct mhi_controller *mhi_cntrl, void *priv, + enum MHI_CB cb); + int (*link_status)(struct mhi_controller *mhi_cntrl, void *priv); + void (*wake_get)(struct mhi_controller *mhi_cntrl, bool override); + void (*wake_put)(struct mhi_controller *mhi_cntrl, bool override); + int (*runtime_get)(struct mhi_controller *mhi_cntrl, void *priv); + void (*runtime_put)(struct mhi_controller *mhi_cntrl, void *priv); + void (*lpm_disable)(struct mhi_controller *mhi_cntrl, void *priv); + void (*lpm_enable)(struct mhi_controller *mhi_cntrl, void *priv); + int (*map_single)(struct mhi_controller *mhi_cntrl, + struct mhi_buf_info *buf); + void (*unmap_single)(struct mhi_controller *mhi_cntrl, + struct mhi_buf_info *buf); + + /* channel to control DTR messaging */ + struct mhi_device *dtr_dev; + + /* bounce buffer settings */ + bool bounce_buf; + size_t buffer_len; + + /* controller specific data */ + void *priv_data; + void *log_buf; + struct dentry *dentry; + struct dentry *parent; +}; + +/** + * struct mhi_device - mhi device structure associated bind to channel + * @dev: Device associated with the channels + * @mtu: Maximum # of bytes controller support + * @ul_chan_id: MHI channel id for UL transfer + * @dl_chan_id: MHI channel id for DL transfer + * @tiocm: Device current terminal settings + * @priv: Driver private data + */ +struct mhi_device { + struct device dev; + u32 dev_id; + u32 domain; + u32 bus; + u32 slot; + size_t mtu; + int ul_chan_id; + int dl_chan_id; + int ul_event_id; + int dl_event_id; + u32 tiocm; + const struct mhi_device_id *id; + const char *chan_name; + struct mhi_controller *mhi_cntrl; + struct mhi_chan *ul_chan; + struct mhi_chan *dl_chan; + atomic_t dev_wake; + enum mhi_device_type dev_type; + void *priv_data; + int (*ul_xfer)(struct mhi_device *mhi_dev, struct mhi_chan *mhi_chan, + void *buf, size_t len, enum MHI_FLAGS flags); + int (*dl_xfer)(struct mhi_device *mhi_dev, struct mhi_chan *mhi_chan, + void *buf, size_t len, enum MHI_FLAGS flags); + void (*status_cb)(struct mhi_device *mhi_dev, enum MHI_CB cb); +}; + +/** + * struct mhi_result - Completed buffer information + * @buf_addr: Address of data buffer + * @dir: Channel direction + * @bytes_xfer: # of bytes transferred + * @transaction_status: Status of last trasnferred + */ +struct mhi_result { + void *buf_addr; + enum dma_data_direction dir; + size_t bytes_xferd; + int transaction_status; +}; + +/** + * struct mhi_driver - mhi driver information + * @id_table: NULL terminated channel ID names + * @ul_xfer_cb: UL data transfer callback + * @dl_xfer_cb: DL data transfer callback + * @status_cb: Asynchronous status callback + */ +struct mhi_driver { + const struct mhi_device_id *id_table; + int (*probe)(struct mhi_device *mhi_dev, + const struct mhi_device_id *id); + void (*remove)(struct mhi_device *mhi_dev); + void (*ul_xfer_cb)(struct mhi_device *mhi_dev, + struct mhi_result *result); + void (*dl_xfer_cb)(struct mhi_device *mhi_dev, + struct mhi_result *result); + void (*status_cb)(struct mhi_device *mhi_dev, enum MHI_CB mhi_cb); + struct device_driver driver; +}; + +#define to_mhi_driver(drv) container_of(drv, struct mhi_driver, driver) +#define to_mhi_device(dev) container_of(dev, struct mhi_device, dev) + +static inline void mhi_device_set_devdata(struct mhi_device *mhi_dev, + void *priv) +{ + mhi_dev->priv_data = priv; +} + +static inline void *mhi_device_get_devdata(struct mhi_device *mhi_dev) +{ + return mhi_dev->priv_data; +} + +static inline void *mhi_controller_get_devdata(struct mhi_controller *mhi_cntrl) +{ + return mhi_cntrl->priv_data; +} + +static inline void mhi_free_controller(struct mhi_controller *mhi_cntrl) +{ + kfree(mhi_cntrl); +} + +/** + * mhi_driver_register - Register driver with MHI framework + * @mhi_drv: mhi_driver structure + */ +int mhi_driver_register(struct mhi_driver *mhi_drv); + +/** + * mhi_driver_unregister - Unregister a driver for mhi_devices + * @mhi_drv: mhi_driver structure + */ +void mhi_driver_unregister(struct mhi_driver *mhi_drv); + +/** + * mhi_alloc_controller - Allocate mhi_controller structure + * Allocate controller structure and additional data for controller + * private data. You may get the private data pointer by calling + * mhi_controller_get_devdata + * @size: # of additional bytes to allocate + */ +struct mhi_controller *mhi_alloc_controller(size_t size); + +/** + * of_register_mhi_controller - Register MHI controller + * Registers MHI controller with MHI bus framework. DT must be supported + * @mhi_cntrl: MHI controller to register + */ +int of_register_mhi_controller(struct mhi_controller *mhi_cntrl); + +void mhi_unregister_mhi_controller(struct mhi_controller *mhi_cntrl); + +/** + * mhi_bdf_to_controller - Look up a registered controller + * Search for controller based on device identification + * @domain: RC domain of the device + * @bus: Bus device connected to + * @slot: Slot device assigned to + * @dev_id: Device Identification + */ +struct mhi_controller *mhi_bdf_to_controller(u32 domain, u32 bus, u32 slot, + u32 dev_id); + +#endif /* _MHI_H_ */ diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index 7d361be..453ec01 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -734,4 +734,16 @@ struct tb_service_id { #define TBSVC_MATCH_PROTOCOL_VERSION 0x0004 #define TBSVC_MATCH_PROTOCOL_REVISION 0x0008 +#define MHI_NAME_SIZE 32 + +/** + * struct mhi_device_id - MHI device identification + * @chan: MHI channel name + * @driver_data: driver data; + */ +struct mhi_device_id { + const char chan[MHI_NAME_SIZE]; + kernel_ulong_t driver_data; +}; + #endif /* LINUX_MOD_DEVICETABLE_H */ -- The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project -- To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html