From 2c3c1048746a4622d8c89a29670120dc8fab93c4 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 20:49:45 +0200 Subject: Adding upstream version 6.1.76. Signed-off-by: Daniel Baumann --- .../most/Documentation/ABI/sysfs-class-most.txt | 315 ++++++ .../staging/most/Documentation/driver_usage.txt | 237 +++++ drivers/staging/most/Kconfig | 29 + drivers/staging/most/Makefile | 6 + drivers/staging/most/TODO | 8 + drivers/staging/most/dim2/Kconfig | 17 + drivers/staging/most/dim2/Makefile | 4 + drivers/staging/most/dim2/dim2.c | 1106 ++++++++++++++++++++ drivers/staging/most/dim2/errors.h | 51 + drivers/staging/most/dim2/hal.c | 975 +++++++++++++++++ drivers/staging/most/dim2/hal.h | 102 ++ drivers/staging/most/dim2/reg.h | 157 +++ drivers/staging/most/dim2/sysfs.h | 19 + drivers/staging/most/i2c/Kconfig | 13 + drivers/staging/most/i2c/Makefile | 4 + drivers/staging/most/i2c/i2c.c | 371 +++++++ drivers/staging/most/net/Kconfig | 14 + drivers/staging/most/net/Makefile | 4 + drivers/staging/most/net/net.c | 581 ++++++++++ drivers/staging/most/video/Kconfig | 13 + drivers/staging/most/video/Makefile | 4 + drivers/staging/most/video/video.c | 584 +++++++++++ 22 files changed, 4614 insertions(+) create mode 100644 drivers/staging/most/Documentation/ABI/sysfs-class-most.txt create mode 100644 drivers/staging/most/Documentation/driver_usage.txt create mode 100644 drivers/staging/most/Kconfig create mode 100644 drivers/staging/most/Makefile create mode 100644 drivers/staging/most/TODO create mode 100644 drivers/staging/most/dim2/Kconfig create mode 100644 drivers/staging/most/dim2/Makefile create mode 100644 drivers/staging/most/dim2/dim2.c create mode 100644 drivers/staging/most/dim2/errors.h create mode 100644 drivers/staging/most/dim2/hal.c create mode 100644 drivers/staging/most/dim2/hal.h create mode 100644 drivers/staging/most/dim2/reg.h create mode 100644 drivers/staging/most/dim2/sysfs.h create mode 100644 drivers/staging/most/i2c/Kconfig create mode 100644 drivers/staging/most/i2c/Makefile create mode 100644 drivers/staging/most/i2c/i2c.c create mode 100644 drivers/staging/most/net/Kconfig create mode 100644 drivers/staging/most/net/Makefile create mode 100644 drivers/staging/most/net/net.c create mode 100644 drivers/staging/most/video/Kconfig create mode 100644 drivers/staging/most/video/Makefile create mode 100644 drivers/staging/most/video/video.c (limited to 'drivers/staging/most') diff --git a/drivers/staging/most/Documentation/ABI/sysfs-class-most.txt b/drivers/staging/most/Documentation/ABI/sysfs-class-most.txt new file mode 100644 index 000000000..48aa45acc --- /dev/null +++ b/drivers/staging/most/Documentation/ABI/sysfs-class-most.txt @@ -0,0 +1,315 @@ +What: /sys/class/most/mostcore/aims +Date: June 2015 +KernelVersion: 4.3 +Contact: Christian Gromm +Description: + List of AIMs that have been loaded. +Users: + +What: /sys/class/most/mostcore/aims//add_link +Date: June 2015 +KernelVersion: 4.3 +Contact: Christian Gromm +Description: + This is used to establish a connection of a channel and the + current AIM. +Users: + +What: /sys/class/most/mostcore/aims//remove_link +Date: June 2015 +KernelVersion: 4.3 +Contact: Christian Gromm +Description: + This is used to remove a connected channel from the + current AIM. +Users: + +What: /sys/class/most/mostcore/devices +Date: June 2015 +KernelVersion: 4.3 +Contact: Christian Gromm +Description: + List of attached MOST interfaces. +Users: + +What: /sys/class/most/mostcore/devices//description +Date: June 2015 +KernelVersion: 4.3 +Contact: Christian Gromm +Description: + Provides information about the interface type and the physical + location of the device. Hardware attached via USB, for instance, + might return +Users: + +What: /sys/class/most/mostcore/devices//interface +Date: June 2015 +KernelVersion: 4.3 +Contact: Christian Gromm +Description: + Indicates the type of peripheral interface the current device + uses. +Users: + +What: /sys/class/most/mostcore/devices//dci +Date: June 2016 +KernelVersion: 4.9 +Contact: Christian Gromm +Description: + If the network interface controller is attached via USB, a dci + directory is created that allows applications to use the + controller's direct communication interface (DCI) to exchange + information. +Users: + +What: /sys/class/most/mostcore/devices//dci/arb_address +Date: June 2016 +KernelVersion: 4.9 +Contact: Christian Gromm +Description: + This is used to set an arbitrary DCI register address an + application wants to read from or write to. +Users: + +What: /sys/class/most/mostcore/devices//dci/arb_value +Date: June 2016 +KernelVersion: 4.9 +Contact: Christian Gromm +Description: + This is used to read from or write to the arbitrary DCI register + whose address is stored in arb_address. +Users: + +What: /sys/class/most/mostcore/devices//dci/mep_eui48_hi +Date: June 2016 +KernelVersion: 4.9 +Contact: Christian Gromm +Description: + This is used to check and configure the MAC address. +Users: + +What: /sys/class/most/mostcore/devices//dci/mep_eui48_lo +Date: June 2016 +KernelVersion: 4.9 +Contact: Christian Gromm +Description: + This is used to check and configure the MAC address. +Users: + +What: /sys/class/most/mostcore/devices//dci/mep_eui48_mi +Date: June 2016 +KernelVersion: 4.9 +Contact: Christian Gromm +Description: + This is used to check and configure the MAC address. +Users: + +What: /sys/class/most/mostcore/devices//dci/mep_filter +Date: June 2016 +KernelVersion: 4.9 +Contact: Christian Gromm +Description: + This is used to check and configure the MEP filter address. +Users: + +What: /sys/class/most/mostcore/devices//dci/mep_hash0 +Date: June 2016 +KernelVersion: 4.9 +Contact: Christian Gromm +Description: + This is used to check and configure the MEP hash table. +Users: + +What: /sys/class/most/mostcore/devices//dci/mep_hash1 +Date: June 2016 +KernelVersion: 4.9 +Contact: Christian Gromm +Description: + This is used to check and configure the MEP hash table. +Users: + +What: /sys/class/most/mostcore/devices//dci/mep_hash2 +Date: June 2016 +KernelVersion: 4.9 +Contact: Christian Gromm +Description: + This is used to check and configure the MEP hash table. +Users: + +What: /sys/class/most/mostcore/devices//dci/mep_hash3 +Date: June 2016 +KernelVersion: 4.9 +Contact: Christian Gromm +Description: + This is used to check and configure the MEP hash table. +Users: + +What: /sys/class/most/mostcore/devices//dci/ni_state +Date: June 2016 +KernelVersion: 4.9 +Contact: Christian Gromm +Description: + Indicates the current network interface state. +Users: + +What: /sys/class/most/mostcore/devices//dci/node_address +Date: June 2016 +KernelVersion: 4.9 +Contact: Christian Gromm +Description: + Indicates the current node address. +Users: + +What: /sys/class/most/mostcore/devices//dci/node_position +Date: June 2016 +KernelVersion: 4.9 +Contact: Christian Gromm +Description: + Indicates the current node position. +Users: + +What: /sys/class/most/mostcore/devices//dci/packet_bandwidth +Date: June 2016 +KernelVersion: 4.9 +Contact: Christian Gromm +Description: + Indicates the configured packet bandwidth. +Users: + +What: /sys/class/most/mostcore/devices//dci/sync_ep +Date: June 2016 +KernelVersion: 4.9 +Contact: Christian Gromm +Description: + Triggers the controller's synchronization process for a certain + endpoint. +Users: + +What: /sys/class/most/mostcore/devices/// +Date: June 2015 +KernelVersion: 4.3 +Contact: Christian Gromm +Description: + For every channel of the device a directory is created, whose + name is dictated by the HDM. This enables an application to + collect information about the channel's capabilities and + configure it. +Users: + +What: /sys/class/most/mostcore/devices///available_datatypes +Date: June 2015 +KernelVersion: 4.3 +Contact: Christian Gromm +Description: + Indicates the data types the current channel can transport. +Users: + +What: /sys/class/most/mostcore/devices///available_directions +Date: June 2015 +KernelVersion: 4.3 +Contact: Christian Gromm +Description: + Indicates the directions the current channel is capable of. +Users: + +What: /sys/class/most/mostcore/devices///number_of_packet_buffers +Date: June 2015 +KernelVersion: 4.3 +Contact: Christian Gromm +Description: + Indicates the number of packet buffers the current channel can + handle. +Users: + +What: /sys/class/most/mostcore/devices///number_of_stream_buffers +Date: June 2015 +KernelVersion: 4.3 +Contact: Christian Gromm +Description: + Indicates the number of streaming buffers the current channel can + handle. +Users: + +What: /sys/class/most/mostcore/devices///size_of_packet_buffer +Date: June 2015 +KernelVersion: 4.3 +Contact: Christian Gromm +Description: + Indicates the size of a packet buffer the current channel can + handle. +Users: + +What: /sys/class/most/mostcore/devices///size_of_stream_buffer +Date: June 2015 +KernelVersion: 4.3 +Contact: Christian Gromm +Description: + Indicates the size of a streaming buffer the current channel can + handle. +Users: + +What: /sys/class/most/mostcore/devices///set_number_of_buffers +Date: June 2015 +KernelVersion: 4.3 +Contact: Christian Gromm +Description: + This is to configure the number of buffers of the current channel. +Users: + +What: /sys/class/most/mostcore/devices///set_buffer_size +Date: June 2015 +KernelVersion: 4.3 +Contact: Christian Gromm +Description: + This is to configure the size of a buffer of the current channel. +Users: + +What: /sys/class/most/mostcore/devices///set_direction +Date: June 2015 +KernelVersion: 4.3 +Contact: Christian Gromm +Description: + This is to configure the direction of the current channel. + The following strings will be accepted: + 'dir_tx', + 'dir_rx' +Users: + +What: /sys/class/most/mostcore/devices///set_datatype +Date: June 2015 +KernelVersion: 4.3 +Contact: Christian Gromm +Description: + This is to configure the data type of the current channel. + The following strings will be accepted: + 'control', + 'async', + 'sync', + 'isoc_avp' +Users: + +What: /sys/class/most/mostcore/devices///set_subbuffer_size +Date: June 2015 +KernelVersion: 4.3 +Contact: Christian Gromm +Description: + This is to configure the subbuffer size of the current channel. +Users: + +What: /sys/class/most/mostcore/devices///set_packets_per_xact +Date: June 2015 +KernelVersion: 4.3 +Contact: Christian Gromm +Description: + This is to configure the number of packets per transaction of + the current channel. This is only needed network interface + controller is attached via USB. +Users: + +What: /sys/class/most/mostcore/devices///channel_starving +Date: June 2015 +KernelVersion: 4.3 +Contact: Christian Gromm +Description: + Indicates whether current channel ran out of buffers. +Users: diff --git a/drivers/staging/most/Documentation/driver_usage.txt b/drivers/staging/most/Documentation/driver_usage.txt new file mode 100644 index 000000000..2fa8dea1d --- /dev/null +++ b/drivers/staging/most/Documentation/driver_usage.txt @@ -0,0 +1,237 @@ + + Section 1 Overview + +The Media Oriented Systems Transport (MOST) driver gives Linux applications +access a MOST network: The Automotive Information Backbone and the de-facto +standard for high-bandwidth automotive multimedia networking. + +MOST defines the protocol, hardware and software layers necessary to allow +for the efficient and low-cost transport of control, real-time and packet +data using a single medium (physical layer). Media currently in use are +fiber optics, unshielded twisted pair cables (UTP) and coax cables. MOST +also supports various speed grades up to 150 Mbps. +For more information on MOST, visit the MOST Cooperation website: +www.mostcooperation.com. + +Cars continue to evolve into sophisticated consumer electronics platforms, +increasing the demand for reliable and simple solutions to support audio, +video and data communications. MOST can be used to connect multiple +consumer devices via optical or electrical physical layers directly to one +another or in a network configuration. As a synchronous network, MOST +provides excellent Quality of Service and seamless connectivity for +audio/video streaming. Therefore, the driver perfectly fits to the mission +of Automotive Grade Linux to create open source software solutions for +automotive applications. + +The MOST driver uses module stacking to divide the associated modules into +three layers. From bottom up these layers are: the adapter layer, the core +layer and the application layer. The core layer implements the MOST +subsystem and consists basically of the module core.c and its API. It +registers the MOST bus with the kernel's device model, handles the data +routing through all three layers, the configuration of the driver, the +representation of the configuration interface in sysfs and the buffer +management. + +For each of the other two layers a set of modules is provided. Those can be +arbitrarily combined with the core to meet the connectivity of the desired +system architecture. + +A module of the adapter layer is basically a device driver for a different +subsystem. It is registered with the core to connect the MOST subsystem to +the attached network interface controller hardware. Hence, a given module +of this layer is designed to handle exactly one of the peripheral +interfaces (e.g. USB, MediaLB, I2C) the hardware provides. + +A module of the application layer is referred to as a core component, +which kind of extends the core by providing connectivity to the user space. +Applications, then, can access a MOST network via character devices, an +ALSA soundcard, a Network adapter or a V4L2 capture device. + +To physically access MOST, an Intelligent Network Interface Controller +(INIC) is needed. For more information on available controllers visit: +www.microchip.com + + + + Section 1.1 Adapter Layer + +The adapter layer contains a pool of device drivers. For each peripheral +interface the hardware supports there is one suitable module that handles +the interface. Adapter drivers encapsulate the peripheral interface +specific knowledge of the MOST driver stack and provide an easy way of +extending the number of supported interfaces. Currently the following +interfaces are available: + + 1) MediaLB (DIM2) + Host wants to communicate with hardware via MediaLB. + + 2) I2C + Host wants to communicate with the hardware via I2C. + + 3) USB + Host wants to communicate with the hardware via USB. + +Once an adapter driver recognizes a MOST device being attached, it +registers it with the core, which, in turn, assigns the necessary members +of the embedded struct device (e.g. the bus this device belongs to and +attribute groups) and registers it with the kernel's device model. + + + Section 1.2 Core Layer + +This layer implements the MOST subsystem. It contains the core module and +the header file most.h that exposes the API of the core. When inserted in +the kernel, it registers the MOST bus_type with the kernel's device model +and registers itself as a device driver for this bus. Besides these meta +tasks the core populates the configuration directory for a registered MOST +device (represented by struct most_interface) in sysfs and processes the +configuration of the device's interface. The core layer also handles the +buffer management and the data/message routing. + + + Section 1.3 Application Layer + +This layer contains a pool of device drivers that are components of the +core designed to make up the userspace experience of the MOST driver stack. +Depending on how an application is meant to interface the driver, one or +more modules of this pool can be registered with the core. Currently the +following components are available + + 1) Character Device + Userspace can access the driver by means of character devices. + + 2) Networking + Standard networking applications (e.g. iperf) can by used to access + the driver via the networking subsystem. + + 3) Video4Linux (v4l2) + Standard video applications (e.g. VLC) can by used to access the + driver via the V4L subsystem. + + 4) Advanced Linux Sound Architecture (ALSA) + Standard sound applications (e.g. aplay, arecord, audacity) can by + used to access the driver via the ALSA subsystem. + + + Section 2 Usage of the MOST Driver + + Section 2.1 Configuration and Data Link + +The driver is to be configured via configfs. Each loaded component kernel +object (see section 1.3) registers a subsystem with configfs, which is used to +configure and establish communication pathways (links) to attached devices on +the bus. To do so, the user has to descend into the component's configuration +directory and create a new directory (child config itmes). The name of this +directory will be used as a reference for the link and it will contain the +following attributes: + + - buffer_size + configure the buffer size for this channel + - subbuffer_size + configure the sub-buffer size for this channel (needed for + synchronous and isochrnous data) + - num_buffers + configure number of buffers used for this channel + - datatype + configure type of data that will travel over this channel + - direction + configure whether this link will be an input or output + - dbr_size + configure DBR data buffer size (this is used for MediaLB communication + only) + - packets_per_xact + configure the number of packets that will be collected from the + network before being transmitted via USB (this is used for USB + communication only) + - device + name of the device the link is to be attached to + - channel + name of the channel the link is to be attached to + - comp_params + pass parameters needed by some components + - create_link + write '1' to this attribute to trigger the creation of the link. In + case of speculative configuration, the creation is post-poned until + a physical device is being attached to the bus. + - destroy_link + write '1' to this attribute to destroy an already established link + + +See ABI/sysfs-bus-most.txt and ABI/configfs-most.txt + + + Section 2.2 Configure a Sound Card + +Setting up synchronous channels to be mapped as an ALSA sound adapter is a two +step process. Firstly, a directory (child config group) has to be created +inside the most_sound's configuration directory. This adapter dir will +represent the sound adapter. The name of the directory is for user reference +only and has no further influence, as all sound adapters will be given a static +name in ALSA. The sound adapter will have the following attribute: + + - create_card + write '1' to this attribute to trigger the registration of the card + with the ALSA subsystem. + In case of speculative configuration, the creation is post-poned + until a physical device is being attached to the bus. + +Secondly, links will have to be created inside the adapter dir as described in +section 2.1. These links will become the PCM devices of the sound card. The +name of a PCM device will be inherited from the directory name. When all +channels have been configured and created, the sound card itself can be created +by writing '1' to the create_card attribute. + +The sound component needs an additional parameter to determine the audio +resolution that is going to be used. +The following audio formats are available: + + - "1x8" (Mono) + - "2x16" (16-bit stereo) + - "2x24" (24-bit stereo) + - "2x32" (32-bit stereo) + - "6x16" (16-bit surround 5.1) + +The resolution string has to be written to the link directory's comp_params +attribute. + + Section 2.3 USB Padding + +When transceiving synchronous or isochronous data, the number of packets +per USB transaction and the sub-buffer size need to be configured. These +values are needed for the driver to process buffer padding, as expected by +hardware, which is for performance optimization purposes of the USB +transmission. + +When transmitting synchronous data the allocated channel width needs to be +written to 'subbuffer_size'. Additionally, the number of MOST frames that +should travel to the host within one USB transaction need to be written to +'packets_per_xact'. + +The driver, then, calculates the synchronous threshold as follows: + + frame_size = subbuffer_size * packets_per_xact + +In case 'packets_per_xact' is set to 0xFF the maximum number of packets, +allocated within one MOST frame, is calculated that fit into _one_ 512 byte +USB full packet. + + frame_size = floor(MTU_USB / bandwidth_sync) * bandwidth_sync + +This frame_size is the number of synchronous data within an USB +transaction, which renders MTU_USB - frame_size bytes for padding. + +When transmitting isochronous AVP data the desired packet size needs to be +written to 'subbuffer_size' and hardware will always expect two isochronous +packets within one USB transaction. This renders + + MTU_USB - (2 * subbuffer_size) + +bytes for padding. + +Note that at least (2 * subbuffer_size) bytes for isochronous data or +(subbuffer_size * packts_per_xact) bytes for synchronous data need to +be put in the transmission buffer and passed to the driver. + +Since adapter drivers are allowed to change a chosen configuration to best +fit its constraints, it is recommended to always double check the +configuration and read back the previously written files. diff --git a/drivers/staging/most/Kconfig b/drivers/staging/most/Kconfig new file mode 100644 index 000000000..6f420cbcd --- /dev/null +++ b/drivers/staging/most/Kconfig @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0 +menuconfig MOST_COMPONENTS + tristate "MOST support" + depends on HAS_DMA && CONFIGFS_FS && MOST + default n + help + Say Y here if you want to enable MOST support. + This driver needs at least one additional component to enable the + desired access from userspace (e.g. character devices) and one that + matches the network controller's hardware interface (e.g. USB). + + To compile this driver as a module, choose M here: the + module will be called most_core. + + If in doubt, say N here. + + + +if MOST_COMPONENTS + +source "drivers/staging/most/net/Kconfig" + +source "drivers/staging/most/video/Kconfig" + +source "drivers/staging/most/dim2/Kconfig" + +source "drivers/staging/most/i2c/Kconfig" + +endif diff --git a/drivers/staging/most/Makefile b/drivers/staging/most/Makefile new file mode 100644 index 000000000..8b3fc5a7a --- /dev/null +++ b/drivers/staging/most/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_MOST_NET) += net/ +obj-$(CONFIG_MOST_VIDEO) += video/ +obj-$(CONFIG_MOST_DIM2) += dim2/ +obj-$(CONFIG_MOST_I2C) += i2c/ diff --git a/drivers/staging/most/TODO b/drivers/staging/most/TODO new file mode 100644 index 000000000..4fa11a9d2 --- /dev/null +++ b/drivers/staging/most/TODO @@ -0,0 +1,8 @@ +* Get through code review with Greg Kroah-Hartman + +Contact: +To: +Christian Gromm +Cc: +Michael Fabry +Christian Gromm diff --git a/drivers/staging/most/dim2/Kconfig b/drivers/staging/most/dim2/Kconfig new file mode 100644 index 000000000..c211f0d91 --- /dev/null +++ b/drivers/staging/most/dim2/Kconfig @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# MediaLB configuration +# + +config MOST_DIM2 + tristate "DIM2" + depends on HAS_IOMEM && OF + + help + Say Y here if you want to connect via MediaLB to network transceiver. + This device driver is platform dependent and needs an additional + platform driver to be installed. For more information contact + maintainer of this driver. + + To compile this driver as a module, choose M here: the + module will be called most_dim2. diff --git a/drivers/staging/most/dim2/Makefile b/drivers/staging/most/dim2/Makefile new file mode 100644 index 000000000..5f9612af3 --- /dev/null +++ b/drivers/staging/most/dim2/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_MOST_DIM2) += most_dim2.o + +most_dim2-objs := dim2.o hal.o diff --git a/drivers/staging/most/dim2/dim2.c b/drivers/staging/most/dim2/dim2.c new file mode 100644 index 000000000..97dff82b7 --- /dev/null +++ b/drivers/staging/most/dim2/dim2.c @@ -0,0 +1,1106 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dim2.c - MediaLB DIM2 Hardware Dependent Module + * + * Copyright (C) 2015-2016, Microchip Technology Germany II GmbH & Co. KG + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "hal.h" +#include "errors.h" +#include "sysfs.h" + +#define DMA_CHANNELS (32 - 1) /* channel 0 is a system channel */ + +#define MAX_BUFFERS_PACKET 32 +#define MAX_BUFFERS_STREAMING 32 +#define MAX_BUF_SIZE_PACKET 2048 +#define MAX_BUF_SIZE_STREAMING (8 * 1024) + +/* + * The parameter representing the number of frames per sub-buffer for + * synchronous channels. Valid values: [0 .. 6]. + * + * The values 0, 1, 2, 3, 4, 5, 6 represent corresponding number of frames per + * sub-buffer 1, 2, 4, 8, 16, 32, 64. + */ +static u8 fcnt = 4; /* (1 << fcnt) frames per subbuffer */ +module_param(fcnt, byte, 0000); +MODULE_PARM_DESC(fcnt, "Num of frames per sub-buffer for sync channels as a power of 2"); + +static DEFINE_SPINLOCK(dim_lock); + +/** + * struct hdm_channel - private structure to keep channel specific data + * @name: channel name + * @is_initialized: identifier to know whether the channel is initialized + * @ch: HAL specific channel data + * @reset_dbr_size: reset DBR data buffer size + * @pending_list: list to keep MBO's before starting transfer + * @started_list: list to keep MBO's after starting transfer + * @direction: channel direction (TX or RX) + * @data_type: channel data type + */ +struct hdm_channel { + char name[sizeof "caNNN"]; + bool is_initialized; + struct dim_channel ch; + u16 *reset_dbr_size; + struct list_head pending_list; /* before dim_enqueue_buffer() */ + struct list_head started_list; /* after dim_enqueue_buffer() */ + enum most_channel_direction direction; + enum most_channel_data_type data_type; +}; + +/* + * struct dim2_hdm - private structure to keep interface specific data + * @hch: an array of channel specific data + * @most_iface: most interface structure + * @capabilities: an array of channel capability data + * @io_base: I/O register base address + * @netinfo_task: thread to deliver network status + * @netinfo_waitq: waitq for the thread to sleep + * @deliver_netinfo: to identify whether network status received + * @mac_addrs: INIC mac address + * @link_state: network link state + * @atx_idx: index of async tx channel + */ +struct dim2_hdm { + struct device dev; + struct hdm_channel hch[DMA_CHANNELS]; + struct most_channel_capability capabilities[DMA_CHANNELS]; + struct most_interface most_iface; + char name[16 + sizeof "dim2-"]; + void __iomem *io_base; + u8 clk_speed; + struct clk *clk; + struct clk *clk_pll; + struct task_struct *netinfo_task; + wait_queue_head_t netinfo_waitq; + int deliver_netinfo; + unsigned char mac_addrs[6]; + unsigned char link_state; + int atx_idx; + struct medialb_bus bus; + void (*on_netinfo)(struct most_interface *most_iface, + unsigned char link_state, unsigned char *addrs); + void (*disable_platform)(struct platform_device *pdev); +}; + +struct dim2_platform_data { + int (*enable)(struct platform_device *pdev); + void (*disable)(struct platform_device *pdev); + u8 fcnt; +}; + +#define iface_to_hdm(iface) container_of(iface, struct dim2_hdm, most_iface) + +/* Macro to identify a network status message */ +#define PACKET_IS_NET_INFO(p) \ + (((p)[1] == 0x18) && ((p)[2] == 0x05) && ((p)[3] == 0x0C) && \ + ((p)[13] == 0x3C) && ((p)[14] == 0x00) && ((p)[15] == 0x0A)) + +static ssize_t state_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + bool state; + unsigned long flags; + + spin_lock_irqsave(&dim_lock, flags); + state = dim_get_lock_state(); + spin_unlock_irqrestore(&dim_lock, flags); + + return sysfs_emit(buf, "%s\n", state ? "locked" : ""); +} + +static DEVICE_ATTR_RO(state); + +static struct attribute *dim2_attrs[] = { + &dev_attr_state.attr, + NULL, +}; + +ATTRIBUTE_GROUPS(dim2); + +/** + * dimcb_on_error - callback from HAL to report miscommunication between + * HDM and HAL + * @error_id: Error ID + * @error_message: Error message. Some text in a free format + */ +void dimcb_on_error(u8 error_id, const char *error_message) +{ + pr_err("%s: error_id - %d, error_message - %s\n", __func__, error_id, + error_message); +} + +/** + * try_start_dim_transfer - try to transfer a buffer on a channel + * @hdm_ch: channel specific data + * + * Transfer a buffer from pending_list if the channel is ready + */ +static int try_start_dim_transfer(struct hdm_channel *hdm_ch) +{ + u16 buf_size; + struct list_head *head = &hdm_ch->pending_list; + struct mbo *mbo; + unsigned long flags; + struct dim_ch_state_t st; + + BUG_ON(!hdm_ch); + BUG_ON(!hdm_ch->is_initialized); + + spin_lock_irqsave(&dim_lock, flags); + if (list_empty(head)) { + spin_unlock_irqrestore(&dim_lock, flags); + return -EAGAIN; + } + + if (!dim_get_channel_state(&hdm_ch->ch, &st)->ready) { + spin_unlock_irqrestore(&dim_lock, flags); + return -EAGAIN; + } + + mbo = list_first_entry(head, struct mbo, list); + buf_size = mbo->buffer_length; + + if (dim_dbr_space(&hdm_ch->ch) < buf_size) { + spin_unlock_irqrestore(&dim_lock, flags); + return -EAGAIN; + } + + BUG_ON(mbo->bus_address == 0); + if (!dim_enqueue_buffer(&hdm_ch->ch, mbo->bus_address, buf_size)) { + list_del(head->next); + spin_unlock_irqrestore(&dim_lock, flags); + mbo->processed_length = 0; + mbo->status = MBO_E_INVAL; + mbo->complete(mbo); + return -EFAULT; + } + + list_move_tail(head->next, &hdm_ch->started_list); + spin_unlock_irqrestore(&dim_lock, flags); + + return 0; +} + +/** + * deliver_netinfo_thread - thread to deliver network status to mostcore + * @data: private data + * + * Wait for network status and deliver it to mostcore once it is received + */ +static int deliver_netinfo_thread(void *data) +{ + struct dim2_hdm *dev = data; + + while (!kthread_should_stop()) { + wait_event_interruptible(dev->netinfo_waitq, + dev->deliver_netinfo || + kthread_should_stop()); + + if (dev->deliver_netinfo) { + dev->deliver_netinfo--; + if (dev->on_netinfo) { + dev->on_netinfo(&dev->most_iface, + dev->link_state, + dev->mac_addrs); + } + } + } + + return 0; +} + +/** + * retrieve_netinfo - retrieve network status from received buffer + * @dev: private data + * @mbo: received MBO + * + * Parse the message in buffer and get node address, link state, MAC address. + * Wake up a thread to deliver this status to mostcore + */ +static void retrieve_netinfo(struct dim2_hdm *dev, struct mbo *mbo) +{ + u8 *data = mbo->virt_address; + + pr_info("Node Address: 0x%03x\n", (u16)data[16] << 8 | data[17]); + dev->link_state = data[18]; + pr_info("NIState: %d\n", dev->link_state); + memcpy(dev->mac_addrs, data + 19, 6); + dev->deliver_netinfo++; + wake_up_interruptible(&dev->netinfo_waitq); +} + +/** + * service_done_flag - handle completed buffers + * @dev: private data + * @ch_idx: channel index + * + * Return back the completed buffers to mostcore, using completion callback + */ +static void service_done_flag(struct dim2_hdm *dev, int ch_idx) +{ + struct hdm_channel *hdm_ch = dev->hch + ch_idx; + struct dim_ch_state_t st; + struct list_head *head; + struct mbo *mbo; + int done_buffers; + unsigned long flags; + u8 *data; + + BUG_ON(!hdm_ch); + BUG_ON(!hdm_ch->is_initialized); + + spin_lock_irqsave(&dim_lock, flags); + + done_buffers = dim_get_channel_state(&hdm_ch->ch, &st)->done_buffers; + if (!done_buffers) { + spin_unlock_irqrestore(&dim_lock, flags); + return; + } + + if (!dim_detach_buffers(&hdm_ch->ch, done_buffers)) { + spin_unlock_irqrestore(&dim_lock, flags); + return; + } + spin_unlock_irqrestore(&dim_lock, flags); + + head = &hdm_ch->started_list; + + while (done_buffers) { + spin_lock_irqsave(&dim_lock, flags); + if (list_empty(head)) { + spin_unlock_irqrestore(&dim_lock, flags); + pr_crit("hard error: started_mbo list is empty whereas DIM2 has sent buffers\n"); + break; + } + + mbo = list_first_entry(head, struct mbo, list); + list_del(head->next); + spin_unlock_irqrestore(&dim_lock, flags); + + data = mbo->virt_address; + + if (hdm_ch->data_type == MOST_CH_ASYNC && + hdm_ch->direction == MOST_CH_RX && + PACKET_IS_NET_INFO(data)) { + retrieve_netinfo(dev, mbo); + + spin_lock_irqsave(&dim_lock, flags); + list_add_tail(&mbo->list, &hdm_ch->pending_list); + spin_unlock_irqrestore(&dim_lock, flags); + } else { + if (hdm_ch->data_type == MOST_CH_CONTROL || + hdm_ch->data_type == MOST_CH_ASYNC) { + u32 const data_size = + (u32)data[0] * 256 + data[1] + 2; + + mbo->processed_length = + min_t(u32, data_size, + mbo->buffer_length); + } else { + mbo->processed_length = mbo->buffer_length; + } + mbo->status = MBO_SUCCESS; + mbo->complete(mbo); + } + + done_buffers--; + } +} + +static struct dim_channel **get_active_channels(struct dim2_hdm *dev, + struct dim_channel **buffer) +{ + int idx = 0; + int ch_idx; + + for (ch_idx = 0; ch_idx < DMA_CHANNELS; ch_idx++) { + if (dev->hch[ch_idx].is_initialized) + buffer[idx++] = &dev->hch[ch_idx].ch; + } + buffer[idx++] = NULL; + + return buffer; +} + +static irqreturn_t dim2_mlb_isr(int irq, void *_dev) +{ + struct dim2_hdm *dev = _dev; + unsigned long flags; + + spin_lock_irqsave(&dim_lock, flags); + dim_service_mlb_int_irq(); + spin_unlock_irqrestore(&dim_lock, flags); + + if (dev->atx_idx >= 0 && dev->hch[dev->atx_idx].is_initialized) + while (!try_start_dim_transfer(dev->hch + dev->atx_idx)) + continue; + + return IRQ_HANDLED; +} + +static irqreturn_t dim2_task_irq(int irq, void *_dev) +{ + struct dim2_hdm *dev = _dev; + unsigned long flags; + int ch_idx; + + for (ch_idx = 0; ch_idx < DMA_CHANNELS; ch_idx++) { + if (!dev->hch[ch_idx].is_initialized) + continue; + + spin_lock_irqsave(&dim_lock, flags); + dim_service_channel(&dev->hch[ch_idx].ch); + spin_unlock_irqrestore(&dim_lock, flags); + + service_done_flag(dev, ch_idx); + while (!try_start_dim_transfer(dev->hch + ch_idx)) + continue; + } + + return IRQ_HANDLED; +} + +/** + * dim2_ahb_isr - interrupt service routine + * @irq: irq number + * @_dev: private data + * + * Acknowledge the interrupt and service each initialized channel, + * if needed, in task context. + */ +static irqreturn_t dim2_ahb_isr(int irq, void *_dev) +{ + struct dim2_hdm *dev = _dev; + struct dim_channel *buffer[DMA_CHANNELS + 1]; + unsigned long flags; + + spin_lock_irqsave(&dim_lock, flags); + dim_service_ahb_int_irq(get_active_channels(dev, buffer)); + spin_unlock_irqrestore(&dim_lock, flags); + + return IRQ_WAKE_THREAD; +} + +/** + * complete_all_mbos - complete MBO's in a list + * @head: list head + * + * Delete all the entries in list and return back MBO's to mostcore using + * completion call back. + */ +static void complete_all_mbos(struct list_head *head) +{ + unsigned long flags; + struct mbo *mbo; + + for (;;) { + spin_lock_irqsave(&dim_lock, flags); + if (list_empty(head)) { + spin_unlock_irqrestore(&dim_lock, flags); + break; + } + + mbo = list_first_entry(head, struct mbo, list); + list_del(head->next); + spin_unlock_irqrestore(&dim_lock, flags); + + mbo->processed_length = 0; + mbo->status = MBO_E_CLOSE; + mbo->complete(mbo); + } +} + +/** + * configure_channel - initialize a channel + * @most_iface: interface the channel belongs to + * @ch_idx: channel index to be configured + * @ccfg: structure that holds the configuration information + * + * Receives configuration information from mostcore and initialize + * the corresponding channel. Return 0 on success, negative on failure. + */ +static int configure_channel(struct most_interface *most_iface, int ch_idx, + struct most_channel_config *ccfg) +{ + struct dim2_hdm *dev = iface_to_hdm(most_iface); + bool const is_tx = ccfg->direction == MOST_CH_TX; + u16 const sub_size = ccfg->subbuffer_size; + u16 const buf_size = ccfg->buffer_size; + u16 new_size; + unsigned long flags; + u8 hal_ret; + int const ch_addr = ch_idx * 2 + 2; + struct hdm_channel *const hdm_ch = dev->hch + ch_idx; + + BUG_ON(ch_idx < 0 || ch_idx >= DMA_CHANNELS); + + if (hdm_ch->is_initialized) + return -EPERM; + + /* do not reset if the property was set by user, see poison_channel */ + hdm_ch->reset_dbr_size = ccfg->dbr_size ? NULL : &ccfg->dbr_size; + + /* zero value is default dbr_size, see dim2 hal */ + hdm_ch->ch.dbr_size = ccfg->dbr_size; + + switch (ccfg->data_type) { + case MOST_CH_CONTROL: + new_size = dim_norm_ctrl_async_buffer_size(buf_size); + if (new_size == 0) { + pr_err("%s: too small buffer size\n", hdm_ch->name); + return -EINVAL; + } + ccfg->buffer_size = new_size; + if (new_size != buf_size) + pr_warn("%s: fixed buffer size (%d -> %d)\n", + hdm_ch->name, buf_size, new_size); + spin_lock_irqsave(&dim_lock, flags); + hal_ret = dim_init_control(&hdm_ch->ch, is_tx, ch_addr, + is_tx ? new_size * 2 : new_size); + break; + case MOST_CH_ASYNC: + new_size = dim_norm_ctrl_async_buffer_size(buf_size); + if (new_size == 0) { + pr_err("%s: too small buffer size\n", hdm_ch->name); + return -EINVAL; + } + ccfg->buffer_size = new_size; + if (new_size != buf_size) + pr_warn("%s: fixed buffer size (%d -> %d)\n", + hdm_ch->name, buf_size, new_size); + spin_lock_irqsave(&dim_lock, flags); + hal_ret = dim_init_async(&hdm_ch->ch, is_tx, ch_addr, + is_tx ? new_size * 2 : new_size); + break; + case MOST_CH_ISOC: + new_size = dim_norm_isoc_buffer_size(buf_size, sub_size); + if (new_size == 0) { + pr_err("%s: invalid sub-buffer size or too small buffer size\n", + hdm_ch->name); + return -EINVAL; + } + ccfg->buffer_size = new_size; + if (new_size != buf_size) + pr_warn("%s: fixed buffer size (%d -> %d)\n", + hdm_ch->name, buf_size, new_size); + spin_lock_irqsave(&dim_lock, flags); + hal_ret = dim_init_isoc(&hdm_ch->ch, is_tx, ch_addr, sub_size); + break; + case MOST_CH_SYNC: + new_size = dim_norm_sync_buffer_size(buf_size, sub_size); + if (new_size == 0) { + pr_err("%s: invalid sub-buffer size or too small buffer size\n", + hdm_ch->name); + return -EINVAL; + } + ccfg->buffer_size = new_size; + if (new_size != buf_size) + pr_warn("%s: fixed buffer size (%d -> %d)\n", + hdm_ch->name, buf_size, new_size); + spin_lock_irqsave(&dim_lock, flags); + hal_ret = dim_init_sync(&hdm_ch->ch, is_tx, ch_addr, sub_size); + break; + default: + pr_err("%s: configure failed, bad channel type: %d\n", + hdm_ch->name, ccfg->data_type); + return -EINVAL; + } + + if (hal_ret != DIM_NO_ERROR) { + spin_unlock_irqrestore(&dim_lock, flags); + pr_err("%s: configure failed (%d), type: %d, is_tx: %d\n", + hdm_ch->name, hal_ret, ccfg->data_type, (int)is_tx); + return -ENODEV; + } + + hdm_ch->data_type = ccfg->data_type; + hdm_ch->direction = ccfg->direction; + hdm_ch->is_initialized = true; + + if (hdm_ch->data_type == MOST_CH_ASYNC && + hdm_ch->direction == MOST_CH_TX && + dev->atx_idx < 0) + dev->atx_idx = ch_idx; + + spin_unlock_irqrestore(&dim_lock, flags); + ccfg->dbr_size = hdm_ch->ch.dbr_size; + + return 0; +} + +/** + * enqueue - enqueue a buffer for data transfer + * @most_iface: intended interface + * @ch_idx: ID of the channel the buffer is intended for + * @mbo: pointer to the buffer object + * + * Push the buffer into pending_list and try to transfer one buffer from + * pending_list. Return 0 on success, negative on failure. + */ +static int enqueue(struct most_interface *most_iface, int ch_idx, + struct mbo *mbo) +{ + struct dim2_hdm *dev = iface_to_hdm(most_iface); + struct hdm_channel *hdm_ch = dev->hch + ch_idx; + unsigned long flags; + + BUG_ON(ch_idx < 0 || ch_idx >= DMA_CHANNELS); + + if (!hdm_ch->is_initialized) + return -EPERM; + + if (mbo->bus_address == 0) + return -EFAULT; + + spin_lock_irqsave(&dim_lock, flags); + list_add_tail(&mbo->list, &hdm_ch->pending_list); + spin_unlock_irqrestore(&dim_lock, flags); + + (void)try_start_dim_transfer(hdm_ch); + + return 0; +} + +/** + * request_netinfo - triggers retrieving of network info + * @most_iface: pointer to the interface + * @ch_idx: corresponding channel ID + * @on_netinfo: call-back used to deliver network status to mostcore + * + * Send a command to INIC which triggers retrieving of network info by means of + * "Message exchange over MDP/MEP". Return 0 on success, negative on failure. + */ +static void request_netinfo(struct most_interface *most_iface, int ch_idx, + void (*on_netinfo)(struct most_interface *, + unsigned char, unsigned char *)) +{ + struct dim2_hdm *dev = iface_to_hdm(most_iface); + struct mbo *mbo; + u8 *data; + + dev->on_netinfo = on_netinfo; + if (!on_netinfo) + return; + + if (dev->atx_idx < 0) { + pr_err("Async Tx Not initialized\n"); + return; + } + + mbo = most_get_mbo(&dev->most_iface, dev->atx_idx, NULL); + if (!mbo) + return; + + mbo->buffer_length = 5; + + data = mbo->virt_address; + + data[0] = 0x00; /* PML High byte */ + data[1] = 0x03; /* PML Low byte */ + data[2] = 0x02; /* PMHL */ + data[3] = 0x08; /* FPH */ + data[4] = 0x40; /* FMF (FIFO cmd msg - Triggers NAOverMDP) */ + + most_submit_mbo(mbo); +} + +/** + * poison_channel - poison buffers of a channel + * @most_iface: pointer to the interface the channel to be poisoned belongs to + * @ch_idx: corresponding channel ID + * + * Destroy a channel and complete all the buffers in both started_list & + * pending_list. Return 0 on success, negative on failure. + */ +static int poison_channel(struct most_interface *most_iface, int ch_idx) +{ + struct dim2_hdm *dev = iface_to_hdm(most_iface); + struct hdm_channel *hdm_ch = dev->hch + ch_idx; + unsigned long flags; + u8 hal_ret; + int ret = 0; + + BUG_ON(ch_idx < 0 || ch_idx >= DMA_CHANNELS); + + if (!hdm_ch->is_initialized) + return -EPERM; + + spin_lock_irqsave(&dim_lock, flags); + hal_ret = dim_destroy_channel(&hdm_ch->ch); + hdm_ch->is_initialized = false; + if (ch_idx == dev->atx_idx) + dev->atx_idx = -1; + spin_unlock_irqrestore(&dim_lock, flags); + if (hal_ret != DIM_NO_ERROR) { + pr_err("HAL Failed to close channel %s\n", hdm_ch->name); + ret = -EFAULT; + } + + complete_all_mbos(&hdm_ch->started_list); + complete_all_mbos(&hdm_ch->pending_list); + if (hdm_ch->reset_dbr_size) + *hdm_ch->reset_dbr_size = 0; + + return ret; +} + +static void *dma_alloc(struct mbo *mbo, u32 size) +{ + struct device *dev = mbo->ifp->driver_dev; + + return dma_alloc_coherent(dev, size, &mbo->bus_address, GFP_KERNEL); +} + +static void dma_free(struct mbo *mbo, u32 size) +{ + struct device *dev = mbo->ifp->driver_dev; + + dma_free_coherent(dev, size, mbo->virt_address, mbo->bus_address); +} + +static const struct of_device_id dim2_of_match[]; + +static struct { + const char *clock_speed; + u8 clk_speed; +} clk_mt[] = { + { "256fs", CLK_256FS }, + { "512fs", CLK_512FS }, + { "1024fs", CLK_1024FS }, + { "2048fs", CLK_2048FS }, + { "3072fs", CLK_3072FS }, + { "4096fs", CLK_4096FS }, + { "6144fs", CLK_6144FS }, + { "8192fs", CLK_8192FS }, +}; + +/** + * get_dim2_clk_speed - converts string to DIM2 clock speed value + * + * @clock_speed: string in the format "{NUMBER}fs" + * @val: pointer to get one of the CLK_{NUMBER}FS values + * + * By success stores one of the CLK_{NUMBER}FS in the *val and returns 0, + * otherwise returns -EINVAL. + */ +static int get_dim2_clk_speed(const char *clock_speed, u8 *val) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(clk_mt); i++) { + if (!strcmp(clock_speed, clk_mt[i].clock_speed)) { + *val = clk_mt[i].clk_speed; + return 0; + } + } + return -EINVAL; +} + +static void dim2_release(struct device *d) +{ + struct dim2_hdm *dev = container_of(d, struct dim2_hdm, dev); + unsigned long flags; + + kthread_stop(dev->netinfo_task); + + spin_lock_irqsave(&dim_lock, flags); + dim_shutdown(); + spin_unlock_irqrestore(&dim_lock, flags); + + if (dev->disable_platform) + dev->disable_platform(to_platform_device(d->parent)); + + kfree(dev); +} + +/* + * dim2_probe - dim2 probe handler + * @pdev: platform device structure + * + * Register the dim2 interface with mostcore and initialize it. + * Return 0 on success, negative on failure. + */ +static int dim2_probe(struct platform_device *pdev) +{ + const struct dim2_platform_data *pdata; + const struct of_device_id *of_id; + const char *clock_speed; + struct dim2_hdm *dev; + struct resource *res; + int ret, i; + u8 hal_ret; + u8 dev_fcnt = fcnt; + int irq; + + enum { MLB_INT_IDX, AHB0_INT_IDX }; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->atx_idx = -1; + + platform_set_drvdata(pdev, dev); + + ret = of_property_read_string(pdev->dev.of_node, + "microchip,clock-speed", &clock_speed); + if (ret) { + dev_err(&pdev->dev, "missing dt property clock-speed\n"); + goto err_free_dev; + } + + ret = get_dim2_clk_speed(clock_speed, &dev->clk_speed); + if (ret) { + dev_err(&pdev->dev, "bad dt property clock-speed\n"); + goto err_free_dev; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dev->io_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(dev->io_base)) { + ret = PTR_ERR(dev->io_base); + goto err_free_dev; + } + + of_id = of_match_node(dim2_of_match, pdev->dev.of_node); + pdata = of_id->data; + if (pdata) { + if (pdata->enable) { + ret = pdata->enable(pdev); + if (ret) + goto err_free_dev; + } + dev->disable_platform = pdata->disable; + if (pdata->fcnt) + dev_fcnt = pdata->fcnt; + } + + dev_info(&pdev->dev, "sync: num of frames per sub-buffer: %u\n", + dev_fcnt); + hal_ret = dim_startup(dev->io_base, dev->clk_speed, dev_fcnt); + if (hal_ret != DIM_NO_ERROR) { + dev_err(&pdev->dev, "dim_startup failed: %d\n", hal_ret); + ret = -ENODEV; + goto err_disable_platform; + } + + irq = platform_get_irq(pdev, AHB0_INT_IDX); + if (irq < 0) { + ret = irq; + goto err_shutdown_dim; + } + + ret = devm_request_threaded_irq(&pdev->dev, irq, dim2_ahb_isr, + dim2_task_irq, 0, "dim2_ahb0_int", dev); + if (ret) { + dev_err(&pdev->dev, "failed to request ahb0_int irq %d\n", irq); + goto err_shutdown_dim; + } + + irq = platform_get_irq(pdev, MLB_INT_IDX); + if (irq < 0) { + ret = irq; + goto err_shutdown_dim; + } + + ret = devm_request_irq(&pdev->dev, irq, dim2_mlb_isr, 0, + "dim2_mlb_int", dev); + if (ret) { + dev_err(&pdev->dev, "failed to request mlb_int irq %d\n", irq); + goto err_shutdown_dim; + } + + init_waitqueue_head(&dev->netinfo_waitq); + dev->deliver_netinfo = 0; + dev->netinfo_task = kthread_run(&deliver_netinfo_thread, dev, + "dim2_netinfo"); + if (IS_ERR(dev->netinfo_task)) { + ret = PTR_ERR(dev->netinfo_task); + goto err_shutdown_dim; + } + + for (i = 0; i < DMA_CHANNELS; i++) { + struct most_channel_capability *cap = dev->capabilities + i; + struct hdm_channel *hdm_ch = dev->hch + i; + + INIT_LIST_HEAD(&hdm_ch->pending_list); + INIT_LIST_HEAD(&hdm_ch->started_list); + hdm_ch->is_initialized = false; + snprintf(hdm_ch->name, sizeof(hdm_ch->name), "ca%d", i * 2 + 2); + + cap->name_suffix = hdm_ch->name; + cap->direction = MOST_CH_RX | MOST_CH_TX; + cap->data_type = MOST_CH_CONTROL | MOST_CH_ASYNC | + MOST_CH_ISOC | MOST_CH_SYNC; + cap->num_buffers_packet = MAX_BUFFERS_PACKET; + cap->buffer_size_packet = MAX_BUF_SIZE_PACKET; + cap->num_buffers_streaming = MAX_BUFFERS_STREAMING; + cap->buffer_size_streaming = MAX_BUF_SIZE_STREAMING; + } + + { + const char *fmt; + + if (sizeof(res->start) == sizeof(long long)) + fmt = "dim2-%016llx"; + else if (sizeof(res->start) == sizeof(long)) + fmt = "dim2-%016lx"; + else + fmt = "dim2-%016x"; + + snprintf(dev->name, sizeof(dev->name), fmt, res->start); + } + + dev->most_iface.interface = ITYPE_MEDIALB_DIM2; + dev->most_iface.description = dev->name; + dev->most_iface.num_channels = DMA_CHANNELS; + dev->most_iface.channel_vector = dev->capabilities; + dev->most_iface.configure = configure_channel; + dev->most_iface.enqueue = enqueue; + dev->most_iface.dma_alloc = dma_alloc; + dev->most_iface.dma_free = dma_free; + dev->most_iface.poison_channel = poison_channel; + dev->most_iface.request_netinfo = request_netinfo; + dev->most_iface.driver_dev = &pdev->dev; + dev->most_iface.dev = &dev->dev; + dev->dev.init_name = dev->name; + dev->dev.parent = &pdev->dev; + dev->dev.release = dim2_release; + + return most_register_interface(&dev->most_iface); + +err_shutdown_dim: + dim_shutdown(); +err_disable_platform: + if (dev->disable_platform) + dev->disable_platform(pdev); +err_free_dev: + kfree(dev); + + return ret; +} + +/** + * dim2_remove - dim2 remove handler + * @pdev: platform device structure + * + * Unregister the interface from mostcore + */ +static int dim2_remove(struct platform_device *pdev) +{ + struct dim2_hdm *dev = platform_get_drvdata(pdev); + + most_deregister_interface(&dev->most_iface); + + return 0; +} + +/* platform specific functions [[ */ + +static int fsl_mx6_enable(struct platform_device *pdev) +{ + struct dim2_hdm *dev = platform_get_drvdata(pdev); + int ret; + + dev->clk = devm_clk_get(&pdev->dev, "mlb"); + if (IS_ERR_OR_NULL(dev->clk)) { + dev_err(&pdev->dev, "unable to get mlb clock\n"); + return -EFAULT; + } + + ret = clk_prepare_enable(dev->clk); + if (ret) { + dev_err(&pdev->dev, "%s\n", "clk_prepare_enable failed"); + return ret; + } + + if (dev->clk_speed >= CLK_2048FS) { + /* enable pll */ + dev->clk_pll = devm_clk_get(&pdev->dev, "pll8_mlb"); + if (IS_ERR_OR_NULL(dev->clk_pll)) { + dev_err(&pdev->dev, "unable to get mlb pll clock\n"); + clk_disable_unprepare(dev->clk); + return -EFAULT; + } + + writel(0x888, dev->io_base + 0x38); + clk_prepare_enable(dev->clk_pll); + } + + return 0; +} + +static void fsl_mx6_disable(struct platform_device *pdev) +{ + struct dim2_hdm *dev = platform_get_drvdata(pdev); + + if (dev->clk_speed >= CLK_2048FS) + clk_disable_unprepare(dev->clk_pll); + + clk_disable_unprepare(dev->clk); +} + +static int rcar_gen2_enable(struct platform_device *pdev) +{ + struct dim2_hdm *dev = platform_get_drvdata(pdev); + int ret; + + dev->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(dev->clk)) { + dev_err(&pdev->dev, "cannot get clock\n"); + return PTR_ERR(dev->clk); + } + + ret = clk_prepare_enable(dev->clk); + if (ret) { + dev_err(&pdev->dev, "%s\n", "clk_prepare_enable failed"); + return ret; + } + + if (dev->clk_speed >= CLK_2048FS) { + /* enable MLP pll and LVDS drivers */ + writel(0x03, dev->io_base + 0x600); + /* set bias */ + writel(0x888, dev->io_base + 0x38); + } else { + /* PLL */ + writel(0x04, dev->io_base + 0x600); + } + + + /* BBCR = 0b11 */ + writel(0x03, dev->io_base + 0x500); + writel(0x0002FF02, dev->io_base + 0x508); + + return 0; +} + +static void rcar_gen2_disable(struct platform_device *pdev) +{ + struct dim2_hdm *dev = platform_get_drvdata(pdev); + + clk_disable_unprepare(dev->clk); + + /* disable PLLs and LVDS drivers */ + writel(0x0, dev->io_base + 0x600); +} + +static int rcar_gen3_enable(struct platform_device *pdev) +{ + struct dim2_hdm *dev = platform_get_drvdata(pdev); + u32 enable_512fs = dev->clk_speed == CLK_512FS; + int ret; + + dev->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(dev->clk)) { + dev_err(&pdev->dev, "cannot get clock\n"); + return PTR_ERR(dev->clk); + } + + ret = clk_prepare_enable(dev->clk); + if (ret) { + dev_err(&pdev->dev, "%s\n", "clk_prepare_enable failed"); + return ret; + } + + /* PLL */ + writel(0x04, dev->io_base + 0x600); + + writel(enable_512fs, dev->io_base + 0x604); + + /* BBCR = 0b11 */ + writel(0x03, dev->io_base + 0x500); + writel(0x0002FF02, dev->io_base + 0x508); + + return 0; +} + +static void rcar_gen3_disable(struct platform_device *pdev) +{ + struct dim2_hdm *dev = platform_get_drvdata(pdev); + + clk_disable_unprepare(dev->clk); + + /* disable PLLs and LVDS drivers */ + writel(0x0, dev->io_base + 0x600); +} + +/* ]] platform specific functions */ + +enum dim2_platforms { FSL_MX6, RCAR_GEN2, RCAR_GEN3 }; + +static struct dim2_platform_data plat_data[] = { + [FSL_MX6] = { + .enable = fsl_mx6_enable, + .disable = fsl_mx6_disable, + }, + [RCAR_GEN2] = { + .enable = rcar_gen2_enable, + .disable = rcar_gen2_disable, + }, + [RCAR_GEN3] = { + .enable = rcar_gen3_enable, + .disable = rcar_gen3_disable, + .fcnt = 3, + }, +}; + +static const struct of_device_id dim2_of_match[] = { + { + .compatible = "fsl,imx6q-mlb150", + .data = plat_data + FSL_MX6 + }, + { + .compatible = "renesas,mlp", + .data = plat_data + RCAR_GEN2 + }, + { + .compatible = "renesas,rcar-gen3-mlp", + .data = plat_data + RCAR_GEN3 + }, + { + .compatible = "xlnx,axi4-os62420_3pin-1.00.a", + }, + { + .compatible = "xlnx,axi4-os62420_6pin-1.00.a", + }, + {}, +}; + +MODULE_DEVICE_TABLE(of, dim2_of_match); + +static struct platform_driver dim2_driver = { + .probe = dim2_probe, + .remove = dim2_remove, + .driver = { + .name = "hdm_dim2", + .of_match_table = dim2_of_match, + .dev_groups = dim2_groups, + }, +}; + +module_platform_driver(dim2_driver); + +MODULE_AUTHOR("Andrey Shvetsov "); +MODULE_DESCRIPTION("MediaLB DIM2 Hardware Dependent Module"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/most/dim2/errors.h b/drivers/staging/most/dim2/errors.h new file mode 100644 index 000000000..268332e57 --- /dev/null +++ b/drivers/staging/most/dim2/errors.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * errors.h - Definitions of errors for DIM2 HAL API + * (MediaLB, Device Interface Macro IP, OS62420) + * + * Copyright (C) 2015, Microchip Technology Germany II GmbH & Co. KG + */ + +#ifndef _MOST_DIM_ERRORS_H +#define _MOST_DIM_ERRORS_H + +/** + * MOST DIM errors. + */ +enum dim_errors_t { + /** Not an error */ + DIM_NO_ERROR = 0, + + /** Bad base address for DIM2 IP */ + DIM_INIT_ERR_DIM_ADDR = 0x10, + + /**< Bad MediaLB clock */ + DIM_INIT_ERR_MLB_CLOCK, + + /** Bad channel address */ + DIM_INIT_ERR_CHANNEL_ADDRESS, + + /** Out of DBR memory */ + DIM_INIT_ERR_OUT_OF_MEMORY, + + /** DIM API is called while DIM is not initialized successfully */ + DIM_ERR_DRIVER_NOT_INITIALIZED = 0x20, + + /** + * Configuration does not respect hardware limitations + * for isochronous or synchronous channels + */ + DIM_ERR_BAD_CONFIG, + + /** + * Buffer size does not respect hardware limitations + * for isochronous or synchronous channels + */ + DIM_ERR_BAD_BUFFER_SIZE, + + DIM_ERR_UNDERFLOW, + + DIM_ERR_OVERFLOW, +}; + +#endif /* _MOST_DIM_ERRORS_H */ diff --git a/drivers/staging/most/dim2/hal.c b/drivers/staging/most/dim2/hal.c new file mode 100644 index 000000000..65282c276 --- /dev/null +++ b/drivers/staging/most/dim2/hal.c @@ -0,0 +1,975 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * hal.c - DIM2 HAL implementation + * (MediaLB, Device Interface Macro IP, OS62420) + * + * Copyright (C) 2015-2016, Microchip Technology Germany II GmbH & Co. KG + */ + +/* Author: Andrey Shvetsov */ + +#include "hal.h" +#include "errors.h" +#include "reg.h" +#include +#include +#include + +/* + * Size factor for isochronous DBR buffer. + * Minimal value is 3. + */ +#define ISOC_DBR_FACTOR 3u + +/* + * Number of 32-bit units for DBR map. + * + * 1: block size is 512, max allocation is 16K + * 2: block size is 256, max allocation is 8K + * 4: block size is 128, max allocation is 4K + * 8: block size is 64, max allocation is 2K + * + * Min allocated space is block size. + * Max possible allocated space is 32 blocks. + */ +#define DBR_MAP_SIZE 2 + +/* -------------------------------------------------------------------------- */ +/* not configurable area */ + +#define CDT 0x00 +#define ADT 0x40 +#define MLB_CAT 0x80 +#define AHB_CAT 0x88 + +#define DBR_SIZE (16 * 1024) /* specified by IP */ +#define DBR_BLOCK_SIZE (DBR_SIZE / 32 / DBR_MAP_SIZE) + +#define ROUND_UP_TO(x, d) (DIV_ROUND_UP(x, (d)) * (d)) + +/* -------------------------------------------------------------------------- */ +/* generic helper functions and macros */ + +static inline u32 bit_mask(u8 position) +{ + return (u32)1 << position; +} + +static inline bool dim_on_error(u8 error_id, const char *error_message) +{ + dimcb_on_error(error_id, error_message); + return false; +} + +/* -------------------------------------------------------------------------- */ +/* types and local variables */ + +struct async_tx_dbr { + u8 ch_addr; + u16 rpc; + u16 wpc; + u16 rest_size; + u16 sz_queue[CDT0_RPC_MASK + 1]; +}; + +struct lld_global_vars_t { + bool dim_is_initialized; + bool mcm_is_initialized; + struct dim2_regs __iomem *dim2; /* DIM2 core base address */ + struct async_tx_dbr atx_dbr; + u32 fcnt; + u32 dbr_map[DBR_MAP_SIZE]; +}; + +static struct lld_global_vars_t g = { false }; + +/* -------------------------------------------------------------------------- */ + +static int dbr_get_mask_size(u16 size) +{ + int i; + + for (i = 0; i < 6; i++) + if (size <= (DBR_BLOCK_SIZE << i)) + return 1 << i; + return 0; +} + +/** + * alloc_dbr() - Allocates DBR memory. + * @size: Allocating memory size. + * Returns: Offset in DBR memory by success or DBR_SIZE if out of memory. + */ +static int alloc_dbr(u16 size) +{ + int mask_size; + int i, block_idx = 0; + + if (size <= 0) + return DBR_SIZE; /* out of memory */ + + mask_size = dbr_get_mask_size(size); + if (mask_size == 0) + return DBR_SIZE; /* out of memory */ + + for (i = 0; i < DBR_MAP_SIZE; i++) { + u32 const blocks = DIV_ROUND_UP(size, DBR_BLOCK_SIZE); + u32 mask = ~((~(u32)0) << blocks); + + do { + if ((g.dbr_map[i] & mask) == 0) { + g.dbr_map[i] |= mask; + return block_idx * DBR_BLOCK_SIZE; + } + block_idx += mask_size; + /* do shift left with 2 steps in case mask_size == 32 */ + mask <<= mask_size - 1; + } while ((mask <<= 1) != 0); + } + + return DBR_SIZE; /* out of memory */ +} + +static void free_dbr(int offs, int size) +{ + int block_idx = offs / DBR_BLOCK_SIZE; + u32 const blocks = DIV_ROUND_UP(size, DBR_BLOCK_SIZE); + u32 mask = ~((~(u32)0) << blocks); + + mask <<= block_idx % 32; + g.dbr_map[block_idx / 32] &= ~mask; +} + +/* -------------------------------------------------------------------------- */ + +static void dim2_transfer_madr(u32 val) +{ + writel(val, &g.dim2->MADR); + + /* wait for transfer completion */ + while ((readl(&g.dim2->MCTL) & 1) != 1) + continue; + + writel(0, &g.dim2->MCTL); /* clear transfer complete */ +} + +static void dim2_clear_dbr(u16 addr, u16 size) +{ + enum { MADR_TB_BIT = 30, MADR_WNR_BIT = 31 }; + + u16 const end_addr = addr + size; + u32 const cmd = bit_mask(MADR_WNR_BIT) | bit_mask(MADR_TB_BIT); + + writel(0, &g.dim2->MCTL); /* clear transfer complete */ + writel(0, &g.dim2->MDAT0); + + for (; addr < end_addr; addr++) + dim2_transfer_madr(cmd | addr); +} + +static u32 dim2_read_ctr(u32 ctr_addr, u16 mdat_idx) +{ + dim2_transfer_madr(ctr_addr); + + return readl((&g.dim2->MDAT0) + mdat_idx); +} + +static void dim2_write_ctr_mask(u32 ctr_addr, const u32 *mask, const u32 *value) +{ + enum { MADR_WNR_BIT = 31 }; + + writel(0, &g.dim2->MCTL); /* clear transfer complete */ + + if (mask[0] != 0) + writel(value[0], &g.dim2->MDAT0); + if (mask[1] != 0) + writel(value[1], &g.dim2->MDAT1); + if (mask[2] != 0) + writel(value[2], &g.dim2->MDAT2); + if (mask[3] != 0) + writel(value[3], &g.dim2->MDAT3); + + writel(mask[0], &g.dim2->MDWE0); + writel(mask[1], &g.dim2->MDWE1); + writel(mask[2], &g.dim2->MDWE2); + writel(mask[3], &g.dim2->MDWE3); + + dim2_transfer_madr(bit_mask(MADR_WNR_BIT) | ctr_addr); +} + +static inline void dim2_write_ctr(u32 ctr_addr, const u32 *value) +{ + u32 const mask[4] = { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; + + dim2_write_ctr_mask(ctr_addr, mask, value); +} + +static inline void dim2_clear_ctr(u32 ctr_addr) +{ + u32 const value[4] = { 0, 0, 0, 0 }; + + dim2_write_ctr(ctr_addr, value); +} + +static void dim2_configure_cat(u8 cat_base, u8 ch_addr, u8 ch_type, + bool read_not_write) +{ + bool isoc_fce = ch_type == CAT_CT_VAL_ISOC; + bool sync_mfe = ch_type == CAT_CT_VAL_SYNC; + u16 const cat = + (read_not_write << CAT_RNW_BIT) | + (ch_type << CAT_CT_SHIFT) | + (ch_addr << CAT_CL_SHIFT) | + (isoc_fce << CAT_FCE_BIT) | + (sync_mfe << CAT_MFE_BIT) | + (false << CAT_MT_BIT) | + (true << CAT_CE_BIT); + u8 const ctr_addr = cat_base + ch_addr / 8; + u8 const idx = (ch_addr % 8) / 2; + u8 const shift = (ch_addr % 2) * 16; + u32 mask[4] = { 0, 0, 0, 0 }; + u32 value[4] = { 0, 0, 0, 0 }; + + mask[idx] = (u32)0xFFFF << shift; + value[idx] = cat << shift; + dim2_write_ctr_mask(ctr_addr, mask, value); +} + +static void dim2_clear_cat(u8 cat_base, u8 ch_addr) +{ + u8 const ctr_addr = cat_base + ch_addr / 8; + u8 const idx = (ch_addr % 8) / 2; + u8 const shift = (ch_addr % 2) * 16; + u32 mask[4] = { 0, 0, 0, 0 }; + u32 value[4] = { 0, 0, 0, 0 }; + + mask[idx] = (u32)0xFFFF << shift; + dim2_write_ctr_mask(ctr_addr, mask, value); +} + +static void dim2_configure_cdt(u8 ch_addr, u16 dbr_address, u16 hw_buffer_size, + u16 packet_length) +{ + u32 cdt[4] = { 0, 0, 0, 0 }; + + if (packet_length) + cdt[1] = ((packet_length - 1) << CDT1_BS_ISOC_SHIFT); + + cdt[3] = + ((hw_buffer_size - 1) << CDT3_BD_SHIFT) | + (dbr_address << CDT3_BA_SHIFT); + dim2_write_ctr(CDT + ch_addr, cdt); +} + +static u16 dim2_rpc(u8 ch_addr) +{ + u32 cdt0 = dim2_read_ctr(CDT + ch_addr, 0); + + return (cdt0 >> CDT0_RPC_SHIFT) & CDT0_RPC_MASK; +} + +static void dim2_clear_cdt(u8 ch_addr) +{ + u32 cdt[4] = { 0, 0, 0, 0 }; + + dim2_write_ctr(CDT + ch_addr, cdt); +} + +static void dim2_configure_adt(u8 ch_addr) +{ + u32 adt[4] = { 0, 0, 0, 0 }; + + adt[0] = + (true << ADT0_CE_BIT) | + (true << ADT0_LE_BIT) | + (0 << ADT0_PG_BIT); + + dim2_write_ctr(ADT + ch_addr, adt); +} + +static void dim2_clear_adt(u8 ch_addr) +{ + u32 adt[4] = { 0, 0, 0, 0 }; + + dim2_write_ctr(ADT + ch_addr, adt); +} + +static void dim2_start_ctrl_async(u8 ch_addr, u8 idx, u32 buf_addr, + u16 buffer_size) +{ + u8 const shift = idx * 16; + + u32 mask[4] = { 0, 0, 0, 0 }; + u32 adt[4] = { 0, 0, 0, 0 }; + + mask[1] = + bit_mask(ADT1_PS_BIT + shift) | + bit_mask(ADT1_RDY_BIT + shift) | + (ADT1_CTRL_ASYNC_BD_MASK << (ADT1_BD_SHIFT + shift)); + adt[1] = + (true << (ADT1_PS_BIT + shift)) | + (true << (ADT1_RDY_BIT + shift)) | + ((buffer_size - 1) << (ADT1_BD_SHIFT + shift)); + + mask[idx + 2] = 0xFFFFFFFF; + adt[idx + 2] = buf_addr; + + dim2_write_ctr_mask(ADT + ch_addr, mask, adt); +} + +static void dim2_start_isoc_sync(u8 ch_addr, u8 idx, u32 buf_addr, + u16 buffer_size) +{ + u8 const shift = idx * 16; + + u32 mask[4] = { 0, 0, 0, 0 }; + u32 adt[4] = { 0, 0, 0, 0 }; + + mask[1] = + bit_mask(ADT1_RDY_BIT + shift) | + (ADT1_ISOC_SYNC_BD_MASK << (ADT1_BD_SHIFT + shift)); + adt[1] = + (true << (ADT1_RDY_BIT + shift)) | + ((buffer_size - 1) << (ADT1_BD_SHIFT + shift)); + + mask[idx + 2] = 0xFFFFFFFF; + adt[idx + 2] = buf_addr; + + dim2_write_ctr_mask(ADT + ch_addr, mask, adt); +} + +static void dim2_clear_ctram(void) +{ + u32 ctr_addr; + + for (ctr_addr = 0; ctr_addr < 0x90; ctr_addr++) + dim2_clear_ctr(ctr_addr); +} + +static void dim2_configure_channel( + u8 ch_addr, u8 type, u8 is_tx, u16 dbr_address, u16 hw_buffer_size, + u16 packet_length) +{ + dim2_configure_cdt(ch_addr, dbr_address, hw_buffer_size, packet_length); + dim2_configure_cat(MLB_CAT, ch_addr, type, is_tx ? 1 : 0); + + dim2_configure_adt(ch_addr); + dim2_configure_cat(AHB_CAT, ch_addr, type, is_tx ? 0 : 1); + + /* unmask interrupt for used channel, enable mlb_sys_int[0] interrupt */ + writel(readl(&g.dim2->ACMR0) | bit_mask(ch_addr), &g.dim2->ACMR0); +} + +static void dim2_clear_channel(u8 ch_addr) +{ + /* mask interrupt for used channel, disable mlb_sys_int[0] interrupt */ + writel(readl(&g.dim2->ACMR0) & ~bit_mask(ch_addr), &g.dim2->ACMR0); + + dim2_clear_cat(AHB_CAT, ch_addr); + dim2_clear_adt(ch_addr); + + dim2_clear_cat(MLB_CAT, ch_addr); + dim2_clear_cdt(ch_addr); + + /* clear channel status bit */ + writel(bit_mask(ch_addr), &g.dim2->ACSR0); +} + +/* -------------------------------------------------------------------------- */ +/* trace async tx dbr fill state */ + +static inline u16 norm_pc(u16 pc) +{ + return pc & CDT0_RPC_MASK; +} + +static void dbrcnt_init(u8 ch_addr, u16 dbr_size) +{ + g.atx_dbr.rest_size = dbr_size; + g.atx_dbr.rpc = dim2_rpc(ch_addr); + g.atx_dbr.wpc = g.atx_dbr.rpc; +} + +static void dbrcnt_enq(int buf_sz) +{ + g.atx_dbr.rest_size -= buf_sz; + g.atx_dbr.sz_queue[norm_pc(g.atx_dbr.wpc)] = buf_sz; + g.atx_dbr.wpc++; +} + +u16 dim_dbr_space(struct dim_channel *ch) +{ + u16 cur_rpc; + struct async_tx_dbr *dbr = &g.atx_dbr; + + if (ch->addr != dbr->ch_addr) + return 0xFFFF; + + cur_rpc = dim2_rpc(ch->addr); + + while (norm_pc(dbr->rpc) != cur_rpc) { + dbr->rest_size += dbr->sz_queue[norm_pc(dbr->rpc)]; + dbr->rpc++; + } + + if ((u16)(dbr->wpc - dbr->rpc) >= CDT0_RPC_MASK) + return 0; + + return dbr->rest_size; +} + +/* -------------------------------------------------------------------------- */ +/* channel state helpers */ + +static void state_init(struct int_ch_state *state) +{ + state->request_counter = 0; + state->service_counter = 0; + + state->idx1 = 0; + state->idx2 = 0; + state->level = 0; +} + +/* -------------------------------------------------------------------------- */ +/* macro helper functions */ + +static inline bool check_channel_address(u32 ch_address) +{ + return ch_address > 0 && (ch_address % 2) == 0 && + (ch_address / 2) <= (u32)CAT_CL_MASK; +} + +static inline bool check_packet_length(u32 packet_length) +{ + u16 const max_size = ((u16)CDT3_BD_ISOC_MASK + 1u) / ISOC_DBR_FACTOR; + + if (packet_length <= 0) + return false; /* too small */ + + if (packet_length > max_size) + return false; /* too big */ + + if (packet_length - 1u > (u32)CDT1_BS_ISOC_MASK) + return false; /* too big */ + + return true; +} + +static inline bool check_bytes_per_frame(u32 bytes_per_frame) +{ + u16 const bd_factor = g.fcnt + 2; + u16 const max_size = ((u16)CDT3_BD_MASK + 1u) >> bd_factor; + + if (bytes_per_frame <= 0) + return false; /* too small */ + + if (bytes_per_frame > max_size) + return false; /* too big */ + + return true; +} + +u16 dim_norm_ctrl_async_buffer_size(u16 buf_size) +{ + u16 const max_size = (u16)ADT1_CTRL_ASYNC_BD_MASK + 1u; + + if (buf_size > max_size) + return max_size; + + return buf_size; +} + +static inline u16 norm_isoc_buffer_size(u16 buf_size, u16 packet_length) +{ + u16 n; + u16 const max_size = (u16)ADT1_ISOC_SYNC_BD_MASK + 1u; + + if (buf_size > max_size) + buf_size = max_size; + + n = buf_size / packet_length; + + if (n < 2u) + return 0; /* too small buffer for given packet_length */ + + return packet_length * n; +} + +static inline u16 norm_sync_buffer_size(u16 buf_size, u16 bytes_per_frame) +{ + u16 n; + u16 const max_size = (u16)ADT1_ISOC_SYNC_BD_MASK + 1u; + u32 const unit = bytes_per_frame << g.fcnt; + + if (buf_size > max_size) + buf_size = max_size; + + n = buf_size / unit; + + if (n < 1u) + return 0; /* too small buffer for given bytes_per_frame */ + + return unit * n; +} + +static void dim2_cleanup(void) +{ + /* disable MediaLB */ + writel(false << MLBC0_MLBEN_BIT, &g.dim2->MLBC0); + + dim2_clear_ctram(); + + /* disable mlb_int interrupt */ + writel(0, &g.dim2->MIEN); + + /* clear status for all dma channels */ + writel(0xFFFFFFFF, &g.dim2->ACSR0); + writel(0xFFFFFFFF, &g.dim2->ACSR1); + + /* mask interrupts for all channels */ + writel(0, &g.dim2->ACMR0); + writel(0, &g.dim2->ACMR1); +} + +static void dim2_initialize(bool enable_6pin, u8 mlb_clock) +{ + dim2_cleanup(); + + /* configure and enable MediaLB */ + writel(enable_6pin << MLBC0_MLBPEN_BIT | + mlb_clock << MLBC0_MLBCLK_SHIFT | + g.fcnt << MLBC0_FCNT_SHIFT | + true << MLBC0_MLBEN_BIT, + &g.dim2->MLBC0); + + /* activate all HBI channels */ + writel(0xFFFFFFFF, &g.dim2->HCMR0); + writel(0xFFFFFFFF, &g.dim2->HCMR1); + + /* enable HBI */ + writel(bit_mask(HCTL_EN_BIT), &g.dim2->HCTL); + + /* configure DMA */ + writel(ACTL_DMA_MODE_VAL_DMA_MODE_1 << ACTL_DMA_MODE_BIT | + true << ACTL_SCE_BIT, &g.dim2->ACTL); +} + +static bool dim2_is_mlb_locked(void) +{ + u32 const mask0 = bit_mask(MLBC0_MLBLK_BIT); + u32 const mask1 = bit_mask(MLBC1_CLKMERR_BIT) | + bit_mask(MLBC1_LOCKERR_BIT); + u32 const c1 = readl(&g.dim2->MLBC1); + u32 const nda_mask = (u32)MLBC1_NDA_MASK << MLBC1_NDA_SHIFT; + + writel(c1 & nda_mask, &g.dim2->MLBC1); + return (readl(&g.dim2->MLBC1) & mask1) == 0 && + (readl(&g.dim2->MLBC0) & mask0) != 0; +} + +/* -------------------------------------------------------------------------- */ +/* channel help routines */ + +static inline bool service_channel(u8 ch_addr, u8 idx) +{ + u8 const shift = idx * 16; + u32 const adt1 = dim2_read_ctr(ADT + ch_addr, 1); + u32 mask[4] = { 0, 0, 0, 0 }; + u32 adt_w[4] = { 0, 0, 0, 0 }; + + if (((adt1 >> (ADT1_DNE_BIT + shift)) & 1) == 0) + return false; + + mask[1] = + bit_mask(ADT1_DNE_BIT + shift) | + bit_mask(ADT1_ERR_BIT + shift) | + bit_mask(ADT1_RDY_BIT + shift); + dim2_write_ctr_mask(ADT + ch_addr, mask, adt_w); + + /* clear channel status bit */ + writel(bit_mask(ch_addr), &g.dim2->ACSR0); + + return true; +} + +/* -------------------------------------------------------------------------- */ +/* channel init routines */ + +static void isoc_init(struct dim_channel *ch, u8 ch_addr, u16 packet_length) +{ + state_init(&ch->state); + + ch->addr = ch_addr; + + ch->packet_length = packet_length; + ch->bytes_per_frame = 0; + ch->done_sw_buffers_number = 0; +} + +static void sync_init(struct dim_channel *ch, u8 ch_addr, u16 bytes_per_frame) +{ + state_init(&ch->state); + + ch->addr = ch_addr; + + ch->packet_length = 0; + ch->bytes_per_frame = bytes_per_frame; + ch->done_sw_buffers_number = 0; +} + +static void channel_init(struct dim_channel *ch, u8 ch_addr) +{ + state_init(&ch->state); + + ch->addr = ch_addr; + + ch->packet_length = 0; + ch->bytes_per_frame = 0; + ch->done_sw_buffers_number = 0; +} + +/* returns true if channel interrupt state is cleared */ +static bool channel_service_interrupt(struct dim_channel *ch) +{ + struct int_ch_state *const state = &ch->state; + + if (!service_channel(ch->addr, state->idx2)) + return false; + + state->idx2 ^= 1; + state->request_counter++; + return true; +} + +static bool channel_start(struct dim_channel *ch, u32 buf_addr, u16 buf_size) +{ + struct int_ch_state *const state = &ch->state; + + if (buf_size <= 0) + return dim_on_error(DIM_ERR_BAD_BUFFER_SIZE, "Bad buffer size"); + + if (ch->packet_length == 0 && ch->bytes_per_frame == 0 && + buf_size != dim_norm_ctrl_async_buffer_size(buf_size)) + return dim_on_error(DIM_ERR_BAD_BUFFER_SIZE, + "Bad control/async buffer size"); + + if (ch->packet_length && + buf_size != norm_isoc_buffer_size(buf_size, ch->packet_length)) + return dim_on_error(DIM_ERR_BAD_BUFFER_SIZE, + "Bad isochronous buffer size"); + + if (ch->bytes_per_frame && + buf_size != norm_sync_buffer_size(buf_size, ch->bytes_per_frame)) + return dim_on_error(DIM_ERR_BAD_BUFFER_SIZE, + "Bad synchronous buffer size"); + + if (state->level >= 2u) + return dim_on_error(DIM_ERR_OVERFLOW, "Channel overflow"); + + ++state->level; + + if (ch->addr == g.atx_dbr.ch_addr) + dbrcnt_enq(buf_size); + + if (ch->packet_length || ch->bytes_per_frame) + dim2_start_isoc_sync(ch->addr, state->idx1, buf_addr, buf_size); + else + dim2_start_ctrl_async(ch->addr, state->idx1, buf_addr, + buf_size); + state->idx1 ^= 1; + + return true; +} + +static u8 channel_service(struct dim_channel *ch) +{ + struct int_ch_state *const state = &ch->state; + + if (state->service_counter != state->request_counter) { + state->service_counter++; + if (state->level == 0) + return DIM_ERR_UNDERFLOW; + + --state->level; + ch->done_sw_buffers_number++; + } + + return DIM_NO_ERROR; +} + +static bool channel_detach_buffers(struct dim_channel *ch, u16 buffers_number) +{ + if (buffers_number > ch->done_sw_buffers_number) + return dim_on_error(DIM_ERR_UNDERFLOW, "Channel underflow"); + + ch->done_sw_buffers_number -= buffers_number; + return true; +} + +/* -------------------------------------------------------------------------- */ +/* API */ + +u8 dim_startup(struct dim2_regs __iomem *dim_base_address, u32 mlb_clock, + u32 fcnt) +{ + g.dim_is_initialized = false; + + if (!dim_base_address) + return DIM_INIT_ERR_DIM_ADDR; + + /* MediaLB clock: 0 - 256 fs, 1 - 512 fs, 2 - 1024 fs, 3 - 2048 fs */ + /* MediaLB clock: 4 - 3072 fs, 5 - 4096 fs, 6 - 6144 fs, 7 - 8192 fs */ + if (mlb_clock >= 8) + return DIM_INIT_ERR_MLB_CLOCK; + + if (fcnt > MLBC0_FCNT_MAX_VAL) + return DIM_INIT_ERR_MLB_CLOCK; + + g.dim2 = dim_base_address; + g.fcnt = fcnt; + g.dbr_map[0] = 0; + g.dbr_map[1] = 0; + + dim2_initialize(mlb_clock >= 3, mlb_clock); + + g.dim_is_initialized = true; + + return DIM_NO_ERROR; +} + +void dim_shutdown(void) +{ + g.dim_is_initialized = false; + dim2_cleanup(); +} + +bool dim_get_lock_state(void) +{ + return dim2_is_mlb_locked(); +} + +static u8 init_ctrl_async(struct dim_channel *ch, u8 type, u8 is_tx, + u16 ch_address, u16 hw_buffer_size) +{ + if (!g.dim_is_initialized || !ch) + return DIM_ERR_DRIVER_NOT_INITIALIZED; + + if (!check_channel_address(ch_address)) + return DIM_INIT_ERR_CHANNEL_ADDRESS; + + if (!ch->dbr_size) + ch->dbr_size = ROUND_UP_TO(hw_buffer_size, DBR_BLOCK_SIZE); + ch->dbr_addr = alloc_dbr(ch->dbr_size); + if (ch->dbr_addr >= DBR_SIZE) + return DIM_INIT_ERR_OUT_OF_MEMORY; + + channel_init(ch, ch_address / 2); + + dim2_configure_channel(ch->addr, type, is_tx, + ch->dbr_addr, ch->dbr_size, 0); + + return DIM_NO_ERROR; +} + +void dim_service_mlb_int_irq(void) +{ + writel(0, &g.dim2->MS0); + writel(0, &g.dim2->MS1); +} + +/* + * Retrieves maximal possible correct buffer size for isochronous data type + * conform to given packet length and not bigger than given buffer size. + * + * Returns non-zero correct buffer size or zero by error. + */ +u16 dim_norm_isoc_buffer_size(u16 buf_size, u16 packet_length) +{ + if (!check_packet_length(packet_length)) + return 0; + + return norm_isoc_buffer_size(buf_size, packet_length); +} + +/* + * Retrieves maximal possible correct buffer size for synchronous data type + * conform to given bytes per frame and not bigger than given buffer size. + * + * Returns non-zero correct buffer size or zero by error. + */ +u16 dim_norm_sync_buffer_size(u16 buf_size, u16 bytes_per_frame) +{ + if (!check_bytes_per_frame(bytes_per_frame)) + return 0; + + return norm_sync_buffer_size(buf_size, bytes_per_frame); +} + +u8 dim_init_control(struct dim_channel *ch, u8 is_tx, u16 ch_address, + u16 max_buffer_size) +{ + return init_ctrl_async(ch, CAT_CT_VAL_CONTROL, is_tx, ch_address, + max_buffer_size); +} + +u8 dim_init_async(struct dim_channel *ch, u8 is_tx, u16 ch_address, + u16 max_buffer_size) +{ + u8 ret = init_ctrl_async(ch, CAT_CT_VAL_ASYNC, is_tx, ch_address, + max_buffer_size); + + if (is_tx && !g.atx_dbr.ch_addr) { + g.atx_dbr.ch_addr = ch->addr; + dbrcnt_init(ch->addr, ch->dbr_size); + writel(bit_mask(20), &g.dim2->MIEN); + } + + return ret; +} + +u8 dim_init_isoc(struct dim_channel *ch, u8 is_tx, u16 ch_address, + u16 packet_length) +{ + if (!g.dim_is_initialized || !ch) + return DIM_ERR_DRIVER_NOT_INITIALIZED; + + if (!check_channel_address(ch_address)) + return DIM_INIT_ERR_CHANNEL_ADDRESS; + + if (!check_packet_length(packet_length)) + return DIM_ERR_BAD_CONFIG; + + if (!ch->dbr_size) + ch->dbr_size = packet_length * ISOC_DBR_FACTOR; + ch->dbr_addr = alloc_dbr(ch->dbr_size); + if (ch->dbr_addr >= DBR_SIZE) + return DIM_INIT_ERR_OUT_OF_MEMORY; + + isoc_init(ch, ch_address / 2, packet_length); + + dim2_configure_channel(ch->addr, CAT_CT_VAL_ISOC, is_tx, ch->dbr_addr, + ch->dbr_size, packet_length); + + return DIM_NO_ERROR; +} + +u8 dim_init_sync(struct dim_channel *ch, u8 is_tx, u16 ch_address, + u16 bytes_per_frame) +{ + u16 bd_factor = g.fcnt + 2; + + if (!g.dim_is_initialized || !ch) + return DIM_ERR_DRIVER_NOT_INITIALIZED; + + if (!check_channel_address(ch_address)) + return DIM_INIT_ERR_CHANNEL_ADDRESS; + + if (!check_bytes_per_frame(bytes_per_frame)) + return DIM_ERR_BAD_CONFIG; + + if (!ch->dbr_size) + ch->dbr_size = bytes_per_frame << bd_factor; + ch->dbr_addr = alloc_dbr(ch->dbr_size); + if (ch->dbr_addr >= DBR_SIZE) + return DIM_INIT_ERR_OUT_OF_MEMORY; + + sync_init(ch, ch_address / 2, bytes_per_frame); + + dim2_clear_dbr(ch->dbr_addr, ch->dbr_size); + dim2_configure_channel(ch->addr, CAT_CT_VAL_SYNC, is_tx, + ch->dbr_addr, ch->dbr_size, 0); + + return DIM_NO_ERROR; +} + +u8 dim_destroy_channel(struct dim_channel *ch) +{ + if (!g.dim_is_initialized || !ch) + return DIM_ERR_DRIVER_NOT_INITIALIZED; + + if (ch->addr == g.atx_dbr.ch_addr) { + writel(0, &g.dim2->MIEN); + g.atx_dbr.ch_addr = 0; + } + + dim2_clear_channel(ch->addr); + if (ch->dbr_addr < DBR_SIZE) + free_dbr(ch->dbr_addr, ch->dbr_size); + ch->dbr_addr = DBR_SIZE; + + return DIM_NO_ERROR; +} + +void dim_service_ahb_int_irq(struct dim_channel *const *channels) +{ + bool state_changed; + + if (!g.dim_is_initialized) { + dim_on_error(DIM_ERR_DRIVER_NOT_INITIALIZED, + "DIM is not initialized"); + return; + } + + if (!channels) { + dim_on_error(DIM_ERR_DRIVER_NOT_INITIALIZED, "Bad channels"); + return; + } + + /* + * Use while-loop and a flag to make sure the age is changed back at + * least once, otherwise the interrupt may never come if CPU generates + * interrupt on changing age. + * This cycle runs not more than number of channels, because + * channel_service_interrupt() routine doesn't start the channel again. + */ + do { + struct dim_channel *const *ch = channels; + + state_changed = false; + + while (*ch) { + state_changed |= channel_service_interrupt(*ch); + ++ch; + } + } while (state_changed); +} + +u8 dim_service_channel(struct dim_channel *ch) +{ + if (!g.dim_is_initialized || !ch) + return DIM_ERR_DRIVER_NOT_INITIALIZED; + + return channel_service(ch); +} + +struct dim_ch_state_t *dim_get_channel_state(struct dim_channel *ch, + struct dim_ch_state_t *state_ptr) +{ + if (!ch || !state_ptr) + return NULL; + + state_ptr->ready = ch->state.level < 2; + state_ptr->done_buffers = ch->done_sw_buffers_number; + + return state_ptr; +} + +bool dim_enqueue_buffer(struct dim_channel *ch, u32 buffer_addr, + u16 buffer_size) +{ + if (!ch) + return dim_on_error(DIM_ERR_DRIVER_NOT_INITIALIZED, + "Bad channel"); + + return channel_start(ch, buffer_addr, buffer_size); +} + +bool dim_detach_buffers(struct dim_channel *ch, u16 buffers_number) +{ + if (!ch) + return dim_on_error(DIM_ERR_DRIVER_NOT_INITIALIZED, + "Bad channel"); + + return channel_detach_buffers(ch, buffers_number); +} diff --git a/drivers/staging/most/dim2/hal.h b/drivers/staging/most/dim2/hal.h new file mode 100644 index 000000000..20531449a --- /dev/null +++ b/drivers/staging/most/dim2/hal.h @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * hal.h - DIM2 HAL interface + * (MediaLB, Device Interface Macro IP, OS62420) + * + * Copyright (C) 2015, Microchip Technology Germany II GmbH & Co. KG + */ + +#ifndef _DIM2_HAL_H +#define _DIM2_HAL_H + +#include +#include "reg.h" + +/* + * The values below are specified in the hardware specification. + * So, they should not be changed until the hardware specification changes. + */ +enum mlb_clk_speed { + CLK_256FS = 0, + CLK_512FS = 1, + CLK_1024FS = 2, + CLK_2048FS = 3, + CLK_3072FS = 4, + CLK_4096FS = 5, + CLK_6144FS = 6, + CLK_8192FS = 7, +}; + +struct dim_ch_state_t { + bool ready; /* Shows readiness to enqueue next buffer */ + u16 done_buffers; /* Number of completed buffers */ +}; + +struct int_ch_state { + /* changed only in interrupt context */ + volatile int request_counter; + + /* changed only in task context */ + volatile int service_counter; + + u8 idx1; + u8 idx2; + u8 level; /* [0..2], buffering level */ +}; + +struct dim_channel { + struct int_ch_state state; + u8 addr; + u16 dbr_addr; + u16 dbr_size; + u16 packet_length; /*< Isochronous packet length in bytes. */ + u16 bytes_per_frame; /*< Synchronous bytes per frame. */ + u16 done_sw_buffers_number; /*< Done software buffers number. */ +}; + +u8 dim_startup(struct dim2_regs __iomem *dim_base_address, u32 mlb_clock, + u32 fcnt); + +void dim_shutdown(void); + +bool dim_get_lock_state(void); + +u16 dim_norm_ctrl_async_buffer_size(u16 buf_size); + +u16 dim_norm_isoc_buffer_size(u16 buf_size, u16 packet_length); + +u16 dim_norm_sync_buffer_size(u16 buf_size, u16 bytes_per_frame); + +u8 dim_init_control(struct dim_channel *ch, u8 is_tx, u16 ch_address, + u16 max_buffer_size); + +u8 dim_init_async(struct dim_channel *ch, u8 is_tx, u16 ch_address, + u16 max_buffer_size); + +u8 dim_init_isoc(struct dim_channel *ch, u8 is_tx, u16 ch_address, + u16 packet_length); + +u8 dim_init_sync(struct dim_channel *ch, u8 is_tx, u16 ch_address, + u16 bytes_per_frame); + +u8 dim_destroy_channel(struct dim_channel *ch); + +void dim_service_mlb_int_irq(void); + +void dim_service_ahb_int_irq(struct dim_channel *const *channels); + +u8 dim_service_channel(struct dim_channel *ch); + +struct dim_ch_state_t *dim_get_channel_state(struct dim_channel *ch, + struct dim_ch_state_t *state_ptr); + +u16 dim_dbr_space(struct dim_channel *ch); + +bool dim_enqueue_buffer(struct dim_channel *ch, u32 buffer_addr, + u16 buffer_size); + +bool dim_detach_buffers(struct dim_channel *ch, u16 buffers_number); + +void dimcb_on_error(u8 error_id, const char *error_message); + +#endif /* _DIM2_HAL_H */ diff --git a/drivers/staging/most/dim2/reg.h b/drivers/staging/most/dim2/reg.h new file mode 100644 index 000000000..b0f36c208 --- /dev/null +++ b/drivers/staging/most/dim2/reg.h @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * reg.h - Definitions for registers of DIM2 + * (MediaLB, Device Interface Macro IP, OS62420) + * + * Copyright (C) 2015, Microchip Technology Germany II GmbH & Co. KG + */ + +#ifndef DIM2_OS62420_H +#define DIM2_OS62420_H + +#include + +struct dim2_regs { + u32 MLBC0; /* 0x00 */ + u32 rsvd0[1]; /* 0x01 */ + u32 MLBPC0; /* 0x02 */ + u32 MS0; /* 0x03 */ + u32 rsvd1[1]; /* 0x04 */ + u32 MS1; /* 0x05 */ + u32 rsvd2[2]; /* 0x06 */ + u32 MSS; /* 0x08 */ + u32 MSD; /* 0x09 */ + u32 rsvd3[1]; /* 0x0A */ + u32 MIEN; /* 0x0B */ + u32 rsvd4[1]; /* 0x0C */ + u32 MLBPC2; /* 0x0D */ + u32 MLBPC1; /* 0x0E */ + u32 MLBC1; /* 0x0F */ + u32 rsvd5[0x10]; /* 0x10 */ + u32 HCTL; /* 0x20 */ + u32 rsvd6[1]; /* 0x21 */ + u32 HCMR0; /* 0x22 */ + u32 HCMR1; /* 0x23 */ + u32 HCER0; /* 0x24 */ + u32 HCER1; /* 0x25 */ + u32 HCBR0; /* 0x26 */ + u32 HCBR1; /* 0x27 */ + u32 rsvd7[8]; /* 0x28 */ + u32 MDAT0; /* 0x30 */ + u32 MDAT1; /* 0x31 */ + u32 MDAT2; /* 0x32 */ + u32 MDAT3; /* 0x33 */ + u32 MDWE0; /* 0x34 */ + u32 MDWE1; /* 0x35 */ + u32 MDWE2; /* 0x36 */ + u32 MDWE3; /* 0x37 */ + u32 MCTL; /* 0x38 */ + u32 MADR; /* 0x39 */ + u32 rsvd8[0xb6]; /* 0x3A */ + u32 ACTL; /* 0xF0 */ + u32 rsvd9[3]; /* 0xF1 */ + u32 ACSR0; /* 0xF4 */ + u32 ACSR1; /* 0xF5 */ + u32 ACMR0; /* 0xF6 */ + u32 ACMR1; /* 0xF7 */ +}; + +#define DIM2_MASK(n) (~((~(u32)0) << (n))) + +enum { + MLBC0_MLBLK_BIT = 7, + + MLBC0_MLBPEN_BIT = 5, + + MLBC0_MLBCLK_SHIFT = 2, + MLBC0_MLBCLK_VAL_256FS = 0, + MLBC0_MLBCLK_VAL_512FS = 1, + MLBC0_MLBCLK_VAL_1024FS = 2, + MLBC0_MLBCLK_VAL_2048FS = 3, + + MLBC0_FCNT_SHIFT = 15, + MLBC0_FCNT_MASK = 7, + MLBC0_FCNT_MAX_VAL = 6, + + MLBC0_MLBEN_BIT = 0, + + MIEN_CTX_BREAK_BIT = 29, + MIEN_CTX_PE_BIT = 28, + MIEN_CTX_DONE_BIT = 27, + + MIEN_CRX_BREAK_BIT = 26, + MIEN_CRX_PE_BIT = 25, + MIEN_CRX_DONE_BIT = 24, + + MIEN_ATX_BREAK_BIT = 22, + MIEN_ATX_PE_BIT = 21, + MIEN_ATX_DONE_BIT = 20, + + MIEN_ARX_BREAK_BIT = 19, + MIEN_ARX_PE_BIT = 18, + MIEN_ARX_DONE_BIT = 17, + + MIEN_SYNC_PE_BIT = 16, + + MIEN_ISOC_BUFO_BIT = 1, + MIEN_ISOC_PE_BIT = 0, + + MLBC1_NDA_SHIFT = 8, + MLBC1_NDA_MASK = 0xFF, + + MLBC1_CLKMERR_BIT = 7, + MLBC1_LOCKERR_BIT = 6, + + ACTL_DMA_MODE_BIT = 2, + ACTL_DMA_MODE_VAL_DMA_MODE_0 = 0, + ACTL_DMA_MODE_VAL_DMA_MODE_1 = 1, + ACTL_SCE_BIT = 0, + + HCTL_EN_BIT = 15 +}; + +enum { + CDT0_RPC_SHIFT = 16 + 11, + CDT0_RPC_MASK = DIM2_MASK(5), + + CDT1_BS_ISOC_SHIFT = 0, + CDT1_BS_ISOC_MASK = DIM2_MASK(9), + + CDT3_BD_SHIFT = 0, + CDT3_BD_MASK = DIM2_MASK(12), + CDT3_BD_ISOC_MASK = DIM2_MASK(13), + CDT3_BA_SHIFT = 16, + + ADT0_CE_BIT = 15, + ADT0_LE_BIT = 14, + ADT0_PG_BIT = 13, + + ADT1_RDY_BIT = 15, + ADT1_DNE_BIT = 14, + ADT1_ERR_BIT = 13, + ADT1_PS_BIT = 12, + ADT1_MEP_BIT = 11, + ADT1_BD_SHIFT = 0, + ADT1_CTRL_ASYNC_BD_MASK = DIM2_MASK(11), + ADT1_ISOC_SYNC_BD_MASK = DIM2_MASK(13), + + CAT_FCE_BIT = 14, + CAT_MFE_BIT = 14, + + CAT_MT_BIT = 13, + + CAT_RNW_BIT = 12, + + CAT_CE_BIT = 11, + + CAT_CT_SHIFT = 8, + CAT_CT_VAL_SYNC = 0, + CAT_CT_VAL_CONTROL = 1, + CAT_CT_VAL_ASYNC = 2, + CAT_CT_VAL_ISOC = 3, + + CAT_CL_SHIFT = 0, + CAT_CL_MASK = DIM2_MASK(6) +}; + +#endif /* DIM2_OS62420_H */ diff --git a/drivers/staging/most/dim2/sysfs.h b/drivers/staging/most/dim2/sysfs.h new file mode 100644 index 000000000..09115cf4e --- /dev/null +++ b/drivers/staging/most/dim2/sysfs.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * sysfs.h - MediaLB sysfs information + * + * Copyright (C) 2015, Microchip Technology Germany II GmbH & Co. KG + */ + +/* Author: Andrey Shvetsov */ + +#ifndef DIM2_SYSFS_H +#define DIM2_SYSFS_H + +#include + +struct medialb_bus { + struct kobject kobj_group; +}; + +#endif /* DIM2_SYSFS_H */ diff --git a/drivers/staging/most/i2c/Kconfig b/drivers/staging/most/i2c/Kconfig new file mode 100644 index 000000000..ff64283cb --- /dev/null +++ b/drivers/staging/most/i2c/Kconfig @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# MOST I2C configuration +# + +config MOST_I2C + tristate "I2C" + depends on I2C + help + Say Y here if you want to connect via I2C to network transceiver. + + To compile this driver as a module, choose M here: the + module will be called most_i2c. diff --git a/drivers/staging/most/i2c/Makefile b/drivers/staging/most/i2c/Makefile new file mode 100644 index 000000000..71099dd0f --- /dev/null +++ b/drivers/staging/most/i2c/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_MOST_I2C) += most_i2c.o + +most_i2c-objs := i2c.o diff --git a/drivers/staging/most/i2c/i2c.c b/drivers/staging/most/i2c/i2c.c new file mode 100644 index 000000000..285a071f0 --- /dev/null +++ b/drivers/staging/most/i2c/i2c.c @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * i2c.c - Hardware Dependent Module for I2C Interface + * + * Copyright (C) 2013-2015, Microchip Technology Germany II GmbH & Co. KG + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include + +enum { CH_RX, CH_TX, NUM_CHANNELS }; + +#define MAX_BUFFERS_CONTROL 32 +#define MAX_BUF_SIZE_CONTROL 256 + +/** + * list_first_mbo - get the first mbo from a list + * @ptr: the list head to take the mbo from. + */ +#define list_first_mbo(ptr) \ + list_first_entry(ptr, struct mbo, list) + +static unsigned int polling_rate; +module_param(polling_rate, uint, 0644); +MODULE_PARM_DESC(polling_rate, "Polling rate [Hz]. Default = 0 (use IRQ)"); + +struct hdm_i2c { + struct most_interface most_iface; + struct most_channel_capability capabilities[NUM_CHANNELS]; + struct i2c_client *client; + struct rx { + struct delayed_work dwork; + struct list_head list; + bool int_disabled; + unsigned int delay; + } rx; + char name[64]; +}; + +#define to_hdm(iface) container_of(iface, struct hdm_i2c, most_iface) + +static irqreturn_t most_irq_handler(int, void *); +static void pending_rx_work(struct work_struct *); + +/** + * configure_channel - called from MOST core to configure a channel + * @most_iface: interface the channel belongs to + * @ch_idx: channel to be configured + * @channel_config: structure that holds the configuration information + * + * Return 0 on success, negative on failure. + * + * Receives configuration information from MOST core and initialize the + * corresponding channel. + */ +static int configure_channel(struct most_interface *most_iface, + int ch_idx, + struct most_channel_config *channel_config) +{ + int ret; + struct hdm_i2c *dev = to_hdm(most_iface); + unsigned int delay, pr; + + BUG_ON(ch_idx < 0 || ch_idx >= NUM_CHANNELS); + + if (channel_config->data_type != MOST_CH_CONTROL) { + pr_err("bad data type for channel %d\n", ch_idx); + return -EPERM; + } + + if (channel_config->direction != dev->capabilities[ch_idx].direction) { + pr_err("bad direction for channel %d\n", ch_idx); + return -EPERM; + } + + if (channel_config->direction == MOST_CH_RX) { + if (!polling_rate) { + if (dev->client->irq <= 0) { + pr_err("bad irq: %d\n", dev->client->irq); + return -ENOENT; + } + dev->rx.int_disabled = false; + ret = request_irq(dev->client->irq, most_irq_handler, 0, + dev->client->name, dev); + if (ret) { + pr_err("request_irq(%d) failed: %d\n", + dev->client->irq, ret); + return ret; + } + } else { + delay = msecs_to_jiffies(MSEC_PER_SEC / polling_rate); + dev->rx.delay = delay ? delay : 1; + pr = MSEC_PER_SEC / jiffies_to_msecs(dev->rx.delay); + pr_info("polling rate is %u Hz\n", pr); + } + } + + return 0; +} + +/** + * enqueue - called from MOST core to enqueue a buffer for data transfer + * @most_iface: intended interface + * @ch_idx: ID of the channel the buffer is intended for + * @mbo: pointer to the buffer object + * + * Return 0 on success, negative on failure. + * + * Transmit the data over I2C if it is a "write" request or push the buffer into + * list if it is an "read" request + */ +static int enqueue(struct most_interface *most_iface, + int ch_idx, struct mbo *mbo) +{ + struct hdm_i2c *dev = to_hdm(most_iface); + int ret; + + BUG_ON(ch_idx < 0 || ch_idx >= NUM_CHANNELS); + + if (ch_idx == CH_RX) { + /* RX */ + if (!polling_rate) + disable_irq(dev->client->irq); + cancel_delayed_work_sync(&dev->rx.dwork); + list_add_tail(&mbo->list, &dev->rx.list); + if (dev->rx.int_disabled || polling_rate) + pending_rx_work(&dev->rx.dwork.work); + if (!polling_rate) + enable_irq(dev->client->irq); + } else { + /* TX */ + ret = i2c_master_send(dev->client, mbo->virt_address, + mbo->buffer_length); + if (ret <= 0) { + mbo->processed_length = 0; + mbo->status = MBO_E_INVAL; + } else { + mbo->processed_length = mbo->buffer_length; + mbo->status = MBO_SUCCESS; + } + mbo->complete(mbo); + } + + return 0; +} + +/** + * poison_channel - called from MOST core to poison buffers of a channel + * @most_iface: pointer to the interface the channel to be poisoned belongs to + * @ch_idx: corresponding channel ID + * + * Return 0 on success, negative on failure. + * + * If channel direction is RX, complete the buffers in list with + * status MBO_E_CLOSE + */ +static int poison_channel(struct most_interface *most_iface, + int ch_idx) +{ + struct hdm_i2c *dev = to_hdm(most_iface); + struct mbo *mbo; + + BUG_ON(ch_idx < 0 || ch_idx >= NUM_CHANNELS); + + if (ch_idx == CH_RX) { + if (!polling_rate) + free_irq(dev->client->irq, dev); + cancel_delayed_work_sync(&dev->rx.dwork); + + while (!list_empty(&dev->rx.list)) { + mbo = list_first_mbo(&dev->rx.list); + list_del(&mbo->list); + + mbo->processed_length = 0; + mbo->status = MBO_E_CLOSE; + mbo->complete(mbo); + } + } + + return 0; +} + +static void do_rx_work(struct hdm_i2c *dev) +{ + struct mbo *mbo; + unsigned char msg[MAX_BUF_SIZE_CONTROL]; + int ret; + u16 pml, data_size; + + /* Read PML (2 bytes) */ + ret = i2c_master_recv(dev->client, msg, 2); + if (ret <= 0) { + pr_err("Failed to receive PML\n"); + return; + } + + pml = (msg[0] << 8) | msg[1]; + if (!pml) + return; + + data_size = pml + 2; + + /* Read the whole message, including PML */ + ret = i2c_master_recv(dev->client, msg, data_size); + if (ret <= 0) { + pr_err("Failed to receive a Port Message\n"); + return; + } + + mbo = list_first_mbo(&dev->rx.list); + list_del(&mbo->list); + + mbo->processed_length = min(data_size, mbo->buffer_length); + memcpy(mbo->virt_address, msg, mbo->processed_length); + mbo->status = MBO_SUCCESS; + mbo->complete(mbo); +} + +/** + * pending_rx_work - Read pending messages through I2C + * @work: definition of this work item + * + * Invoked by the Interrupt Service Routine, most_irq_handler() + */ +static void pending_rx_work(struct work_struct *work) +{ + struct hdm_i2c *dev = container_of(work, struct hdm_i2c, rx.dwork.work); + + if (list_empty(&dev->rx.list)) + return; + + do_rx_work(dev); + + if (polling_rate) { + schedule_delayed_work(&dev->rx.dwork, dev->rx.delay); + } else { + dev->rx.int_disabled = false; + enable_irq(dev->client->irq); + } +} + +/* + * most_irq_handler - Interrupt Service Routine + * @irq: irq number + * @_dev: private data + * + * Schedules a delayed work + * + * By default the interrupt line behavior is Active Low. Once an interrupt is + * generated by the device, until driver clears the interrupt (by reading + * the PMP message), device keeps the interrupt line in low state. Since i2c + * read is done in work queue, the interrupt line must be disabled temporarily + * to avoid ISR being called repeatedly. Re-enable the interrupt in workqueue, + * after reading the message. + * + * Note: If we use the interrupt line in Falling edge mode, there is a + * possibility to miss interrupts when ISR is getting executed. + * + */ +static irqreturn_t most_irq_handler(int irq, void *_dev) +{ + struct hdm_i2c *dev = _dev; + + disable_irq_nosync(irq); + dev->rx.int_disabled = true; + schedule_delayed_work(&dev->rx.dwork, 0); + + return IRQ_HANDLED; +} + +/* + * i2c_probe - i2c probe handler + * @client: i2c client device structure + * @id: i2c client device id + * + * Return 0 on success, negative on failure. + * + * Register the i2c client device as a MOST interface + */ +static int i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct hdm_i2c *dev; + int ret, i; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + /* ID format: i2c--
*/ + snprintf(dev->name, sizeof(dev->name), "i2c-%d-%04x", + client->adapter->nr, client->addr); + + for (i = 0; i < NUM_CHANNELS; i++) { + dev->capabilities[i].data_type = MOST_CH_CONTROL; + dev->capabilities[i].num_buffers_packet = MAX_BUFFERS_CONTROL; + dev->capabilities[i].buffer_size_packet = MAX_BUF_SIZE_CONTROL; + } + dev->capabilities[CH_RX].direction = MOST_CH_RX; + dev->capabilities[CH_RX].name_suffix = "rx"; + dev->capabilities[CH_TX].direction = MOST_CH_TX; + dev->capabilities[CH_TX].name_suffix = "tx"; + + dev->most_iface.interface = ITYPE_I2C; + dev->most_iface.description = dev->name; + dev->most_iface.num_channels = NUM_CHANNELS; + dev->most_iface.channel_vector = dev->capabilities; + dev->most_iface.configure = configure_channel; + dev->most_iface.enqueue = enqueue; + dev->most_iface.poison_channel = poison_channel; + + INIT_LIST_HEAD(&dev->rx.list); + + INIT_DELAYED_WORK(&dev->rx.dwork, pending_rx_work); + + dev->client = client; + i2c_set_clientdata(client, dev); + + ret = most_register_interface(&dev->most_iface); + if (ret) { + pr_err("Failed to register i2c as a MOST interface\n"); + kfree(dev); + return ret; + } + + return 0; +} + +/* + * i2c_remove - i2c remove handler + * @client: i2c client device structure + * + * Return 0 on success. + * + * Unregister the i2c client device as a MOST interface + */ +static void i2c_remove(struct i2c_client *client) +{ + struct hdm_i2c *dev = i2c_get_clientdata(client); + + most_deregister_interface(&dev->most_iface); + kfree(dev); +} + +static const struct i2c_device_id i2c_id[] = { + { "most_i2c", 0 }, + { }, /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(i2c, i2c_id); + +static struct i2c_driver i2c_driver = { + .driver = { + .name = "hdm_i2c", + }, + .probe = i2c_probe, + .remove = i2c_remove, + .id_table = i2c_id, +}; + +module_i2c_driver(i2c_driver); + +MODULE_AUTHOR("Andrey Shvetsov "); +MODULE_DESCRIPTION("I2C Hardware Dependent Module"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/most/net/Kconfig b/drivers/staging/most/net/Kconfig new file mode 100644 index 000000000..ed8ac7e07 --- /dev/null +++ b/drivers/staging/most/net/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# MOST Networking configuration +# + +config MOST_NET + tristate "Net" + depends on NET + + help + Say Y here if you want to commumicate via a networking device. + + To compile this driver as a module, choose M here: the + module will be called most_net. diff --git a/drivers/staging/most/net/Makefile b/drivers/staging/most/net/Makefile new file mode 100644 index 000000000..1582c97eb --- /dev/null +++ b/drivers/staging/most/net/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_MOST_NET) += most_net.o + +most_net-objs := net.o diff --git a/drivers/staging/most/net/net.c b/drivers/staging/most/net/net.c new file mode 100644 index 000000000..1d1fe8bff --- /dev/null +++ b/drivers/staging/most/net/net.c @@ -0,0 +1,581 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * net.c - Networking component for Mostcore + * + * Copyright (C) 2015, Microchip Technology Germany II GmbH & Co. KG + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MEP_HDR_LEN 8 +#define MDP_HDR_LEN 16 +#define MAMAC_DATA_LEN (1024 - MDP_HDR_LEN) + +#define PMHL 5 + +#define PMS_TELID_UNSEGM_MAMAC 0x0A +#define PMS_FIFONO_MDP 0x01 +#define PMS_FIFONO_MEP 0x04 +#define PMS_MSGTYPE_DATA 0x04 +#define PMS_DEF_PRIO 0 +#define MEP_DEF_RETRY 15 + +#define PMS_FIFONO_MASK 0x07 +#define PMS_FIFONO_SHIFT 3 +#define PMS_RETRY_SHIFT 4 +#define PMS_TELID_MASK 0x0F +#define PMS_TELID_SHIFT 4 + +#define HB(value) ((u8)((u16)(value) >> 8)) +#define LB(value) ((u8)(value)) + +#define EXTRACT_BIT_SET(bitset_name, value) \ + (((value) >> bitset_name##_SHIFT) & bitset_name##_MASK) + +#define PMS_IS_MEP(buf, len) \ + ((len) > MEP_HDR_LEN && \ + EXTRACT_BIT_SET(PMS_FIFONO, (buf)[3]) == PMS_FIFONO_MEP) + +static inline bool pms_is_mamac(char *buf, u32 len) +{ + return (len > MDP_HDR_LEN && + EXTRACT_BIT_SET(PMS_FIFONO, buf[3]) == PMS_FIFONO_MDP && + EXTRACT_BIT_SET(PMS_TELID, buf[14]) == PMS_TELID_UNSEGM_MAMAC); +} + +struct net_dev_channel { + bool linked; + int ch_id; +}; + +struct net_dev_context { + struct most_interface *iface; + bool is_mamac; + struct net_device *dev; + struct net_dev_channel rx; + struct net_dev_channel tx; + struct list_head list; +}; + +static LIST_HEAD(net_devices); +static DEFINE_MUTEX(probe_disc_mt); /* ch->linked = true, most_nd_open */ +static DEFINE_SPINLOCK(list_lock); /* list_head, ch->linked = false, dev_hold */ +static struct most_component comp; + +static int skb_to_mamac(const struct sk_buff *skb, struct mbo *mbo) +{ + u8 *buff = mbo->virt_address; + static const u8 broadcast[] = { 0x03, 0xFF }; + const u8 *dest_addr = skb->data + 4; + const u8 *eth_type = skb->data + 12; + unsigned int payload_len = skb->len - ETH_HLEN; + unsigned int mdp_len = payload_len + MDP_HDR_LEN; + + if (mdp_len < skb->len) { + pr_err("drop: too large packet! (%u)\n", skb->len); + return -EINVAL; + } + + if (mbo->buffer_length < mdp_len) { + pr_err("drop: too small buffer! (%d for %d)\n", + mbo->buffer_length, mdp_len); + return -EINVAL; + } + + if (skb->len < ETH_HLEN) { + pr_err("drop: too small packet! (%d)\n", skb->len); + return -EINVAL; + } + + if (dest_addr[0] == 0xFF && dest_addr[1] == 0xFF) + dest_addr = broadcast; + + *buff++ = HB(mdp_len - 2); + *buff++ = LB(mdp_len - 2); + + *buff++ = PMHL; + *buff++ = (PMS_FIFONO_MDP << PMS_FIFONO_SHIFT) | PMS_MSGTYPE_DATA; + *buff++ = PMS_DEF_PRIO; + *buff++ = dest_addr[0]; + *buff++ = dest_addr[1]; + *buff++ = 0x00; + + *buff++ = HB(payload_len + 6); + *buff++ = LB(payload_len + 6); + + /* end of FPH here */ + + *buff++ = eth_type[0]; + *buff++ = eth_type[1]; + *buff++ = 0; + *buff++ = 0; + + *buff++ = PMS_TELID_UNSEGM_MAMAC << 4 | HB(payload_len); + *buff++ = LB(payload_len); + + memcpy(buff, skb->data + ETH_HLEN, payload_len); + mbo->buffer_length = mdp_len; + return 0; +} + +static int skb_to_mep(const struct sk_buff *skb, struct mbo *mbo) +{ + u8 *buff = mbo->virt_address; + unsigned int mep_len = skb->len + MEP_HDR_LEN; + + if (mep_len < skb->len) { + pr_err("drop: too large packet! (%u)\n", skb->len); + return -EINVAL; + } + + if (mbo->buffer_length < mep_len) { + pr_err("drop: too small buffer! (%d for %d)\n", + mbo->buffer_length, mep_len); + return -EINVAL; + } + + *buff++ = HB(mep_len - 2); + *buff++ = LB(mep_len - 2); + + *buff++ = PMHL; + *buff++ = (PMS_FIFONO_MEP << PMS_FIFONO_SHIFT) | PMS_MSGTYPE_DATA; + *buff++ = (MEP_DEF_RETRY << PMS_RETRY_SHIFT) | PMS_DEF_PRIO; + *buff++ = 0; + *buff++ = 0; + *buff++ = 0; + + memcpy(buff, skb->data, skb->len); + mbo->buffer_length = mep_len; + return 0; +} + +static int most_nd_set_mac_address(struct net_device *dev, void *p) +{ + struct net_dev_context *nd = netdev_priv(dev); + int err = eth_mac_addr(dev, p); + + if (err) + return err; + + nd->is_mamac = + (dev->dev_addr[0] == 0 && dev->dev_addr[1] == 0 && + dev->dev_addr[2] == 0 && dev->dev_addr[3] == 0); + + /* + * Set default MTU for the given packet type. + * It is still possible to change MTU using ip tools afterwards. + */ + dev->mtu = nd->is_mamac ? MAMAC_DATA_LEN : ETH_DATA_LEN; + + return 0; +} + +static void on_netinfo(struct most_interface *iface, + unsigned char link_stat, unsigned char *mac_addr); + +static int most_nd_open(struct net_device *dev) +{ + struct net_dev_context *nd = netdev_priv(dev); + int ret = 0; + + mutex_lock(&probe_disc_mt); + + if (most_start_channel(nd->iface, nd->rx.ch_id, &comp)) { + netdev_err(dev, "most_start_channel() failed\n"); + ret = -EBUSY; + goto unlock; + } + + if (most_start_channel(nd->iface, nd->tx.ch_id, &comp)) { + netdev_err(dev, "most_start_channel() failed\n"); + most_stop_channel(nd->iface, nd->rx.ch_id, &comp); + ret = -EBUSY; + goto unlock; + } + + netif_carrier_off(dev); + if (is_valid_ether_addr(dev->dev_addr)) + netif_dormant_off(dev); + else + netif_dormant_on(dev); + netif_wake_queue(dev); + if (nd->iface->request_netinfo) + nd->iface->request_netinfo(nd->iface, nd->tx.ch_id, on_netinfo); + +unlock: + mutex_unlock(&probe_disc_mt); + return ret; +} + +static int most_nd_stop(struct net_device *dev) +{ + struct net_dev_context *nd = netdev_priv(dev); + + netif_stop_queue(dev); + if (nd->iface->request_netinfo) + nd->iface->request_netinfo(nd->iface, nd->tx.ch_id, NULL); + most_stop_channel(nd->iface, nd->rx.ch_id, &comp); + most_stop_channel(nd->iface, nd->tx.ch_id, &comp); + + return 0; +} + +static netdev_tx_t most_nd_start_xmit(struct sk_buff *skb, + struct net_device *dev) +{ + struct net_dev_context *nd = netdev_priv(dev); + struct mbo *mbo; + int ret; + + mbo = most_get_mbo(nd->iface, nd->tx.ch_id, &comp); + + if (!mbo) { + netif_stop_queue(dev); + dev->stats.tx_fifo_errors++; + return NETDEV_TX_BUSY; + } + + if (nd->is_mamac) + ret = skb_to_mamac(skb, mbo); + else + ret = skb_to_mep(skb, mbo); + + if (ret) { + most_put_mbo(mbo); + dev->stats.tx_dropped++; + kfree_skb(skb); + return NETDEV_TX_OK; + } + + most_submit_mbo(mbo); + dev->stats.tx_packets++; + dev->stats.tx_bytes += skb->len; + kfree_skb(skb); + return NETDEV_TX_OK; +} + +static const struct net_device_ops most_nd_ops = { + .ndo_open = most_nd_open, + .ndo_stop = most_nd_stop, + .ndo_start_xmit = most_nd_start_xmit, + .ndo_set_mac_address = most_nd_set_mac_address, +}; + +static void most_nd_setup(struct net_device *dev) +{ + ether_setup(dev); + dev->netdev_ops = &most_nd_ops; +} + +static struct net_dev_context *get_net_dev(struct most_interface *iface) +{ + struct net_dev_context *nd; + + list_for_each_entry(nd, &net_devices, list) + if (nd->iface == iface) + return nd; + return NULL; +} + +static struct net_dev_context *get_net_dev_hold(struct most_interface *iface) +{ + struct net_dev_context *nd; + unsigned long flags; + + spin_lock_irqsave(&list_lock, flags); + nd = get_net_dev(iface); + if (nd && nd->rx.linked && nd->tx.linked) + dev_hold(nd->dev); + else + nd = NULL; + spin_unlock_irqrestore(&list_lock, flags); + return nd; +} + +static int comp_probe_channel(struct most_interface *iface, int channel_idx, + struct most_channel_config *ccfg, char *name, + char *args) +{ + struct net_dev_context *nd; + struct net_dev_channel *ch; + struct net_device *dev; + unsigned long flags; + int ret = 0; + + if (!iface) + return -EINVAL; + + if (ccfg->data_type != MOST_CH_ASYNC) + return -EINVAL; + + mutex_lock(&probe_disc_mt); + nd = get_net_dev(iface); + if (!nd) { + dev = alloc_netdev(sizeof(struct net_dev_context), "meth%d", + NET_NAME_UNKNOWN, most_nd_setup); + if (!dev) { + ret = -ENOMEM; + goto unlock; + } + + nd = netdev_priv(dev); + nd->iface = iface; + nd->dev = dev; + + spin_lock_irqsave(&list_lock, flags); + list_add(&nd->list, &net_devices); + spin_unlock_irqrestore(&list_lock, flags); + + ch = ccfg->direction == MOST_CH_TX ? &nd->tx : &nd->rx; + } else { + ch = ccfg->direction == MOST_CH_TX ? &nd->tx : &nd->rx; + if (ch->linked) { + pr_err("direction is allocated\n"); + ret = -EINVAL; + goto unlock; + } + + if (register_netdev(nd->dev)) { + pr_err("register_netdev() failed\n"); + ret = -EINVAL; + goto unlock; + } + } + ch->ch_id = channel_idx; + ch->linked = true; + +unlock: + mutex_unlock(&probe_disc_mt); + return ret; +} + +static int comp_disconnect_channel(struct most_interface *iface, + int channel_idx) +{ + struct net_dev_context *nd; + struct net_dev_channel *ch; + unsigned long flags; + int ret = 0; + + mutex_lock(&probe_disc_mt); + nd = get_net_dev(iface); + if (!nd) { + ret = -EINVAL; + goto unlock; + } + + if (nd->rx.linked && channel_idx == nd->rx.ch_id) { + ch = &nd->rx; + } else if (nd->tx.linked && channel_idx == nd->tx.ch_id) { + ch = &nd->tx; + } else { + ret = -EINVAL; + goto unlock; + } + + if (nd->rx.linked && nd->tx.linked) { + spin_lock_irqsave(&list_lock, flags); + ch->linked = false; + spin_unlock_irqrestore(&list_lock, flags); + + /* + * do not call most_stop_channel() here, because channels are + * going to be closed in ndo_stop() after unregister_netdev() + */ + unregister_netdev(nd->dev); + } else { + spin_lock_irqsave(&list_lock, flags); + list_del(&nd->list); + spin_unlock_irqrestore(&list_lock, flags); + + free_netdev(nd->dev); + } + +unlock: + mutex_unlock(&probe_disc_mt); + return ret; +} + +static int comp_resume_tx_channel(struct most_interface *iface, + int channel_idx) +{ + struct net_dev_context *nd; + + nd = get_net_dev_hold(iface); + if (!nd) + return 0; + + if (nd->tx.ch_id != channel_idx) + goto put_nd; + + netif_wake_queue(nd->dev); + +put_nd: + dev_put(nd->dev); + return 0; +} + +static int comp_rx_data(struct mbo *mbo) +{ + const u32 zero = 0; + struct net_dev_context *nd; + char *buf = mbo->virt_address; + u32 len = mbo->processed_length; + struct sk_buff *skb; + struct net_device *dev; + unsigned int skb_len; + int ret = 0; + + nd = get_net_dev_hold(mbo->ifp); + if (!nd) + return -EIO; + + if (nd->rx.ch_id != mbo->hdm_channel_id) { + ret = -EIO; + goto put_nd; + } + + dev = nd->dev; + + if (nd->is_mamac) { + if (!pms_is_mamac(buf, len)) { + ret = -EIO; + goto put_nd; + } + + skb = dev_alloc_skb(len - MDP_HDR_LEN + 2 * ETH_ALEN + 2); + } else { + if (!PMS_IS_MEP(buf, len)) { + ret = -EIO; + goto put_nd; + } + + skb = dev_alloc_skb(len - MEP_HDR_LEN); + } + + if (!skb) { + dev->stats.rx_dropped++; + pr_err_once("drop packet: no memory for skb\n"); + goto out; + } + + skb->dev = dev; + + if (nd->is_mamac) { + /* dest */ + ether_addr_copy(skb_put(skb, ETH_ALEN), dev->dev_addr); + + /* src */ + skb_put_data(skb, &zero, 4); + skb_put_data(skb, buf + 5, 2); + + /* eth type */ + skb_put_data(skb, buf + 10, 2); + + buf += MDP_HDR_LEN; + len -= MDP_HDR_LEN; + } else { + buf += MEP_HDR_LEN; + len -= MEP_HDR_LEN; + } + + skb_put_data(skb, buf, len); + skb->protocol = eth_type_trans(skb, dev); + skb_len = skb->len; + if (netif_rx(skb) == NET_RX_SUCCESS) { + dev->stats.rx_packets++; + dev->stats.rx_bytes += skb_len; + } else { + dev->stats.rx_dropped++; + } + +out: + most_put_mbo(mbo); + +put_nd: + dev_put(nd->dev); + return ret; +} + +static struct most_component comp = { + .mod = THIS_MODULE, + .name = "net", + .probe_channel = comp_probe_channel, + .disconnect_channel = comp_disconnect_channel, + .tx_completion = comp_resume_tx_channel, + .rx_completion = comp_rx_data, +}; + +static int __init most_net_init(void) +{ + int err; + + err = most_register_component(&comp); + if (err) + return err; + err = most_register_configfs_subsys(&comp); + if (err) { + most_deregister_component(&comp); + return err; + } + return 0; +} + +static void __exit most_net_exit(void) +{ + most_deregister_configfs_subsys(&comp); + most_deregister_component(&comp); +} + +/** + * on_netinfo - callback for HDM to be informed about HW's MAC + * @iface: most interface instance + * @link_stat: link status + * @mac_addr: MAC address + */ +static void on_netinfo(struct most_interface *iface, + unsigned char link_stat, unsigned char *mac_addr) +{ + struct net_dev_context *nd; + struct net_device *dev; + const u8 *m = mac_addr; + + nd = get_net_dev_hold(iface); + if (!nd) + return; + + dev = nd->dev; + + if (link_stat) + netif_carrier_on(dev); + else + netif_carrier_off(dev); + + if (m && is_valid_ether_addr(m)) { + if (!is_valid_ether_addr(dev->dev_addr)) { + netdev_info(dev, "set mac %pM\n", m); + eth_hw_addr_set(dev, m); + netif_dormant_off(dev); + } else if (!ether_addr_equal(dev->dev_addr, m)) { + netdev_warn(dev, "reject mac %pM\n", m); + } + } + + dev_put(nd->dev); +} + +module_init(most_net_init); +module_exit(most_net_exit); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Andrey Shvetsov "); +MODULE_DESCRIPTION("Networking Component Module for Mostcore"); diff --git a/drivers/staging/most/video/Kconfig b/drivers/staging/most/video/Kconfig new file mode 100644 index 000000000..e16cc5e10 --- /dev/null +++ b/drivers/staging/most/video/Kconfig @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# MOST V4L2 configuration +# + +config MOST_VIDEO + tristate "Video" + depends on VIDEO_DEV + help + Say Y here if you want to commumicate via Video 4 Linux. + + To compile this driver as a module, choose M here: the + module will be called most_video. diff --git a/drivers/staging/most/video/Makefile b/drivers/staging/most/video/Makefile new file mode 100644 index 000000000..856175fec --- /dev/null +++ b/drivers/staging/most/video/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_MOST_VIDEO) += most_video.o + +most_video-objs := video.o diff --git a/drivers/staging/most/video/video.c b/drivers/staging/most/video/video.c new file mode 100644 index 000000000..ffa97ef21 --- /dev/null +++ b/drivers/staging/most/video/video.c @@ -0,0 +1,584 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * video.c - V4L2 component for Mostcore + * + * Copyright (C) 2015, Microchip Technology Germany II GmbH & Co. KG + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define V4L2_CMP_MAX_INPUT 1 + +static struct most_component comp; + +struct most_video_dev { + struct most_interface *iface; + int ch_idx; + struct list_head list; + bool mute; + + struct list_head pending_mbos; + spinlock_t list_lock; + + struct v4l2_device v4l2_dev; + atomic_t access_ref; + struct video_device *vdev; + unsigned int ctrl_input; + + struct mutex lock; + + wait_queue_head_t wait_data; +}; + +struct comp_fh { + /* must be the first field of this struct! */ + struct v4l2_fh fh; + struct most_video_dev *mdev; + u32 offs; +}; + +static LIST_HEAD(video_devices); +static DEFINE_SPINLOCK(list_lock); + +static inline bool data_ready(struct most_video_dev *mdev) +{ + return !list_empty(&mdev->pending_mbos); +} + +static inline struct mbo *get_top_mbo(struct most_video_dev *mdev) +{ + return list_first_entry(&mdev->pending_mbos, struct mbo, list); +} + +static int comp_vdev_open(struct file *filp) +{ + int ret; + struct video_device *vdev = video_devdata(filp); + struct most_video_dev *mdev = video_drvdata(filp); + struct comp_fh *fh; + + switch (vdev->vfl_type) { + case VFL_TYPE_VIDEO: + break; + default: + return -EINVAL; + } + + fh = kzalloc(sizeof(*fh), GFP_KERNEL); + if (!fh) + return -ENOMEM; + + if (!atomic_inc_and_test(&mdev->access_ref)) { + v4l2_err(&mdev->v4l2_dev, "too many clients\n"); + ret = -EBUSY; + goto err_dec; + } + + fh->mdev = mdev; + v4l2_fh_init(&fh->fh, vdev); + filp->private_data = fh; + + v4l2_fh_add(&fh->fh); + + ret = most_start_channel(mdev->iface, mdev->ch_idx, &comp); + if (ret) { + v4l2_err(&mdev->v4l2_dev, "most_start_channel() failed\n"); + goto err_rm; + } + + return 0; + +err_rm: + v4l2_fh_del(&fh->fh); + v4l2_fh_exit(&fh->fh); + +err_dec: + atomic_dec(&mdev->access_ref); + kfree(fh); + return ret; +} + +static int comp_vdev_close(struct file *filp) +{ + struct comp_fh *fh = filp->private_data; + struct most_video_dev *mdev = fh->mdev; + struct mbo *mbo, *tmp; + + /* + * We need to put MBOs back before we call most_stop_channel() + * to deallocate MBOs. + * From the other hand mostcore still calling rx_completion() + * to deliver MBOs until most_stop_channel() is called. + * Use mute to work around this issue. + * This must be implemented in core. + */ + + spin_lock_irq(&mdev->list_lock); + mdev->mute = true; + list_for_each_entry_safe(mbo, tmp, &mdev->pending_mbos, list) { + list_del(&mbo->list); + spin_unlock_irq(&mdev->list_lock); + most_put_mbo(mbo); + spin_lock_irq(&mdev->list_lock); + } + spin_unlock_irq(&mdev->list_lock); + most_stop_channel(mdev->iface, mdev->ch_idx, &comp); + mdev->mute = false; + + v4l2_fh_del(&fh->fh); + v4l2_fh_exit(&fh->fh); + + atomic_dec(&mdev->access_ref); + kfree(fh); + return 0; +} + +static ssize_t comp_vdev_read(struct file *filp, char __user *buf, + size_t count, loff_t *pos) +{ + struct comp_fh *fh = filp->private_data; + struct most_video_dev *mdev = fh->mdev; + int ret = 0; + + if (*pos) + return -ESPIPE; + + if (!mdev) + return -ENODEV; + + /* wait for the first buffer */ + if (!(filp->f_flags & O_NONBLOCK)) { + if (wait_event_interruptible(mdev->wait_data, data_ready(mdev))) + return -ERESTARTSYS; + } + + if (!data_ready(mdev)) + return -EAGAIN; + + while (count > 0 && data_ready(mdev)) { + struct mbo *const mbo = get_top_mbo(mdev); + int const rem = mbo->processed_length - fh->offs; + int const cnt = rem < count ? rem : count; + + if (copy_to_user(buf, mbo->virt_address + fh->offs, cnt)) { + v4l2_err(&mdev->v4l2_dev, "read: copy_to_user failed\n"); + if (!ret) + ret = -EFAULT; + return ret; + } + + fh->offs += cnt; + count -= cnt; + buf += cnt; + ret += cnt; + + if (cnt >= rem) { + fh->offs = 0; + spin_lock_irq(&mdev->list_lock); + list_del(&mbo->list); + spin_unlock_irq(&mdev->list_lock); + most_put_mbo(mbo); + } + } + return ret; +} + +static __poll_t comp_vdev_poll(struct file *filp, poll_table *wait) +{ + struct comp_fh *fh = filp->private_data; + struct most_video_dev *mdev = fh->mdev; + __poll_t mask = 0; + + /* only wait if no data is available */ + if (!data_ready(mdev)) + poll_wait(filp, &mdev->wait_data, wait); + if (data_ready(mdev)) + mask |= EPOLLIN | EPOLLRDNORM; + + return mask; +} + +static void comp_set_format_struct(struct v4l2_format *f) +{ + f->fmt.pix.width = 8; + f->fmt.pix.height = 8; + f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG; + f->fmt.pix.bytesperline = 0; + f->fmt.pix.sizeimage = 188 * 2; + f->fmt.pix.colorspace = V4L2_COLORSPACE_REC709; + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.priv = 0; +} + +static int comp_set_format(struct most_video_dev *mdev, unsigned int cmd, + struct v4l2_format *format) +{ + if (format->fmt.pix.pixelformat != V4L2_PIX_FMT_MPEG) + return -EINVAL; + + if (cmd == VIDIOC_TRY_FMT) + return 0; + + comp_set_format_struct(format); + + return 0; +} + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct comp_fh *fh = priv; + struct most_video_dev *mdev = fh->mdev; + + strscpy(cap->driver, "v4l2_component", sizeof(cap->driver)); + strscpy(cap->card, "MOST", sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), + "%s", mdev->iface->description); + return 0; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index) + return -EINVAL; + + strscpy(f->description, "MPEG", sizeof(f->description)); + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + f->flags = V4L2_FMT_FLAG_COMPRESSED; + f->pixelformat = V4L2_PIX_FMT_MPEG; + + return 0; +} + +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + comp_set_format_struct(f); + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct comp_fh *fh = priv; + struct most_video_dev *mdev = fh->mdev; + + return comp_set_format(mdev, VIDIOC_TRY_FMT, f); +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct comp_fh *fh = priv; + struct most_video_dev *mdev = fh->mdev; + + return comp_set_format(mdev, VIDIOC_S_FMT, f); +} + +static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *norm) +{ + *norm = V4L2_STD_UNKNOWN; + return 0; +} + +static int vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *input) +{ + struct comp_fh *fh = priv; + struct most_video_dev *mdev = fh->mdev; + + if (input->index >= V4L2_CMP_MAX_INPUT) + return -EINVAL; + + strscpy(input->name, "MOST Video", sizeof(input->name)); + input->type |= V4L2_INPUT_TYPE_CAMERA; + input->audioset = 0; + + input->std = mdev->vdev->tvnorms; + + return 0; +} + +static int vidioc_g_input(struct file *file, void *priv, unsigned int *i) +{ + struct comp_fh *fh = priv; + struct most_video_dev *mdev = fh->mdev; + *i = mdev->ctrl_input; + return 0; +} + +static int vidioc_s_input(struct file *file, void *priv, unsigned int index) +{ + struct comp_fh *fh = priv; + struct most_video_dev *mdev = fh->mdev; + + if (index >= V4L2_CMP_MAX_INPUT) + return -EINVAL; + mdev->ctrl_input = index; + return 0; +} + +static const struct v4l2_file_operations comp_fops = { + .owner = THIS_MODULE, + .open = comp_vdev_open, + .release = comp_vdev_close, + .read = comp_vdev_read, + .poll = comp_vdev_poll, + .unlocked_ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops video_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + .vidioc_g_std = vidioc_g_std, + .vidioc_enum_input = vidioc_enum_input, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, +}; + +static const struct video_device comp_videodev_template = { + .fops = &comp_fops, + .release = video_device_release, + .ioctl_ops = &video_ioctl_ops, + .tvnorms = V4L2_STD_UNKNOWN, + .device_caps = V4L2_CAP_READWRITE | V4L2_CAP_VIDEO_CAPTURE, +}; + +/**************************************************************************/ + +static struct most_video_dev *get_comp_dev( + struct most_interface *iface, int channel_idx) +{ + struct most_video_dev *mdev; + unsigned long flags; + + spin_lock_irqsave(&list_lock, flags); + list_for_each_entry(mdev, &video_devices, list) { + if (mdev->iface == iface && mdev->ch_idx == channel_idx) { + spin_unlock_irqrestore(&list_lock, flags); + return mdev; + } + } + spin_unlock_irqrestore(&list_lock, flags); + return NULL; +} + +static int comp_rx_data(struct mbo *mbo) +{ + unsigned long flags; + struct most_video_dev *mdev = + get_comp_dev(mbo->ifp, mbo->hdm_channel_id); + + if (!mdev) + return -EIO; + + spin_lock_irqsave(&mdev->list_lock, flags); + if (unlikely(mdev->mute)) { + spin_unlock_irqrestore(&mdev->list_lock, flags); + return -EIO; + } + + list_add_tail(&mbo->list, &mdev->pending_mbos); + spin_unlock_irqrestore(&mdev->list_lock, flags); + wake_up_interruptible(&mdev->wait_data); + return 0; +} + +static int comp_register_videodev(struct most_video_dev *mdev) +{ + int ret; + + init_waitqueue_head(&mdev->wait_data); + + /* allocate and fill v4l2 video struct */ + mdev->vdev = video_device_alloc(); + if (!mdev->vdev) + return -ENOMEM; + + /* Fill the video capture device struct */ + *mdev->vdev = comp_videodev_template; + mdev->vdev->v4l2_dev = &mdev->v4l2_dev; + mdev->vdev->lock = &mdev->lock; + snprintf(mdev->vdev->name, sizeof(mdev->vdev->name), "MOST: %s", + mdev->v4l2_dev.name); + + /* Register the v4l2 device */ + video_set_drvdata(mdev->vdev, mdev); + ret = video_register_device(mdev->vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + v4l2_err(&mdev->v4l2_dev, "video_register_device failed (%d)\n", + ret); + video_device_release(mdev->vdev); + } + + return ret; +} + +static void comp_unregister_videodev(struct most_video_dev *mdev) +{ + video_unregister_device(mdev->vdev); +} + +static void comp_v4l2_dev_release(struct v4l2_device *v4l2_dev) +{ + struct most_video_dev *mdev = + container_of(v4l2_dev, struct most_video_dev, v4l2_dev); + + v4l2_device_unregister(v4l2_dev); + kfree(mdev); +} + +static int comp_probe_channel(struct most_interface *iface, int channel_idx, + struct most_channel_config *ccfg, char *name, + char *args) +{ + int ret; + struct most_video_dev *mdev = get_comp_dev(iface, channel_idx); + + if (mdev) { + pr_err("channel already linked\n"); + return -EEXIST; + } + + if (ccfg->direction != MOST_CH_RX) { + pr_err("wrong direction, expect rx\n"); + return -EINVAL; + } + + if (ccfg->data_type != MOST_CH_SYNC && + ccfg->data_type != MOST_CH_ISOC) { + pr_err("wrong channel type, expect sync or isoc\n"); + return -EINVAL; + } + + mdev = kzalloc(sizeof(*mdev), GFP_KERNEL); + if (!mdev) + return -ENOMEM; + + mutex_init(&mdev->lock); + atomic_set(&mdev->access_ref, -1); + spin_lock_init(&mdev->list_lock); + INIT_LIST_HEAD(&mdev->pending_mbos); + mdev->iface = iface; + mdev->ch_idx = channel_idx; + mdev->v4l2_dev.release = comp_v4l2_dev_release; + + /* Create the v4l2_device */ + strscpy(mdev->v4l2_dev.name, name, sizeof(mdev->v4l2_dev.name)); + ret = v4l2_device_register(NULL, &mdev->v4l2_dev); + if (ret) { + pr_err("v4l2_device_register() failed\n"); + kfree(mdev); + return ret; + } + + ret = comp_register_videodev(mdev); + if (ret) + goto err_unreg; + + spin_lock_irq(&list_lock); + list_add(&mdev->list, &video_devices); + spin_unlock_irq(&list_lock); + return 0; + +err_unreg: + v4l2_device_disconnect(&mdev->v4l2_dev); + v4l2_device_put(&mdev->v4l2_dev); + return ret; +} + +static int comp_disconnect_channel(struct most_interface *iface, + int channel_idx) +{ + struct most_video_dev *mdev = get_comp_dev(iface, channel_idx); + + if (!mdev) { + pr_err("no such channel is linked\n"); + return -ENOENT; + } + + spin_lock_irq(&list_lock); + list_del(&mdev->list); + spin_unlock_irq(&list_lock); + + comp_unregister_videodev(mdev); + v4l2_device_disconnect(&mdev->v4l2_dev); + v4l2_device_put(&mdev->v4l2_dev); + return 0; +} + +static struct most_component comp = { + .mod = THIS_MODULE, + .name = "video", + .probe_channel = comp_probe_channel, + .disconnect_channel = comp_disconnect_channel, + .rx_completion = comp_rx_data, +}; + +static int __init comp_init(void) +{ + int err; + + err = most_register_component(&comp); + if (err) + return err; + err = most_register_configfs_subsys(&comp); + if (err) { + most_deregister_component(&comp); + return err; + } + return 0; +} + +static void __exit comp_exit(void) +{ + struct most_video_dev *mdev, *tmp; + + /* + * As the mostcore currently doesn't call disconnect_channel() + * for linked channels while we call most_deregister_component() + * we simulate this call here. + * This must be fixed in core. + */ + spin_lock_irq(&list_lock); + list_for_each_entry_safe(mdev, tmp, &video_devices, list) { + list_del(&mdev->list); + spin_unlock_irq(&list_lock); + + comp_unregister_videodev(mdev); + v4l2_device_disconnect(&mdev->v4l2_dev); + v4l2_device_put(&mdev->v4l2_dev); + spin_lock_irq(&list_lock); + } + spin_unlock_irq(&list_lock); + + most_deregister_configfs_subsys(&comp); + most_deregister_component(&comp); + BUG_ON(!list_empty(&video_devices)); +} + +module_init(comp_init); +module_exit(comp_exit); + +MODULE_DESCRIPTION("V4L2 Component Module for Mostcore"); +MODULE_AUTHOR("Andrey Shvetsov "); +MODULE_LICENSE("GPL"); -- cgit v1.2.3