From 5b7c4cabbb65f5c469464da6c5f614cbd7f730f2 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Tue, 21 Feb 2023 18:24:12 -0800 Subject: Merge tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next 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(). ... --- sound/oss/dmasound/dmasound_paula.c | 739 ++++++++++++++++++++++++++++++++++++ 1 file changed, 739 insertions(+) create mode 100644 sound/oss/dmasound/dmasound_paula.c (limited to 'sound/oss/dmasound/dmasound_paula.c') diff --git a/sound/oss/dmasound/dmasound_paula.c b/sound/oss/dmasound/dmasound_paula.c new file mode 100644 index 000000000..23cf8284c --- /dev/null +++ b/sound/oss/dmasound/dmasound_paula.c @@ -0,0 +1,739 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * linux/sound/oss/dmasound/dmasound_paula.c + * + * Amiga `Paula' DMA Sound Driver + * + * See linux/sound/oss/dmasound/dmasound_core.c for copyright and credits + * prior to 28/01/2001 + * + * 28/01/2001 [0.1] Iain Sandoe + * - added versioning + * - put in and populated the hardware_afmts field. + * [0.2] - put in SNDCTL_DSP_GETCAPS value. + * [0.3] - put in constraint on state buffer usage. + * [0.4] - put in default hard/soft settings +*/ + + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "dmasound.h" + +#define DMASOUND_PAULA_REVISION 0 +#define DMASOUND_PAULA_EDITION 4 + +#define custom amiga_custom + /* + * The minimum period for audio depends on htotal (for OCS/ECS/AGA) + * (Imported from arch/m68k/amiga/amisound.c) + */ + +extern volatile u_short amiga_audio_min_period; + + + /* + * amiga_mksound() should be able to restore the period after beeping + * (Imported from arch/m68k/amiga/amisound.c) + */ + +extern u_short amiga_audio_period; + + + /* + * Audio DMA masks + */ + +#define AMI_AUDIO_OFF (DMAF_AUD0 | DMAF_AUD1 | DMAF_AUD2 | DMAF_AUD3) +#define AMI_AUDIO_8 (DMAF_SETCLR | DMAF_MASTER | DMAF_AUD0 | DMAF_AUD1) +#define AMI_AUDIO_14 (AMI_AUDIO_8 | DMAF_AUD2 | DMAF_AUD3) + + + /* + * Helper pointers for 16(14)-bit sound + */ + +static int write_sq_block_size_half, write_sq_block_size_quarter; + + +/*** Low level stuff *********************************************************/ + + +static void *AmiAlloc(unsigned int size, gfp_t flags); +static void AmiFree(void *obj, unsigned int size); +static int AmiIrqInit(void); +#ifdef MODULE +static void AmiIrqCleanUp(void); +#endif +static void AmiSilence(void); +static void AmiInit(void); +static int AmiSetFormat(int format); +static int AmiSetVolume(int volume); +static int AmiSetTreble(int treble); +static void AmiPlayNextFrame(int index); +static void AmiPlay(void); +static irqreturn_t AmiInterrupt(int irq, void *dummy); + +#ifdef CONFIG_HEARTBEAT + + /* + * Heartbeat interferes with sound since the 7 kHz low-pass filter and the + * power LED are controlled by the same line. + */ + +static void (*saved_heartbeat)(int) = NULL; + +static inline void disable_heartbeat(void) +{ + if (mach_heartbeat) { + saved_heartbeat = mach_heartbeat; + mach_heartbeat = NULL; + } + AmiSetTreble(dmasound.treble); +} + +static inline void enable_heartbeat(void) +{ + if (saved_heartbeat) + mach_heartbeat = saved_heartbeat; +} +#else /* !CONFIG_HEARTBEAT */ +#define disable_heartbeat() do { } while (0) +#define enable_heartbeat() do { } while (0) +#endif /* !CONFIG_HEARTBEAT */ + + +/*** Mid level stuff *********************************************************/ + +static void AmiMixerInit(void); +static int AmiMixerIoctl(u_int cmd, u_long arg); +static int AmiWriteSqSetup(void); +static int AmiStateInfo(char *buffer, size_t space); + + +/*** Translations ************************************************************/ + +/* ++TeSche: radically changed for new expanding purposes... + * + * These two routines now deal with copying/expanding/translating the samples + * from user space into our buffer at the right frequency. They take care about + * how much data there's actually to read, how much buffer space there is and + * to convert samples into the right frequency/encoding. They will only work on + * complete samples so it may happen they leave some bytes in the input stream + * if the user didn't write a multiple of the current sample size. They both + * return the number of bytes they've used from both streams so you may detect + * such a situation. Luckily all programs should be able to cope with that. + * + * I think I've optimized anything as far as one can do in plain C, all + * variables should fit in registers and the loops are really short. There's + * one loop for every possible situation. Writing a more generalized and thus + * parameterized loop would only produce slower code. Feel free to optimize + * this in assembler if you like. :) + * + * I think these routines belong here because they're not yet really hardware + * independent, especially the fact that the Falcon can play 16bit samples + * only in stereo is hardcoded in both of them! + * + * ++geert: split in even more functions (one per format) + */ + + + /* + * Native format + */ + +static ssize_t ami_ct_s8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, ssize_t frameLeft) +{ + ssize_t count, used; + + if (!dmasound.soft.stereo) { + void *p = &frame[*frameUsed]; + count = min_t(unsigned long, userCount, frameLeft) & ~1; + used = count; + if (copy_from_user(p, userPtr, count)) + return -EFAULT; + } else { + u_char *left = &frame[*frameUsed>>1]; + u_char *right = left+write_sq_block_size_half; + count = min_t(unsigned long, userCount, frameLeft)>>1 & ~1; + used = count*2; + while (count > 0) { + if (get_user(*left++, userPtr++) + || get_user(*right++, userPtr++)) + return -EFAULT; + count--; + } + } + *frameUsed += used; + return used; +} + + + /* + * Copy and convert 8 bit data + */ + +#define GENERATE_AMI_CT8(funcname, convsample) \ +static ssize_t funcname(const u_char __user *userPtr, size_t userCount, \ + u_char frame[], ssize_t *frameUsed, \ + ssize_t frameLeft) \ +{ \ + ssize_t count, used; \ + \ + if (!dmasound.soft.stereo) { \ + u_char *p = &frame[*frameUsed]; \ + count = min_t(size_t, userCount, frameLeft) & ~1; \ + used = count; \ + while (count > 0) { \ + u_char data; \ + if (get_user(data, userPtr++)) \ + return -EFAULT; \ + *p++ = convsample(data); \ + count--; \ + } \ + } else { \ + u_char *left = &frame[*frameUsed>>1]; \ + u_char *right = left+write_sq_block_size_half; \ + count = min_t(size_t, userCount, frameLeft)>>1 & ~1; \ + used = count*2; \ + while (count > 0) { \ + u_char data; \ + if (get_user(data, userPtr++)) \ + return -EFAULT; \ + *left++ = convsample(data); \ + if (get_user(data, userPtr++)) \ + return -EFAULT; \ + *right++ = convsample(data); \ + count--; \ + } \ + } \ + *frameUsed += used; \ + return used; \ +} + +#define AMI_CT_ULAW(x) (dmasound_ulaw2dma8[(x)]) +#define AMI_CT_ALAW(x) (dmasound_alaw2dma8[(x)]) +#define AMI_CT_U8(x) ((x) ^ 0x80) + +GENERATE_AMI_CT8(ami_ct_ulaw, AMI_CT_ULAW) +GENERATE_AMI_CT8(ami_ct_alaw, AMI_CT_ALAW) +GENERATE_AMI_CT8(ami_ct_u8, AMI_CT_U8) + + + /* + * Copy and convert 16 bit data + */ + +#define GENERATE_AMI_CT_16(funcname, convsample) \ +static ssize_t funcname(const u_char __user *userPtr, size_t userCount, \ + u_char frame[], ssize_t *frameUsed, \ + ssize_t frameLeft) \ +{ \ + const u_short __user *ptr = (const u_short __user *)userPtr; \ + ssize_t count, used; \ + u_short data; \ + \ + if (!dmasound.soft.stereo) { \ + u_char *high = &frame[*frameUsed>>1]; \ + u_char *low = high+write_sq_block_size_half; \ + count = min_t(size_t, userCount, frameLeft)>>1 & ~1; \ + used = count*2; \ + while (count > 0) { \ + if (get_user(data, ptr++)) \ + return -EFAULT; \ + data = convsample(data); \ + *high++ = data>>8; \ + *low++ = (data>>2) & 0x3f; \ + count--; \ + } \ + } else { \ + u_char *lefth = &frame[*frameUsed>>2]; \ + u_char *leftl = lefth+write_sq_block_size_quarter; \ + u_char *righth = lefth+write_sq_block_size_half; \ + u_char *rightl = righth+write_sq_block_size_quarter; \ + count = min_t(size_t, userCount, frameLeft)>>2 & ~1; \ + used = count*4; \ + while (count > 0) { \ + if (get_user(data, ptr++)) \ + return -EFAULT; \ + data = convsample(data); \ + *lefth++ = data>>8; \ + *leftl++ = (data>>2) & 0x3f; \ + if (get_user(data, ptr++)) \ + return -EFAULT; \ + data = convsample(data); \ + *righth++ = data>>8; \ + *rightl++ = (data>>2) & 0x3f; \ + count--; \ + } \ + } \ + *frameUsed += used; \ + return used; \ +} + +#define AMI_CT_S16BE(x) (x) +#define AMI_CT_U16BE(x) ((x) ^ 0x8000) +#define AMI_CT_S16LE(x) (le2be16((x))) +#define AMI_CT_U16LE(x) (le2be16((x)) ^ 0x8000) + +GENERATE_AMI_CT_16(ami_ct_s16be, AMI_CT_S16BE) +GENERATE_AMI_CT_16(ami_ct_u16be, AMI_CT_U16BE) +GENERATE_AMI_CT_16(ami_ct_s16le, AMI_CT_S16LE) +GENERATE_AMI_CT_16(ami_ct_u16le, AMI_CT_U16LE) + + +static TRANS transAmiga = { + .ct_ulaw = ami_ct_ulaw, + .ct_alaw = ami_ct_alaw, + .ct_s8 = ami_ct_s8, + .ct_u8 = ami_ct_u8, + .ct_s16be = ami_ct_s16be, + .ct_u16be = ami_ct_u16be, + .ct_s16le = ami_ct_s16le, + .ct_u16le = ami_ct_u16le, +}; + +/*** Low level stuff *********************************************************/ + +static inline void StopDMA(void) +{ + custom.aud[0].audvol = custom.aud[1].audvol = 0; + custom.aud[2].audvol = custom.aud[3].audvol = 0; + custom.dmacon = AMI_AUDIO_OFF; + enable_heartbeat(); +} + +static void *AmiAlloc(unsigned int size, gfp_t flags) +{ + return amiga_chip_alloc((long)size, "dmasound [Paula]"); +} + +static void AmiFree(void *obj, unsigned int size) +{ + amiga_chip_free (obj); +} + +static int __init AmiIrqInit(void) +{ + /* turn off DMA for audio channels */ + StopDMA(); + + /* Register interrupt handler. */ + if (request_irq(IRQ_AMIGA_AUD0, AmiInterrupt, 0, "DMA sound", + AmiInterrupt)) + return 0; + return 1; +} + +#ifdef MODULE +static void AmiIrqCleanUp(void) +{ + /* turn off DMA for audio channels */ + StopDMA(); + /* release the interrupt */ + free_irq(IRQ_AMIGA_AUD0, AmiInterrupt); +} +#endif /* MODULE */ + +static void AmiSilence(void) +{ + /* turn off DMA for audio channels */ + StopDMA(); +} + + +static void AmiInit(void) +{ + int period, i; + + AmiSilence(); + + if (dmasound.soft.speed) + period = amiga_colorclock/dmasound.soft.speed-1; + else + period = amiga_audio_min_period; + dmasound.hard = dmasound.soft; + dmasound.trans_write = &transAmiga; + + if (period < amiga_audio_min_period) { + /* we would need to squeeze the sound, but we won't do that */ + period = amiga_audio_min_period; + } else if (period > 65535) { + period = 65535; + } + dmasound.hard.speed = amiga_colorclock/(period+1); + + for (i = 0; i < 4; i++) + custom.aud[i].audper = period; + amiga_audio_period = period; +} + + +static int AmiSetFormat(int format) +{ + int size; + + /* Amiga sound DMA supports 8bit and 16bit (pseudo 14 bit) modes */ + + switch (format) { + case AFMT_QUERY: + return dmasound.soft.format; + case AFMT_MU_LAW: + case AFMT_A_LAW: + case AFMT_U8: + case AFMT_S8: + size = 8; + break; + case AFMT_S16_BE: + case AFMT_U16_BE: + case AFMT_S16_LE: + case AFMT_U16_LE: + size = 16; + break; + default: /* :-) */ + size = 8; + format = AFMT_S8; + } + + dmasound.soft.format = format; + dmasound.soft.size = size; + if (dmasound.minDev == SND_DEV_DSP) { + dmasound.dsp.format = format; + dmasound.dsp.size = dmasound.soft.size; + } + AmiInit(); + + return format; +} + + +#define VOLUME_VOXWARE_TO_AMI(v) \ + (((v) < 0) ? 0 : ((v) > 100) ? 64 : ((v) * 64)/100) +#define VOLUME_AMI_TO_VOXWARE(v) ((v)*100/64) + +static int AmiSetVolume(int volume) +{ + dmasound.volume_left = VOLUME_VOXWARE_TO_AMI(volume & 0xff); + custom.aud[0].audvol = dmasound.volume_left; + dmasound.volume_right = VOLUME_VOXWARE_TO_AMI((volume & 0xff00) >> 8); + custom.aud[1].audvol = dmasound.volume_right; + if (dmasound.hard.size == 16) { + if (dmasound.volume_left == 64 && dmasound.volume_right == 64) { + custom.aud[2].audvol = 1; + custom.aud[3].audvol = 1; + } else { + custom.aud[2].audvol = 0; + custom.aud[3].audvol = 0; + } + } + return VOLUME_AMI_TO_VOXWARE(dmasound.volume_left) | + (VOLUME_AMI_TO_VOXWARE(dmasound.volume_right) << 8); +} + +static int AmiSetTreble(int treble) +{ + dmasound.treble = treble; + if (treble < 50) + ciaa.pra &= ~0x02; + else + ciaa.pra |= 0x02; + return treble; +} + + +#define AMI_PLAY_LOADED 1 +#define AMI_PLAY_PLAYING 2 +#define AMI_PLAY_MASK 3 + + +static void AmiPlayNextFrame(int index) +{ + u_char *start, *ch0, *ch1, *ch2, *ch3; + u_long size; + + /* used by AmiPlay() if all doubts whether there really is something + * to be played are already wiped out. + */ + start = write_sq.buffers[write_sq.front]; + size = (write_sq.count == index ? write_sq.rear_size + : write_sq.block_size)>>1; + + if (dmasound.hard.stereo) { + ch0 = start; + ch1 = start+write_sq_block_size_half; + size >>= 1; + } else { + ch0 = start; + ch1 = start; + } + + disable_heartbeat(); + custom.aud[0].audvol = dmasound.volume_left; + custom.aud[1].audvol = dmasound.volume_right; + if (dmasound.hard.size == 8) { + custom.aud[0].audlc = (u_short *)ZTWO_PADDR(ch0); + custom.aud[0].audlen = size; + custom.aud[1].audlc = (u_short *)ZTWO_PADDR(ch1); + custom.aud[1].audlen = size; + custom.dmacon = AMI_AUDIO_8; + } else { + size >>= 1; + custom.aud[0].audlc = (u_short *)ZTWO_PADDR(ch0); + custom.aud[0].audlen = size; + custom.aud[1].audlc = (u_short *)ZTWO_PADDR(ch1); + custom.aud[1].audlen = size; + if (dmasound.volume_left == 64 && dmasound.volume_right == 64) { + /* We can play pseudo 14-bit only with the maximum volume */ + ch3 = ch0+write_sq_block_size_quarter; + ch2 = ch1+write_sq_block_size_quarter; + custom.aud[2].audvol = 1; /* we are being affected by the beeps */ + custom.aud[3].audvol = 1; /* restoring volume here helps a bit */ + custom.aud[2].audlc = (u_short *)ZTWO_PADDR(ch2); + custom.aud[2].audlen = size; + custom.aud[3].audlc = (u_short *)ZTWO_PADDR(ch3); + custom.aud[3].audlen = size; + custom.dmacon = AMI_AUDIO_14; + } else { + custom.aud[2].audvol = 0; + custom.aud[3].audvol = 0; + custom.dmacon = AMI_AUDIO_8; + } + } + write_sq.front = (write_sq.front+1) % write_sq.max_count; + write_sq.active |= AMI_PLAY_LOADED; +} + + +static void AmiPlay(void) +{ + int minframes = 1; + + custom.intena = IF_AUD0; + + if (write_sq.active & AMI_PLAY_LOADED) { + /* There's already a frame loaded */ + custom.intena = IF_SETCLR | IF_AUD0; + return; + } + + if (write_sq.active & AMI_PLAY_PLAYING) + /* Increase threshold: frame 1 is already being played */ + minframes = 2; + + if (write_sq.count < minframes) { + /* Nothing to do */ + custom.intena = IF_SETCLR | IF_AUD0; + return; + } + + if (write_sq.count <= minframes && + write_sq.rear_size < write_sq.block_size && !write_sq.syncing) { + /* hmmm, the only existing frame is not + * yet filled and we're not syncing? + */ + custom.intena = IF_SETCLR | IF_AUD0; + return; + } + + AmiPlayNextFrame(minframes); + + custom.intena = IF_SETCLR | IF_AUD0; +} + + +static irqreturn_t AmiInterrupt(int irq, void *dummy) +{ + int minframes = 1; + + custom.intena = IF_AUD0; + + if (!write_sq.active) { + /* Playing was interrupted and sq_reset() has already cleared + * the sq variables, so better don't do anything here. + */ + WAKE_UP(write_sq.sync_queue); + return IRQ_HANDLED; + } + + if (write_sq.active & AMI_PLAY_PLAYING) { + /* We've just finished a frame */ + write_sq.count--; + WAKE_UP(write_sq.action_queue); + } + + if (write_sq.active & AMI_PLAY_LOADED) + /* Increase threshold: frame 1 is already being played */ + minframes = 2; + + /* Shift the flags */ + write_sq.active = (write_sq.active<<1) & AMI_PLAY_MASK; + + if (!write_sq.active) + /* No frame is playing, disable audio DMA */ + StopDMA(); + + custom.intena = IF_SETCLR | IF_AUD0; + + if (write_sq.count >= minframes) + /* Try to play the next frame */ + AmiPlay(); + + if (!write_sq.active) + /* Nothing to play anymore. + Wake up a process waiting for audio output to drain. */ + WAKE_UP(write_sq.sync_queue); + return IRQ_HANDLED; +} + +/*** Mid level stuff *********************************************************/ + + +/* + * /dev/mixer abstraction + */ + +static void __init AmiMixerInit(void) +{ + dmasound.volume_left = 64; + dmasound.volume_right = 64; + custom.aud[0].audvol = dmasound.volume_left; + custom.aud[3].audvol = 1; /* For pseudo 14bit */ + custom.aud[1].audvol = dmasound.volume_right; + custom.aud[2].audvol = 1; /* For pseudo 14bit */ + dmasound.treble = 50; +} + +static int AmiMixerIoctl(u_int cmd, u_long arg) +{ + int data; + switch (cmd) { + case SOUND_MIXER_READ_DEVMASK: + return IOCTL_OUT(arg, SOUND_MASK_VOLUME | SOUND_MASK_TREBLE); + case SOUND_MIXER_READ_RECMASK: + return IOCTL_OUT(arg, 0); + case SOUND_MIXER_READ_STEREODEVS: + return IOCTL_OUT(arg, SOUND_MASK_VOLUME); + case SOUND_MIXER_READ_VOLUME: + return IOCTL_OUT(arg, + VOLUME_AMI_TO_VOXWARE(dmasound.volume_left) | + VOLUME_AMI_TO_VOXWARE(dmasound.volume_right) << 8); + case SOUND_MIXER_WRITE_VOLUME: + IOCTL_IN(arg, data); + return IOCTL_OUT(arg, dmasound_set_volume(data)); + case SOUND_MIXER_READ_TREBLE: + return IOCTL_OUT(arg, dmasound.treble); + case SOUND_MIXER_WRITE_TREBLE: + IOCTL_IN(arg, data); + return IOCTL_OUT(arg, dmasound_set_treble(data)); + } + return -EINVAL; +} + + +static int AmiWriteSqSetup(void) +{ + write_sq_block_size_half = write_sq.block_size>>1; + write_sq_block_size_quarter = write_sq_block_size_half>>1; + return 0; +} + + +static int AmiStateInfo(char *buffer, size_t space) +{ + int len = 0; + len += sprintf(buffer+len, "\tsound.volume_left = %d [0...64]\n", + dmasound.volume_left); + len += sprintf(buffer+len, "\tsound.volume_right = %d [0...64]\n", + dmasound.volume_right); + if (len >= space) { + printk(KERN_ERR "dmasound_paula: overflowed state buffer alloc.\n") ; + len = space ; + } + return len; +} + + +/*** Machine definitions *****************************************************/ + +static SETTINGS def_hard = { + .format = AFMT_S8, + .stereo = 0, + .size = 8, + .speed = 8000 +} ; + +static SETTINGS def_soft = { + .format = AFMT_U8, + .stereo = 0, + .size = 8, + .speed = 8000 +} ; + +static MACHINE machAmiga = { + .name = "Amiga", + .name2 = "AMIGA", + .owner = THIS_MODULE, + .dma_alloc = AmiAlloc, + .dma_free = AmiFree, + .irqinit = AmiIrqInit, +#ifdef MODULE + .irqcleanup = AmiIrqCleanUp, +#endif /* MODULE */ + .init = AmiInit, + .silence = AmiSilence, + .setFormat = AmiSetFormat, + .setVolume = AmiSetVolume, + .setTreble = AmiSetTreble, + .play = AmiPlay, + .mixer_init = AmiMixerInit, + .mixer_ioctl = AmiMixerIoctl, + .write_sq_setup = AmiWriteSqSetup, + .state_info = AmiStateInfo, + .min_dsp_speed = 8000, + .version = ((DMASOUND_PAULA_REVISION<<8) | DMASOUND_PAULA_EDITION), + .hardware_afmts = (AFMT_S8 | AFMT_S16_BE), /* h'ware-supported formats *only* here */ + .capabilities = DSP_CAP_BATCH /* As per SNDCTL_DSP_GETCAPS */ +}; + + +/*** Config & Setup **********************************************************/ + + +static int __init amiga_audio_probe(struct platform_device *pdev) +{ + dmasound.mach = machAmiga; + dmasound.mach.default_hard = def_hard ; + dmasound.mach.default_soft = def_soft ; + return dmasound_init(); +} + +static int __exit amiga_audio_remove(struct platform_device *pdev) +{ + dmasound_deinit(); + return 0; +} + +static struct platform_driver amiga_audio_driver = { + .remove = __exit_p(amiga_audio_remove), + .driver = { + .name = "amiga-audio", + }, +}; + +module_platform_driver_probe(amiga_audio_driver, amiga_audio_probe); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:amiga-audio"); -- cgit v1.2.3