diff options
author | 2023-02-21 18:24:12 -0800 | |
---|---|---|
committer | 2023-02-21 18:24:12 -0800 | |
commit | 5b7c4cabbb65f5c469464da6c5f614cbd7f730f2 (patch) | |
tree | cc5c2d0a898769fd59549594fedb3ee6f84e59a0 /sound/soc/sprd/sprd-mcdt.c | |
download | linux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.tar.gz linux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.zip |
Merge tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-nextgrafted
Pull networking updates from Jakub Kicinski:
"Core:
- Add dedicated kmem_cache for typical/small skb->head, avoid having
to access struct page at kfree time, and improve memory use.
- Introduce sysctl to set default RPS configuration for new netdevs.
- Define Netlink protocol specification format which can be used to
describe messages used by each family and auto-generate parsers.
Add tools for generating kernel data structures and uAPI headers.
- Expose all net/core sysctls inside netns.
- Remove 4s sleep in netpoll if carrier is instantly detected on
boot.
- Add configurable limit of MDB entries per port, and port-vlan.
- Continue populating drop reasons throughout the stack.
- Retire a handful of legacy Qdiscs and classifiers.
Protocols:
- Support IPv4 big TCP (TSO frames larger than 64kB).
- Add IP_LOCAL_PORT_RANGE socket option, to control local port range
on socket by socket basis.
- Track and report in procfs number of MPTCP sockets used.
- Support mixing IPv4 and IPv6 flows in the in-kernel MPTCP path
manager.
- IPv6: don't check net.ipv6.route.max_size and rely on garbage
collection to free memory (similarly to IPv4).
- Support Penultimate Segment Pop (PSP) flavor in SRv6 (RFC8986).
- ICMP: add per-rate limit counters.
- Add support for user scanning requests in ieee802154.
- Remove static WEP support.
- Support minimal Wi-Fi 7 Extremely High Throughput (EHT) rate
reporting.
- WiFi 7 EHT channel puncturing support (client & AP).
BPF:
- Add a rbtree data structure following the "next-gen data structure"
precedent set by recently added linked list, that is, by using
kfunc + kptr instead of adding a new BPF map type.
- Expose XDP hints via kfuncs with initial support for RX hash and
timestamp metadata.
- Add BPF_F_NO_TUNNEL_KEY extension to bpf_skb_set_tunnel_key to
better support decap on GRE tunnel devices not operating in collect
metadata.
- Improve x86 JIT's codegen for PROBE_MEM runtime error checks.
- Remove the need for trace_printk_lock for bpf_trace_printk and
bpf_trace_vprintk helpers.
- Extend libbpf's bpf_tracing.h support for tracing arguments of
kprobes/uprobes and syscall as a special case.
- Significantly reduce the search time for module symbols by
livepatch and BPF.
- Enable cpumasks to be used as kptrs, which is useful for tracing
programs tracking which tasks end up running on which CPUs in
different time intervals.
- Add support for BPF trampoline on s390x and riscv64.
- Add capability to export the XDP features supported by the NIC.
- Add __bpf_kfunc tag for marking kernel functions as kfuncs.
- Add cgroup.memory=nobpf kernel parameter option to disable BPF
memory accounting for container environments.
Netfilter:
- Remove the CLUSTERIP target. It has been marked as obsolete for
years, and we still have WARN splats wrt races of the out-of-band
/proc interface installed by this target.
- Add 'destroy' commands to nf_tables. They are identical to the
existing 'delete' commands, but do not return an error if the
referenced object (set, chain, rule...) did not exist.
Driver API:
- Improve cpumask_local_spread() locality to help NICs set the right
IRQ affinity on AMD platforms.
- Separate C22 and C45 MDIO bus transactions more clearly.
- Introduce new DCB table to control DSCP rewrite on egress.
- Support configuration of Physical Layer Collision Avoidance (PLCA)
Reconciliation Sublayer (RS) (802.3cg-2019). Modern version of
shared medium Ethernet.
- Support for MAC Merge layer (IEEE 802.3-2018 clause 99). Allowing
preemption of low priority frames by high priority frames.
- Add support for controlling MACSec offload using netlink SET.
- Rework devlink instance refcounts to allow registration and
de-registration under the instance lock. Split the code into
multiple files, drop some of the unnecessarily granular locks and
factor out common parts of netlink operation handling.
- Add TX frame aggregation parameters (for USB drivers).
- Add a new attr TCA_EXT_WARN_MSG to report TC (offload) warning
messages with notifications for debug.
- Allow offloading of UDP NEW connections via act_ct.
- Add support for per action HW stats in TC.
- Support hardware miss to TC action (continue processing in SW from
a specific point in the action chain).
- Warn if old Wireless Extension user space interface is used with
modern cfg80211/mac80211 drivers. Do not support Wireless
Extensions for Wi-Fi 7 devices at all. Everyone should switch to
using nl80211 interface instead.
- Improve the CAN bit timing configuration. Use extack to return
error messages directly to user space, update the SJW handling,
including the definition of a new default value that will benefit
CAN-FD controllers, by increasing their oscillator tolerance.
New hardware / drivers:
- Ethernet:
- nVidia BlueField-3 support (control traffic driver)
- Ethernet support for imx93 SoCs
- Motorcomm yt8531 gigabit Ethernet PHY
- onsemi NCN26000 10BASE-T1S PHY (with support for PLCA)
- Microchip LAN8841 PHY (incl. cable diagnostics and PTP)
- Amlogic gxl MDIO mux
- WiFi:
- RealTek RTL8188EU (rtl8xxxu)
- Qualcomm Wi-Fi 7 devices (ath12k)
- CAN:
- Renesas R-Car V4H
Drivers:
- Bluetooth:
- Set Per Platform Antenna Gain (PPAG) for Intel controllers.
- Ethernet NICs:
- Intel (1G, igc):
- support TSN / Qbv / packet scheduling features of i226 model
- Intel (100G, ice):
- use GNSS subsystem instead of TTY
- multi-buffer XDP support
- extend support for GPIO pins to E823 devices
- nVidia/Mellanox:
- update the shared buffer configuration on PFC commands
- implement PTP adjphase function for HW offset control
- TC support for Geneve and GRE with VF tunnel offload
- more efficient crypto key management method
- multi-port eswitch support
- Netronome/Corigine:
- add DCB IEEE support
- support IPsec offloading for NFP3800
- Freescale/NXP (enetc):
- support XDP_REDIRECT for XDP non-linear buffers
- improve reconfig, avoid link flap and waiting for idle
- support MAC Merge layer
- Other NICs:
- sfc/ef100: add basic devlink support for ef100
- ionic: rx_push mode operation (writing descriptors via MMIO)
- bnxt: use the auxiliary bus abstraction for RDMA
- r8169: disable ASPM and reset bus in case of tx timeout
- cpsw: support QSGMII mode for J721e CPSW9G
- cpts: support pulse-per-second output
- ngbe: add an mdio bus driver
- usbnet: optimize usbnet_bh() by avoiding unnecessary queuing
- r8152: handle devices with FW with NCM support
- amd-xgbe: support 10Mbps, 2.5GbE speeds and rx-adaptation
- virtio-net: support multi buffer XDP
- virtio/vsock: replace virtio_vsock_pkt with sk_buff
- tsnep: XDP support
- Ethernet high-speed switches:
- nVidia/Mellanox (mlxsw):
- add support for latency TLV (in FW control messages)
- Microchip (sparx5):
- separate explicit and implicit traffic forwarding rules, make
the implicit rules always active
- add support for egress DSCP rewrite
- IS0 VCAP support (Ingress Classification)
- IS2 VCAP filters (protos, L3 addrs, L4 ports, flags, ToS
etc.)
- ES2 VCAP support (Egress Access Control)
- support for Per-Stream Filtering and Policing (802.1Q,
8.6.5.1)
- Ethernet embedded switches:
- Marvell (mv88e6xxx):
- add MAB (port auth) offload support
- enable PTP receive for mv88e6390
- NXP (ocelot):
- support MAC Merge layer
- support for the the vsc7512 internal copper phys
- Microchip:
- lan9303: convert to PHYLINK
- lan966x: support TC flower filter statistics
- lan937x: PTP support for KSZ9563/KSZ8563 and LAN937x
- lan937x: support Credit Based Shaper configuration
- ksz9477: support Energy Efficient Ethernet
- other:
- qca8k: convert to regmap read/write API, use bulk operations
- rswitch: Improve TX timestamp accuracy
- Intel WiFi (iwlwifi):
- EHT (Wi-Fi 7) rate reporting
- STEP equalizer support: transfer some STEP (connection to radio
on platforms with integrated wifi) related parameters from the
BIOS to the firmware.
- Qualcomm 802.11ax WiFi (ath11k):
- IPQ5018 support
- Fine Timing Measurement (FTM) responder role support
- channel 177 support
- MediaTek WiFi (mt76):
- per-PHY LED support
- mt7996: EHT (Wi-Fi 7) support
- Wireless Ethernet Dispatch (WED) reset support
- switch to using page pool allocator
- RealTek WiFi (rtw89):
- support new version of Bluetooth co-existance
- Mobile:
- rmnet: support TX aggregation"
* tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next: (1872 commits)
page_pool: add a comment explaining the fragment counter usage
net: ethtool: fix __ethtool_dev_mm_supported() implementation
ethtool: pse-pd: Fix double word in comments
xsk: add linux/vmalloc.h to xsk.c
sefltests: netdevsim: wait for devlink instance after netns removal
selftest: fib_tests: Always cleanup before exit
net/mlx5e: Align IPsec ASO result memory to be as required by hardware
net/mlx5e: TC, Set CT miss to the specific ct action instance
net/mlx5e: Rename CHAIN_TO_REG to MAPPED_OBJ_TO_REG
net/mlx5: Refactor tc miss handling to a single function
net/mlx5: Kconfig: Make tc offload depend on tc skb extension
net/sched: flower: Support hardware miss to tc action
net/sched: flower: Move filter handle initialization earlier
net/sched: cls_api: Support hardware miss to tc action
net/sched: Rename user cookie and act cookie
sfc: fix builds without CONFIG_RTC_LIB
sfc: clean up some inconsistent indentings
net/mlx4_en: Introduce flexible array to silence overflow warning
net: lan966x: Fix possible deadlock inside PTP
net/ulp: Remove redundant ->clone() test in inet_clone_ulp().
...
Diffstat (limited to 'sound/soc/sprd/sprd-mcdt.c')
-rw-r--r-- | sound/soc/sprd/sprd-mcdt.c | 1008 |
1 files changed, 1008 insertions, 0 deletions
diff --git a/sound/soc/sprd/sprd-mcdt.c b/sound/soc/sprd/sprd-mcdt.c new file mode 100644 index 000000000..f6a55fa60 --- /dev/null +++ b/sound/soc/sprd/sprd-mcdt.c @@ -0,0 +1,1008 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2019 Spreadtrum Communications Inc. + +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> + +#include "sprd-mcdt.h" + +/* MCDT registers definition */ +#define MCDT_CH0_TXD 0x0 +#define MCDT_CH0_RXD 0x28 +#define MCDT_DAC0_WTMK 0x60 +#define MCDT_ADC0_WTMK 0x88 +#define MCDT_DMA_EN 0xb0 + +#define MCDT_INT_EN0 0xb4 +#define MCDT_INT_EN1 0xb8 +#define MCDT_INT_EN2 0xbc + +#define MCDT_INT_CLR0 0xc0 +#define MCDT_INT_CLR1 0xc4 +#define MCDT_INT_CLR2 0xc8 + +#define MCDT_INT_RAW1 0xcc +#define MCDT_INT_RAW2 0xd0 +#define MCDT_INT_RAW3 0xd4 + +#define MCDT_INT_MSK1 0xd8 +#define MCDT_INT_MSK2 0xdc +#define MCDT_INT_MSK3 0xe0 + +#define MCDT_DAC0_FIFO_ADDR_ST 0xe4 +#define MCDT_ADC0_FIFO_ADDR_ST 0xe8 + +#define MCDT_CH_FIFO_ST0 0x134 +#define MCDT_CH_FIFO_ST1 0x138 +#define MCDT_CH_FIFO_ST2 0x13c + +#define MCDT_INT_MSK_CFG0 0x140 +#define MCDT_INT_MSK_CFG1 0x144 + +#define MCDT_DMA_CFG0 0x148 +#define MCDT_FIFO_CLR 0x14c +#define MCDT_DMA_CFG1 0x150 +#define MCDT_DMA_CFG2 0x154 +#define MCDT_DMA_CFG3 0x158 +#define MCDT_DMA_CFG4 0x15c +#define MCDT_DMA_CFG5 0x160 + +/* Channel water mark definition */ +#define MCDT_CH_FIFO_AE_SHIFT 16 +#define MCDT_CH_FIFO_AE_MASK GENMASK(24, 16) +#define MCDT_CH_FIFO_AF_MASK GENMASK(8, 0) + +/* DMA channel select definition */ +#define MCDT_DMA_CH0_SEL_MASK GENMASK(3, 0) +#define MCDT_DMA_CH0_SEL_SHIFT 0 +#define MCDT_DMA_CH1_SEL_MASK GENMASK(7, 4) +#define MCDT_DMA_CH1_SEL_SHIFT 4 +#define MCDT_DMA_CH2_SEL_MASK GENMASK(11, 8) +#define MCDT_DMA_CH2_SEL_SHIFT 8 +#define MCDT_DMA_CH3_SEL_MASK GENMASK(15, 12) +#define MCDT_DMA_CH3_SEL_SHIFT 12 +#define MCDT_DMA_CH4_SEL_MASK GENMASK(19, 16) +#define MCDT_DMA_CH4_SEL_SHIFT 16 +#define MCDT_DAC_DMA_SHIFT 16 + +/* DMA channel ACK select definition */ +#define MCDT_DMA_ACK_SEL_MASK GENMASK(3, 0) + +/* Channel FIFO definition */ +#define MCDT_CH_FIFO_ADDR_SHIFT 16 +#define MCDT_CH_FIFO_ADDR_MASK GENMASK(9, 0) +#define MCDT_ADC_FIFO_SHIFT 16 +#define MCDT_FIFO_LENGTH 512 + +#define MCDT_ADC_CHANNEL_NUM 10 +#define MCDT_DAC_CHANNEL_NUM 10 +#define MCDT_CHANNEL_NUM (MCDT_ADC_CHANNEL_NUM + MCDT_DAC_CHANNEL_NUM) + +enum sprd_mcdt_fifo_int { + MCDT_ADC_FIFO_AE_INT, + MCDT_ADC_FIFO_AF_INT, + MCDT_DAC_FIFO_AE_INT, + MCDT_DAC_FIFO_AF_INT, + MCDT_ADC_FIFO_OV_INT, + MCDT_DAC_FIFO_OV_INT +}; + +enum sprd_mcdt_fifo_sts { + MCDT_ADC_FIFO_REAL_FULL, + MCDT_ADC_FIFO_REAL_EMPTY, + MCDT_ADC_FIFO_AF, + MCDT_ADC_FIFO_AE, + MCDT_DAC_FIFO_REAL_FULL, + MCDT_DAC_FIFO_REAL_EMPTY, + MCDT_DAC_FIFO_AF, + MCDT_DAC_FIFO_AE +}; + +struct sprd_mcdt_dev { + struct device *dev; + void __iomem *base; + spinlock_t lock; + struct sprd_mcdt_chan chan[MCDT_CHANNEL_NUM]; +}; + +static LIST_HEAD(sprd_mcdt_chan_list); +static DEFINE_MUTEX(sprd_mcdt_list_mutex); + +static void sprd_mcdt_update(struct sprd_mcdt_dev *mcdt, u32 reg, u32 val, + u32 mask) +{ + u32 orig = readl_relaxed(mcdt->base + reg); + u32 tmp; + + tmp = (orig & ~mask) | val; + writel_relaxed(tmp, mcdt->base + reg); +} + +static void sprd_mcdt_dac_set_watermark(struct sprd_mcdt_dev *mcdt, u8 channel, + u32 full, u32 empty) +{ + u32 reg = MCDT_DAC0_WTMK + channel * 4; + u32 water_mark = + (empty << MCDT_CH_FIFO_AE_SHIFT) & MCDT_CH_FIFO_AE_MASK; + + water_mark |= full & MCDT_CH_FIFO_AF_MASK; + sprd_mcdt_update(mcdt, reg, water_mark, + MCDT_CH_FIFO_AE_MASK | MCDT_CH_FIFO_AF_MASK); +} + +static void sprd_mcdt_adc_set_watermark(struct sprd_mcdt_dev *mcdt, u8 channel, + u32 full, u32 empty) +{ + u32 reg = MCDT_ADC0_WTMK + channel * 4; + u32 water_mark = + (empty << MCDT_CH_FIFO_AE_SHIFT) & MCDT_CH_FIFO_AE_MASK; + + water_mark |= full & MCDT_CH_FIFO_AF_MASK; + sprd_mcdt_update(mcdt, reg, water_mark, + MCDT_CH_FIFO_AE_MASK | MCDT_CH_FIFO_AF_MASK); +} + +static void sprd_mcdt_dac_dma_enable(struct sprd_mcdt_dev *mcdt, u8 channel, + bool enable) +{ + u32 shift = MCDT_DAC_DMA_SHIFT + channel; + + if (enable) + sprd_mcdt_update(mcdt, MCDT_DMA_EN, BIT(shift), BIT(shift)); + else + sprd_mcdt_update(mcdt, MCDT_DMA_EN, 0, BIT(shift)); +} + +static void sprd_mcdt_adc_dma_enable(struct sprd_mcdt_dev *mcdt, u8 channel, + bool enable) +{ + if (enable) + sprd_mcdt_update(mcdt, MCDT_DMA_EN, BIT(channel), BIT(channel)); + else + sprd_mcdt_update(mcdt, MCDT_DMA_EN, 0, BIT(channel)); +} + +static void sprd_mcdt_ap_int_enable(struct sprd_mcdt_dev *mcdt, u8 channel, + bool enable) +{ + if (enable) + sprd_mcdt_update(mcdt, MCDT_INT_MSK_CFG0, BIT(channel), + BIT(channel)); + else + sprd_mcdt_update(mcdt, MCDT_INT_MSK_CFG0, 0, BIT(channel)); +} + +static void sprd_mcdt_dac_write_fifo(struct sprd_mcdt_dev *mcdt, u8 channel, + u32 val) +{ + u32 reg = MCDT_CH0_TXD + channel * 4; + + writel_relaxed(val, mcdt->base + reg); +} + +static void sprd_mcdt_adc_read_fifo(struct sprd_mcdt_dev *mcdt, u8 channel, + u32 *val) +{ + u32 reg = MCDT_CH0_RXD + channel * 4; + + *val = readl_relaxed(mcdt->base + reg); +} + +static void sprd_mcdt_dac_dma_chn_select(struct sprd_mcdt_dev *mcdt, u8 channel, + enum sprd_mcdt_dma_chan dma_chan) +{ + switch (dma_chan) { + case SPRD_MCDT_DMA_CH0: + sprd_mcdt_update(mcdt, MCDT_DMA_CFG0, + channel << MCDT_DMA_CH0_SEL_SHIFT, + MCDT_DMA_CH0_SEL_MASK); + break; + + case SPRD_MCDT_DMA_CH1: + sprd_mcdt_update(mcdt, MCDT_DMA_CFG0, + channel << MCDT_DMA_CH1_SEL_SHIFT, + MCDT_DMA_CH1_SEL_MASK); + break; + + case SPRD_MCDT_DMA_CH2: + sprd_mcdt_update(mcdt, MCDT_DMA_CFG0, + channel << MCDT_DMA_CH2_SEL_SHIFT, + MCDT_DMA_CH2_SEL_MASK); + break; + + case SPRD_MCDT_DMA_CH3: + sprd_mcdt_update(mcdt, MCDT_DMA_CFG0, + channel << MCDT_DMA_CH3_SEL_SHIFT, + MCDT_DMA_CH3_SEL_MASK); + break; + + case SPRD_MCDT_DMA_CH4: + sprd_mcdt_update(mcdt, MCDT_DMA_CFG0, + channel << MCDT_DMA_CH4_SEL_SHIFT, + MCDT_DMA_CH4_SEL_MASK); + break; + } +} + +static void sprd_mcdt_adc_dma_chn_select(struct sprd_mcdt_dev *mcdt, u8 channel, + enum sprd_mcdt_dma_chan dma_chan) +{ + switch (dma_chan) { + case SPRD_MCDT_DMA_CH0: + sprd_mcdt_update(mcdt, MCDT_DMA_CFG1, + channel << MCDT_DMA_CH0_SEL_SHIFT, + MCDT_DMA_CH0_SEL_MASK); + break; + + case SPRD_MCDT_DMA_CH1: + sprd_mcdt_update(mcdt, MCDT_DMA_CFG1, + channel << MCDT_DMA_CH1_SEL_SHIFT, + MCDT_DMA_CH1_SEL_MASK); + break; + + case SPRD_MCDT_DMA_CH2: + sprd_mcdt_update(mcdt, MCDT_DMA_CFG1, + channel << MCDT_DMA_CH2_SEL_SHIFT, + MCDT_DMA_CH2_SEL_MASK); + break; + + case SPRD_MCDT_DMA_CH3: + sprd_mcdt_update(mcdt, MCDT_DMA_CFG1, + channel << MCDT_DMA_CH3_SEL_SHIFT, + MCDT_DMA_CH3_SEL_MASK); + break; + + case SPRD_MCDT_DMA_CH4: + sprd_mcdt_update(mcdt, MCDT_DMA_CFG1, + channel << MCDT_DMA_CH4_SEL_SHIFT, + MCDT_DMA_CH4_SEL_MASK); + break; + } +} + +static u32 sprd_mcdt_dma_ack_shift(u8 channel) +{ + switch (channel) { + default: + case 0: + case 8: + return 0; + case 1: + case 9: + return 4; + case 2: + return 8; + case 3: + return 12; + case 4: + return 16; + case 5: + return 20; + case 6: + return 24; + case 7: + return 28; + } +} + +static void sprd_mcdt_dac_dma_ack_select(struct sprd_mcdt_dev *mcdt, u8 channel, + enum sprd_mcdt_dma_chan dma_chan) +{ + u32 reg, shift = sprd_mcdt_dma_ack_shift(channel), ack = dma_chan; + + switch (channel) { + case 0 ... 7: + reg = MCDT_DMA_CFG2; + break; + + case 8 ... 9: + reg = MCDT_DMA_CFG3; + break; + + default: + return; + } + + sprd_mcdt_update(mcdt, reg, ack << shift, + MCDT_DMA_ACK_SEL_MASK << shift); +} + +static void sprd_mcdt_adc_dma_ack_select(struct sprd_mcdt_dev *mcdt, u8 channel, + enum sprd_mcdt_dma_chan dma_chan) +{ + u32 reg, shift = sprd_mcdt_dma_ack_shift(channel), ack = dma_chan; + + switch (channel) { + case 0 ... 7: + reg = MCDT_DMA_CFG4; + break; + + case 8 ... 9: + reg = MCDT_DMA_CFG5; + break; + + default: + return; + } + + sprd_mcdt_update(mcdt, reg, ack << shift, + MCDT_DMA_ACK_SEL_MASK << shift); +} + +static bool sprd_mcdt_chan_fifo_sts(struct sprd_mcdt_dev *mcdt, u8 channel, + enum sprd_mcdt_fifo_sts fifo_sts) +{ + u32 reg, shift; + + switch (channel) { + case 0 ... 3: + reg = MCDT_CH_FIFO_ST0; + break; + case 4 ... 7: + reg = MCDT_CH_FIFO_ST1; + break; + case 8 ... 9: + reg = MCDT_CH_FIFO_ST2; + break; + default: + return false; + } + + switch (channel) { + case 0: + case 4: + case 8: + shift = fifo_sts; + break; + + case 1: + case 5: + case 9: + shift = 8 + fifo_sts; + break; + + case 2: + case 6: + shift = 16 + fifo_sts; + break; + + case 3: + case 7: + shift = 24 + fifo_sts; + break; + + default: + return false; + } + + return !!(readl_relaxed(mcdt->base + reg) & BIT(shift)); +} + +static void sprd_mcdt_dac_fifo_clear(struct sprd_mcdt_dev *mcdt, u8 channel) +{ + sprd_mcdt_update(mcdt, MCDT_FIFO_CLR, BIT(channel), BIT(channel)); +} + +static void sprd_mcdt_adc_fifo_clear(struct sprd_mcdt_dev *mcdt, u8 channel) +{ + u32 shift = MCDT_ADC_FIFO_SHIFT + channel; + + sprd_mcdt_update(mcdt, MCDT_FIFO_CLR, BIT(shift), BIT(shift)); +} + +static u32 sprd_mcdt_dac_fifo_avail(struct sprd_mcdt_dev *mcdt, u8 channel) +{ + u32 reg = MCDT_DAC0_FIFO_ADDR_ST + channel * 8; + u32 r_addr = (readl_relaxed(mcdt->base + reg) >> + MCDT_CH_FIFO_ADDR_SHIFT) & MCDT_CH_FIFO_ADDR_MASK; + u32 w_addr = readl_relaxed(mcdt->base + reg) & MCDT_CH_FIFO_ADDR_MASK; + + if (w_addr >= r_addr) + return 4 * (MCDT_FIFO_LENGTH - w_addr + r_addr); + else + return 4 * (r_addr - w_addr); +} + +static u32 sprd_mcdt_adc_fifo_avail(struct sprd_mcdt_dev *mcdt, u8 channel) +{ + u32 reg = MCDT_ADC0_FIFO_ADDR_ST + channel * 8; + u32 r_addr = (readl_relaxed(mcdt->base + reg) >> + MCDT_CH_FIFO_ADDR_SHIFT) & MCDT_CH_FIFO_ADDR_MASK; + u32 w_addr = readl_relaxed(mcdt->base + reg) & MCDT_CH_FIFO_ADDR_MASK; + + if (w_addr >= r_addr) + return 4 * (w_addr - r_addr); + else + return 4 * (MCDT_FIFO_LENGTH - r_addr + w_addr); +} + +static u32 sprd_mcdt_int_type_shift(u8 channel, + enum sprd_mcdt_fifo_int int_type) +{ + switch (channel) { + case 0: + case 4: + case 8: + return int_type; + + case 1: + case 5: + case 9: + return 8 + int_type; + + case 2: + case 6: + return 16 + int_type; + + case 3: + case 7: + return 24 + int_type; + + default: + return 0; + } +} + +static void sprd_mcdt_chan_int_en(struct sprd_mcdt_dev *mcdt, u8 channel, + enum sprd_mcdt_fifo_int int_type, bool enable) +{ + u32 reg, shift = sprd_mcdt_int_type_shift(channel, int_type); + + switch (channel) { + case 0 ... 3: + reg = MCDT_INT_EN0; + break; + case 4 ... 7: + reg = MCDT_INT_EN1; + break; + case 8 ... 9: + reg = MCDT_INT_EN2; + break; + default: + return; + } + + if (enable) + sprd_mcdt_update(mcdt, reg, BIT(shift), BIT(shift)); + else + sprd_mcdt_update(mcdt, reg, 0, BIT(shift)); +} + +static void sprd_mcdt_chan_int_clear(struct sprd_mcdt_dev *mcdt, u8 channel, + enum sprd_mcdt_fifo_int int_type) +{ + u32 reg, shift = sprd_mcdt_int_type_shift(channel, int_type); + + switch (channel) { + case 0 ... 3: + reg = MCDT_INT_CLR0; + break; + case 4 ... 7: + reg = MCDT_INT_CLR1; + break; + case 8 ... 9: + reg = MCDT_INT_CLR2; + break; + default: + return; + } + + sprd_mcdt_update(mcdt, reg, BIT(shift), BIT(shift)); +} + +static bool sprd_mcdt_chan_int_sts(struct sprd_mcdt_dev *mcdt, u8 channel, + enum sprd_mcdt_fifo_int int_type) +{ + u32 reg, shift = sprd_mcdt_int_type_shift(channel, int_type); + + switch (channel) { + case 0 ... 3: + reg = MCDT_INT_MSK1; + break; + case 4 ... 7: + reg = MCDT_INT_MSK2; + break; + case 8 ... 9: + reg = MCDT_INT_MSK3; + break; + default: + return false; + } + + return !!(readl_relaxed(mcdt->base + reg) & BIT(shift)); +} + +static irqreturn_t sprd_mcdt_irq_handler(int irq, void *dev_id) +{ + struct sprd_mcdt_dev *mcdt = (struct sprd_mcdt_dev *)dev_id; + int i; + + spin_lock(&mcdt->lock); + + for (i = 0; i < MCDT_ADC_CHANNEL_NUM; i++) { + if (sprd_mcdt_chan_int_sts(mcdt, i, MCDT_ADC_FIFO_AF_INT)) { + struct sprd_mcdt_chan *chan = &mcdt->chan[i]; + + sprd_mcdt_chan_int_clear(mcdt, i, MCDT_ADC_FIFO_AF_INT); + if (chan->cb) + chan->cb->notify(chan->cb->data); + } + } + + for (i = 0; i < MCDT_DAC_CHANNEL_NUM; i++) { + if (sprd_mcdt_chan_int_sts(mcdt, i, MCDT_DAC_FIFO_AE_INT)) { + struct sprd_mcdt_chan *chan = + &mcdt->chan[i + MCDT_ADC_CHANNEL_NUM]; + + sprd_mcdt_chan_int_clear(mcdt, i, MCDT_DAC_FIFO_AE_INT); + if (chan->cb) + chan->cb->notify(chan->cb->data); + } + } + + spin_unlock(&mcdt->lock); + + return IRQ_HANDLED; +} + +/** + * sprd_mcdt_chan_write - write data to the MCDT channel's fifo + * @chan: the MCDT channel + * @tx_buf: send buffer + * @size: data size + * + * Note: We can not write data to the channel fifo when enabling the DMA mode, + * otherwise the channel fifo data will be invalid. + * + * If there are not enough space of the channel fifo, it will return errors + * to users. + * + * Returns 0 on success, or an appropriate error code on failure. + */ +int sprd_mcdt_chan_write(struct sprd_mcdt_chan *chan, char *tx_buf, u32 size) +{ + struct sprd_mcdt_dev *mcdt = chan->mcdt; + unsigned long flags; + int avail, i = 0, words = size / 4; + u32 *buf = (u32 *)tx_buf; + + spin_lock_irqsave(&mcdt->lock, flags); + + if (chan->dma_enable) { + dev_err(mcdt->dev, + "Can not write data when DMA mode enabled\n"); + spin_unlock_irqrestore(&mcdt->lock, flags); + return -EINVAL; + } + + if (sprd_mcdt_chan_fifo_sts(mcdt, chan->id, MCDT_DAC_FIFO_REAL_FULL)) { + dev_err(mcdt->dev, "Channel fifo is full now\n"); + spin_unlock_irqrestore(&mcdt->lock, flags); + return -EBUSY; + } + + avail = sprd_mcdt_dac_fifo_avail(mcdt, chan->id); + if (size > avail) { + dev_err(mcdt->dev, + "Data size is larger than the available fifo size\n"); + spin_unlock_irqrestore(&mcdt->lock, flags); + return -EBUSY; + } + + while (i++ < words) + sprd_mcdt_dac_write_fifo(mcdt, chan->id, *buf++); + + spin_unlock_irqrestore(&mcdt->lock, flags); + return 0; +} +EXPORT_SYMBOL_GPL(sprd_mcdt_chan_write); + +/** + * sprd_mcdt_chan_read - read data from the MCDT channel's fifo + * @chan: the MCDT channel + * @rx_buf: receive buffer + * @size: data size + * + * Note: We can not read data from the channel fifo when enabling the DMA mode, + * otherwise the reading data will be invalid. + * + * Usually user need start to read data once receiving the fifo full interrupt. + * + * Returns data size of reading successfully, or an error code on failure. + */ +int sprd_mcdt_chan_read(struct sprd_mcdt_chan *chan, char *rx_buf, u32 size) +{ + struct sprd_mcdt_dev *mcdt = chan->mcdt; + unsigned long flags; + int i = 0, avail, words = size / 4; + u32 *buf = (u32 *)rx_buf; + + spin_lock_irqsave(&mcdt->lock, flags); + + if (chan->dma_enable) { + dev_err(mcdt->dev, "Can not read data when DMA mode enabled\n"); + spin_unlock_irqrestore(&mcdt->lock, flags); + return -EINVAL; + } + + if (sprd_mcdt_chan_fifo_sts(mcdt, chan->id, MCDT_ADC_FIFO_REAL_EMPTY)) { + dev_err(mcdt->dev, "Channel fifo is empty\n"); + spin_unlock_irqrestore(&mcdt->lock, flags); + return -EBUSY; + } + + avail = sprd_mcdt_adc_fifo_avail(mcdt, chan->id); + if (size > avail) + words = avail / 4; + + while (i++ < words) + sprd_mcdt_adc_read_fifo(mcdt, chan->id, buf++); + + spin_unlock_irqrestore(&mcdt->lock, flags); + return words * 4; +} +EXPORT_SYMBOL_GPL(sprd_mcdt_chan_read); + +/** + * sprd_mcdt_chan_int_enable - enable the interrupt mode for the MCDT channel + * @chan: the MCDT channel + * @water_mark: water mark to trigger a interrupt + * @cb: callback when a interrupt happened + * + * Now it only can enable fifo almost full interrupt for ADC channel and fifo + * almost empty interrupt for DAC channel. Morevoer for interrupt mode, user + * should use sprd_mcdt_chan_read() or sprd_mcdt_chan_write() to read or write + * data manually. + * + * For ADC channel, user can start to read data once receiving one fifo full + * interrupt. For DAC channel, user can start to write data once receiving one + * fifo empty interrupt or just call sprd_mcdt_chan_write() to write data + * directly. + * + * Returns 0 on success, or an error code on failure. + */ +int sprd_mcdt_chan_int_enable(struct sprd_mcdt_chan *chan, u32 water_mark, + struct sprd_mcdt_chan_callback *cb) +{ + struct sprd_mcdt_dev *mcdt = chan->mcdt; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&mcdt->lock, flags); + + if (chan->dma_enable || chan->int_enable) { + dev_err(mcdt->dev, "Failed to set interrupt mode.\n"); + spin_unlock_irqrestore(&mcdt->lock, flags); + return -EINVAL; + } + + switch (chan->type) { + case SPRD_MCDT_ADC_CHAN: + sprd_mcdt_adc_fifo_clear(mcdt, chan->id); + sprd_mcdt_adc_set_watermark(mcdt, chan->id, water_mark, + MCDT_FIFO_LENGTH - 1); + sprd_mcdt_chan_int_en(mcdt, chan->id, + MCDT_ADC_FIFO_AF_INT, true); + sprd_mcdt_ap_int_enable(mcdt, chan->id, true); + break; + + case SPRD_MCDT_DAC_CHAN: + sprd_mcdt_dac_fifo_clear(mcdt, chan->id); + sprd_mcdt_dac_set_watermark(mcdt, chan->id, + MCDT_FIFO_LENGTH - 1, water_mark); + sprd_mcdt_chan_int_en(mcdt, chan->id, + MCDT_DAC_FIFO_AE_INT, true); + sprd_mcdt_ap_int_enable(mcdt, chan->id, true); + break; + + default: + dev_err(mcdt->dev, "Unsupported channel type\n"); + ret = -EINVAL; + } + + if (!ret) { + chan->cb = cb; + chan->int_enable = true; + } + + spin_unlock_irqrestore(&mcdt->lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(sprd_mcdt_chan_int_enable); + +/** + * sprd_mcdt_chan_int_disable - disable the interrupt mode for the MCDT channel + * @chan: the MCDT channel + */ +void sprd_mcdt_chan_int_disable(struct sprd_mcdt_chan *chan) +{ + struct sprd_mcdt_dev *mcdt = chan->mcdt; + unsigned long flags; + + spin_lock_irqsave(&mcdt->lock, flags); + + if (!chan->int_enable) { + spin_unlock_irqrestore(&mcdt->lock, flags); + return; + } + + switch (chan->type) { + case SPRD_MCDT_ADC_CHAN: + sprd_mcdt_chan_int_en(mcdt, chan->id, + MCDT_ADC_FIFO_AF_INT, false); + sprd_mcdt_chan_int_clear(mcdt, chan->id, MCDT_ADC_FIFO_AF_INT); + sprd_mcdt_ap_int_enable(mcdt, chan->id, false); + break; + + case SPRD_MCDT_DAC_CHAN: + sprd_mcdt_chan_int_en(mcdt, chan->id, + MCDT_DAC_FIFO_AE_INT, false); + sprd_mcdt_chan_int_clear(mcdt, chan->id, MCDT_DAC_FIFO_AE_INT); + sprd_mcdt_ap_int_enable(mcdt, chan->id, false); + break; + + default: + break; + } + + chan->int_enable = false; + spin_unlock_irqrestore(&mcdt->lock, flags); +} +EXPORT_SYMBOL_GPL(sprd_mcdt_chan_int_disable); + +/** + * sprd_mcdt_chan_dma_enable - enable the DMA mode for the MCDT channel + * @chan: the MCDT channel + * @dma_chan: specify which DMA channel will be used for this MCDT channel + * @water_mark: water mark to trigger a DMA request + * + * Enable the DMA mode for the MCDT channel, that means we can use DMA to + * transfer data to the channel fifo and do not need reading/writing data + * manually. + * + * Returns 0 on success, or an error code on failure. + */ +int sprd_mcdt_chan_dma_enable(struct sprd_mcdt_chan *chan, + enum sprd_mcdt_dma_chan dma_chan, + u32 water_mark) +{ + struct sprd_mcdt_dev *mcdt = chan->mcdt; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&mcdt->lock, flags); + + if (chan->dma_enable || chan->int_enable || + dma_chan > SPRD_MCDT_DMA_CH4) { + dev_err(mcdt->dev, "Failed to set DMA mode\n"); + spin_unlock_irqrestore(&mcdt->lock, flags); + return -EINVAL; + } + + switch (chan->type) { + case SPRD_MCDT_ADC_CHAN: + sprd_mcdt_adc_fifo_clear(mcdt, chan->id); + sprd_mcdt_adc_set_watermark(mcdt, chan->id, + water_mark, MCDT_FIFO_LENGTH - 1); + sprd_mcdt_adc_dma_enable(mcdt, chan->id, true); + sprd_mcdt_adc_dma_chn_select(mcdt, chan->id, dma_chan); + sprd_mcdt_adc_dma_ack_select(mcdt, chan->id, dma_chan); + break; + + case SPRD_MCDT_DAC_CHAN: + sprd_mcdt_dac_fifo_clear(mcdt, chan->id); + sprd_mcdt_dac_set_watermark(mcdt, chan->id, + MCDT_FIFO_LENGTH - 1, water_mark); + sprd_mcdt_dac_dma_enable(mcdt, chan->id, true); + sprd_mcdt_dac_dma_chn_select(mcdt, chan->id, dma_chan); + sprd_mcdt_dac_dma_ack_select(mcdt, chan->id, dma_chan); + break; + + default: + dev_err(mcdt->dev, "Unsupported channel type\n"); + ret = -EINVAL; + } + + if (!ret) + chan->dma_enable = true; + + spin_unlock_irqrestore(&mcdt->lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(sprd_mcdt_chan_dma_enable); + +/** + * sprd_mcdt_chan_dma_disable - disable the DMA mode for the MCDT channel + * @chan: the MCDT channel + */ +void sprd_mcdt_chan_dma_disable(struct sprd_mcdt_chan *chan) +{ + struct sprd_mcdt_dev *mcdt = chan->mcdt; + unsigned long flags; + + spin_lock_irqsave(&mcdt->lock, flags); + + if (!chan->dma_enable) { + spin_unlock_irqrestore(&mcdt->lock, flags); + return; + } + + switch (chan->type) { + case SPRD_MCDT_ADC_CHAN: + sprd_mcdt_adc_dma_enable(mcdt, chan->id, false); + sprd_mcdt_adc_fifo_clear(mcdt, chan->id); + break; + + case SPRD_MCDT_DAC_CHAN: + sprd_mcdt_dac_dma_enable(mcdt, chan->id, false); + sprd_mcdt_dac_fifo_clear(mcdt, chan->id); + break; + + default: + break; + } + + chan->dma_enable = false; + spin_unlock_irqrestore(&mcdt->lock, flags); +} +EXPORT_SYMBOL_GPL(sprd_mcdt_chan_dma_disable); + +/** + * sprd_mcdt_request_chan - request one MCDT channel + * @channel: channel id + * @type: channel type, it can be one ADC channel or DAC channel + * + * Rreturn NULL if no available channel. + */ +struct sprd_mcdt_chan *sprd_mcdt_request_chan(u8 channel, + enum sprd_mcdt_channel_type type) +{ + struct sprd_mcdt_chan *temp; + + mutex_lock(&sprd_mcdt_list_mutex); + + list_for_each_entry(temp, &sprd_mcdt_chan_list, list) { + if (temp->type == type && temp->id == channel) { + list_del_init(&temp->list); + break; + } + } + + if (list_entry_is_head(temp, &sprd_mcdt_chan_list, list)) + temp = NULL; + + mutex_unlock(&sprd_mcdt_list_mutex); + + return temp; +} +EXPORT_SYMBOL_GPL(sprd_mcdt_request_chan); + +/** + * sprd_mcdt_free_chan - free one MCDT channel + * @chan: the channel to be freed + */ +void sprd_mcdt_free_chan(struct sprd_mcdt_chan *chan) +{ + struct sprd_mcdt_chan *temp; + + sprd_mcdt_chan_dma_disable(chan); + sprd_mcdt_chan_int_disable(chan); + + mutex_lock(&sprd_mcdt_list_mutex); + + list_for_each_entry(temp, &sprd_mcdt_chan_list, list) { + if (temp == chan) { + mutex_unlock(&sprd_mcdt_list_mutex); + return; + } + } + + list_add_tail(&chan->list, &sprd_mcdt_chan_list); + mutex_unlock(&sprd_mcdt_list_mutex); +} +EXPORT_SYMBOL_GPL(sprd_mcdt_free_chan); + +static void sprd_mcdt_init_chans(struct sprd_mcdt_dev *mcdt, + struct resource *res) +{ + int i; + + for (i = 0; i < MCDT_CHANNEL_NUM; i++) { + struct sprd_mcdt_chan *chan = &mcdt->chan[i]; + + if (i < MCDT_ADC_CHANNEL_NUM) { + chan->id = i; + chan->type = SPRD_MCDT_ADC_CHAN; + chan->fifo_phys = res->start + MCDT_CH0_RXD + i * 4; + } else { + chan->id = i - MCDT_ADC_CHANNEL_NUM; + chan->type = SPRD_MCDT_DAC_CHAN; + chan->fifo_phys = res->start + MCDT_CH0_TXD + + (i - MCDT_ADC_CHANNEL_NUM) * 4; + } + + chan->mcdt = mcdt; + INIT_LIST_HEAD(&chan->list); + + mutex_lock(&sprd_mcdt_list_mutex); + list_add_tail(&chan->list, &sprd_mcdt_chan_list); + mutex_unlock(&sprd_mcdt_list_mutex); + } +} + +static int sprd_mcdt_probe(struct platform_device *pdev) +{ + struct sprd_mcdt_dev *mcdt; + struct resource *res; + int ret, irq; + + mcdt = devm_kzalloc(&pdev->dev, sizeof(*mcdt), GFP_KERNEL); + if (!mcdt) + return -ENOMEM; + + mcdt->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(mcdt->base)) + return PTR_ERR(mcdt->base); + + mcdt->dev = &pdev->dev; + spin_lock_init(&mcdt->lock); + platform_set_drvdata(pdev, mcdt); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(&pdev->dev, irq, sprd_mcdt_irq_handler, + 0, "sprd-mcdt", mcdt); + if (ret) { + dev_err(&pdev->dev, "Failed to request MCDT IRQ\n"); + return ret; + } + + sprd_mcdt_init_chans(mcdt, res); + + return 0; +} + +static int sprd_mcdt_remove(struct platform_device *pdev) +{ + struct sprd_mcdt_chan *chan, *temp; + + mutex_lock(&sprd_mcdt_list_mutex); + + list_for_each_entry_safe(chan, temp, &sprd_mcdt_chan_list, list) + list_del(&chan->list); + + mutex_unlock(&sprd_mcdt_list_mutex); + + return 0; +} + +static const struct of_device_id sprd_mcdt_of_match[] = { + { .compatible = "sprd,sc9860-mcdt", }, + { } +}; +MODULE_DEVICE_TABLE(of, sprd_mcdt_of_match); + +static struct platform_driver sprd_mcdt_driver = { + .probe = sprd_mcdt_probe, + .remove = sprd_mcdt_remove, + .driver = { + .name = "sprd-mcdt", + .of_match_table = sprd_mcdt_of_match, + }, +}; + +module_platform_driver(sprd_mcdt_driver); + +MODULE_DESCRIPTION("Spreadtrum Multi-Channel Data Transfer Driver"); +MODULE_LICENSE("GPL v2"); |