From bb3945a497a915b850ca4470c0939be70d265181 Mon Sep 17 00:00:00 2001 From: Xianting Tian Date: Sat, 7 Aug 2021 22:55:37 +0800 Subject: [PATCH 01/52] riscv: add ARCH_DMA_MINALIGN support Introduce ARCH_DMA_MINALIGN to riscv arch. Signed-off-by: Xianting Tian --- arch/riscv/include/asm/cache.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/riscv/include/asm/cache.h b/arch/riscv/include/asm/cache.h index 9b58b104559ee..2945bbe2b6bcb 100644 --- a/arch/riscv/include/asm/cache.h +++ b/arch/riscv/include/asm/cache.h @@ -11,6 +11,8 @@ #define L1_CACHE_BYTES (1 << L1_CACHE_SHIFT) +#define ARCH_DMA_MINALIGN L1_CACHE_BYTES + /* * RISC-V requires the stack pointer to be 16-byte aligned, so ensure that * the flat loader aligns it accordingly. From edcc56e4b4b503a11f3261a1ff4461be06ea589a Mon Sep 17 00:00:00 2001 From: Matteo Croce Date: Wed, 29 Sep 2021 19:22:32 +0200 Subject: [PATCH 02/52] riscv: optimized memcpy Write a C version of memcpy() which uses the biggest data size allowed, without generating unaligned accesses. The procedure is made of three steps: First copy data one byte at time until the destination buffer is aligned to a long boundary. Then copy the data one long at time shifting the current and the next u8 to compose a long at every cycle. Finally, copy the remainder one byte at time. On a BeagleV, the TCP RX throughput increased by 45%: before: $ iperf3 -c beaglev Connecting to host beaglev, port 5201 [ 5] local 192.168.85.6 port 44840 connected to 192.168.85.48 port 5201 [ ID] Interval Transfer Bitrate Retr Cwnd [ 5] 0.00-1.00 sec 76.4 MBytes 641 Mbits/sec 27 624 KBytes [ 5] 1.00-2.00 sec 72.5 MBytes 608 Mbits/sec 0 708 KBytes [ 5] 2.00-3.00 sec 73.8 MBytes 619 Mbits/sec 10 451 KBytes [ 5] 3.00-4.00 sec 72.5 MBytes 608 Mbits/sec 0 564 KBytes [ 5] 4.00-5.00 sec 73.8 MBytes 619 Mbits/sec 0 658 KBytes [ 5] 5.00-6.00 sec 73.8 MBytes 619 Mbits/sec 14 522 KBytes [ 5] 6.00-7.00 sec 73.8 MBytes 619 Mbits/sec 0 621 KBytes [ 5] 7.00-8.00 sec 72.5 MBytes 608 Mbits/sec 0 706 KBytes [ 5] 8.00-9.00 sec 73.8 MBytes 619 Mbits/sec 20 580 KBytes [ 5] 9.00-10.00 sec 73.8 MBytes 619 Mbits/sec 0 672 KBytes - - - - - - - - - - - - - - - - - - - - - - - - - [ ID] Interval Transfer Bitrate Retr [ 5] 0.00-10.00 sec 736 MBytes 618 Mbits/sec 71 sender [ 5] 0.00-10.01 sec 733 MBytes 615 Mbits/sec receiver after: $ iperf3 -c beaglev Connecting to host beaglev, port 5201 [ 5] local 192.168.85.6 port 44864 connected to 192.168.85.48 port 5201 [ ID] Interval Transfer Bitrate Retr Cwnd [ 5] 0.00-1.00 sec 109 MBytes 912 Mbits/sec 48 559 KBytes [ 5] 1.00-2.00 sec 108 MBytes 902 Mbits/sec 0 690 KBytes [ 5] 2.00-3.00 sec 106 MBytes 891 Mbits/sec 36 396 KBytes [ 5] 3.00-4.00 sec 108 MBytes 902 Mbits/sec 0 567 KBytes [ 5] 4.00-5.00 sec 106 MBytes 891 Mbits/sec 0 699 KBytes [ 5] 5.00-6.00 sec 106 MBytes 891 Mbits/sec 32 414 KBytes [ 5] 6.00-7.00 sec 106 MBytes 891 Mbits/sec 0 583 KBytes [ 5] 7.00-8.00 sec 106 MBytes 891 Mbits/sec 0 708 KBytes [ 5] 8.00-9.00 sec 106 MBytes 891 Mbits/sec 28 433 KBytes [ 5] 9.00-10.00 sec 108 MBytes 902 Mbits/sec 0 591 KBytes - - - - - - - - - - - - - - - - - - - - - - - - - [ ID] Interval Transfer Bitrate Retr [ 5] 0.00-10.00 sec 1.04 GBytes 897 Mbits/sec 144 sender [ 5] 0.00-10.01 sec 1.04 GBytes 894 Mbits/sec receiver And the decreased CPU time of the memcpy() is observable with perf top. This is the `perf top -Ue task-clock` output when doing the test: before: Overhead Shared O Symbol 42.22% [kernel] [k] memcpy 35.00% [kernel] [k] __asm_copy_to_user 3.50% [kernel] [k] sifive_l2_flush64_range 2.30% [kernel] [k] stmmac_napi_poll_rx 1.11% [kernel] [k] memset after: Overhead Shared O Symbol 45.69% [kernel] [k] __asm_copy_to_user 29.06% [kernel] [k] memcpy 4.09% [kernel] [k] sifive_l2_flush64_range 2.77% [kernel] [k] stmmac_napi_poll_rx 1.24% [kernel] [k] memset Signed-off-by: Matteo Croce Signed-off-by: Emil Renner Berthing --- arch/riscv/include/asm/string.h | 6 +- arch/riscv/kernel/riscv_ksyms.c | 2 - arch/riscv/lib/Makefile | 7 ++- arch/riscv/lib/memcpy.S | 108 -------------------------------- arch/riscv/lib/string.c | 91 +++++++++++++++++++++++++++ arch/riscv/purgatory/Makefile | 6 +- 6 files changed, 104 insertions(+), 116 deletions(-) delete mode 100644 arch/riscv/lib/memcpy.S create mode 100644 arch/riscv/lib/string.c diff --git a/arch/riscv/include/asm/string.h b/arch/riscv/include/asm/string.h index 9090493665555..23fdcbffb6a1f 100644 --- a/arch/riscv/include/asm/string.h +++ b/arch/riscv/include/asm/string.h @@ -12,9 +12,11 @@ #define __HAVE_ARCH_MEMSET extern asmlinkage void *memset(void *, int, size_t); extern asmlinkage void *__memset(void *, int, size_t); + #define __HAVE_ARCH_MEMCPY -extern asmlinkage void *memcpy(void *, const void *, size_t); -extern asmlinkage void *__memcpy(void *, const void *, size_t); +extern void *memcpy(void *dest, const void *src, size_t count); +extern void *__memcpy(void *dest, const void *src, size_t count); + #define __HAVE_ARCH_MEMMOVE extern asmlinkage void *memmove(void *, const void *, size_t); extern asmlinkage void *__memmove(void *, const void *, size_t); diff --git a/arch/riscv/kernel/riscv_ksyms.c b/arch/riscv/kernel/riscv_ksyms.c index 5ab1c7e1a6ed5..3f6d512a5b978 100644 --- a/arch/riscv/kernel/riscv_ksyms.c +++ b/arch/riscv/kernel/riscv_ksyms.c @@ -10,8 +10,6 @@ * Assembly functions that may be used (directly or indirectly) by modules */ EXPORT_SYMBOL(memset); -EXPORT_SYMBOL(memcpy); EXPORT_SYMBOL(memmove); EXPORT_SYMBOL(__memset); -EXPORT_SYMBOL(__memcpy); EXPORT_SYMBOL(__memmove); diff --git a/arch/riscv/lib/Makefile b/arch/riscv/lib/Makefile index 25d5c9664e57e..023d78f20960d 100644 --- a/arch/riscv/lib/Makefile +++ b/arch/riscv/lib/Makefile @@ -1,9 +1,14 @@ # SPDX-License-Identifier: GPL-2.0-only lib-y += delay.o -lib-y += memcpy.o lib-y += memset.o lib-y += memmove.o lib-$(CONFIG_MMU) += uaccess.o lib-$(CONFIG_64BIT) += tishift.o +lib-y += string.o + +# string.o implements standard library functions like memset/memcpy etc. +# Use -ffreestanding to ensure that the compiler does not try to "optimize" +# them into calls to themselves. +CFLAGS_string.o := -ffreestanding obj-$(CONFIG_FUNCTION_ERROR_INJECTION) += error-inject.o diff --git a/arch/riscv/lib/memcpy.S b/arch/riscv/lib/memcpy.S deleted file mode 100644 index 51ab716253fa3..0000000000000 --- a/arch/riscv/lib/memcpy.S +++ /dev/null @@ -1,108 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Copyright (C) 2013 Regents of the University of California - */ - -#include -#include - -/* void *memcpy(void *, const void *, size_t) */ -ENTRY(__memcpy) -WEAK(memcpy) - move t6, a0 /* Preserve return value */ - - /* Defer to byte-oriented copy for small sizes */ - sltiu a3, a2, 128 - bnez a3, 4f - /* Use word-oriented copy only if low-order bits match */ - andi a3, t6, SZREG-1 - andi a4, a1, SZREG-1 - bne a3, a4, 4f - - beqz a3, 2f /* Skip if already aligned */ - /* - * Round to nearest double word-aligned address - * greater than or equal to start address - */ - andi a3, a1, ~(SZREG-1) - addi a3, a3, SZREG - /* Handle initial misalignment */ - sub a4, a3, a1 -1: - lb a5, 0(a1) - addi a1, a1, 1 - sb a5, 0(t6) - addi t6, t6, 1 - bltu a1, a3, 1b - sub a2, a2, a4 /* Update count */ - -2: - andi a4, a2, ~((16*SZREG)-1) - beqz a4, 4f - add a3, a1, a4 -3: - REG_L a4, 0(a1) - REG_L a5, SZREG(a1) - REG_L a6, 2*SZREG(a1) - REG_L a7, 3*SZREG(a1) - REG_L t0, 4*SZREG(a1) - REG_L t1, 5*SZREG(a1) - REG_L t2, 6*SZREG(a1) - REG_L t3, 7*SZREG(a1) - REG_L t4, 8*SZREG(a1) - REG_L t5, 9*SZREG(a1) - REG_S a4, 0(t6) - REG_S a5, SZREG(t6) - REG_S a6, 2*SZREG(t6) - REG_S a7, 3*SZREG(t6) - REG_S t0, 4*SZREG(t6) - REG_S t1, 5*SZREG(t6) - REG_S t2, 6*SZREG(t6) - REG_S t3, 7*SZREG(t6) - REG_S t4, 8*SZREG(t6) - REG_S t5, 9*SZREG(t6) - REG_L a4, 10*SZREG(a1) - REG_L a5, 11*SZREG(a1) - REG_L a6, 12*SZREG(a1) - REG_L a7, 13*SZREG(a1) - REG_L t0, 14*SZREG(a1) - REG_L t1, 15*SZREG(a1) - addi a1, a1, 16*SZREG - REG_S a4, 10*SZREG(t6) - REG_S a5, 11*SZREG(t6) - REG_S a6, 12*SZREG(t6) - REG_S a7, 13*SZREG(t6) - REG_S t0, 14*SZREG(t6) - REG_S t1, 15*SZREG(t6) - addi t6, t6, 16*SZREG - bltu a1, a3, 3b - andi a2, a2, (16*SZREG)-1 /* Update count */ - -4: - /* Handle trailing misalignment */ - beqz a2, 6f - add a3, a1, a2 - - /* Use word-oriented copy if co-aligned to word boundary */ - or a5, a1, t6 - or a5, a5, a3 - andi a5, a5, 3 - bnez a5, 5f -7: - lw a4, 0(a1) - addi a1, a1, 4 - sw a4, 0(t6) - addi t6, t6, 4 - bltu a1, a3, 7b - - ret - -5: - lb a4, 0(a1) - addi a1, a1, 1 - sb a4, 0(t6) - addi t6, t6, 1 - bltu a1, a3, 5b -6: - ret -END(__memcpy) diff --git a/arch/riscv/lib/string.c b/arch/riscv/lib/string.c new file mode 100644 index 0000000000000..32509ab0232e4 --- /dev/null +++ b/arch/riscv/lib/string.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * String functions optimized for hardware which doesn't + * handle unaligned memory accesses efficiently. + * + * Copyright (C) 2021 Matteo Croce + */ + +#define __NO_FORTIFY +#include +#include + +/* Minimum size for a word copy to be convenient */ +#define BYTES_LONG sizeof(long) +#define WORD_MASK (BYTES_LONG - 1) +#define MIN_THRESHOLD (BYTES_LONG * 2) + +/* convenience union to avoid cast between different pointer types */ +union types { + u8 *as_u8; + unsigned long *as_ulong; + uintptr_t as_uptr; +}; + +union const_types { + const u8 *as_u8; + unsigned long *as_ulong; + uintptr_t as_uptr; +}; + +void *__memcpy(void *dest, const void *src, size_t count) +{ + union const_types s = { .as_u8 = src }; + union types d = { .as_u8 = dest }; + int distance = 0; + + if (!IS_ENABLED(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS)) { + if (count < MIN_THRESHOLD) + goto copy_remainder; + + /* Copy a byte at time until destination is aligned. */ + for (; d.as_uptr & WORD_MASK; count--) + *d.as_u8++ = *s.as_u8++; + + distance = s.as_uptr & WORD_MASK; + } + + if (distance) { + unsigned long last, next; + + /* + * s is distance bytes ahead of d, and d just reached + * the alignment boundary. Move s backward to word align it + * and shift data to compensate for distance, in order to do + * word-by-word copy. + */ + s.as_u8 -= distance; + + next = s.as_ulong[0]; + for (; count >= BYTES_LONG; count -= BYTES_LONG) { + last = next; + next = s.as_ulong[1]; + + d.as_ulong[0] = last >> (distance * 8) | + next << ((BYTES_LONG - distance) * 8); + + d.as_ulong++; + s.as_ulong++; + } + + /* Restore s with the original offset. */ + s.as_u8 += distance; + } else { + /* + * If the source and dest lower bits are the same, do a simple + * 32/64 bit wide copy. + */ + for (; count >= BYTES_LONG; count -= BYTES_LONG) + *d.as_ulong++ = *s.as_ulong++; + } + +copy_remainder: + while (count--) + *d.as_u8++ = *s.as_u8++; + + return dest; +} +EXPORT_SYMBOL(__memcpy); + +void *memcpy(void *dest, const void *src, size_t count) __weak __alias(__memcpy); +EXPORT_SYMBOL(memcpy); diff --git a/arch/riscv/purgatory/Makefile b/arch/riscv/purgatory/Makefile index d4df200f7edf9..e2a020e69f083 100644 --- a/arch/riscv/purgatory/Makefile +++ b/arch/riscv/purgatory/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 OBJECT_FILES_NON_STANDARD := y -purgatory-y := purgatory.o sha256.o entry.o string.o ctype.o memcpy.o memset.o +purgatory-y := purgatory.o sha256.o entry.o string.o ctype.o rvstring.o memset.o targets += $(purgatory-y) PURGATORY_OBJS = $(addprefix $(obj)/,$(purgatory-y)) @@ -12,8 +12,8 @@ $(obj)/string.o: $(srctree)/lib/string.c FORCE $(obj)/ctype.o: $(srctree)/lib/ctype.c FORCE $(call if_changed_rule,cc_o_c) -$(obj)/memcpy.o: $(srctree)/arch/riscv/lib/memcpy.S FORCE - $(call if_changed_rule,as_o_S) +$(obj)/rvstring.o: $(srctree)/arch/riscv/lib/string.c FORCE + $(call if_changed_rule,cc_o_c) $(obj)/memset.o: $(srctree)/arch/riscv/lib/memset.S FORCE $(call if_changed_rule,as_o_S) From ab6f606bd826269bf94356729058c19a2cb526a6 Mon Sep 17 00:00:00 2001 From: Matteo Croce Date: Wed, 29 Sep 2021 19:22:33 +0200 Subject: [PATCH 03/52] riscv: optimized memmove When the destination buffer is before the source one, or when the buffers doesn't overlap, it's safe to use memcpy() instead, which is optimized to use a bigger data size possible. Signed-off-by: Matteo Croce --- arch/riscv/include/asm/string.h | 6 +- arch/riscv/kernel/riscv_ksyms.c | 2 - arch/riscv/lib/Makefile | 1 - arch/riscv/lib/memmove.S | 316 -------------------------------- arch/riscv/lib/string.c | 23 +++ 5 files changed, 26 insertions(+), 322 deletions(-) delete mode 100644 arch/riscv/lib/memmove.S diff --git a/arch/riscv/include/asm/string.h b/arch/riscv/include/asm/string.h index 23fdcbffb6a1f..02120466d5a13 100644 --- a/arch/riscv/include/asm/string.h +++ b/arch/riscv/include/asm/string.h @@ -16,10 +16,10 @@ extern asmlinkage void *__memset(void *, int, size_t); #define __HAVE_ARCH_MEMCPY extern void *memcpy(void *dest, const void *src, size_t count); extern void *__memcpy(void *dest, const void *src, size_t count); - #define __HAVE_ARCH_MEMMOVE -extern asmlinkage void *memmove(void *, const void *, size_t); -extern asmlinkage void *__memmove(void *, const void *, size_t); +extern void *memmove(void *dest, const void *src, size_t count); +extern void *__memmove(void *dest, const void *src, size_t count); + /* For those files which don't want to check by kasan. */ #if defined(CONFIG_KASAN) && !defined(__SANITIZE_ADDRESS__) #define memcpy(dst, src, len) __memcpy(dst, src, len) diff --git a/arch/riscv/kernel/riscv_ksyms.c b/arch/riscv/kernel/riscv_ksyms.c index 3f6d512a5b978..361565c4db7e0 100644 --- a/arch/riscv/kernel/riscv_ksyms.c +++ b/arch/riscv/kernel/riscv_ksyms.c @@ -10,6 +10,4 @@ * Assembly functions that may be used (directly or indirectly) by modules */ EXPORT_SYMBOL(memset); -EXPORT_SYMBOL(memmove); EXPORT_SYMBOL(__memset); -EXPORT_SYMBOL(__memmove); diff --git a/arch/riscv/lib/Makefile b/arch/riscv/lib/Makefile index 023d78f20960d..9166b61cc2dcb 100644 --- a/arch/riscv/lib/Makefile +++ b/arch/riscv/lib/Makefile @@ -1,7 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only lib-y += delay.o lib-y += memset.o -lib-y += memmove.o lib-$(CONFIG_MMU) += uaccess.o lib-$(CONFIG_64BIT) += tishift.o lib-y += string.o diff --git a/arch/riscv/lib/memmove.S b/arch/riscv/lib/memmove.S deleted file mode 100644 index e0609e1f0864d..0000000000000 --- a/arch/riscv/lib/memmove.S +++ /dev/null @@ -1,316 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Copyright (C) 2022 Michael T. Kloos - */ - -#include -#include - -SYM_FUNC_START(__memmove) -SYM_FUNC_START_WEAK(memmove) - /* - * Returns - * a0 - dest - * - * Parameters - * a0 - Inclusive first byte of dest - * a1 - Inclusive first byte of src - * a2 - Length of copy n - * - * Because the return matches the parameter register a0, - * we will not clobber or modify that register. - * - * Note: This currently only works on little-endian. - * To port to big-endian, reverse the direction of shifts - * in the 2 misaligned fixup copy loops. - */ - - /* Return if nothing to do */ - beq a0, a1, return_from_memmove - beqz a2, return_from_memmove - - /* - * Register Uses - * Forward Copy: a1 - Index counter of src - * Reverse Copy: a4 - Index counter of src - * Forward Copy: t3 - Index counter of dest - * Reverse Copy: t4 - Index counter of dest - * Both Copy Modes: t5 - Inclusive first multibyte/aligned of dest - * Both Copy Modes: t6 - Non-Inclusive last multibyte/aligned of dest - * Both Copy Modes: t0 - Link / Temporary for load-store - * Both Copy Modes: t1 - Temporary for load-store - * Both Copy Modes: t2 - Temporary for load-store - * Both Copy Modes: a5 - dest to src alignment offset - * Both Copy Modes: a6 - Shift ammount - * Both Copy Modes: a7 - Inverse Shift ammount - * Both Copy Modes: a2 - Alternate breakpoint for unrolled loops - */ - - /* - * Solve for some register values now. - * Byte copy does not need t5 or t6. - */ - mv t3, a0 - add t4, a0, a2 - add a4, a1, a2 - - /* - * Byte copy if copying less than (2 * SZREG) bytes. This can - * cause problems with the bulk copy implementation and is - * small enough not to bother. - */ - andi t0, a2, -(2 * SZREG) - beqz t0, byte_copy - - /* - * Now solve for t5 and t6. - */ - andi t5, t3, -SZREG - andi t6, t4, -SZREG - /* - * If dest(Register t3) rounded down to the nearest naturally - * aligned SZREG address, does not equal dest, then add SZREG - * to find the low-bound of SZREG alignment in the dest memory - * region. Note that this could overshoot the dest memory - * region if n is less than SZREG. This is one reason why - * we always byte copy if n is less than SZREG. - * Otherwise, dest is already naturally aligned to SZREG. - */ - beq t5, t3, 1f - addi t5, t5, SZREG - 1: - - /* - * If the dest and src are co-aligned to SZREG, then there is - * no need for the full rigmarole of a full misaligned fixup copy. - * Instead, do a simpler co-aligned copy. - */ - xor t0, a0, a1 - andi t1, t0, (SZREG - 1) - beqz t1, coaligned_copy - /* Fall through to misaligned fixup copy */ - -misaligned_fixup_copy: - bltu a1, a0, misaligned_fixup_copy_reverse - -misaligned_fixup_copy_forward: - jal t0, byte_copy_until_aligned_forward - - andi a5, a1, (SZREG - 1) /* Find the alignment offset of src (a1) */ - slli a6, a5, 3 /* Multiply by 8 to convert that to bits to shift */ - sub a5, a1, t3 /* Find the difference between src and dest */ - andi a1, a1, -SZREG /* Align the src pointer */ - addi a2, t6, SZREG /* The other breakpoint for the unrolled loop*/ - - /* - * Compute The Inverse Shift - * a7 = XLEN - a6 = XLEN + -a6 - * 2s complement negation to find the negative: -a6 = ~a6 + 1 - * Add that to XLEN. XLEN = SZREG * 8. - */ - not a7, a6 - addi a7, a7, (SZREG * 8 + 1) - - /* - * Fix Misalignment Copy Loop - Forward - * load_val0 = load_ptr[0]; - * do { - * load_val1 = load_ptr[1]; - * store_ptr += 2; - * store_ptr[0 - 2] = (load_val0 >> {a6}) | (load_val1 << {a7}); - * - * if (store_ptr == {a2}) - * break; - * - * load_val0 = load_ptr[2]; - * load_ptr += 2; - * store_ptr[1 - 2] = (load_val1 >> {a6}) | (load_val0 << {a7}); - * - * } while (store_ptr != store_ptr_end); - * store_ptr = store_ptr_end; - */ - - REG_L t0, (0 * SZREG)(a1) - 1: - REG_L t1, (1 * SZREG)(a1) - addi t3, t3, (2 * SZREG) - srl t0, t0, a6 - sll t2, t1, a7 - or t2, t0, t2 - REG_S t2, ((0 * SZREG) - (2 * SZREG))(t3) - - beq t3, a2, 2f - - REG_L t0, (2 * SZREG)(a1) - addi a1, a1, (2 * SZREG) - srl t1, t1, a6 - sll t2, t0, a7 - or t2, t1, t2 - REG_S t2, ((1 * SZREG) - (2 * SZREG))(t3) - - bne t3, t6, 1b - 2: - mv t3, t6 /* Fix the dest pointer in case the loop was broken */ - - add a1, t3, a5 /* Restore the src pointer */ - j byte_copy_forward /* Copy any remaining bytes */ - -misaligned_fixup_copy_reverse: - jal t0, byte_copy_until_aligned_reverse - - andi a5, a4, (SZREG - 1) /* Find the alignment offset of src (a4) */ - slli a6, a5, 3 /* Multiply by 8 to convert that to bits to shift */ - sub a5, a4, t4 /* Find the difference between src and dest */ - andi a4, a4, -SZREG /* Align the src pointer */ - addi a2, t5, -SZREG /* The other breakpoint for the unrolled loop*/ - - /* - * Compute The Inverse Shift - * a7 = XLEN - a6 = XLEN + -a6 - * 2s complement negation to find the negative: -a6 = ~a6 + 1 - * Add that to XLEN. XLEN = SZREG * 8. - */ - not a7, a6 - addi a7, a7, (SZREG * 8 + 1) - - /* - * Fix Misalignment Copy Loop - Reverse - * load_val1 = load_ptr[0]; - * do { - * load_val0 = load_ptr[-1]; - * store_ptr -= 2; - * store_ptr[1] = (load_val0 >> {a6}) | (load_val1 << {a7}); - * - * if (store_ptr == {a2}) - * break; - * - * load_val1 = load_ptr[-2]; - * load_ptr -= 2; - * store_ptr[0] = (load_val1 >> {a6}) | (load_val0 << {a7}); - * - * } while (store_ptr != store_ptr_end); - * store_ptr = store_ptr_end; - */ - - REG_L t1, ( 0 * SZREG)(a4) - 1: - REG_L t0, (-1 * SZREG)(a4) - addi t4, t4, (-2 * SZREG) - sll t1, t1, a7 - srl t2, t0, a6 - or t2, t1, t2 - REG_S t2, ( 1 * SZREG)(t4) - - beq t4, a2, 2f - - REG_L t1, (-2 * SZREG)(a4) - addi a4, a4, (-2 * SZREG) - sll t0, t0, a7 - srl t2, t1, a6 - or t2, t0, t2 - REG_S t2, ( 0 * SZREG)(t4) - - bne t4, t5, 1b - 2: - mv t4, t5 /* Fix the dest pointer in case the loop was broken */ - - add a4, t4, a5 /* Restore the src pointer */ - j byte_copy_reverse /* Copy any remaining bytes */ - -/* - * Simple copy loops for SZREG co-aligned memory locations. - * These also make calls to do byte copies for any unaligned - * data at their terminations. - */ -coaligned_copy: - bltu a1, a0, coaligned_copy_reverse - -coaligned_copy_forward: - jal t0, byte_copy_until_aligned_forward - - 1: - REG_L t1, ( 0 * SZREG)(a1) - addi a1, a1, SZREG - addi t3, t3, SZREG - REG_S t1, (-1 * SZREG)(t3) - bne t3, t6, 1b - - j byte_copy_forward /* Copy any remaining bytes */ - -coaligned_copy_reverse: - jal t0, byte_copy_until_aligned_reverse - - 1: - REG_L t1, (-1 * SZREG)(a4) - addi a4, a4, -SZREG - addi t4, t4, -SZREG - REG_S t1, ( 0 * SZREG)(t4) - bne t4, t5, 1b - - j byte_copy_reverse /* Copy any remaining bytes */ - -/* - * These are basically sub-functions within the function. They - * are used to byte copy until the dest pointer is in alignment. - * At which point, a bulk copy method can be used by the - * calling code. These work on the same registers as the bulk - * copy loops. Therefore, the register values can be picked - * up from where they were left and we avoid code duplication - * without any overhead except the call in and return jumps. - */ -byte_copy_until_aligned_forward: - beq t3, t5, 2f - 1: - lb t1, 0(a1) - addi a1, a1, 1 - addi t3, t3, 1 - sb t1, -1(t3) - bne t3, t5, 1b - 2: - jalr zero, 0x0(t0) /* Return to multibyte copy loop */ - -byte_copy_until_aligned_reverse: - beq t4, t6, 2f - 1: - lb t1, -1(a4) - addi a4, a4, -1 - addi t4, t4, -1 - sb t1, 0(t4) - bne t4, t6, 1b - 2: - jalr zero, 0x0(t0) /* Return to multibyte copy loop */ - -/* - * Simple byte copy loops. - * These will byte copy until they reach the end of data to copy. - * At that point, they will call to return from memmove. - */ -byte_copy: - bltu a1, a0, byte_copy_reverse - -byte_copy_forward: - beq t3, t4, 2f - 1: - lb t1, 0(a1) - addi a1, a1, 1 - addi t3, t3, 1 - sb t1, -1(t3) - bne t3, t4, 1b - 2: - ret - -byte_copy_reverse: - beq t4, t3, 2f - 1: - lb t1, -1(a4) - addi a4, a4, -1 - addi t4, t4, -1 - sb t1, 0(t4) - bne t4, t3, 1b - 2: - -return_from_memmove: - ret - -SYM_FUNC_END(memmove) -SYM_FUNC_END(__memmove) diff --git a/arch/riscv/lib/string.c b/arch/riscv/lib/string.c index 32509ab0232e4..bd52581e2068c 100644 --- a/arch/riscv/lib/string.c +++ b/arch/riscv/lib/string.c @@ -89,3 +89,26 @@ EXPORT_SYMBOL(__memcpy); void *memcpy(void *dest, const void *src, size_t count) __weak __alias(__memcpy); EXPORT_SYMBOL(memcpy); + +/* + * Simply check if the buffer overlaps an call memcpy() in case, + * otherwise do a simple one byte at time backward copy. + */ +void *__memmove(void *dest, const void *src, size_t count) +{ + if (dest < src || src + count <= dest) + return __memcpy(dest, src, count); + + if (dest > src) { + const char *s = src + count; + char *tmp = dest + count; + + while (count--) + *--tmp = *--s; + } + return dest; +} +EXPORT_SYMBOL(__memmove); + +void *memmove(void *dest, const void *src, size_t count) __weak __alias(__memmove); +EXPORT_SYMBOL(memmove); From eb2c9d6c7c44f3c3a17668a8f59a2a8fd57569a5 Mon Sep 17 00:00:00 2001 From: Matteo Croce Date: Wed, 29 Sep 2021 19:22:34 +0200 Subject: [PATCH 04/52] riscv: optimized memset The generic memset is defined as a byte at time write. This is always safe, but it's slower than a 4 byte or even 8 byte write. Write a generic memset which fills the data one byte at time until the destination is aligned, then fills using the largest size allowed, and finally fills the remaining data one byte at time. Signed-off-by: Matteo Croce Signed-off-by: Emil Renner Berthing --- arch/riscv/include/asm/string.h | 8 +-- arch/riscv/kernel/Makefile | 1 - arch/riscv/kernel/riscv_ksyms.c | 13 ---- arch/riscv/lib/Makefile | 1 - arch/riscv/lib/memset.S | 113 -------------------------------- arch/riscv/lib/string.c | 41 ++++++++++++ arch/riscv/purgatory/Makefile | 5 +- 7 files changed, 44 insertions(+), 138 deletions(-) delete mode 100644 arch/riscv/kernel/riscv_ksyms.c delete mode 100644 arch/riscv/lib/memset.S diff --git a/arch/riscv/include/asm/string.h b/arch/riscv/include/asm/string.h index 02120466d5a13..3b79b14a2bf12 100644 --- a/arch/riscv/include/asm/string.h +++ b/arch/riscv/include/asm/string.h @@ -6,13 +6,9 @@ #ifndef _ASM_RISCV_STRING_H #define _ASM_RISCV_STRING_H -#include -#include - #define __HAVE_ARCH_MEMSET -extern asmlinkage void *memset(void *, int, size_t); -extern asmlinkage void *__memset(void *, int, size_t); - +extern void *memset(void *s, int c, size_t count); +extern void *__memset(void *s, int c, size_t count); #define __HAVE_ARCH_MEMCPY extern void *memcpy(void *dest, const void *src, size_t count); extern void *__memcpy(void *dest, const void *src, size_t count); diff --git a/arch/riscv/kernel/Makefile b/arch/riscv/kernel/Makefile index 33bb60a354cd2..f915f521af102 100644 --- a/arch/riscv/kernel/Makefile +++ b/arch/riscv/kernel/Makefile @@ -46,7 +46,6 @@ obj-y += syscall_table.o obj-y += sys_riscv.o obj-y += time.o obj-y += traps.o -obj-y += riscv_ksyms.o obj-y += stacktrace.o obj-y += cacheinfo.o obj-y += patch.o diff --git a/arch/riscv/kernel/riscv_ksyms.c b/arch/riscv/kernel/riscv_ksyms.c deleted file mode 100644 index 361565c4db7e0..0000000000000 --- a/arch/riscv/kernel/riscv_ksyms.c +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (C) 2017 Zihao Yu - */ - -#include -#include - -/* - * Assembly functions that may be used (directly or indirectly) by modules - */ -EXPORT_SYMBOL(memset); -EXPORT_SYMBOL(__memset); diff --git a/arch/riscv/lib/Makefile b/arch/riscv/lib/Makefile index 9166b61cc2dcb..482e28132d776 100644 --- a/arch/riscv/lib/Makefile +++ b/arch/riscv/lib/Makefile @@ -1,6 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only lib-y += delay.o -lib-y += memset.o lib-$(CONFIG_MMU) += uaccess.o lib-$(CONFIG_64BIT) += tishift.o lib-y += string.o diff --git a/arch/riscv/lib/memset.S b/arch/riscv/lib/memset.S deleted file mode 100644 index 34c5360c6705c..0000000000000 --- a/arch/riscv/lib/memset.S +++ /dev/null @@ -1,113 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Copyright (C) 2013 Regents of the University of California - */ - - -#include -#include - -/* void *memset(void *, int, size_t) */ -ENTRY(__memset) -WEAK(memset) - move t0, a0 /* Preserve return value */ - - /* Defer to byte-oriented fill for small sizes */ - sltiu a3, a2, 16 - bnez a3, 4f - - /* - * Round to nearest XLEN-aligned address - * greater than or equal to start address - */ - addi a3, t0, SZREG-1 - andi a3, a3, ~(SZREG-1) - beq a3, t0, 2f /* Skip if already aligned */ - /* Handle initial misalignment */ - sub a4, a3, t0 -1: - sb a1, 0(t0) - addi t0, t0, 1 - bltu t0, a3, 1b - sub a2, a2, a4 /* Update count */ - -2: /* Duff's device with 32 XLEN stores per iteration */ - /* Broadcast value into all bytes */ - andi a1, a1, 0xff - slli a3, a1, 8 - or a1, a3, a1 - slli a3, a1, 16 - or a1, a3, a1 -#ifdef CONFIG_64BIT - slli a3, a1, 32 - or a1, a3, a1 -#endif - - /* Calculate end address */ - andi a4, a2, ~(SZREG-1) - add a3, t0, a4 - - andi a4, a4, 31*SZREG /* Calculate remainder */ - beqz a4, 3f /* Shortcut if no remainder */ - neg a4, a4 - addi a4, a4, 32*SZREG /* Calculate initial offset */ - - /* Adjust start address with offset */ - sub t0, t0, a4 - - /* Jump into loop body */ - /* Assumes 32-bit instruction lengths */ - la a5, 3f -#ifdef CONFIG_64BIT - srli a4, a4, 1 -#endif - add a5, a5, a4 - jr a5 -3: - REG_S a1, 0(t0) - REG_S a1, SZREG(t0) - REG_S a1, 2*SZREG(t0) - REG_S a1, 3*SZREG(t0) - REG_S a1, 4*SZREG(t0) - REG_S a1, 5*SZREG(t0) - REG_S a1, 6*SZREG(t0) - REG_S a1, 7*SZREG(t0) - REG_S a1, 8*SZREG(t0) - REG_S a1, 9*SZREG(t0) - REG_S a1, 10*SZREG(t0) - REG_S a1, 11*SZREG(t0) - REG_S a1, 12*SZREG(t0) - REG_S a1, 13*SZREG(t0) - REG_S a1, 14*SZREG(t0) - REG_S a1, 15*SZREG(t0) - REG_S a1, 16*SZREG(t0) - REG_S a1, 17*SZREG(t0) - REG_S a1, 18*SZREG(t0) - REG_S a1, 19*SZREG(t0) - REG_S a1, 20*SZREG(t0) - REG_S a1, 21*SZREG(t0) - REG_S a1, 22*SZREG(t0) - REG_S a1, 23*SZREG(t0) - REG_S a1, 24*SZREG(t0) - REG_S a1, 25*SZREG(t0) - REG_S a1, 26*SZREG(t0) - REG_S a1, 27*SZREG(t0) - REG_S a1, 28*SZREG(t0) - REG_S a1, 29*SZREG(t0) - REG_S a1, 30*SZREG(t0) - REG_S a1, 31*SZREG(t0) - addi t0, t0, 32*SZREG - bltu t0, a3, 3b - andi a2, a2, SZREG-1 /* Update count */ - -4: - /* Handle trailing misalignment */ - beqz a2, 6f - add a3, t0, a2 -5: - sb a1, 0(t0) - addi t0, t0, 1 - bltu t0, a3, 5b -6: - ret -END(__memset) diff --git a/arch/riscv/lib/string.c b/arch/riscv/lib/string.c index bd52581e2068c..7fc9ec5c26a79 100644 --- a/arch/riscv/lib/string.c +++ b/arch/riscv/lib/string.c @@ -112,3 +112,44 @@ EXPORT_SYMBOL(__memmove); void *memmove(void *dest, const void *src, size_t count) __weak __alias(__memmove); EXPORT_SYMBOL(memmove); + +void *__memset(void *s, int c, size_t count) +{ + union types dest = { .as_u8 = s }; + + if (count >= MIN_THRESHOLD) { + unsigned long cu = (unsigned long)c; + + /* Compose an ulong with 'c' repeated 4/8 times */ +#ifdef CONFIG_ARCH_HAS_FAST_MULTIPLIER + cu *= 0x0101010101010101UL; +#else + cu |= cu << 8; + cu |= cu << 16; + /* Suppress warning on 32 bit machines */ + cu |= (cu << 16) << 16; +#endif + if (!IS_ENABLED(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS)) { + /* + * Fill the buffer one byte at time until + * the destination is word aligned. + */ + for (; count && dest.as_uptr & WORD_MASK; count--) + *dest.as_u8++ = c; + } + + /* Copy using the largest size allowed */ + for (; count >= BYTES_LONG; count -= BYTES_LONG) + *dest.as_ulong++ = cu; + } + + /* copy the remainder */ + while (count--) + *dest.as_u8++ = c; + + return s; +} +EXPORT_SYMBOL(__memset); + +void *memset(void *s, int c, size_t count) __weak __alias(__memset); +EXPORT_SYMBOL(memset); diff --git a/arch/riscv/purgatory/Makefile b/arch/riscv/purgatory/Makefile index e2a020e69f083..239da129784b2 100644 --- a/arch/riscv/purgatory/Makefile +++ b/arch/riscv/purgatory/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 OBJECT_FILES_NON_STANDARD := y -purgatory-y := purgatory.o sha256.o entry.o string.o ctype.o rvstring.o memset.o +purgatory-y := purgatory.o sha256.o entry.o string.o ctype.o rvstring.o targets += $(purgatory-y) PURGATORY_OBJS = $(addprefix $(obj)/,$(purgatory-y)) @@ -15,9 +15,6 @@ $(obj)/ctype.o: $(srctree)/lib/ctype.c FORCE $(obj)/rvstring.o: $(srctree)/arch/riscv/lib/string.c FORCE $(call if_changed_rule,cc_o_c) -$(obj)/memset.o: $(srctree)/arch/riscv/lib/memset.S FORCE - $(call if_changed_rule,as_o_S) - $(obj)/sha256.o: $(srctree)/lib/crypto/sha256.c FORCE $(call if_changed_rule,cc_o_c) From e703f92f8b44b2c4d04b8f9e7f87880c49d558b5 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Thu, 25 Nov 2021 14:21:18 +0100 Subject: [PATCH 05/52] riscv: dts: starfive: Group tuples in interrupt properties To improve human readability and enable automatic validation, the tuples in the various properties containing interrupt specifiers should be grouped. Fix this by grouping the tuples of "interrupts-extended" properties using angle brackets. Signed-off-by: Geert Uytterhoeven --- arch/riscv/boot/dts/starfive/jh7100.dtsi | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/arch/riscv/boot/dts/starfive/jh7100.dtsi b/arch/riscv/boot/dts/starfive/jh7100.dtsi index 69f22f9aad9db..d74fc29af6428 100644 --- a/arch/riscv/boot/dts/starfive/jh7100.dtsi +++ b/arch/riscv/boot/dts/starfive/jh7100.dtsi @@ -106,15 +106,15 @@ clint: clint@2000000 { compatible = "starfive,jh7100-clint", "sifive,clint0"; reg = <0x0 0x2000000 0x0 0x10000>; - interrupts-extended = <&cpu0_intc 3 &cpu0_intc 7 - &cpu1_intc 3 &cpu1_intc 7>; + interrupts-extended = <&cpu0_intc 3>, <&cpu0_intc 7>, + <&cpu1_intc 3>, <&cpu1_intc 7>; }; plic: interrupt-controller@c000000 { compatible = "starfive,jh7100-plic", "sifive,plic-1.0.0"; reg = <0x0 0xc000000 0x0 0x4000000>; - interrupts-extended = <&cpu0_intc 11 &cpu0_intc 9 - &cpu1_intc 11 &cpu1_intc 9>; + interrupts-extended = <&cpu0_intc 11>, <&cpu0_intc 9>, + <&cpu1_intc 11>, <&cpu1_intc 9>; interrupt-controller; #address-cells = <0>; #interrupt-cells = <1>; From 9b60ac869dafcb48499e0332de07c419ccc45080 Mon Sep 17 00:00:00 2001 From: Mark Kettenis Date: Mon, 6 Jun 2022 18:29:23 +0200 Subject: [PATCH 06/52] riscv: dts: starfive: correct number of external interrupts The PLIC integrated on the Vic_U7_Core integrated on the StarFive JH7100 SoC actually supports 133 external interrupts. 127 of these are exposed to the outside world; the remainder are used by other devices that are part of the core-complex such as the L2 cache controller. But all 133 interrupts are external interrupts as far as the PLIC is concerned. Fix the property so that the driver can manage these additional external interrupts, which is important since the interrupts for the L2 cache controller are enabled by default. Fixes: ec85362fb121 ("RISC-V: Add initial StarFive JH7100 device tree") Signed-off-by: Mark Kettenis Link: https://lore.kernel.org/r/20220606162924.71418-1-kettenis@openbsd.org --- arch/riscv/boot/dts/starfive/jh7100.dtsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/riscv/boot/dts/starfive/jh7100.dtsi b/arch/riscv/boot/dts/starfive/jh7100.dtsi index d74fc29af6428..ae422021e1ddd 100644 --- a/arch/riscv/boot/dts/starfive/jh7100.dtsi +++ b/arch/riscv/boot/dts/starfive/jh7100.dtsi @@ -118,7 +118,7 @@ interrupt-controller; #address-cells = <0>; #interrupt-cells = <1>; - riscv,ndev = <127>; + riscv,ndev = <133>; }; clkgen: clock-controller@11800000 { From 2822e5ceb25685d1d5bd13b8e3dd0118718aae18 Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Sat, 20 Nov 2021 17:13:22 +0100 Subject: [PATCH 07/52] RISC-V: Add StarFive JH7100 audio clock node Add device tree node for the audio clocks on the StarFive JH7100 RISC-V SoC. Signed-off-by: Emil Renner Berthing --- arch/riscv/boot/dts/starfive/jh7100.dtsi | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/arch/riscv/boot/dts/starfive/jh7100.dtsi b/arch/riscv/boot/dts/starfive/jh7100.dtsi index ae422021e1ddd..891c3acf56419 100644 --- a/arch/riscv/boot/dts/starfive/jh7100.dtsi +++ b/arch/riscv/boot/dts/starfive/jh7100.dtsi @@ -121,6 +121,16 @@ riscv,ndev = <133>; }; + audclk: clock-controller@10480000 { + compatible = "starfive,jh7100-audclk"; + reg = <0x0 0x10480000 0x0 0x10000>; + clocks = <&clkgen JH7100_CLK_AUDIO_SRC>, + <&clkgen JH7100_CLK_AUDIO_12288>, + <&clkgen JH7100_CLK_DOM7AHB_BUS>; + clock-names = "audio_src", "audio_12288", "dom7ahb_bus"; + #clock-cells = <1>; + }; + clkgen: clock-controller@11800000 { compatible = "starfive,jh7100-clkgen"; reg = <0x0 0x11800000 0x0 0x10000>; From d14939a100cb5863a7a48c37fb0eab682bd0f086 Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Sat, 20 Nov 2021 19:29:25 +0100 Subject: [PATCH 08/52] dt-bindings: reset: Add StarFive JH7100 audio reset definitions Add all resets for the StarFive JH7100 audio reset controller. Signed-off-by: Emil Renner Berthing --- .../dt-bindings/reset/starfive-jh7100-audio.h | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 include/dt-bindings/reset/starfive-jh7100-audio.h diff --git a/include/dt-bindings/reset/starfive-jh7100-audio.h b/include/dt-bindings/reset/starfive-jh7100-audio.h new file mode 100644 index 0000000000000..30e3d4cf067a6 --- /dev/null +++ b/include/dt-bindings/reset/starfive-jh7100-audio.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0 OR MIT */ +/* + * Copyright (C) 2021 Emil Renner Berthing + */ + +#ifndef __DT_BINDINGS_RESET_STARFIVE_JH7100_AUDIO_H__ +#define __DT_BINDINGS_RESET_STARFIVE_JH7100_AUDIO_H__ + +#define JH7100_AUDRSTN_APB_BUS 0 +#define JH7100_AUDRSTN_I2SADC_APB 1 +#define JH7100_AUDRSTN_I2SADC_SRST 2 +#define JH7100_AUDRSTN_PDM_APB 3 +#define JH7100_AUDRSTN_I2SVAD_APB 4 +#define JH7100_AUDRSTN_I2SVAD_SRST 5 +#define JH7100_AUDRSTN_SPDIF_APB 6 +#define JH7100_AUDRSTN_PWMDAC_APB 7 +#define JH7100_AUDRSTN_I2SDAC_APB 8 +#define JH7100_AUDRSTN_I2SDAC_SRST 9 +#define JH7100_AUDRSTN_I2S1_APB 10 +#define JH7100_AUDRSTN_I2S1_SRST 11 +#define JH7100_AUDRSTN_I2SDAC16K_APB 12 +#define JH7100_AUDRSTN_I2SDAC16K_SRST 13 +#define JH7100_AUDRSTN_DMA1P_AHB 14 +#define JH7100_AUDRSTN_USB_APB 15 +#define JH7100_AUDRST_USB_AXI 16 +#define JH7100_AUDRST_USB_PWRUP_RST_N 17 +#define JH7100_AUDRST_USB_PONRST 18 + +#define JH7100_AUDRSTN_END 19 + +#endif /* __DT_BINDINGS_RESET_STARFIVE_JH7100_AUDIO_H__ */ From c31244883d66a4de148a40376ba50d5c6dff1345 Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Tue, 7 Dec 2021 21:48:51 +0100 Subject: [PATCH 09/52] dt-bindings: reset: Add starfive,jh7100-audrst bindings Add bindings for the audio reset controller on the StarFive JH7100 RISC-V SoC. Signed-off-by: Emil Renner Berthing --- .../reset/starfive,jh7100-audrst.yaml | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 Documentation/devicetree/bindings/reset/starfive,jh7100-audrst.yaml diff --git a/Documentation/devicetree/bindings/reset/starfive,jh7100-audrst.yaml b/Documentation/devicetree/bindings/reset/starfive,jh7100-audrst.yaml new file mode 100644 index 0000000000000..6ed85cc84c955 --- /dev/null +++ b/Documentation/devicetree/bindings/reset/starfive,jh7100-audrst.yaml @@ -0,0 +1,38 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/reset/starfive,jh7100-audrst.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: StarFive JH7100 SoC Audio Reset Controller Device Tree Bindings + +maintainers: + - Emil Renner Berthing + +properties: + compatible: + enum: + - starfive,jh7100-audrst + + reg: + maxItems: 1 + + "#reset-cells": + const: 1 + +required: + - compatible + - reg + - "#reset-cells" + +additionalProperties: false + +examples: + - | + reset-controller@10490000 { + compatible = "starfive,jh7100-audrst"; + reg = <0x10490000 0x10000>; + #reset-cells = <1>; + }; + +... From dbe6e8e7aa9f8acc18cb20adb7320ca106c2213c Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Sat, 20 Nov 2021 18:30:33 +0100 Subject: [PATCH 10/52] reset: Create subdirectory for StarFive drivers This moves the StarFive JH7100 reset driver to a new subdirectory in preparation for adding more StarFive reset drivers. Signed-off-by: Emil Renner Berthing --- MAINTAINERS | 2 +- drivers/reset/Kconfig | 8 +------- drivers/reset/Makefile | 2 +- drivers/reset/starfive/Kconfig | 8 ++++++++ drivers/reset/starfive/Makefile | 2 ++ drivers/reset/{ => starfive}/reset-starfive-jh7100.c | 0 6 files changed, 13 insertions(+), 9 deletions(-) create mode 100644 drivers/reset/starfive/Kconfig create mode 100644 drivers/reset/starfive/Makefile rename drivers/reset/{ => starfive}/reset-starfive-jh7100.c (100%) diff --git a/MAINTAINERS b/MAINTAINERS index 64379c699903b..19daf0d23dedc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19123,7 +19123,7 @@ STARFIVE JH7100 RESET CONTROLLER DRIVER M: Emil Renner Berthing S: Maintained F: Documentation/devicetree/bindings/reset/starfive,jh7100-reset.yaml -F: drivers/reset/reset-starfive-jh7100.c +F: drivers/reset/starfive/reset-starfive-jh7100.c F: include/dt-bindings/reset/starfive-jh7100.h STATIC BRANCH/CALL diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig index 93c8d07ee3280..7e73a3a91c33c 100644 --- a/drivers/reset/Kconfig +++ b/drivers/reset/Kconfig @@ -224,13 +224,6 @@ config RESET_SOCFPGA This enables the reset driver for the SoCFPGA ARMv7 platforms. This driver gets initialized early during platform init calls. -config RESET_STARFIVE_JH7100 - bool "StarFive JH7100 Reset Driver" - depends on SOC_STARFIVE || COMPILE_TEST - default SOC_STARFIVE - help - This enables the reset controller driver for the StarFive JH7100 SoC. - config RESET_SUNXI bool "Allwinner SoCs Reset Driver" if COMPILE_TEST && !ARCH_SUNXI default ARCH_SUNXI @@ -295,6 +288,7 @@ config RESET_ZYNQ help This enables the reset controller driver for Xilinx Zynq SoCs. +source "drivers/reset/starfive/Kconfig" source "drivers/reset/sti/Kconfig" source "drivers/reset/hisilicon/Kconfig" source "drivers/reset/tegra/Kconfig" diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile index a80a9c4008a78..c27952b279bb2 100644 --- a/drivers/reset/Makefile +++ b/drivers/reset/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 obj-y += core.o obj-y += hisilicon/ +obj-y += starfive/ obj-$(CONFIG_ARCH_STI) += sti/ obj-$(CONFIG_ARCH_TEGRA) += tegra/ obj-$(CONFIG_RESET_A10SR) += reset-a10sr.o @@ -29,7 +30,6 @@ obj-$(CONFIG_RESET_RZG2L_USBPHY_CTRL) += reset-rzg2l-usbphy-ctrl.o obj-$(CONFIG_RESET_SCMI) += reset-scmi.o obj-$(CONFIG_RESET_SIMPLE) += reset-simple.o obj-$(CONFIG_RESET_SOCFPGA) += reset-socfpga.o -obj-$(CONFIG_RESET_STARFIVE_JH7100) += reset-starfive-jh7100.o obj-$(CONFIG_RESET_SUNXI) += reset-sunxi.o obj-$(CONFIG_RESET_TI_SCI) += reset-ti-sci.o obj-$(CONFIG_RESET_TI_SYSCON) += reset-ti-syscon.o diff --git a/drivers/reset/starfive/Kconfig b/drivers/reset/starfive/Kconfig new file mode 100644 index 0000000000000..cddebdba71773 --- /dev/null +++ b/drivers/reset/starfive/Kconfig @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config RESET_STARFIVE_JH7100 + bool "StarFive JH7100 Reset Driver" + depends on SOC_STARFIVE || COMPILE_TEST + default SOC_STARFIVE + help + This enables the reset controller driver for the StarFive JH7100 SoC. diff --git a/drivers/reset/starfive/Makefile b/drivers/reset/starfive/Makefile new file mode 100644 index 0000000000000..670d049423f54 --- /dev/null +++ b/drivers/reset/starfive/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_RESET_STARFIVE_JH7100) += reset-starfive-jh7100.o diff --git a/drivers/reset/reset-starfive-jh7100.c b/drivers/reset/starfive/reset-starfive-jh7100.c similarity index 100% rename from drivers/reset/reset-starfive-jh7100.c rename to drivers/reset/starfive/reset-starfive-jh7100.c From fb6a913f4e022c4006774c08d53959cb13adfa86 Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Wed, 24 Nov 2021 01:30:54 +0100 Subject: [PATCH 11/52] reset: starfive: Use 32bit I/O on 32bit registers The driver currently uses 64bit I/O on the 32bit registers. This works because there are 4 assert registers and 4 status register, so they're only ever accessed on 64bit boundaries. There are however other reset controllers for audio and video on the SoC with only one status register that isn't 64bit aligned so 64bit I/O would result in an unaligned access exception. Switch to 32bit I/O in preparation for supporting these resets too. Signed-off-by: Emil Renner Berthing --- .../reset/starfive/reset-starfive-jh7100.c | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/drivers/reset/starfive/reset-starfive-jh7100.c b/drivers/reset/starfive/reset-starfive-jh7100.c index fc44b2fb3e031..7563d317f5c8e 100644 --- a/drivers/reset/starfive/reset-starfive-jh7100.c +++ b/drivers/reset/starfive/reset-starfive-jh7100.c @@ -34,16 +34,16 @@ * lines don't though, so store the expected value of the status registers when * all lines are asserted. */ -static const u64 jh7100_reset_asserted[2] = { +static const u32 jh7100_reset_asserted[4] = { /* STATUS0 */ - BIT_ULL_MASK(JH7100_RST_U74) | - BIT_ULL_MASK(JH7100_RST_VP6_DRESET) | - BIT_ULL_MASK(JH7100_RST_VP6_BRESET) | + BIT(JH7100_RST_U74 % 32) | + BIT(JH7100_RST_VP6_DRESET % 32) | + BIT(JH7100_RST_VP6_BRESET % 32), /* STATUS1 */ - BIT_ULL_MASK(JH7100_RST_HIFI4_DRESET) | - BIT_ULL_MASK(JH7100_RST_HIFI4_BRESET), + BIT(JH7100_RST_HIFI4_DRESET % 32) | + BIT(JH7100_RST_HIFI4_BRESET % 32), /* STATUS2 */ - BIT_ULL_MASK(JH7100_RST_E24) | + BIT(JH7100_RST_E24 % 32), /* STATUS3 */ 0, }; @@ -65,12 +65,12 @@ static int jh7100_reset_update(struct reset_controller_dev *rcdev, unsigned long id, bool assert) { struct jh7100_reset *data = jh7100_reset_from(rcdev); - unsigned long offset = BIT_ULL_WORD(id); - u64 mask = BIT_ULL_MASK(id); - void __iomem *reg_assert = data->base + JH7100_RESET_ASSERT0 + offset * sizeof(u64); - void __iomem *reg_status = data->base + JH7100_RESET_STATUS0 + offset * sizeof(u64); - u64 done = jh7100_reset_asserted[offset] & mask; - u64 value; + unsigned long offset = id / 32; + u32 mask = BIT(id % 32); + void __iomem *reg_assert = data->base + JH7100_RESET_ASSERT0 + offset * sizeof(u32); + void __iomem *reg_status = data->base + JH7100_RESET_STATUS0 + offset * sizeof(u32); + u32 done = jh7100_reset_asserted[offset] & mask; + u32 value; unsigned long flags; int ret; @@ -79,15 +79,15 @@ static int jh7100_reset_update(struct reset_controller_dev *rcdev, spin_lock_irqsave(&data->lock, flags); - value = readq(reg_assert); + value = readl(reg_assert); if (assert) value |= mask; else value &= ~mask; - writeq(value, reg_assert); + writel(value, reg_assert); /* if the associated clock is gated, deasserting might otherwise hang forever */ - ret = readq_poll_timeout_atomic(reg_status, value, (value & mask) == done, 0, 1000); + ret = readl_poll_timeout_atomic(reg_status, value, (value & mask) == done, 0, 1000); spin_unlock_irqrestore(&data->lock, flags); return ret; @@ -121,10 +121,10 @@ static int jh7100_reset_status(struct reset_controller_dev *rcdev, unsigned long id) { struct jh7100_reset *data = jh7100_reset_from(rcdev); - unsigned long offset = BIT_ULL_WORD(id); - u64 mask = BIT_ULL_MASK(id); - void __iomem *reg_status = data->base + JH7100_RESET_STATUS0 + offset * sizeof(u64); - u64 value = readq(reg_status); + unsigned long offset = id / 32; + u32 mask = BIT(id % 32); + void __iomem *reg_status = data->base + JH7100_RESET_STATUS0 + offset * sizeof(u32); + u32 value = readl(reg_status); return !((value ^ jh7100_reset_asserted[offset]) & mask); } From 642cf0d834adeba31968ed04c8f1cbe643559c08 Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Sat, 20 Nov 2021 19:30:49 +0100 Subject: [PATCH 12/52] reset: starfive: Add JH7100 audio reset driver The audio resets are almost identical to the system resets, there are just fewer of them. So factor out and export a generic probe function, so most of the reset controller implementation can be shared. Signed-off-by: Emil Renner Berthing --- MAINTAINERS | 8 +-- drivers/reset/starfive/Kconfig | 7 +++ drivers/reset/starfive/Makefile | 1 + .../starfive/reset-starfive-jh7100-audio.c | 58 +++++++++++++++++++ .../reset/starfive/reset-starfive-jh7100.c | 37 ++++++++---- .../reset/starfive/reset-starfive-jh7100.h | 16 +++++ 6 files changed, 112 insertions(+), 15 deletions(-) create mode 100644 drivers/reset/starfive/reset-starfive-jh7100-audio.c create mode 100644 drivers/reset/starfive/reset-starfive-jh7100.h diff --git a/MAINTAINERS b/MAINTAINERS index 19daf0d23dedc..143102427e20e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19119,12 +19119,12 @@ F: Documentation/devicetree/bindings/pinctrl/starfive,jh7100-pinctrl.yaml F: drivers/pinctrl/pinctrl-starfive.c F: include/dt-bindings/pinctrl/pinctrl-starfive.h -STARFIVE JH7100 RESET CONTROLLER DRIVER +STARFIVE JH7100 RESET CONTROLLER DRIVERS M: Emil Renner Berthing S: Maintained -F: Documentation/devicetree/bindings/reset/starfive,jh7100-reset.yaml -F: drivers/reset/starfive/reset-starfive-jh7100.c -F: include/dt-bindings/reset/starfive-jh7100.h +F: Documentation/devicetree/bindings/reset/starfive,jh7100-*.yaml +F: drivers/reset/starfive/reset-starfive-jh7100* +F: include/dt-bindings/reset/starfive-jh7100*.h STATIC BRANCH/CALL M: Peter Zijlstra diff --git a/drivers/reset/starfive/Kconfig b/drivers/reset/starfive/Kconfig index cddebdba71773..5f88d3792d960 100644 --- a/drivers/reset/starfive/Kconfig +++ b/drivers/reset/starfive/Kconfig @@ -6,3 +6,10 @@ config RESET_STARFIVE_JH7100 default SOC_STARFIVE help This enables the reset controller driver for the StarFive JH7100 SoC. + +config RESET_STARFIVE_JH7100_AUDIO + tristate "StarFive JH7100 Audio Reset Driver" + depends on RESET_STARFIVE_JH7100 + default m if SOC_STARFIVE + help + This enables the audio reset driver for the StarFive JH7100 SoC. diff --git a/drivers/reset/starfive/Makefile b/drivers/reset/starfive/Makefile index 670d049423f54..d3a55a75dd0f8 100644 --- a/drivers/reset/starfive/Makefile +++ b/drivers/reset/starfive/Makefile @@ -1,2 +1,3 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_RESET_STARFIVE_JH7100) += reset-starfive-jh7100.o +obj-$(CONFIG_RESET_STARFIVE_JH7100_AUDIO) += reset-starfive-jh7100-audio.o diff --git a/drivers/reset/starfive/reset-starfive-jh7100-audio.c b/drivers/reset/starfive/reset-starfive-jh7100-audio.c new file mode 100644 index 0000000000000..bae8f8c90d281 --- /dev/null +++ b/drivers/reset/starfive/reset-starfive-jh7100-audio.c @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Audio reset driver for the StarFive JH7100 SoC + * + * Copyright (C) 2021 Emil Renner Berthing + */ + +#include +#include +#include +#include + +#include + +#include "reset-starfive-jh7100.h" + +/* register offsets */ +#define JH7100_AUDRST_ASSERT0 0x00 +#define JH7100_AUDRST_STATUS0 0x04 + +/* + * Writing a 1 to the n'th bit of the ASSERT register asserts + * line n, and writing a 0 deasserts the same line. + * Most reset lines have their status inverted so a 0 bit in the STATUS + * register means the line is asserted and a 1 means it's deasserted. A few + * lines don't though, so store the expected value of the status registers when + * all lines are asserted. + */ +static const u32 jh7100_audrst_asserted[1] = { + BIT(JH7100_AUDRST_USB_AXI) | + BIT(JH7100_AUDRST_USB_PWRUP_RST_N) | + BIT(JH7100_AUDRST_USB_PONRST) +}; + +static int jh7100_audrst_probe(struct platform_device *pdev) +{ + return reset_starfive_jh7100_generic_probe(pdev, jh7100_audrst_asserted, + JH7100_AUDRST_STATUS0, JH7100_AUDRSTN_END); +} + +static const struct of_device_id jh7100_audrst_dt_ids[] = { + { .compatible = "starfive,jh7100-audrst" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, jh7100_audrst_dt_ids); + +static struct platform_driver jh7100_audrst_driver = { + .probe = jh7100_audrst_probe, + .driver = { + .name = "jh7100-reset-audio", + .of_match_table = jh7100_audrst_dt_ids, + }, +}; +module_platform_driver(jh7100_audrst_driver); + +MODULE_AUTHOR("Emil Renner Berthing"); +MODULE_DESCRIPTION("StarFive JH7100 audio reset driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/reset/starfive/reset-starfive-jh7100.c b/drivers/reset/starfive/reset-starfive-jh7100.c index 7563d317f5c8e..8e8733908c702 100644 --- a/drivers/reset/starfive/reset-starfive-jh7100.c +++ b/drivers/reset/starfive/reset-starfive-jh7100.c @@ -16,6 +16,8 @@ #include +#include "reset-starfive-jh7100.h" + /* register offsets */ #define JH7100_RESET_ASSERT0 0x00 #define JH7100_RESET_ASSERT1 0x04 @@ -52,7 +54,9 @@ struct jh7100_reset { struct reset_controller_dev rcdev; /* protect registers against concurrent read-modify-write */ spinlock_t lock; - void __iomem *base; + void __iomem *assert; + void __iomem *status; + const u32 *asserted; }; static inline struct jh7100_reset * @@ -67,9 +71,9 @@ static int jh7100_reset_update(struct reset_controller_dev *rcdev, struct jh7100_reset *data = jh7100_reset_from(rcdev); unsigned long offset = id / 32; u32 mask = BIT(id % 32); - void __iomem *reg_assert = data->base + JH7100_RESET_ASSERT0 + offset * sizeof(u32); - void __iomem *reg_status = data->base + JH7100_RESET_STATUS0 + offset * sizeof(u32); - u32 done = jh7100_reset_asserted[offset] & mask; + void __iomem *reg_assert = data->assert + offset * sizeof(u32); + void __iomem *reg_status = data->status + offset * sizeof(u32); + u32 done = data->asserted[offset] & mask; u32 value; unsigned long flags; int ret; @@ -123,10 +127,10 @@ static int jh7100_reset_status(struct reset_controller_dev *rcdev, struct jh7100_reset *data = jh7100_reset_from(rcdev); unsigned long offset = id / 32; u32 mask = BIT(id % 32); - void __iomem *reg_status = data->base + JH7100_RESET_STATUS0 + offset * sizeof(u32); + void __iomem *reg_status = data->status + offset * sizeof(u32); u32 value = readl(reg_status); - return !((value ^ jh7100_reset_asserted[offset]) & mask); + return !((value ^ data->asserted[offset]) & mask); } static const struct reset_control_ops jh7100_reset_ops = { @@ -136,7 +140,8 @@ static const struct reset_control_ops jh7100_reset_ops = { .status = jh7100_reset_status, }; -static int __init jh7100_reset_probe(struct platform_device *pdev) +int reset_starfive_jh7100_generic_probe(struct platform_device *pdev, const u32 *asserted, + unsigned int status_offset, unsigned int nr_resets) { struct jh7100_reset *data; @@ -144,19 +149,29 @@ static int __init jh7100_reset_probe(struct platform_device *pdev) if (!data) return -ENOMEM; - data->base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(data->base)) - return PTR_ERR(data->base); + data->assert = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(data->assert)) + return PTR_ERR(data->assert); data->rcdev.ops = &jh7100_reset_ops; data->rcdev.owner = THIS_MODULE; - data->rcdev.nr_resets = JH7100_RSTN_END; + data->rcdev.nr_resets = nr_resets; data->rcdev.dev = &pdev->dev; data->rcdev.of_node = pdev->dev.of_node; + spin_lock_init(&data->lock); + data->status = data->assert + status_offset; + data->asserted = asserted; return devm_reset_controller_register(&pdev->dev, &data->rcdev); } +EXPORT_SYMBOL_GPL(reset_starfive_jh7100_generic_probe); + +static int __init jh7100_reset_probe(struct platform_device *pdev) +{ + return reset_starfive_jh7100_generic_probe(pdev, jh7100_reset_asserted, + JH7100_RESET_STATUS0, JH7100_RSTN_END); +} static const struct of_device_id jh7100_reset_dt_ids[] = { { .compatible = "starfive,jh7100-reset" }, diff --git a/drivers/reset/starfive/reset-starfive-jh7100.h b/drivers/reset/starfive/reset-starfive-jh7100.h new file mode 100644 index 0000000000000..ee8f3e3b1644d --- /dev/null +++ b/drivers/reset/starfive/reset-starfive-jh7100.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 Emil Renner Berthing + */ + +#ifndef _RESET_STARFIVE_JH7100_H_ +#define _RESET_STARFIVE_JH7100_H_ + +#include + +int reset_starfive_jh7100_generic_probe(struct platform_device *pdev, + const u32 *asserted, + unsigned int status_offset, + unsigned int nr_resets); + +#endif From 2ad48f84e2ee5c9714062236b16e7114c1735f16 Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Sat, 20 Nov 2021 21:33:08 +0100 Subject: [PATCH 13/52] RISC-V: Add StarFive JH7100 audio reset node Add device tree node for the audio resets on the StarFive JH7100 RISC-V SoC. Signed-off-by: Emil Renner Berthing --- arch/riscv/boot/dts/starfive/jh7100.dtsi | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arch/riscv/boot/dts/starfive/jh7100.dtsi b/arch/riscv/boot/dts/starfive/jh7100.dtsi index 891c3acf56419..247e3864d2041 100644 --- a/arch/riscv/boot/dts/starfive/jh7100.dtsi +++ b/arch/riscv/boot/dts/starfive/jh7100.dtsi @@ -131,6 +131,12 @@ #clock-cells = <1>; }; + audrst: reset-controller@10490000 { + compatible = "starfive,jh7100-audrst"; + reg = <0x0 0x10490000 0x0 0x10000>; + #reset-cells = <1>; + }; + clkgen: clock-controller@11800000 { compatible = "starfive,jh7100-clkgen"; reg = <0x0 0x11800000 0x0 0x10000>; From 25fc503c967c661386be97fa1f8d17fbe16179ad Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Thu, 14 Oct 2021 20:35:43 +0200 Subject: [PATCH 14/52] clk: starfive: jh7100: Keep more clocks alive Signed-off-by: Emil Renner Berthing --- drivers/clk/starfive/clk-starfive-jh7100.c | 36 +++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/drivers/clk/starfive/clk-starfive-jh7100.c b/drivers/clk/starfive/clk-starfive-jh7100.c index 691aeebc70927..86d9d52ca114b 100644 --- a/drivers/clk/starfive/clk-starfive-jh7100.c +++ b/drivers/clk/starfive/clk-starfive-jh7100.c @@ -99,9 +99,9 @@ static const struct jh7100_clk_data jh7100_clk_data[] __initconst = { JH7100_GATE(JH7100_CLK_DMA2PNOC_AXI, "dma2pnoc_axi", 0, JH7100_CLK_CPU_AXI), JH7100_GATE(JH7100_CLK_SGDMA2P_AHB, "sgdma2p_ahb", 0, JH7100_CLK_AHB_BUS), JH7100__DIV(JH7100_CLK_DLA_BUS, "dla_bus", 4, JH7100_CLK_DLA_ROOT), - JH7100_GATE(JH7100_CLK_DLA_AXI, "dla_axi", 0, JH7100_CLK_DLA_BUS), - JH7100_GATE(JH7100_CLK_DLANOC_AXI, "dlanoc_axi", 0, JH7100_CLK_DLA_BUS), - JH7100_GATE(JH7100_CLK_DLA_APB, "dla_apb", 0, JH7100_CLK_APB1_BUS), + JH7100_GATE(JH7100_CLK_DLA_AXI, "dla_axi", CLK_IGNORE_UNUSED, JH7100_CLK_DLA_BUS), + JH7100_GATE(JH7100_CLK_DLANOC_AXI, "dlanoc_axi", CLK_IGNORE_UNUSED, JH7100_CLK_DLA_BUS), + JH7100_GATE(JH7100_CLK_DLA_APB, "dla_apb", CLK_IGNORE_UNUSED, JH7100_CLK_APB1_BUS), JH7100_GDIV(JH7100_CLK_VP6_CORE, "vp6_core", 0, 4, JH7100_CLK_DSP_ROOT_DIV), JH7100__DIV(JH7100_CLK_VP6BUS_SRC, "vp6bus_src", 4, JH7100_CLK_DSP_ROOT), JH7100_GDIV(JH7100_CLK_VP6_AXI, "vp6_axi", 0, 4, JH7100_CLK_VP6BUS_SRC), @@ -163,11 +163,11 @@ static const struct jh7100_clk_data jh7100_clk_data[] __initconst = { JH7100_GATE(JH7100_CLK_DMA1P_AXI, "dma1p_axi", 0, JH7100_CLK_SGDMA1P_BUS), JH7100_GDIV(JH7100_CLK_X2C_AXI, "x2c_axi", CLK_IS_CRITICAL, 8, JH7100_CLK_CPUNBUS_ROOT_DIV), JH7100__DIV(JH7100_CLK_USB_BUS, "usb_bus", 8, JH7100_CLK_CPUNBUS_ROOT_DIV), - JH7100_GATE(JH7100_CLK_USB_AXI, "usb_axi", 0, JH7100_CLK_USB_BUS), - JH7100_GATE(JH7100_CLK_USBNOC_AXI, "usbnoc_axi", 0, JH7100_CLK_USB_BUS), + JH7100_GATE(JH7100_CLK_USB_AXI, "usb_axi", CLK_IGNORE_UNUSED, JH7100_CLK_USB_BUS), + JH7100_GATE(JH7100_CLK_USBNOC_AXI, "usbnoc_axi", CLK_IGNORE_UNUSED, JH7100_CLK_USB_BUS), JH7100__DIV(JH7100_CLK_USBPHY_ROOTDIV, "usbphy_rootdiv", 4, JH7100_CLK_GMACUSB_ROOT), - JH7100_GDIV(JH7100_CLK_USBPHY_125M, "usbphy_125m", 0, 8, JH7100_CLK_USBPHY_ROOTDIV), - JH7100_GDIV(JH7100_CLK_USBPHY_PLLDIV25M, "usbphy_plldiv25m", 0, 32, JH7100_CLK_USBPHY_ROOTDIV), + JH7100_GDIV(JH7100_CLK_USBPHY_125M, "usbphy_125m", CLK_IGNORE_UNUSED, 8, JH7100_CLK_USBPHY_ROOTDIV), + JH7100_GDIV(JH7100_CLK_USBPHY_PLLDIV25M, "usbphy_plldiv25m", CLK_IGNORE_UNUSED, 32, JH7100_CLK_USBPHY_ROOTDIV), JH7100__MUX(JH7100_CLK_USBPHY_25M, "usbphy_25m", 2, JH7100_CLK_OSC_SYS, JH7100_CLK_USBPHY_PLLDIV25M), @@ -185,23 +185,23 @@ static const struct jh7100_clk_data jh7100_clk_data[] __initconst = { JH7100__DIV(JH7100_CLK_VIN_BUS, "vin_bus", 8, JH7100_CLK_VIN_SRC), JH7100_GATE(JH7100_CLK_VIN_AXI, "vin_axi", 0, JH7100_CLK_VIN_BUS), JH7100_GATE(JH7100_CLK_VINNOC_AXI, "vinnoc_axi", 0, JH7100_CLK_VIN_BUS), - JH7100_GDIV(JH7100_CLK_VOUT_SRC, "vout_src", 0, 4, JH7100_CLK_VOUT_ROOT), + JH7100_GDIV(JH7100_CLK_VOUT_SRC, "vout_src", CLK_IGNORE_UNUSED, 4, JH7100_CLK_VOUT_ROOT), JH7100__DIV(JH7100_CLK_DISPBUS_SRC, "dispbus_src", 4, JH7100_CLK_VOUTBUS_ROOT), JH7100__DIV(JH7100_CLK_DISP_BUS, "disp_bus", 4, JH7100_CLK_DISPBUS_SRC), - JH7100_GATE(JH7100_CLK_DISP_AXI, "disp_axi", 0, JH7100_CLK_DISP_BUS), - JH7100_GATE(JH7100_CLK_DISPNOC_AXI, "dispnoc_axi", 0, JH7100_CLK_DISP_BUS), + JH7100_GATE(JH7100_CLK_DISP_AXI, "disp_axi", CLK_IGNORE_UNUSED, JH7100_CLK_DISP_BUS), + JH7100_GATE(JH7100_CLK_DISPNOC_AXI, "dispnoc_axi", CLK_IGNORE_UNUSED, JH7100_CLK_DISP_BUS), JH7100_GATE(JH7100_CLK_SDIO0_AHB, "sdio0_ahb", 0, JH7100_CLK_AHB_BUS), JH7100_GDIV(JH7100_CLK_SDIO0_CCLKINT, "sdio0_cclkint", 0, 24, JH7100_CLK_PERH0_SRC), JH7100__INV(JH7100_CLK_SDIO0_CCLKINT_INV, "sdio0_cclkint_inv", JH7100_CLK_SDIO0_CCLKINT), JH7100_GATE(JH7100_CLK_SDIO1_AHB, "sdio1_ahb", 0, JH7100_CLK_AHB_BUS), JH7100_GDIV(JH7100_CLK_SDIO1_CCLKINT, "sdio1_cclkint", 0, 24, JH7100_CLK_PERH1_SRC), JH7100__INV(JH7100_CLK_SDIO1_CCLKINT_INV, "sdio1_cclkint_inv", JH7100_CLK_SDIO1_CCLKINT), - JH7100_GATE(JH7100_CLK_GMAC_AHB, "gmac_ahb", 0, JH7100_CLK_AHB_BUS), + JH7100_GATE(JH7100_CLK_GMAC_AHB, "gmac_ahb", CLK_IGNORE_UNUSED, JH7100_CLK_AHB_BUS), JH7100__DIV(JH7100_CLK_GMAC_ROOT_DIV, "gmac_root_div", 8, JH7100_CLK_GMACUSB_ROOT), - JH7100_GDIV(JH7100_CLK_GMAC_PTP_REF, "gmac_ptp_refclk", 0, 31, JH7100_CLK_GMAC_ROOT_DIV), - JH7100_GDIV(JH7100_CLK_GMAC_GTX, "gmac_gtxclk", 0, 255, JH7100_CLK_GMAC_ROOT_DIV), - JH7100_GDIV(JH7100_CLK_GMAC_RMII_TX, "gmac_rmii_txclk", 0, 8, JH7100_CLK_GMAC_RMII_REF), - JH7100_GDIV(JH7100_CLK_GMAC_RMII_RX, "gmac_rmii_rxclk", 0, 8, JH7100_CLK_GMAC_RMII_REF), + JH7100_GDIV(JH7100_CLK_GMAC_PTP_REF, "gmac_ptp_refclk", CLK_IGNORE_UNUSED, 31, JH7100_CLK_GMAC_ROOT_DIV), + JH7100_GDIV(JH7100_CLK_GMAC_GTX, "gmac_gtxclk", CLK_IGNORE_UNUSED, 255, JH7100_CLK_GMAC_ROOT_DIV), + JH7100_GDIV(JH7100_CLK_GMAC_RMII_TX, "gmac_rmii_txclk", CLK_IGNORE_UNUSED, 8, JH7100_CLK_GMAC_RMII_REF), + JH7100_GDIV(JH7100_CLK_GMAC_RMII_RX, "gmac_rmii_rxclk", CLK_IGNORE_UNUSED, 8, JH7100_CLK_GMAC_RMII_REF), JH7100__MUX(JH7100_CLK_GMAC_TX, "gmac_tx", 3, JH7100_CLK_GMAC_GTX, JH7100_CLK_GMAC_TX_INV, @@ -211,8 +211,8 @@ static const struct jh7100_clk_data jh7100_clk_data[] __initconst = { JH7100_CLK_GMAC_GR_MII_RX, JH7100_CLK_GMAC_RMII_RX), JH7100__INV(JH7100_CLK_GMAC_RX_INV, "gmac_rx_inv", JH7100_CLK_GMAC_RX_PRE), - JH7100_GATE(JH7100_CLK_GMAC_RMII, "gmac_rmii", 0, JH7100_CLK_GMAC_RMII_REF), - JH7100_GDIV(JH7100_CLK_GMAC_TOPHYREF, "gmac_tophyref", 0, 127, JH7100_CLK_GMAC_ROOT_DIV), + JH7100_GATE(JH7100_CLK_GMAC_RMII, "gmac_rmii", CLK_IGNORE_UNUSED, JH7100_CLK_GMAC_RMII_REF), + JH7100_GDIV(JH7100_CLK_GMAC_TOPHYREF, "gmac_tophyref", CLK_IGNORE_UNUSED, 127, JH7100_CLK_GMAC_ROOT_DIV), JH7100_GATE(JH7100_CLK_SPI2AHB_AHB, "spi2ahb_ahb", 0, JH7100_CLK_AHB_BUS), JH7100_GDIV(JH7100_CLK_SPI2AHB_CORE, "spi2ahb_core", 0, 31, JH7100_CLK_PERH0_SRC), JH7100_GATE(JH7100_CLK_EZMASTER_AHB, "ezmaster_ahb", 0, JH7100_CLK_AHB_BUS), @@ -225,7 +225,7 @@ static const struct jh7100_clk_data jh7100_clk_data[] __initconst = { JH7100_GATE(JH7100_CLK_AES, "aes_clk", 0, JH7100_CLK_SEC_AHB), JH7100_GATE(JH7100_CLK_SHA, "sha_clk", 0, JH7100_CLK_SEC_AHB), JH7100_GATE(JH7100_CLK_PKA, "pka_clk", 0, JH7100_CLK_SEC_AHB), - JH7100_GATE(JH7100_CLK_TRNG_APB, "trng_apb", 0, JH7100_CLK_APB1_BUS), + JH7100_GATE(JH7100_CLK_TRNG_APB, "trng_apb", CLK_IGNORE_UNUSED, JH7100_CLK_APB1_BUS), JH7100_GATE(JH7100_CLK_OTP_APB, "otp_apb", 0, JH7100_CLK_APB1_BUS), JH7100_GATE(JH7100_CLK_UART0_APB, "uart0_apb", 0, JH7100_CLK_APB1_BUS), JH7100_GDIV(JH7100_CLK_UART0_CORE, "uart0_core", 0, 63, JH7100_CLK_PERH1_SRC), From a09b77c4f6f03eb0feb0757cbd51e7901731c341 Mon Sep 17 00:00:00 2001 From: Jianlong Huang Date: Wed, 18 May 2022 08:14:55 +0800 Subject: [PATCH 15/52] pinctrl: starfive: Serialize pinconf_generic calls The pinctrl dt_node_to_map method may be called in parallel which leads us to call pinconf_generic_add_group (and pinconf_generic_add_function) in parallel. This is not supported though and leads to errors, so add a mutex to serialize these calls per device. Signed-off-by: Jianlong Huang Signed-off-by: Emil Renner Berthing --- drivers/pinctrl/pinctrl-starfive.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/pinctrl/pinctrl-starfive.c b/drivers/pinctrl/pinctrl-starfive.c index 2a86c1035cc88..e9848e7cfcfe9 100644 --- a/drivers/pinctrl/pinctrl-starfive.c +++ b/drivers/pinctrl/pinctrl-starfive.c @@ -207,6 +207,7 @@ struct starfive_pinctrl { void __iomem *base; void __iomem *padctl; struct pinctrl_dev *pctl; + struct mutex mutex; }; static inline unsigned int starfive_pin_to_gpio(const struct starfive_pinctrl *sfp, @@ -522,6 +523,7 @@ static int starfive_dt_node_to_map(struct pinctrl_dev *pctldev, nmaps = 0; ngroups = 0; + mutex_lock(&sfp->mutex); for_each_child_of_node(np, child) { int npins; int i; @@ -615,12 +617,14 @@ static int starfive_dt_node_to_map(struct pinctrl_dev *pctldev, *maps = map; *num_maps = nmaps; + mutex_unlock(&sfp->mutex); return 0; put_child: of_node_put(child); free_map: pinctrl_utils_free_map(pctldev, map, nmaps); + mutex_unlock(&sfp->mutex); return ret; } @@ -1267,6 +1271,7 @@ static int starfive_probe(struct platform_device *pdev) platform_set_drvdata(pdev, sfp); sfp->gc.parent = dev; raw_spin_lock_init(&sfp->lock); + mutex_init(&sfp->mutex); ret = devm_pinctrl_register_and_init(dev, &starfive_desc, sfp, &sfp->pctl); if (ret) From c15315d20ff869d9d77fe8550fc7d346500d56db Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Sat, 17 Jul 2021 21:50:38 +0200 Subject: [PATCH 16/52] pinctrl: starfive: Reset pinmux settings Current u-boot doesn't seem to take into account that some GPIOs are configured as inputs/outputs of certain peripherals on power-up. This means it ends up configuring some GPIOs as inputs to more than one peripheral which the documentation explicitly says is illegal. Similarly it also ends up configuring more than one GPIO as output of the same peripheral. While not explicitly mentioned by the documentation this also seems like a bad idea. The easiest way to remedy this mess is to just disconnect all GPIOs from peripherals and have our pinmux configuration set everything up properly. This, however, means that we'd disconnect the serial console from its pins for a while, so add a device tree property to keep certain GPIOs from being reset. Signed-off-by: Emil Renner Berthing --- .../pinctrl/starfive,jh7100-pinctrl.yaml | 4 ++ drivers/pinctrl/pinctrl-starfive.c | 66 +++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/Documentation/devicetree/bindings/pinctrl/starfive,jh7100-pinctrl.yaml b/Documentation/devicetree/bindings/pinctrl/starfive,jh7100-pinctrl.yaml index 92963604422f4..3d89594928d78 100644 --- a/Documentation/devicetree/bindings/pinctrl/starfive,jh7100-pinctrl.yaml +++ b/Documentation/devicetree/bindings/pinctrl/starfive,jh7100-pinctrl.yaml @@ -88,6 +88,10 @@ properties: $ref: /schemas/types.yaml#/definitions/uint32 enum: [0, 1, 2, 3, 4, 5, 6] + starfive,keep-gpiomux: + description: Keep pinmux for these GPIOs from being reset at boot. + $ref: /schemas/types.yaml#/definitions/uint32-array + required: - compatible - reg diff --git a/drivers/pinctrl/pinctrl-starfive.c b/drivers/pinctrl/pinctrl-starfive.c index e9848e7cfcfe9..273a577a0e27b 100644 --- a/drivers/pinctrl/pinctrl-starfive.c +++ b/drivers/pinctrl/pinctrl-starfive.c @@ -200,6 +200,10 @@ static u16 starfive_drive_strength_from_max_mA(u32 i) return (clamp(i, 14U, 63U) - 14) / 7; } +static bool keepmux; +module_param(keepmux, bool, 0644); +MODULE_PARM_DESC(keepmux, "Keep pinmux settings from previous boot stage"); + struct starfive_pinctrl { struct gpio_chip gc; struct pinctrl_gpio_range gpios; @@ -1222,6 +1226,65 @@ static void starfive_disable_clock(void *data) clk_disable_unprepare(data); } +#define GPI_END (GPI_USB_OVER_CURRENT + 1) +static void starfive_pinmux_reset(struct starfive_pinctrl *sfp) +{ + static const DECLARE_BITMAP(defaults, GPI_END) = { + BIT_MASK(GPI_I2C0_PAD_SCK_IN) | + BIT_MASK(GPI_I2C0_PAD_SDA_IN) | + BIT_MASK(GPI_I2C1_PAD_SCK_IN) | + BIT_MASK(GPI_I2C1_PAD_SDA_IN) | + BIT_MASK(GPI_I2C2_PAD_SCK_IN) | + BIT_MASK(GPI_I2C2_PAD_SDA_IN) | + BIT_MASK(GPI_I2C3_PAD_SCK_IN) | + BIT_MASK(GPI_I2C3_PAD_SDA_IN) | + BIT_MASK(GPI_SDIO0_PAD_CARD_DETECT_N) | + + BIT_MASK(GPI_SDIO1_PAD_CARD_DETECT_N) | + BIT_MASK(GPI_SPI0_PAD_SS_IN_N) | + BIT_MASK(GPI_SPI1_PAD_SS_IN_N) | + BIT_MASK(GPI_SPI2_PAD_SS_IN_N) | + BIT_MASK(GPI_SPI2AHB_PAD_SS_N) | + BIT_MASK(GPI_SPI3_PAD_SS_IN_N), + + BIT_MASK(GPI_UART0_PAD_SIN) | + BIT_MASK(GPI_UART1_PAD_SIN) | + BIT_MASK(GPI_UART2_PAD_SIN) | + BIT_MASK(GPI_UART3_PAD_SIN) | + BIT_MASK(GPI_USB_OVER_CURRENT) + }; + DECLARE_BITMAP(keep, NR_GPIOS) = {}; + struct device_node *np = sfp->gc.parent->of_node; + int len = of_property_count_u32_elems(np, "starfive,keep-gpiomux"); + int i; + + for (i = 0; i < len; i++) { + u32 gpio; + + of_property_read_u32_index(np, "starfive,keep-gpiomux", i, &gpio); + if (gpio < NR_GPIOS) + set_bit(gpio, keep); + } + + for (i = 0; i < NR_GPIOS; i++) { + if (test_bit(i, keep)) + continue; + + writel_relaxed(GPO_DISABLE, sfp->base + GPON_DOEN_CFG + 8 * i); + writel_relaxed(GPO_LOW, sfp->base + GPON_DOUT_CFG + 8 * i); + } + + for (i = 0; i < GPI_END; i++) { + void __iomem *reg = sfp->base + GPI_CFG_OFFSET + 4 * i; + u32 din = readl_relaxed(reg); + + if (din >= 2 && din < (NR_GPIOS + 2) && test_bit(din - 2, keep)) + continue; + + writel_relaxed(test_bit(i, defaults), reg); + } +} + static int starfive_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -1283,6 +1346,9 @@ static int starfive_probe(struct platform_device *pdev) writel(value, sfp->padctl + IO_PADSHARE_SEL); } + if (!keepmux) + starfive_pinmux_reset(sfp); + value = readl(sfp->padctl + IO_PADSHARE_SEL); switch (value) { case 0: From cb36751b6fb438a2db5160fc2303521c15a77d1c Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Thu, 14 Oct 2021 20:56:54 +0200 Subject: [PATCH 17/52] serial: 8250_dw: Add starfive,jh7100-hsuart compatible This adds a compatible for the high speed UARTs on the StarFive JH7100 RISC-V SoC. Just like the regular uarts we also need to keep the input clocks at their default rate and rely only on the divisor in the UART. Signed-off-by: Emil Renner Berthing --- drivers/tty/serial/8250/8250_dw.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c index bb6aca07ab563..666ca85ecd61e 100644 --- a/drivers/tty/serial/8250/8250_dw.c +++ b/drivers/tty/serial/8250/8250_dw.c @@ -767,6 +767,7 @@ static const struct of_device_id dw8250_of_match[] = { { .compatible = "cavium,octeon-3860-uart", .data = &dw8250_octeon_3860_data }, { .compatible = "marvell,armada-38x-uart", .data = &dw8250_armada_38x_data }, { .compatible = "renesas,rzn1-uart", .data = &dw8250_renesas_rzn1_data }, + { .compatible = "starfive,jh7100-hsuart", .data = &dw8250_starfive_jh7100_data }, { .compatible = "starfive,jh7100-uart", .data = &dw8250_starfive_jh7100_data }, { /* Sentinel */ } }; From 0bf57dee7accbf6ceeaabf77026c818c67ae87e7 Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Sun, 6 Jun 2021 22:15:22 +0200 Subject: [PATCH 18/52] dt-bindings: hwmon: add starfive,jh7100-temp bindings Add bindings for the temperature sensor on the StarFive JH7100 SoC. Signed-off-by: Emil Renner Berthing Reviewed-by: Rob Herring --- .../bindings/hwmon/starfive,jh7100-temp.yaml | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 Documentation/devicetree/bindings/hwmon/starfive,jh7100-temp.yaml diff --git a/Documentation/devicetree/bindings/hwmon/starfive,jh7100-temp.yaml b/Documentation/devicetree/bindings/hwmon/starfive,jh7100-temp.yaml new file mode 100644 index 0000000000000..3604cca50c25e --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/starfive,jh7100-temp.yaml @@ -0,0 +1,74 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/starfive,jh7100-temp.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: StarFive JH7100 Temperature Sensor + +maintainers: + - Emil Renner Berthing + +description: | + StarFive Technology Co. JH7100 embedded temperature sensor + +properties: + compatible: + enum: + - starfive,jh7100-temp + + reg: + maxItems: 1 + + clocks: + minItems: 2 + maxItems: 2 + + clock-names: + items: + - const: "sense" + - const: "bus" + + '#thermal-sensor-cells': + const: 0 + + interrupts: + maxItems: 1 + + resets: + minItems: 2 + maxItems: 2 + + reset-names: + items: + - const: "sense" + - const: "bus" + +required: + - compatible + - reg + - clocks + - clock-names + - interrupts + - resets + - reset-names + +additionalProperties: false + +examples: + - | + #include + #include + + tmon: tmon@124a0000 { + compatible = "starfive,jh7100-temp"; + reg = <0x124a0000 0x10000>; + clocks = <&clkgen JH7100_CLK_TEMP_SENSE>, + <&clkgen JH7100_CLK_TEMP_APB>; + clock-names = "sense", "bus"; + #thermal-sensor-cells = <0>; + interrupts = <122>; + resets = <&rstgen JH7100_RSTN_TEMP_SENSE>, + <&rstgen JH7100_RSTN_TEMP_APB>; + reset-names = "sense", "bus"; + }; From 1cf3c35c302402b0d5862be2c91802cf21fc0ece Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Sun, 6 Jun 2021 22:31:18 +0200 Subject: [PATCH 19/52] hwmon: (sfctemp) Add StarFive JH7100 temperature sensor Register definitions and conversion constants based on sfctemp driver by Samin in the StarFive 5.10 kernel. Signed-off-by: Emil Renner Berthing Signed-off-by: Samin Guo --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/sfctemp.rst | 32 +++ MAINTAINERS | 8 + drivers/hwmon/Kconfig | 10 + drivers/hwmon/Makefile | 1 + drivers/hwmon/sfctemp.c | 349 ++++++++++++++++++++++++++++++++ 6 files changed, 401 insertions(+) create mode 100644 Documentation/hwmon/sfctemp.rst create mode 100644 drivers/hwmon/sfctemp.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index a72c16872ec2d..16fc87d41d706 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -177,6 +177,7 @@ Hardware Monitoring Kernel Drivers sch5627 sch5636 scpi-hwmon + sfctemp sht15 sht21 sht3x diff --git a/Documentation/hwmon/sfctemp.rst b/Documentation/hwmon/sfctemp.rst new file mode 100644 index 0000000000000..465edce2fea5e --- /dev/null +++ b/Documentation/hwmon/sfctemp.rst @@ -0,0 +1,32 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver sfctemp +===================== + +Supported chips: + - StarFive JH7100 + +Authors: + - Emil Renner Berthing + +Description +----------- + +This driver adds support for reading the built-in temperature sensor on the +JH7100 RISC-V SoC by StarFive Technology Co. Ltd. + +``sysfs`` interface +------------------- + +The temperature sensor can be enabled, disabled and queried via the standard +hwmon interface in sysfs under ``/sys/class/hwmon/hwmonX`` for some value of +``X``: + +================ ==== ============================================= +Name Perm Description +================ ==== ============================================= +temp1_enable RW Enable or disable temperature sensor. + Automatically enabled by the driver, + but may be disabled to save power. +temp1_input RO Temperature reading in milli-degrees Celsius. +================ ==== ============================================= diff --git a/MAINTAINERS b/MAINTAINERS index 143102427e20e..05c9d3ba045b6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18153,6 +18153,14 @@ L: netdev@vger.kernel.org S: Supported F: drivers/net/ethernet/sfc/ +SFCTEMP HWMON DRIVER +M: Emil Renner Berthing +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/hwmon/starfive,jh7100-temp.yaml +F: Documentation/hwmon/sfctemp.rst +F: drivers/hwmon/sfctemp.c + SFF/SFP/SFP+ MODULE SUPPORT M: Russell King L: netdev@vger.kernel.org diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 590d3d550acba..9d1cb8063bccc 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1878,6 +1878,16 @@ config SENSORS_STTS751 This driver can also be built as a module. If so, the module will be called stts751. +config SENSORS_SFCTEMP + tristate "Starfive JH7100 temperature sensor" + depends on SOC_STARFIVE || COMPILE_TEST + help + If you say yes here you get support for temperature sensor + on the Starfive JH7100 SoC. + + This driver can also be built as a module. If so, the module + will be called sfctemp. + config SENSORS_SMM665 tristate "Summit Microelectronics SMM665" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 007e829d1d0d0..1837fbd655aa6 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -178,6 +178,7 @@ obj-$(CONFIG_SENSORS_SBRMI) += sbrmi.o obj-$(CONFIG_SENSORS_SCH56XX_COMMON)+= sch56xx-common.o obj-$(CONFIG_SENSORS_SCH5627) += sch5627.o obj-$(CONFIG_SENSORS_SCH5636) += sch5636.o +obj-$(CONFIG_SENSORS_SFCTEMP) += sfctemp.o obj-$(CONFIG_SENSORS_SL28CPLD) += sl28cpld-hwmon.o obj-$(CONFIG_SENSORS_SHT15) += sht15.o obj-$(CONFIG_SENSORS_SHT21) += sht21.o diff --git a/drivers/hwmon/sfctemp.c b/drivers/hwmon/sfctemp.c new file mode 100644 index 0000000000000..d7af616b8d097 --- /dev/null +++ b/drivers/hwmon/sfctemp.c @@ -0,0 +1,349 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 Emil Renner Berthing + * Copyright (C) 2021 Samin Guo + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * TempSensor reset. The RSTN can be de-asserted once the analog core has + * powered up. Trst(min 100ns) + * 0:reset 1:de-assert + */ +#define SFCTEMP_RSTN BIT(0) + +/* + * TempSensor analog core power down. The analog core will be powered up + * Tpu(min 50us) after PD is de-asserted. RSTN should be held low until the + * analog core is powered up. + * 0:power up 1:power down + */ +#define SFCTEMP_PD BIT(1) + +/* + * TempSensor start conversion enable. + * 0:disable 1:enable + */ +#define SFCTEMP_RUN BIT(2) + +/* + * TempSensor conversion value output. + * Temp(C)=DOUT*Y/4094 - K + */ +#define SFCTEMP_DOUT_POS 16 +#define SFCTEMP_DOUT_MSK GENMASK(27, 16) + +/* DOUT to Celcius conversion constants */ +#define SFCTEMP_Y1000 237500L +#define SFCTEMP_Z 4094L +#define SFCTEMP_K1000 81100L + +struct sfctemp { + /* serialize access to hardware register and enabled below */ + struct mutex lock; + struct completion conversion_done; + void __iomem *regs; + struct clk *clk_sense; + struct clk *clk_bus; + struct reset_control *rst_sense; + struct reset_control *rst_bus; + bool enabled; +}; + +static irqreturn_t sfctemp_isr(int irq, void *data) +{ + struct sfctemp *sfctemp = data; + + complete(&sfctemp->conversion_done); + return IRQ_HANDLED; +} + +static void sfctemp_power_up(struct sfctemp *sfctemp) +{ + /* make sure we're powered down first */ + writel(SFCTEMP_PD, sfctemp->regs); + udelay(1); + + writel(0, sfctemp->regs); + /* wait t_pu(50us) + t_rst(100ns) */ + usleep_range(60, 200); + + /* de-assert reset */ + writel(SFCTEMP_RSTN, sfctemp->regs); + udelay(1); /* wait t_su(500ps) */ +} + +static void sfctemp_power_down(struct sfctemp *sfctemp) +{ + writel(SFCTEMP_PD, sfctemp->regs); +} + +static void sfctemp_run_single(struct sfctemp *sfctemp) +{ + writel(SFCTEMP_RSTN | SFCTEMP_RUN, sfctemp->regs); + udelay(1); + writel(SFCTEMP_RSTN, sfctemp->regs); +} + +static int sfctemp_enable(struct sfctemp *sfctemp) +{ + int ret = 0; + + mutex_lock(&sfctemp->lock); + if (sfctemp->enabled) + goto done; + + ret = clk_prepare_enable(sfctemp->clk_bus); + if (ret) + goto err; + ret = reset_control_deassert(sfctemp->rst_bus); + if (ret) + goto err_disable_bus; + + ret = clk_prepare_enable(sfctemp->clk_sense); + if (ret) + goto err_assert_bus; + ret = reset_control_deassert(sfctemp->rst_sense); + if (ret) + goto err_disable_sense; + + sfctemp_power_up(sfctemp); + sfctemp->enabled = true; +done: + mutex_unlock(&sfctemp->lock); + return ret; + +err_disable_sense: + clk_disable_unprepare(sfctemp->clk_sense); +err_assert_bus: + reset_control_assert(sfctemp->rst_bus); +err_disable_bus: + clk_disable_unprepare(sfctemp->clk_bus); +err: + mutex_unlock(&sfctemp->lock); + return ret; +} + +static int sfctemp_disable(struct sfctemp *sfctemp) +{ + mutex_lock(&sfctemp->lock); + if (!sfctemp->enabled) + goto done; + + sfctemp_power_down(sfctemp); + reset_control_assert(sfctemp->rst_sense); + clk_disable_unprepare(sfctemp->clk_sense); + reset_control_assert(sfctemp->rst_bus); + clk_disable_unprepare(sfctemp->clk_bus); + sfctemp->enabled = false; +done: + mutex_unlock(&sfctemp->lock); + return 0; +} + +static void sfctemp_disable_action(void *data) +{ + sfctemp_disable(data); +} + +static int sfctemp_convert(struct sfctemp *sfctemp, long *val) +{ + int ret; + + mutex_lock(&sfctemp->lock); + if (!sfctemp->enabled) { + ret = -ENODATA; + goto out; + } + + sfctemp_run_single(sfctemp); + + ret = wait_for_completion_interruptible_timeout(&sfctemp->conversion_done, + msecs_to_jiffies(10)); + if (ret <= 0) { + if (ret == 0) + ret = -ETIMEDOUT; + goto out; + } + + /* calculate temperature in milli Celcius */ + *val = (long)((readl(sfctemp->regs) & SFCTEMP_DOUT_MSK) >> SFCTEMP_DOUT_POS) + * SFCTEMP_Y1000 / SFCTEMP_Z - SFCTEMP_K1000; + + ret = 0; +out: + mutex_unlock(&sfctemp->lock); + return ret; +} + +static umode_t sfctemp_is_visible(const void *data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_enable: + return 0644; + case hwmon_temp_input: + return 0444; + } + return 0; + default: + return 0; + } +} + +static int sfctemp_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct sfctemp *sfctemp = dev_get_drvdata(dev); + + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_enable: + *val = sfctemp->enabled; + return 0; + case hwmon_temp_input: + return sfctemp_convert(sfctemp, val); + } + return -EINVAL; + default: + return -EINVAL; + } +} + +static int sfctemp_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct sfctemp *sfctemp = dev_get_drvdata(dev); + + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_enable: + if (val == 0) + return sfctemp_disable(sfctemp); + if (val == 1) + return sfctemp_enable(sfctemp); + break; + } + return -EINVAL; + default: + return -EINVAL; + } +} + +static const struct hwmon_channel_info *sfctemp_info[] = { + HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), + HWMON_CHANNEL_INFO(temp, HWMON_T_ENABLE | HWMON_T_INPUT), + NULL +}; + +static const struct hwmon_ops sfctemp_hwmon_ops = { + .is_visible = sfctemp_is_visible, + .read = sfctemp_read, + .write = sfctemp_write, +}; + +static const struct hwmon_chip_info sfctemp_chip_info = { + .ops = &sfctemp_hwmon_ops, + .info = sfctemp_info, +}; + +static int sfctemp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device *hwmon_dev; + struct sfctemp *sfctemp; + int ret; + + sfctemp = devm_kzalloc(dev, sizeof(*sfctemp), GFP_KERNEL); + if (!sfctemp) + return -ENOMEM; + + dev_set_drvdata(dev, sfctemp); + mutex_init(&sfctemp->lock); + init_completion(&sfctemp->conversion_done); + + sfctemp->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(sfctemp->regs)) + return PTR_ERR(sfctemp->regs); + + sfctemp->clk_sense = devm_clk_get(dev, "sense"); + if (IS_ERR(sfctemp->clk_sense)) + return dev_err_probe(dev, PTR_ERR(sfctemp->clk_sense), + "error getting sense clock\n"); + + sfctemp->clk_bus = devm_clk_get(dev, "bus"); + if (IS_ERR(sfctemp->clk_bus)) + return dev_err_probe(dev, PTR_ERR(sfctemp->clk_bus), + "error getting bus clock\n"); + + sfctemp->rst_sense = devm_reset_control_get_exclusive(dev, "sense"); + if (IS_ERR(sfctemp->rst_sense)) + return dev_err_probe(dev, PTR_ERR(sfctemp->rst_sense), + "error getting sense reset\n"); + + sfctemp->rst_bus = devm_reset_control_get_exclusive(dev, "bus"); + if (IS_ERR(sfctemp->rst_bus)) + return dev_err_probe(dev, PTR_ERR(sfctemp->rst_bus), + "error getting busreset\n"); + + ret = reset_control_assert(sfctemp->rst_sense); + if (ret) + return dev_err_probe(dev, ret, "error asserting sense reset\n"); + + ret = reset_control_assert(sfctemp->rst_bus); + if (ret) + return dev_err_probe(dev, ret, "error asserting bus reset\n"); + + ret = platform_get_irq(pdev, 0); + if (ret < 0) + return ret; + + ret = devm_request_irq(dev, ret, sfctemp_isr, 0, pdev->name, sfctemp); + if (ret) + return dev_err_probe(dev, ret, "error requesting irq\n"); + + ret = devm_add_action(dev, sfctemp_disable_action, sfctemp); + if (ret) + return ret; + + ret = sfctemp_enable(sfctemp); + if (ret) + return dev_err_probe(dev, ret, "error enabling temperature sensor: %d\n", ret); + + hwmon_dev = devm_hwmon_device_register_with_info(dev, pdev->name, sfctemp, + &sfctemp_chip_info, NULL); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct of_device_id sfctemp_of_match[] = { + { .compatible = "starfive,jh7100-temp" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, sfctemp_of_match); + +static struct platform_driver sfctemp_driver = { + .probe = sfctemp_probe, + .driver = { + .name = "sfctemp", + .of_match_table = sfctemp_of_match, + }, +}; +module_platform_driver(sfctemp_driver); + +MODULE_AUTHOR("Emil Renner Berthing"); +MODULE_DESCRIPTION("StarFive JH7100 temperature sensor driver"); +MODULE_LICENSE("GPL"); From e2454f3a7c15b9acc9d0a38438943ec4f8f97dcb Mon Sep 17 00:00:00 2001 From: Samin Guo Date: Wed, 17 Nov 2021 11:06:25 +0800 Subject: [PATCH 20/52] watchdog: Add StarFive SI5 watchdog driver Signed-off-by: Samin Guo Signed-off-by: Walker Chen Signed-off-by: Emil Renner Berthing --- drivers/watchdog/Kconfig | 9 + drivers/watchdog/Makefile | 3 + drivers/watchdog/starfive-wdt.c | 761 ++++++++++++++++++++++++++++++++ 3 files changed, 773 insertions(+) create mode 100644 drivers/watchdog/starfive-wdt.c diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 32fd37698932c..cd40bcdecb21d 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -2062,6 +2062,15 @@ config UML_WATCHDOG tristate "UML watchdog" depends on UML || COMPILE_TEST +# RISCV Architecture + +config STARFIVE_WATCHDOG + tristate "StarFive Watchdog support" + depends on RISCV + select WATCHDOG_CORE + help + Say Y here to support the starfive Si5 watchdog. + # # ISA-based Watchdog Cards # diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index c324e9d820e96..2cf40b3ec20d7 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -208,6 +208,9 @@ obj-$(CONFIG_WATCHDOG_SUN4V) += sun4v_wdt.o # Xen obj-$(CONFIG_XEN_WDT) += xen_wdt.o +# RISCV Architecture +obj-$(CONFIG_STARFIVE_WATCHDOG) += starfive-wdt.o + # Architecture Independent obj-$(CONFIG_BD957XMUF_WATCHDOG) += bd9576_wdt.o obj-$(CONFIG_DA9052_WATCHDOG) += da9052_wdt.o diff --git a/drivers/watchdog/starfive-wdt.c b/drivers/watchdog/starfive-wdt.c new file mode 100644 index 0000000000000..5870a782994c2 --- /dev/null +++ b/drivers/watchdog/starfive-wdt.c @@ -0,0 +1,761 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Watchdog driver for the StarFive JH7100 SoC + * + * Copyright (C) 2021 Samin Guo + * Copyright (C) 2021 Walker Chen + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* JH7100 WatchDog register define */ +#define JH7100_WDGINTSTAUS 0x000 +#define JH7100_WDOGCONTROL 0x104 /* Watchdog Control Register R/W */ +#define JH7100_WDOGLOAD 0x108 /* The initial value to be loaded */ + /* into the counter and is also used */ + /* as the reload value. R/W */ +#define JH7100_WDOGEN 0x110 /* Watchdog enable Register */ +#define JH7100_WDOGRELOAD 0x114 /* Write this register to reload preset */ + /* value to counter. (Write 0 or 1 are both ok) */ +#define JH7100_WDOGVALUE 0x118 /* Watchdog Value Register RO */ +#define JH7100_WDOGINTCLR 0x120 /* Watchdog Clear Interrupt Register WO */ +#define JH7100_WDOGINTMSK 0x124 /* Watchdog Interrupt Mask Register */ +#define JH7100_WDOGLOCK 0x13c /* Watchdog Lock Register R/W */ + +#define JH7100_UNLOCK_KEY 0x378f0765 +#define JH7100_RESEN_SHIFT 0 +#define JH7100_EN_SHIFT 0 +#define JH7100_INTCLR_AVA_SHIFT 1 /* Watchdog can clear interrupt when this bit is 0 */ + +/* WDOGCONTROL */ +#define WDOG_INT_EN 0x0 +#define WDOG_RESET_EN 0x1 + +/* WDOGLOCK */ +#define WDOG_LOCKED BIT(0) + +#define SI5_WATCHDOG_INTCLR 0x1 +#define SI5_WATCHDOG_ENABLE 0x1 +#define SI5_WATCHDOG_ATBOOT 0x0 +#define SI5_WATCHDOG_MAXCNT 0xffffffff + +#define SI5_WATCHDOG_DEFAULT_TIME (15) + +static bool nowayout = WATCHDOG_NOWAYOUT; +static int tmr_margin; +static int tmr_atboot = SI5_WATCHDOG_ATBOOT; +static int soft_noboot; + +module_param(tmr_margin, int, 0); +module_param(tmr_atboot, int, 0); +module_param(nowayout, bool, 0); +module_param(soft_noboot, int, 0); + +MODULE_PARM_DESC(tmr_margin, "Watchdog tmr_margin in seconds. (default=" + __MODULE_STRING(SI5_WATCHDOG_DEFAULT_TIME) ")"); +MODULE_PARM_DESC(tmr_atboot, + "Watchdog is started at boot time if set to 1, default=" + __MODULE_STRING(SI5_WATCHDOG_ATBOOT)); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +MODULE_PARM_DESC(soft_noboot, "Watchdog action, set to 1 to ignore reboots, 0 to reboot (default 0)"); + +struct si5_wdt_variant_t { + u32 unlock_key; + u8 enrst_shift; + u8 en_shift; + u8 intclr_check; + u8 intclr_ava_shift; +}; + +struct si5_wdt_variant { + u32 control; + u32 load; + u32 enable; + u32 reload; + u32 value; + u32 int_clr; + u32 int_mask; + u32 unlock; + struct si5_wdt_variant_t *variant; +}; + +struct stf_si5_wdt { + u64 freq; + struct device *dev; + struct watchdog_device wdt_device; + struct clk *core_clk; + struct clk *apb_clk; + struct reset_control *rst_wdtimer_apb; + struct reset_control *rst_wdt; + + const struct si5_wdt_variant *drv_data; + u32 count; /*count of timeout*/ + u32 reload; /*restore the count*/ + void __iomem *base; + spinlock_t lock; +}; + +#ifdef CONFIG_OF +static struct si5_wdt_variant_t jh7100_variant = { + .unlock_key = JH7100_UNLOCK_KEY, + .enrst_shift = JH7100_RESEN_SHIFT, + .en_shift = JH7100_EN_SHIFT, + .intclr_check = 1, + .intclr_ava_shift = JH7100_INTCLR_AVA_SHIFT, +}; + +static const struct si5_wdt_variant drv_data_jh7100 = { + .control = JH7100_WDOGCONTROL, + .load = JH7100_WDOGLOAD, + .enable = JH7100_WDOGEN, + .reload = JH7100_WDOGRELOAD, + .value = JH7100_WDOGVALUE, + .int_clr = JH7100_WDOGINTCLR, + .int_mask = JH7100_WDOGINTMSK, + .unlock = JH7100_WDOGLOCK, + .variant = &jh7100_variant, +}; + +static const struct of_device_id starfive_wdt_match[] = { + { .compatible = "starfive,si5-wdt", .data = &drv_data_jh7100 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, starfive_wdt_match); +#endif + +static const struct platform_device_id si5wdt_ids[] = { + { + .name = "starfive-si5-wdt", + .driver_data = (unsigned long)&drv_data_jh7100, + }, + {} +}; +MODULE_DEVICE_TABLE(platform, si5wdt_ids); + +static int si5wdt_get_clock_rate(struct stf_si5_wdt *wdt) +{ + int ret; + u32 freq; + + if (!IS_ERR(wdt->core_clk)) { + wdt->freq = clk_get_rate(wdt->core_clk); + return 0; + } + + /* Next we try to get clock-frequency from dts.*/ + ret = of_property_read_u32(wdt->dev->of_node, "clock-frequency", &freq); + if (!ret) { + wdt->freq = (u64)freq; + return 0; + } + else + dev_err(wdt->dev, "get rate failed, need clock-frequency define in dts.\n"); + + return -ENOENT; +} + +static int si5wdt_enable_clock(struct stf_si5_wdt *wdt) +{ + int ret; + + wdt->rst_wdtimer_apb = devm_reset_control_get_exclusive(wdt->dev, "wdtimer_apb"); + if (IS_ERR(wdt->rst_wdtimer_apb)) + return dev_err_probe(wdt->dev, PTR_ERR(wdt->rst_wdtimer_apb), + "failed to get apb reset\n"); + + wdt->rst_wdt = devm_reset_control_get_exclusive(wdt->dev, "wdt"); + if (IS_ERR(wdt->rst_wdt)) + return dev_err_probe(wdt->dev, PTR_ERR(wdt->rst_wdt), + "failed to get core reset\n"); + + wdt->apb_clk = devm_clk_get(wdt->dev, "wdtimer_apb"); + if (IS_ERR(wdt->apb_clk)) + return dev_err_probe(wdt->dev, PTR_ERR(wdt->apb_clk), + "failed to get apb clock\n"); + + wdt->core_clk = devm_clk_get(wdt->dev, "wdt_coreclk"); + if (IS_ERR(wdt->core_clk)) + return dev_err_probe(wdt->dev, PTR_ERR(wdt->core_clk), + "failed to get core clock\n"); + + ret = clk_prepare_enable(wdt->apb_clk); + if (ret) + return dev_err_probe(wdt->dev, ret, "failed to enable apb clock\n"); + + ret = clk_prepare_enable(wdt->core_clk); + if (ret) + return dev_err_probe(wdt->dev, ret, "failed to enable core clock\n"); + + ret = reset_control_assert(wdt->rst_wdtimer_apb); + if (ret) + return dev_err_probe(wdt->dev, ret, "failed to assert apb reset\n"); + + ret = reset_control_assert(wdt->rst_wdt); + if (ret) + return dev_err_probe(wdt->dev, ret, "failed to assert core reset\n"); + + ret = reset_control_deassert(wdt->rst_wdtimer_apb); + if (ret) + return dev_err_probe(wdt->dev, ret, "failed to deassert apb reset\n"); + + ret = reset_control_deassert(wdt->rst_wdt); + if (ret) + return dev_err_probe(wdt->dev, ret, "failed to deassert core reset\n"); + + return 0; +} + +static __maybe_unused +u32 si5wdt_sec_to_ticks(struct stf_si5_wdt *wdt, u32 sec) +{ + return sec * wdt->freq; +} + +static __maybe_unused +u32 si5wdt_ticks_to_sec(struct stf_si5_wdt *wdt, u32 ticks) +{ + return DIV_ROUND_CLOSEST(ticks, wdt->freq); +} + +/* + * Write unlock-key to unlock. Write other value to lock. When lock bit is 1, + * external accesses to other watchdog registers are ignored. + */ +static int si5wdt_is_locked(struct stf_si5_wdt *wdt) +{ + u32 val; + + val = readl(wdt->base + wdt->drv_data->unlock); + return !!(val & WDOG_LOCKED); +} + +static void si5wdt_unlock(struct stf_si5_wdt *wdt) +{ + if (si5wdt_is_locked(wdt)) + writel(wdt->drv_data->variant->unlock_key, + wdt->base + wdt->drv_data->unlock); +} + +static void si5wdt_lock(struct stf_si5_wdt *wdt) +{ + if (!si5wdt_is_locked(wdt)) + writel(~wdt->drv_data->variant->unlock_key, + wdt->base + wdt->drv_data->unlock); +} + +static int __maybe_unused si5wdt_is_running(struct stf_si5_wdt *wdt) +{ + u32 val; + + si5wdt_unlock(wdt); + val = readl(wdt->base + wdt->drv_data->enable); + si5wdt_lock(wdt); + + return !!(val & SI5_WATCHDOG_ENABLE << + wdt->drv_data->variant->en_shift); +} + +static inline void si5wdt_int_enable(struct stf_si5_wdt *wdt) +{ + u32 val; + + if (wdt->drv_data->int_mask) { + val = readl(wdt->base + wdt->drv_data->int_mask); + val &= ~(1<<0); + writel(val, wdt->base + wdt->drv_data->int_mask); + } +} + +static inline void si5wdt_int_disable(struct stf_si5_wdt *wdt) +{ + u32 val; + + if (wdt->drv_data->int_mask) { + val = readl(wdt->base + wdt->drv_data->int_mask); + val |= (1<<0); + writel(val, wdt->base + wdt->drv_data->int_mask); + } +} + +static void si5wdt_enable_reset(struct stf_si5_wdt *wdt) +{ + u32 val; + + val = readl(wdt->base + wdt->drv_data->control); + val |= WDOG_RESET_EN << wdt->drv_data->variant->enrst_shift; + /* enable wdog interrupt to reset */ + writel(val, wdt->base + wdt->drv_data->control); +} + +static void si5wdt_disable_reset(struct stf_si5_wdt *wdt) +{ + u32 val; + + val = readl(wdt->base + wdt->drv_data->control); + val &= ~(WDOG_RESET_EN << wdt->drv_data->variant->enrst_shift); + /*disable wdog interrupt to reset*/ + writel(val, wdt->base + wdt->drv_data->control); +} + +static void si5wdt_int_clr(struct stf_si5_wdt *wdt) +{ + void __iomem *addr; + u8 clr_check; + u8 clr_ava_shift; + + addr = wdt->base + wdt->drv_data->int_clr; + clr_ava_shift = wdt->drv_data->variant->intclr_ava_shift; + clr_check = wdt->drv_data->variant->intclr_check; + if (clr_check) { + /* waiting interrupt can be to clearing */ + do { + + } while (readl(addr) & BIT(clr_ava_shift)); + } + + writel(SI5_WATCHDOG_INTCLR, addr); +} + +static inline void si5wdt_set_count(struct stf_si5_wdt *wdt, u32 val) +{ + writel(val, wdt->base + wdt->drv_data->load); +} + +static inline u32 si5wdt_get_count(struct stf_si5_wdt *wdt) +{ + return readl(wdt->base + wdt->drv_data->value); +} + +static inline void si5wdt_enable(struct stf_si5_wdt *wdt) +{ + u32 val; + + val = readl(wdt->base + wdt->drv_data->enable); + val |= SI5_WATCHDOG_ENABLE << wdt->drv_data->variant->en_shift; + writel(val, wdt->base + wdt->drv_data->enable); +} + +static inline void si5wdt_disable(struct stf_si5_wdt *wdt) +{ + u32 val; + + val = readl(wdt->base + wdt->drv_data->enable); + val &= ~(SI5_WATCHDOG_ENABLE << wdt->drv_data->variant->en_shift); + writel(val, wdt->base + wdt->drv_data->enable); +} + +static inline void +si5wdt_set_relod_count(struct stf_si5_wdt *wdt, u32 count) +{ + writel(count, wdt->base + wdt->drv_data->load); + if (wdt->drv_data->reload) + writel(0x1, wdt->base + wdt->drv_data->reload); + +} + +static int si5wdt_mask_and_disable_reset(struct stf_si5_wdt *wdt, bool mask) +{ + si5wdt_unlock(wdt); + + if (mask) + si5wdt_disable_reset(wdt); + else + si5wdt_enable_reset(wdt); + + si5wdt_lock(wdt); + + return 0; +} + +static unsigned int si5wdt_max_timeout(struct stf_si5_wdt *wdt) +{ + return DIV_ROUND_UP(SI5_WATCHDOG_MAXCNT, wdt->freq) - 1; +} + +static unsigned int si5wdt_get_timeleft(struct watchdog_device *wdd) +{ + struct stf_si5_wdt *wdt = watchdog_get_drvdata(wdd); + u32 count; + + si5wdt_unlock(wdt); + count = si5wdt_get_count(wdt); + si5wdt_lock(wdt); + + return si5wdt_ticks_to_sec(wdt, count); +} + +static int si5wdt_keepalive(struct watchdog_device *wdd) +{ + struct stf_si5_wdt *wdt = watchdog_get_drvdata(wdd); + + spin_lock(&wdt->lock); + + si5wdt_unlock(wdt); + si5wdt_set_relod_count(wdt, wdt->count); + si5wdt_lock(wdt); + + spin_unlock(&wdt->lock); + + return 0; +} + +static irqreturn_t si5wdt_interrupt_handler(int irq, void *data) +{ + /* + * We don't clear the IRQ status. It's supposed to be done by the + * following ping operations. + */ + + return IRQ_HANDLED; +} + +static int si5wdt_stop(struct watchdog_device *wdd) +{ + struct stf_si5_wdt *wdt = watchdog_get_drvdata(wdd); + + spin_lock(&wdt->lock); + + si5wdt_unlock(wdt); + si5wdt_int_disable(wdt); + si5wdt_int_clr(wdt); + si5wdt_disable(wdt); + si5wdt_lock(wdt); + + spin_unlock(&wdt->lock); + + return 0; +} + +static int si5wdt_start(struct watchdog_device *wdd) +{ + struct stf_si5_wdt *wdt = watchdog_get_drvdata(wdd); + + spin_lock(&wdt->lock); + + si5wdt_unlock(wdt); + + if (soft_noboot) + si5wdt_disable_reset(wdt); + else + si5wdt_enable_reset(wdt); + + si5wdt_set_count(wdt, wdt->count); + si5wdt_int_enable(wdt); + si5wdt_enable(wdt); + + si5wdt_lock(wdt); + + spin_unlock(&wdt->lock); + + return 0; +} + +static int si5wdt_restart(struct watchdog_device *wdd, unsigned long action, + void *data) +{ + struct stf_si5_wdt *wdt = watchdog_get_drvdata(wdd); + + si5wdt_unlock(wdt); + /* disable watchdog, to be safe */ + si5wdt_disable(wdt); + + if (soft_noboot) + si5wdt_disable_reset(wdt); + else + si5wdt_enable_reset(wdt); + + /* put initial values into count and data */ + si5wdt_set_count(wdt, wdt->count); + + /* set the watchdog to go and reset... */ + si5wdt_int_clr(wdt); + si5wdt_int_enable(wdt); + si5wdt_enable(wdt); + + /* wait for reset to assert... */ + mdelay(500); + + si5wdt_lock(wdt); + + return 0; +} + +static int si5wdt_set_timeout(struct watchdog_device *wdd, + unsigned int timeout) +{ + struct stf_si5_wdt *wdt = watchdog_get_drvdata(wdd); + + unsigned long freq = wdt->freq; + unsigned int count; + + if (timeout < 1) + return -EINVAL; + + count = timeout * freq; + + if (count > SI5_WATCHDOG_MAXCNT) { + dev_warn(wdt->dev, "timeout %d too big,use the MAX-timeout set.\n", + timeout); + timeout = si5wdt_max_timeout(wdt); + count = timeout * freq; + } + + dev_info(wdt->dev, "Heartbeat: timeout=%d, count=%d (%08x)\n", + timeout, count, count); + + si5wdt_unlock(wdt); + si5wdt_disable(wdt); + si5wdt_set_relod_count(wdt, count); + si5wdt_enable(wdt); + si5wdt_lock(wdt); + + wdt->count = count; + wdd->timeout = timeout; + + return 0; +} + +#define OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE) + +static const struct watchdog_info si5_wdt_ident = { + .options = OPTIONS, + .firmware_version = 0, + .identity = "StarFive SI5 Watchdog", +}; + +static const struct watchdog_ops si5wdt_ops = { + .owner = THIS_MODULE, + .start = si5wdt_start, + .stop = si5wdt_stop, + .ping = si5wdt_keepalive, + .set_timeout = si5wdt_set_timeout, + .restart = si5wdt_restart, + .get_timeleft = si5wdt_get_timeleft, +}; + +static const struct watchdog_device starfive_si5_wdd = { + .info = &si5_wdt_ident, + .ops = &si5wdt_ops, + .timeout = SI5_WATCHDOG_DEFAULT_TIME, +}; + +static inline const struct si5_wdt_variant * +si5_get_wdt_drv_data(struct platform_device *pdev) +{ + const struct si5_wdt_variant *variant; + + variant = of_device_get_match_data(&pdev->dev); + if (!variant) { + /* Device matched by platform_device_id */ + variant = (struct si5_wdt_variant *) + platform_get_device_id(pdev)->driver_data; + } + + return variant; +} + +static int si5wdt_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct stf_si5_wdt *wdt; + int wdt_irq; + int ret; + + wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL); + if (!wdt) + return -ENOMEM; + + wdt->dev = dev; + spin_lock_init(&wdt->lock); + wdt->wdt_device = starfive_si5_wdd; + + wdt->drv_data = si5_get_wdt_drv_data(pdev); + + wdt_irq = platform_get_irq(pdev, 0); + if (wdt_irq < 0) + return wdt_irq; + + /* get the memory region for the watchdog timer */ + wdt->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(wdt->base)) + return PTR_ERR(wdt->base); + + ret = si5wdt_enable_clock(wdt); + if (ret) + return ret; + + si5wdt_get_clock_rate(wdt); + + wdt->wdt_device.min_timeout = 1; + wdt->wdt_device.max_timeout = si5wdt_max_timeout(wdt); + + watchdog_set_drvdata(&wdt->wdt_device, wdt); + + /* + * see if we can actually set the requested timer margin, + * and if not, try the default value. + */ + watchdog_init_timeout(&wdt->wdt_device, tmr_margin, dev); + + ret = si5wdt_set_timeout(&wdt->wdt_device, + wdt->wdt_device.timeout); + if (ret) { + dev_info(dev, "tmr_margin value out of range, default %d used\n", + SI5_WATCHDOG_DEFAULT_TIME); + si5wdt_set_timeout(&wdt->wdt_device, + SI5_WATCHDOG_DEFAULT_TIME); + } + + ret = devm_request_irq(dev, wdt_irq, si5wdt_interrupt_handler, 0, + pdev->name, pdev); + if (ret != 0) { + dev_err(dev, "failed to install irq (%d)\n", ret); + goto err; + } + + watchdog_set_nowayout(&wdt->wdt_device, nowayout); + watchdog_set_restart_priority(&wdt->wdt_device, 128); + + wdt->wdt_device.parent = dev; + + ret = watchdog_register_device(&wdt->wdt_device); + if (ret) + goto err; + + ret = si5wdt_mask_and_disable_reset(wdt, false); + if (ret < 0) + goto err_unregister; + + if (tmr_atboot) { + dev_info(dev, "starting watchdog timer\n"); + si5wdt_start(&wdt->wdt_device); + } else { + + /* + * if we're not enabling the watchdog, then ensure it is + * disabled if it has been left running from the bootloader + * or other source. + */ + si5wdt_stop(&wdt->wdt_device); + } + + platform_set_drvdata(pdev, wdt); + + return 0; + + err_unregister: + watchdog_unregister_device(&wdt->wdt_device); + + err: + return ret; +} + +static int si5wdt_remove(struct platform_device *dev) +{ + int ret; + struct stf_si5_wdt *wdt = platform_get_drvdata(dev); + + ret = si5wdt_mask_and_disable_reset(wdt, true); + if (ret < 0) + return ret; + + watchdog_unregister_device(&wdt->wdt_device); + + clk_disable_unprepare(wdt->core_clk); + + return 0; +} + +static void si5wdt_shutdown(struct platform_device *dev) +{ + struct stf_si5_wdt *wdt = platform_get_drvdata(dev); + + si5wdt_mask_and_disable_reset(wdt, true); + + si5wdt_stop(&wdt->wdt_device); + +} + +#ifdef CONFIG_PM_SLEEP + +static int si5wdt_suspend(struct device *dev) +{ + int ret; + struct stf_si5_wdt *wdt = dev_get_drvdata(dev); + + si5wdt_unlock(wdt); + + /* Save watchdog state, and turn it off. */ + wdt->reload = si5wdt_get_count(wdt); + + ret = si5wdt_mask_and_disable_reset(wdt, true); + if (ret < 0) + return ret; + + /* Note that WTCNT doesn't need to be saved. */ + si5wdt_stop(&wdt->wdt_device); + + si5wdt_lock(wdt); + + return 0; +} + +static int si5wdt_resume(struct device *dev) +{ + int ret; + struct stf_si5_wdt *wdt = dev_get_drvdata(dev); + + si5wdt_unlock(wdt); + + /* Restore watchdog state. */ + si5wdt_set_relod_count(wdt, wdt->reload); + + ret = si5wdt_mask_and_disable_reset(wdt, false); + if (ret < 0) + return ret; + + si5wdt_lock(wdt); + + dev_info(dev, "watchdog resume\n") + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static SIMPLE_DEV_PM_OPS(si5wdt_pm_ops, si5wdt_suspend, si5wdt_resume); + +static struct platform_driver starfive_si5wdt_driver = { + .probe = si5wdt_probe, + .remove = si5wdt_remove, + .shutdown = si5wdt_shutdown, + .id_table = si5wdt_ids, + .driver = { + .name = "starfive-si5-wdt", + .pm = &si5wdt_pm_ops, + .of_match_table = of_match_ptr(starfive_wdt_match), + }, +}; + +module_platform_driver(starfive_si5wdt_driver); + +MODULE_AUTHOR("Samin Guo "); +MODULE_AUTHOR("Walker Chen "); +MODULE_DESCRIPTION("StarFive SI5 Watchdog Device Driver"); +MODULE_LICENSE("GPL v2"); From 6676781f1de2361ee35ade386f66823c76554644 Mon Sep 17 00:00:00 2001 From: Huan Feng Date: Fri, 8 Jan 2021 03:35:42 +0800 Subject: [PATCH 21/52] hwrng: Add StarFive JH7100 Random Number Generator driver Signed-off-by: Emil Renner Berthing --- drivers/char/hw_random/Kconfig | 13 ++ drivers/char/hw_random/Makefile | 1 + drivers/char/hw_random/starfive-vic-rng.c | 256 ++++++++++++++++++++++ drivers/char/hw_random/starfive-vic-rng.h | 167 ++++++++++++++ 4 files changed, 437 insertions(+) create mode 100644 drivers/char/hw_random/starfive-vic-rng.c create mode 100644 drivers/char/hw_random/starfive-vic-rng.h diff --git a/drivers/char/hw_random/Kconfig b/drivers/char/hw_random/Kconfig index b3f2d55dc551b..95c833e2306e3 100644 --- a/drivers/char/hw_random/Kconfig +++ b/drivers/char/hw_random/Kconfig @@ -322,6 +322,19 @@ config HW_RANDOM_POWERNV If unsure, say Y. +config HW_RANDOM_STARFIVE_VIC + tristate "Starfive VIC Random Number Generator support" + depends on HW_RANDOM && (SOC_STARFIVE || COMPILE_TEST) + default SOC_STARFIVE + help + This driver provides kernel-side support for the Random Number + Generator hardware found on Starfive VIC SoC. + + To compile this driver as a module, choose M here: the + module will be called starfive-vic-rng. + + If unsure, say Y. + config HW_RANDOM_HISI tristate "Hisilicon Random Number Generator support" depends on HW_RANDOM && ARCH_HISI diff --git a/drivers/char/hw_random/Makefile b/drivers/char/hw_random/Makefile index 3e948cf044762..157ea23aa46de 100644 --- a/drivers/char/hw_random/Makefile +++ b/drivers/char/hw_random/Makefile @@ -28,6 +28,7 @@ obj-$(CONFIG_HW_RANDOM_OCTEON) += octeon-rng.o obj-$(CONFIG_HW_RANDOM_NOMADIK) += nomadik-rng.o obj-$(CONFIG_HW_RANDOM_PSERIES) += pseries-rng.o obj-$(CONFIG_HW_RANDOM_POWERNV) += powernv-rng.o +obj-$(CONFIG_HW_RANDOM_STARFIVE_VIC) += starfive-vic-rng.o obj-$(CONFIG_HW_RANDOM_HISI) += hisi-rng.o obj-$(CONFIG_HW_RANDOM_BCM2835) += bcm2835-rng.o obj-$(CONFIG_HW_RANDOM_IPROC_RNG200) += iproc-rng200.o diff --git a/drivers/char/hw_random/starfive-vic-rng.c b/drivers/char/hw_random/starfive-vic-rng.c new file mode 100644 index 0000000000000..1e003b755a52d --- /dev/null +++ b/drivers/char/hw_random/starfive-vic-rng.c @@ -0,0 +1,256 @@ +/* + ****************************************************************************** + * @file starfive-vic-rng.c + * @author StarFive Technology + * @version V1.0 + * @date 08/13/2020 + * @brief + ****************************************************************************** + * @copy + * + * THE PRESENT SOFTWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS + * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE + * TIME. AS A RESULT, STARFIVE SHALL NOT BE HELD LIABLE FOR ANY + * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING + * FROM THE CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE + * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. + * + * COPYRIGHT 2020 Shanghai StarFive Technology Co., Ltd. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "starfive-vic-rng.h" + +#define to_vic_rng(p) container_of(p, struct vic_rng, rng) + +struct vic_rng { + struct device *dev; + void __iomem *base; + struct hwrng rng; +}; + +static inline void vic_wait_till_idle(struct vic_rng *hrng) +{ + while(readl(hrng->base + VIC_STAT) & VIC_STAT_BUSY) + ; +} + +static inline void vic_rng_irq_mask_clear(struct vic_rng *hrng) +{ + // clear register: ISTAT + u32 data = readl(hrng->base + VIC_ISTAT); + writel(data, hrng->base + VIC_ISTAT); + writel(0, hrng->base + VIC_ALARM); +} + +static int vic_trng_cmd(struct vic_rng *hrng, u32 cmd) { + int res = 0; + // wait till idle + vic_wait_till_idle(hrng); + switch (cmd) { + case VIC_CTRL_CMD_NOP: + case VIC_CTRL_CMD_GEN_NOISE: + case VIC_CTRL_CMD_GEN_NONCE: + case VIC_CTRL_CMD_CREATE_STATE: + case VIC_CTRL_CMD_RENEW_STATE: + case VIC_CTRL_CMD_REFRESH_ADDIN: + case VIC_CTRL_CMD_GEN_RANDOM: + case VIC_CTRL_CMD_ADVANCE_STATE: + case VIC_CTRL_CMD_KAT: + case VIC_CTRL_CMD_ZEROIZE: + writel(cmd, hrng->base + VIC_CTRL); + break; + default: + res = -1; + break; + } + + return res; +} + +static int vic_rng_init(struct hwrng *rng) +{ + struct vic_rng *hrng = to_vic_rng(rng); + + // wait till idle + + // clear register: ISTAT + vic_rng_irq_mask_clear(hrng); + + // set mission mode + writel(VIC_SMODE_SECURE_EN(1), hrng->base + VIC_SMODE); + + vic_trng_cmd(hrng, VIC_CTRL_CMD_GEN_NOISE); + vic_wait_till_idle(hrng); + + // set interrupt + writel(VIC_IE_ALL, hrng->base + VIC_IE); + + // zeroize + vic_trng_cmd(hrng, VIC_CTRL_CMD_ZEROIZE); + + vic_wait_till_idle(hrng); + + return 0; +} + +static irqreturn_t vic_rng_irq(int irq, void *priv) +{ + u32 status, val; + struct vic_rng *hrng = (struct vic_rng *)priv; + + /* + * clearing the interrupt will also clear the error register + * read error and status before clearing + */ + status = readl(hrng->base + VIC_ISTAT); + + if (status & VIC_ISTAT_ALARMS) { + writel(VIC_ISTAT_ALARMS, hrng->base + VIC_ISTAT); + val = readl(hrng->base + VIC_ALARM); + if (val & VIC_ALARM_ILLEGAL_CMD_SEQ) { + writel(VIC_ALARM_ILLEGAL_CMD_SEQ, hrng->base + VIC_ALARM); + //dev_info(hrng->dev, "ILLEGAL CMD SEQ: LAST_CMD=0x%x\r\n", + //VIC_STAT_LAST_CMD(readl(hrng->base + VIC_STAT))); + } else { + dev_info(hrng->dev, "Failed test: %x\r\n", val); + } + } + + if (status & VIC_ISTAT_ZEROIZE) { + writel(VIC_ISTAT_ZEROIZE, hrng->base + VIC_ISTAT); + //dev_info(hrng->dev, "zeroized\r\n"); + } + + if (status & VIC_ISTAT_KAT_COMPLETE) { + writel(VIC_ISTAT_KAT_COMPLETE, hrng->base + VIC_ISTAT); + //dev_info(hrng->dev, "kat_completed\r\n"); + } + + if (status & VIC_ISTAT_NOISE_RDY) { + writel(VIC_ISTAT_NOISE_RDY, hrng->base + VIC_ISTAT); + //dev_info(hrng->dev, "noise_rdy\r\n"); + } + + if (status & VIC_ISTAT_DONE) { + writel(VIC_ISTAT_DONE, hrng->base + VIC_ISTAT); + //dev_info(hrng->dev, "done\r\n"); + /* + if (VIC_STAT_LAST_CMD(readl(hrng->base + VIC_STAT)) == + VIC_CTRL_CMD_GEN_RANDOM) { + dev_info(hrng->dev, "Need Update Buffer\r\n"); + } + */ + } + vic_rng_irq_mask_clear(hrng); + + return IRQ_HANDLED; +} + +static void vic_rng_cleanup(struct hwrng *rng) +{ + struct vic_rng *hrng = to_vic_rng(rng); + + writel(0, hrng->base + VIC_CTRL); +} + +static int vic_rng_read(struct hwrng *rng, void *buf, size_t max, bool wait) +{ + struct vic_rng *hrng = to_vic_rng(rng); + + vic_trng_cmd(hrng, VIC_CTRL_CMD_ZEROIZE); + vic_trng_cmd(hrng, VIC_CTRL_CMD_GEN_NOISE); + vic_trng_cmd(hrng, VIC_CTRL_CMD_CREATE_STATE); + + vic_wait_till_idle(hrng); + max = min_t(size_t, max, (VIC_RAND_LEN * 4)); + + writel(0x0, hrng->base + VIC_MODE); + vic_trng_cmd(hrng, VIC_CTRL_CMD_GEN_RANDOM); + + vic_wait_till_idle(hrng); + memcpy_fromio(buf, hrng->base + VIC_RAND0, max); + vic_trng_cmd(hrng, VIC_CTRL_CMD_ZEROIZE); + + vic_wait_till_idle(hrng); + return max; +} + +static int vic_rng_probe(struct platform_device *pdev) +{ + int ret; + int irq; + struct vic_rng *rng; + struct resource *res; + + rng = devm_kzalloc(&pdev->dev, sizeof(*rng), GFP_KERNEL); + if (!rng){ + return -ENOMEM; + } + + platform_set_drvdata(pdev, rng); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + rng->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(rng->base)){ + return PTR_ERR(rng->base); + } + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) { + dev_err(&pdev->dev, "Couldn't get irq %d\n", irq); + return irq; + } + + ret = devm_request_irq(&pdev->dev, irq, vic_rng_irq, 0, pdev->name, + (void *)rng); + if (ret) { + dev_err(&pdev->dev, "Can't get interrupt working.\n"); + return ret; + } + + rng->rng.name = pdev->name; + rng->rng.init = vic_rng_init; + rng->rng.cleanup = vic_rng_cleanup; + rng->rng.read = vic_rng_read; + + rng->dev = &pdev->dev; + + ret = devm_hwrng_register(&pdev->dev, &rng->rng); + if (ret) { + dev_err(&pdev->dev, "failed to register hwrng\n"); + return ret; + } + + dev_info(&pdev->dev, "Initialized\n"); + + return 0; +} + +static const struct of_device_id vic_rng_dt_ids[] = { + { .compatible = "starfive,vic-rng" }, + { } +}; +MODULE_DEVICE_TABLE(of, vic_rng_dt_ids); + +static struct platform_driver vic_rng_driver = { + .probe = vic_rng_probe, + .driver = { + .name = "vic-rng", + .of_match_table = vic_rng_dt_ids, + }, +}; + +module_platform_driver(vic_rng_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Huan Feng "); +MODULE_DESCRIPTION("Starfive VIC random number generator driver"); diff --git a/drivers/char/hw_random/starfive-vic-rng.h b/drivers/char/hw_random/starfive-vic-rng.h new file mode 100644 index 0000000000000..b3bbabde0cfb1 --- /dev/null +++ b/drivers/char/hw_random/starfive-vic-rng.h @@ -0,0 +1,167 @@ +/* + ****************************************************************************** + * @file starfive-vic-rng.h + * @author StarFive Technology + * @version V1.0 + * @date 08/13/2020 + * @brief + ****************************************************************************** + * @copy + * + * THE PRESENT SOFTWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING CUSTOMERS + * WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER FOR THEM TO SAVE + * TIME. AS A RESULT, STARFIVE SHALL NOT BE HELD LIABLE FOR ANY + * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING + * FROM THE CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE + * CODING INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. + * + * COPYRIGHT 2020 Shanghai StarFive Technology Co., Ltd. + */ + +#define VIC_CTRL 0x00 +#define VIC_MODE 0x04 +#define VIC_SMODE 0x08 +#define VIC_STAT 0x0C +#define VIC_IE 0x10 +#define VIC_ISTAT 0x14 +#define VIC_ALARM 0x18 +#define VIC_BUILD_ID 0x1C +#define VIC_FEATURES 0x20 +#define VIC_RAND0 0x24 +#define VIC_NPA_DATA0 0x34 +#define VIC_SEED0 0x74 +#define VIC_IA_RDATA 0xA4 +#define VIC_IA_WDATA 0xA8 +#define VIC_IA_ADDR 0xAC +#define VIC_IA_CMD 0xB0 + +/* CTRL */ +#define VIC_CTRL_CMD_NOP 0 +#define VIC_CTRL_CMD_GEN_NOISE 1 +#define VIC_CTRL_CMD_GEN_NONCE 2 +#define VIC_CTRL_CMD_CREATE_STATE 3 +#define VIC_CTRL_CMD_RENEW_STATE 4 +#define VIC_CTRL_CMD_REFRESH_ADDIN 5 +#define VIC_CTRL_CMD_GEN_RANDOM 6 +#define VIC_CTRL_CMD_ADVANCE_STATE 7 +#define VIC_CTRL_CMD_KAT 8 +#define VIC_CTRL_CMD_ZEROIZE 15 + +/* MODE */ +#define _VIC_MODE_ADDIN_PRESENT 4 +#define _VIC_MODE_PRED_RESIST 3 +#define _VIC_MODE_KAT_SEL 2 +#define _VIC_MODE_KAT_VEC 1 +#define _VIC_MODE_SEC_ALG 0 + +#define VIC_MODE_ADDIN_PRESENT (1UL << _VIC_MODE_ADDIN_PRESENT) +#define VIC_MODE_PRED_RESIST (1UL << _VIC_MODE_PRED_RESIST) +#define VIC_MODE_KAT_SEL (1UL << _VIC_MODE_KAT_SEL) +#define VIC_MODE_KAT_VEC (1UL << _VIC_MODE_KAT_VEC) +#define VIC_MODE_SEC_ALG (1UL << _VIC_MODE_SEC_ALG) + +/* SMODE */ +#define _VIC_SMODE_MAX_REJECTS 2 +#define _VIC_SMODE_SECURE_EN 1 +#define _VIC_SMODE_NONCE 0 + +#define VIC_SMODE_MAX_REJECTS(x) ((x) << _VIC_SMODE_MAX_REJECTS) +#define VIC_SMODE_SECURE_EN(x) ((x) << _VIC_SMODE_SECURE_EN) +#define VIC_SMODE_NONCE (1UL << _VIC_SMODE_NONCE) + +/* STAT */ +#define _VIC_STAT_BUSY 31 +#define _VIC_STAT_DRBG_STATE 7 +#define _VIC_STAT_SECURE 6 +#define _VIC_STAT_NONCE_MODE 5 +#define _VIC_STAT_SEC_ALG 4 +#define _VIC_STAT_LAST_CMD 0 + +#define VIC_STAT_BUSY (1UL << _VIC_STAT_BUSY) +#define VIC_STAT_DRBG_STATE (1UL << _VIC_STAT_DRBG_STATE) +#define VIC_STAT_SECURE (1UL << _VIC_STAT_SECURE) +#define VIC_STAT_NONCE_MODE (1UL << _VIC_STAT_NONCE_MODE) +#define VIC_STAT_SEC_ALG (1UL << _VIC_STAT_SEC_ALG) +#define VIC_STAT_LAST_CMD(x) (((x) >> _VIC_STAT_LAST_CMD) & 0xF) + +/* IE */ +#define _VIC_IE_GLBL 31 +#define _VIC_IE_DONE 4 +#define _VIC_IE_ALARMS 3 +#define _VIC_IE_NOISE_RDY 2 +#define _VIC_IE_KAT_COMPLETE 1 +#define _VIC_IE_ZEROIZE 0 + +#define VIC_IE_GLBL (1UL << _VIC_IE_GLBL) +#define VIC_IE_DONE (1UL << _VIC_IE_DONE) +#define VIC_IE_ALARMS (1UL << _VIC_IE_ALARMS) +#define VIC_IE_NOISE_RDY (1UL << _VIC_IE_NOISE_RDY) +#define VIC_IE_KAT_COMPLETE (1UL << _VIC_IE_KAT_COMPLETE) +#define VIC_IE_ZEROIZE (1UL << _VIC_IE_ZEROIZE) +#define VIC_IE_ALL (VIC_IE_GLBL | VIC_IE_DONE | VIC_IE_ALARMS | \ + VIC_IE_NOISE_RDY | VIC_IE_KAT_COMPLETE | VIC_IE_ZEROIZE) + +/* ISTAT */ +#define _VIC_ISTAT_DONE 4 +#define _VIC_ISTAT_ALARMS 3 +#define _VIC_ISTAT_NOISE_RDY 2 +#define _VIC_ISTAT_KAT_COMPLETE 1 +#define _VIC_ISTAT_ZEROIZE 0 + +#define VIC_ISTAT_DONE (1UL << _VIC_ISTAT_DONE) +#define VIC_ISTAT_ALARMS (1UL << _VIC_ISTAT_ALARMS) +#define VIC_ISTAT_NOISE_RDY (1UL << _VIC_ISTAT_NOISE_RDY) +#define VIC_ISTAT_KAT_COMPLETE (1UL << _VIC_ISTAT_KAT_COMPLETE) +#define VIC_ISTAT_ZEROIZE (1UL << _VIC_ISTAT_ZEROIZE) + +/* ALARMS */ +#define VIC_ALARM_ILLEGAL_CMD_SEQ (1UL << 4) +#define VIC_ALARM_FAILED_TEST_ID_OK 0 +#define VIC_ALARM_FAILED_TEST_ID_KAT_STAT 1 +#define VIC_ALARM_FAILED_TEST_ID_KAT 2 +#define VIC_ALARM_FAILED_TEST_ID_MONOBIT 3 +#define VIC_ALARM_FAILED_TEST_ID_RUN 4 +#define VIC_ALARM_FAILED_TEST_ID_LONGRUN 5 +#define VIC_ALARM_FAILED_TEST_ID_AUTOCORRELATION 6 +#define VIC_ALARM_FAILED_TEST_ID_POKER 7 +#define VIC_ALARM_FAILED_TEST_ID_REPETITION_COUNT 8 +#define VIC_ALARM_FAILED_TEST_ID_ADAPATIVE_PROPORTION 9 + +/* BUILD_ID */ +#define VIC_BUILD_ID_STEPPING(x) (((x) >> 28) & 0xF) +#define VIC_BUILD_ID_EPN(x) ((x) & 0xFFFF) + +/* FEATURES */ +#define VIC_FEATURES_AES_256(x) (((x) >> 9) & 1) +#define VIC_FEATURES_EXTRA_PS_PRESENT(x) (((x) >> 8) & 1) +#define VIC_FEATURES_DIAG_LEVEL_NS(x) (((x) >> 7) & 1) +#define VIC_FEATURES_DIAG_LEVEL_CLP800(x) (((x) >> 4) & 7) +#define VIC_FEATURES_DIAG_LEVEL_ST_HLT(x) (((x) >> 1) & 7) +#define VIC_FEATURES_SECURE_RST_STATE(x) ((x) & 1) + +/* IA_CMD */ +#define VIC_IA_CMD_GO (1UL << 31) +#define VIC_IA_CMD_WR (1) + +#define _VIC_SMODE_MAX_REJECTS_MASK 255UL +#define _VIC_SMODE_SECURE_EN_MASK 1UL +#define _VIC_SMODE_NONCE_MASK 1UL +#define _VIC_MODE_SEC_ALG_MASK 1UL +#define _VIC_MODE_ADDIN_PRESENT_MASK 1UL +#define _VIC_MODE_PRED_RESIST_MASK 1UL + +#define VIC_SMODE_SET_MAX_REJECTS(y, x) (((y) & ~(_VIC_SMODE_MAX_REJECTS_MASK << _VIC_SMODE_MAX_REJECTS)) | ((x) << _VIC_SMODE_MAX_REJECTS)) +#define VIC_SMODE_SET_SECURE_EN(y, x) (((y) & ~(_VIC_SMODE_SECURE_EN_MASK << _VIC_SMODE_SECURE_EN)) | ((x) << _VIC_SMODE_SECURE_EN)) +#define VIC_SMODE_SET_NONCE(y, x) (((y) & ~(_VIC_SMODE_NONCE_MASK << _VIC_SMODE_NONCE)) | ((x) << _VIC_SMODE_NONCE)) +#define VIC_SMODE_GET_MAX_REJECTS(x) (((x) >> _VIC_SMODE_MAX_REJECTS) & _VIC_SMODE_MAX_REJECTS_MASK) +#define VIC_SMODE_GET_SECURE_EN(x) (((x) >> _VIC_SMODE_SECURE_EN) & _VIC_SMODE_SECURE_EN_MASK) +#define VIC_SMODE_GET_NONCE(x) (((x) >> _VIC_SMODE_NONCE) & _VIC_SMODE_NONCE_MASK) + +#define VIC_MODE_SET_SEC_ALG(y, x) (((y) & ~(_VIC_MODE_SEC_ALG_MASK << _VIC_MODE_SEC_ALG)) | ((x) << _VIC_MODE_SEC_ALG)) +#define VIC_MODE_SET_PRED_RESIST(y, x) (((y) & ~(_VIC_MODE_PRED_RESIST_MASK << _VIC_MODE_PRED_RESIST)) | ((x) << _VIC_MODE_PRED_RESIST)) +#define VIC_MODE_SET_ADDIN_PRESENT(y, x) (((y) & ~(_VIC_MODE_ADDIN_PRESENT_MASK << _VIC_MODE_ADDIN_PRESENT)) | ((x) << _VIC_MODE_ADDIN_PRESENT)) +#define VIC_MODE_GET_SEC_ALG(x) (((x) >> _VIC_MODE_SEC_ALG) & _VIC_MODE_SEC_ALG_MASK) +#define VIC_MODE_GET_PRED_RESIST(x) (((x) >> _VIC_MODE_PRED_RESIST) & _VIC_MODE_PRED_RESIST_MASK) +#define VIC_MODE_GET_ADDIN_PRESENT(x) (((x) >> _VIC_MODE_ADDIN_PRESENT) & _VIC_MODE_ADDIN_PRESENT_MASK) + +#define VIC_RAND_LEN 4 From c6e0ca81d85a8c91e4f1e001c892c4c524f66263 Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Wed, 6 Apr 2022 01:04:45 +0200 Subject: [PATCH 22/52] dt-bindings: sifive-l2-cache: Support the StarFive JH7100 SoC This cache controller is also used on the StarFive JH7100 SoC. Signed-off-by: Emil Renner Berthing --- Documentation/devicetree/bindings/riscv/sifive-l2-cache.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/riscv/sifive-l2-cache.yaml b/Documentation/devicetree/bindings/riscv/sifive-l2-cache.yaml index e2d330bd4608a..7516057c2c7b2 100644 --- a/Documentation/devicetree/bindings/riscv/sifive-l2-cache.yaml +++ b/Documentation/devicetree/bindings/riscv/sifive-l2-cache.yaml @@ -27,6 +27,7 @@ select: enum: - sifive,fu540-c000-ccache - sifive,fu740-c000-ccache + - starfive,jh7100-ccache required: - compatible @@ -37,6 +38,7 @@ properties: - enum: - sifive,fu540-c000-ccache - sifive,fu740-c000-ccache + - starfive,jh7100-ccache - const: cache cache-block-size: From d94a29c9a5882d2640c825189024fa2a969642d6 Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Mon, 8 Nov 2021 00:41:18 +0100 Subject: [PATCH 23/52] soc: sifive: l2 cache: Convert to platform driver This converts the driver to use the builtin_platform_driver_probe macro to initialize the driver. This macro ends up calling device_initcall as was used previously, but also allocates a platform device which gives us access to much nicer APIs such as platform_ioremap_resource, platform_get_irq and dev_err_probe. Signed-off-by: Emil Renner Berthing --- drivers/soc/sifive/sifive_l2_cache.c | 79 ++++++++++++++-------------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/drivers/soc/sifive/sifive_l2_cache.c b/drivers/soc/sifive/sifive_l2_cache.c index 59640a1d0b28a..010d612f74205 100644 --- a/drivers/soc/sifive/sifive_l2_cache.c +++ b/drivers/soc/sifive/sifive_l2_cache.c @@ -7,9 +7,9 @@ */ #include #include -#include -#include -#include +#include +#include +#include #include #include @@ -96,12 +96,6 @@ static void l2_config_read(void) pr_info("L2CACHE: Index of the largest way enabled: %d\n", regval); } -static const struct of_device_id sifive_l2_ids[] = { - { .compatible = "sifive,fu540-c000-ccache" }, - { .compatible = "sifive,fu740-c000-ccache" }, - { /* end of table */ }, -}; - static ATOMIC_NOTIFIER_HEAD(l2_err_chain); int register_sifive_l2_error_notifier(struct notifier_block *nb) @@ -192,36 +186,29 @@ static irqreturn_t l2_int_handler(int irq, void *device) return IRQ_HANDLED; } -static int __init sifive_l2_init(void) +static int __init sifive_l2_probe(struct platform_device *pdev) { - struct device_node *np; - struct resource res; - int i, rc, intr_num; - - np = of_find_matching_node(NULL, sifive_l2_ids); - if (!np) - return -ENODEV; - - if (of_address_to_resource(np, 0, &res)) - return -ENODEV; - - l2_base = ioremap(res.start, resource_size(&res)); - if (!l2_base) - return -ENOMEM; - - intr_num = of_property_count_u32_elems(np, "interrupts"); - if (!intr_num) { - pr_err("L2CACHE: no interrupts property\n"); - return -ENODEV; - } - - for (i = 0; i < intr_num; i++) { - g_irq[i] = irq_of_parse_and_map(np, i); - rc = request_irq(g_irq[i], l2_int_handler, 0, "l2_ecc", NULL); - if (rc) { - pr_err("L2CACHE: Could not request IRQ %d\n", g_irq[i]); - return rc; - } + struct device *dev = &pdev->dev; + int nirqs; + int ret; + int i; + + l2_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(l2_base)) + return PTR_ERR(l2_base); + + nirqs = platform_irq_count(pdev); + if (nirqs <= 0) + return dev_err_probe(dev, -ENODEV, "no interrupts\n"); + + for (i = 0; i < nirqs; i++) { + g_irq[i] = platform_get_irq(pdev, i); + if (g_irq[i] < 0) + return g_irq[i]; + + ret = devm_request_irq(dev, g_irq[i], l2_int_handler, 0, pdev->name, NULL); + if (ret) + return dev_err_probe(dev, ret, "Could not request IRQ %d\n", g_irq[i]); } l2_config_read(); @@ -234,4 +221,18 @@ static int __init sifive_l2_init(void) #endif return 0; } -device_initcall(sifive_l2_init); + +static const struct of_device_id sifive_l2_match[] = { + { .compatible = "sifive,fu540-c000-ccache" }, + { .compatible = "sifive,fu740-c000-ccache" }, + { /* sentinel */ } +}; + +static struct platform_driver sifive_l2_driver = { + .driver = { + .name = "sifive_l2_cache", + .of_match_table = sifive_l2_match, + .suppress_bind_attrs = true, + }, +}; +builtin_platform_driver_probe(sifive_l2_driver, sifive_l2_probe); From 75f75d54511992dbd47f4cb4e4bb01bb9e3a67b2 Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Wed, 6 Apr 2022 00:38:05 +0200 Subject: [PATCH 24/52] soc: sifive: l2 cache: Add StarFive JH7100 support This adds support for the StarFive JH7100 SoC which also features this SiFive cache controller. Unfortunately the data corrected interrupt is broken and fires continuously, so add a quirk to not register a handler for it. Signed-off-by: Emil Renner Berthing --- arch/riscv/Kconfig.socs | 1 + drivers/soc/Makefile | 2 +- drivers/soc/sifive/Kconfig | 2 +- drivers/soc/sifive/sifive_l2_cache.c | 6 ++++++ 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/arch/riscv/Kconfig.socs b/arch/riscv/Kconfig.socs index 85670dc9fe95d..a798dec597fdd 100644 --- a/arch/riscv/Kconfig.socs +++ b/arch/riscv/Kconfig.socs @@ -22,6 +22,7 @@ config SOC_STARFIVE bool "StarFive SoCs" select PINCTRL select RESET_CONTROLLER + select SIFIVE_L2 select SIFIVE_PLIC help This enables support for StarFive SoC platform hardware. diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile index 919716e0e7001..c5de8970c5f36 100644 --- a/drivers/soc/Makefile +++ b/drivers/soc/Makefile @@ -25,7 +25,7 @@ obj-y += qcom/ obj-y += renesas/ obj-y += rockchip/ obj-$(CONFIG_SOC_SAMSUNG) += samsung/ -obj-$(CONFIG_SOC_SIFIVE) += sifive/ +obj-y += sifive/ obj-y += sunxi/ obj-$(CONFIG_ARCH_TEGRA) += tegra/ obj-y += ti/ diff --git a/drivers/soc/sifive/Kconfig b/drivers/soc/sifive/Kconfig index 58cf8c40d08d5..776b30723c043 100644 --- a/drivers/soc/sifive/Kconfig +++ b/drivers/soc/sifive/Kconfig @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 -if SOC_SIFIVE +if SOC_SIFIVE || SOC_STARFIVE config SIFIVE_L2 bool "Sifive L2 Cache controller" diff --git a/drivers/soc/sifive/sifive_l2_cache.c b/drivers/soc/sifive/sifive_l2_cache.c index 010d612f74205..5fd769b50f004 100644 --- a/drivers/soc/sifive/sifive_l2_cache.c +++ b/drivers/soc/sifive/sifive_l2_cache.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -189,6 +190,7 @@ static irqreturn_t l2_int_handler(int irq, void *device) static int __init sifive_l2_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; + unsigned long quirks = (uintptr_t)device_get_match_data(dev); int nirqs; int ret; int i; @@ -206,6 +208,9 @@ static int __init sifive_l2_probe(struct platform_device *pdev) if (g_irq[i] < 0) return g_irq[i]; + if (quirks & BIT(i)) + continue; + ret = devm_request_irq(dev, g_irq[i], l2_int_handler, 0, pdev->name, NULL); if (ret) return dev_err_probe(dev, ret, "Could not request IRQ %d\n", g_irq[i]); @@ -225,6 +230,7 @@ static int __init sifive_l2_probe(struct platform_device *pdev) static const struct of_device_id sifive_l2_match[] = { { .compatible = "sifive,fu540-c000-ccache" }, { .compatible = "sifive,fu740-c000-ccache" }, + { .compatible = "starfive,jh7100-ccache", .data = (void *)BIT(DATA_UNCORR) }, { /* sentinel */ } }; From a4285a16f1f8cc6297660cecf4fba7296016cefc Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Sat, 12 Jun 2021 16:48:31 -0700 Subject: [PATCH 25/52] soc: sifive: l2 cache: Add non-coherent DMA handling Add functions to flush the caches and handle non-coherent DMA. Signed-off-by: Emil Renner Berthing --- drivers/soc/sifive/sifive_l2_cache.c | 50 ++++++++++++++++++++++++++++ include/soc/sifive/sifive_l2_cache.h | 21 ++++++++++++ 2 files changed, 71 insertions(+) diff --git a/drivers/soc/sifive/sifive_l2_cache.c b/drivers/soc/sifive/sifive_l2_cache.c index 5fd769b50f004..804891abf49f6 100644 --- a/drivers/soc/sifive/sifive_l2_cache.c +++ b/drivers/soc/sifive/sifive_l2_cache.c @@ -5,6 +5,7 @@ * Copyright (C) 2018-2019 SiFive, Inc. * */ +#include #include #include #include @@ -12,6 +13,7 @@ #include #include #include +#include #include #define SIFIVE_L2_DIRECCFIX_LOW 0x100 @@ -30,15 +32,21 @@ #define SIFIVE_L2_DATECCFAIL_HIGH 0x164 #define SIFIVE_L2_DATECCFAIL_COUNT 0x168 +#define SIFIVE_L2_FLUSH64 0x200 +#define SIFIVE_L2_FLUSH32 0x240 + #define SIFIVE_L2_CONFIG 0x00 #define SIFIVE_L2_WAYENABLE 0x08 #define SIFIVE_L2_ECCINJECTERR 0x40 #define SIFIVE_L2_MAX_ECCINTR 4 +#define SIFIVE_L2_LINE_SIZE 64 static void __iomem *l2_base; static int g_irq[SIFIVE_L2_MAX_ECCINTR]; static struct riscv_cacheinfo_ops l2_cache_ops; +static phys_addr_t uncached_offset; +DEFINE_STATIC_KEY_FALSE(sifive_l2_handle_noncoherent_key); enum { DIR_CORR = 0, @@ -111,6 +119,42 @@ int unregister_sifive_l2_error_notifier(struct notifier_block *nb) } EXPORT_SYMBOL_GPL(unregister_sifive_l2_error_notifier); +void sifive_l2_flush_range(phys_addr_t start, size_t len) +{ + phys_addr_t end = start + len; + phys_addr_t line; + + if (!len) + return; + + mb(); + for (line = ALIGN_DOWN(start, SIFIVE_L2_LINE_SIZE); line < end; + line += SIFIVE_L2_LINE_SIZE) { +#ifdef CONFIG_32BIT + writel(line >> 4, l2_base + SIFIVE_L2_FLUSH32); +#else + writeq(line, l2_base + SIFIVE_L2_FLUSH64); +#endif + mb(); + } +} +EXPORT_SYMBOL_GPL(sifive_l2_flush_range); + +void *sifive_l2_set_uncached(void *addr, size_t size) +{ + phys_addr_t phys_addr = __pa(addr) + uncached_offset; + void *mem_base; + + mem_base = memremap(phys_addr, size, MEMREMAP_WT); + if (!mem_base) { + pr_err("%s memremap failed for addr %p\n", __func__, addr); + return ERR_PTR(-EINVAL); + } + + return mem_base; +} +EXPORT_SYMBOL_GPL(sifive_l2_set_uncached); + static int l2_largest_wayenabled(void) { return readl(l2_base + SIFIVE_L2_WAYENABLE) & 0xFF; @@ -191,6 +235,7 @@ static int __init sifive_l2_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; unsigned long quirks = (uintptr_t)device_get_match_data(dev); + u64 offset; int nirqs; int ret; int i; @@ -216,6 +261,11 @@ static int __init sifive_l2_probe(struct platform_device *pdev) return dev_err_probe(dev, ret, "Could not request IRQ %d\n", g_irq[i]); } + if (!device_property_read_u64(dev, "uncached-offset", &offset)) { + uncached_offset = offset; + static_branch_enable(&sifive_l2_handle_noncoherent_key); + } + l2_config_read(); l2_cache_ops.get_priv_group = l2_get_priv_group; diff --git a/include/soc/sifive/sifive_l2_cache.h b/include/soc/sifive/sifive_l2_cache.h index 92ade10ed67e9..2ef41da683477 100644 --- a/include/soc/sifive/sifive_l2_cache.h +++ b/include/soc/sifive/sifive_l2_cache.h @@ -7,10 +7,31 @@ #ifndef __SOC_SIFIVE_L2_CACHE_H #define __SOC_SIFIVE_L2_CACHE_H +#include +#include + extern int register_sifive_l2_error_notifier(struct notifier_block *nb); extern int unregister_sifive_l2_error_notifier(struct notifier_block *nb); #define SIFIVE_L2_ERR_TYPE_CE 0 #define SIFIVE_L2_ERR_TYPE_UE 1 +DECLARE_STATIC_KEY_FALSE(sifive_l2_handle_noncoherent_key); + +static inline bool sifive_l2_handle_noncoherent(void) +{ +#ifdef CONFIG_SIFIVE_L2 + return static_branch_unlikely(&sifive_l2_handle_noncoherent_key); +#else + return false; +#endif +} + +void sifive_l2_flush_range(phys_addr_t start, size_t len); +void *sifive_l2_set_uncached(void *addr, size_t size); +static inline void sifive_l2_clear_uncached(void *addr, size_t size) +{ + memunmap(addr); +} + #endif /* __SOC_SIFIVE_L2_CACHE_H */ From 42dc20355190beb94abbcbff93e0999d7fa63dc9 Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Sat, 12 Jun 2021 16:48:31 -0700 Subject: [PATCH 26/52] RISC-V: Add non-coherent DMA support This implements the cache management operations to support non-coherent DMAs on RISC-V. Signed-off-by: Atish Patra Signed-off-by: Emil Renner Berthing --- arch/riscv/Kconfig | 10 +++++++ arch/riscv/mm/Makefile | 1 + arch/riscv/mm/dma-noncoherent.c | 52 +++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 arch/riscv/mm/dma-noncoherent.c diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig index fcbb81feb7ad8..c794ebe2d590f 100644 --- a/arch/riscv/Kconfig +++ b/arch/riscv/Kconfig @@ -218,6 +218,16 @@ config PGTABLE_LEVELS config LOCKDEP_SUPPORT def_bool y +config RISCV_DMA_NONCOHERENT + bool "Support non-coherent DMA" + default SOC_STARFIVE + select ARCH_HAS_DMA_PREP_COHERENT + select ARCH_HAS_DMA_SET_UNCACHED + select ARCH_HAS_DMA_CLEAR_UNCACHED + select ARCH_HAS_SYNC_DMA_FOR_DEVICE + select ARCH_HAS_SYNC_DMA_FOR_CPU + select ARCH_HAS_SETUP_DMA_OPS + source "arch/riscv/Kconfig.socs" source "arch/riscv/Kconfig.erratas" diff --git a/arch/riscv/mm/Makefile b/arch/riscv/mm/Makefile index ac7a25298a04a..d76aabf4b94d6 100644 --- a/arch/riscv/mm/Makefile +++ b/arch/riscv/mm/Makefile @@ -30,3 +30,4 @@ endif endif obj-$(CONFIG_DEBUG_VIRTUAL) += physaddr.o +obj-$(CONFIG_RISCV_DMA_NONCOHERENT) += dma-noncoherent.o diff --git a/arch/riscv/mm/dma-noncoherent.c b/arch/riscv/mm/dma-noncoherent.c new file mode 100644 index 0000000000000..86d23046b9581 --- /dev/null +++ b/arch/riscv/mm/dma-noncoherent.c @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * RISC-V specific functions to support DMA for non-coherent devices + * + * Copyright (c) 2021 Western Digital Corporation or its affiliates. + */ + +#include + +#include + +void arch_sync_dma_for_device(phys_addr_t paddr, size_t size, enum dma_data_direction dir) +{ + if (sifive_l2_handle_noncoherent()) + sifive_l2_flush_range(paddr, size); +} + +void arch_sync_dma_for_cpu(phys_addr_t paddr, size_t size, enum dma_data_direction dir) +{ + if (sifive_l2_handle_noncoherent()) + sifive_l2_flush_range(paddr, size); +} + +void *arch_dma_set_uncached(void *addr, size_t size) +{ + if (sifive_l2_handle_noncoherent()) + return sifive_l2_set_uncached(addr, size); + + return addr; +} + +void arch_dma_clear_uncached(void *addr, size_t size) +{ + if (sifive_l2_handle_noncoherent()) + sifive_l2_clear_uncached(addr, size); +} + +void arch_dma_prep_coherent(struct page *page, size_t size) +{ + void *flush_addr = page_address(page); + + memset(flush_addr, 0, size); + if (sifive_l2_handle_noncoherent()) + sifive_l2_flush_range(__pa(flush_addr), size); +} + +void arch_setup_dma_ops(struct device *dev, u64 dma_base, u64 size, + const struct iommu_ops *iommu, bool coherent) +{ + /* If a specific device is dma-coherent, set it here */ + dev->dma_coherent = coherent; +} From dc08c23a7fcf9c532ba0fc45e3f4ea57aebc46e0 Mon Sep 17 00:00:00 2001 From: Emil Renner Berthing Date: Sun, 3 Jul 2022 21:01:11 +0200 Subject: [PATCH 27/52] power: reset: tps65086: Allow building as a module Signed-off-by: Emil Renner Berthing --- drivers/power/reset/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig index 4b563db3ab3ec..c434b9a45f6a1 100644 --- a/drivers/power/reset/Kconfig +++ b/drivers/power/reset/Kconfig @@ -205,7 +205,7 @@ config POWER_RESET_ST Reset support for STMicroelectronics boards. config POWER_RESET_TPS65086 - bool "TPS65086 restart driver" + tristate "TPS65086 restart driver" depends on MFD_TPS65086 help This driver adds support for resetting the TPS65086 PMIC on restart. From 8364e1d74eb0e1178536ac5b43adf8c0aa1a6184 Mon Sep 17 00:00:00 2001 From: Samin Guo Date: Fri, 8 Jan 2021 03:11:04 +0800 Subject: [PATCH 28/52] drivers/tty/serial/8250: update driver for JH7100 --- drivers/tty/serial/8250/8250_port.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c index 3c36a06a20b04..051c9fe470e74 100644 --- a/drivers/tty/serial/8250/8250_port.c +++ b/drivers/tty/serial/8250/8250_port.c @@ -73,8 +73,16 @@ static const struct serial8250_config uart_config[] = { }, [PORT_16550] = { .name = "16550", +#ifdef CONFIG_SOC_STARFIVE + .fifo_size = 16, + .tx_loadsz = 16, + .fcr = UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_00, + .rxtrig_bytes = {1, 4, 8, 14}, + .flags = UART_CAP_FIFO, +#else .fifo_size = 1, .tx_loadsz = 1, +#endif }, [PORT_16550A] = { .name = "16550A", From 5d11ef9e3ab3aa3e379eee98d3a2deef7610fcfe Mon Sep 17 00:00:00 2001 From: Chenjieqin Date: Fri, 8 Jan 2021 03:56:54 +0800 Subject: [PATCH 29/52] pwm: sifive-ptc: Add SiFive PWM PTC driver yiming.li: clear CNTR of PWM after setting period & duty_cycle Emil: cleanups, clock, reset and div_u64 Signed-off-by: Emil Renner Berthing --- drivers/pwm/Kconfig | 11 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-sifive-ptc.c | 258 +++++++++++++++++++++++++++++++++++ 3 files changed, 270 insertions(+) create mode 100644 drivers/pwm/pwm-sifive-ptc.c diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 904de8d61828a..858f0a787c4c1 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -494,6 +494,17 @@ config PWM_SIFIVE To compile this driver as a module, choose M here: the module will be called pwm-sifive. +config PWM_SIFIVE_PTC + tristate "SiFive PWM PTC support" + depends on SOC_SIFIVE || SOC_STARFIVE || COMPILE_TEST + depends on OF + depends on COMMON_CLK + help + Generic PWM framework driver for SiFive SoCs. + + To compile this driver as a module, choose M here: the module + will be called pwm-sifive-ptc. + config PWM_SL28CPLD tristate "Kontron sl28cpld PWM support" depends on MFD_SL28CPLD || COMPILE_TEST diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 5c08bdb817b4c..40858a061ee51 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -45,6 +45,7 @@ obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o +obj-$(CONFIG_PWM_SIFIVE_PTC) += pwm-sifive-ptc.o obj-$(CONFIG_PWM_SL28CPLD) += pwm-sl28cpld.o obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o obj-$(CONFIG_PWM_SPRD) += pwm-sprd.o diff --git a/drivers/pwm/pwm-sifive-ptc.c b/drivers/pwm/pwm-sifive-ptc.c new file mode 100644 index 0000000000000..d0e592f48e690 --- /dev/null +++ b/drivers/pwm/pwm-sifive-ptc.c @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2018 SiFive, Inc + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2, as published by + * the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +/* max channel of pwm */ +#define MAX_PWM 8 + +/* PTC Register offsets */ +#define REG_RPTC_CNTR 0x0 +#define REG_RPTC_HRC 0x4 +#define REG_RPTC_LRC 0x8 +#define REG_RPTC_CTRL 0xC + +/* Bit for PWM clock */ +#define BIT_PWM_CLOCK_EN 31 + +/* Bit for clock gen soft reset */ +#define BIT_CLK_GEN_SOFT_RESET 13 + +#define NS_1 1000000000U + +/* Access PTC register (cntr hrc lrc and ctrl), need to replace PWM_BASE_ADDR */ +#define REG_PTC_BASE_ADDR_SUB(base, N) \ + ((base) + (((N) > 3) ? (((N) - 4) * 0x10 + (1 << 15)) : ((N) * 0x10))) +#define REG_PTC_RPTC_CNTR(base, N) (REG_PTC_BASE_ADDR_SUB(base, N)) +#define REG_PTC_RPTC_HRC(base, N) (REG_PTC_BASE_ADDR_SUB(base, N) + 0x4) +#define REG_PTC_RPTC_LRC(base, N) (REG_PTC_BASE_ADDR_SUB(base, N) + 0x8) +#define REG_PTC_RPTC_CTRL(base, N) (REG_PTC_BASE_ADDR_SUB(base, N) + 0xC) + +/* pwm ptc device */ +struct sifive_pwm_ptc_device { + struct pwm_chip chip; + struct clk *clk; + void __iomem *regs; +}; + +static inline struct sifive_pwm_ptc_device *chip_to_sifive_ptc(struct pwm_chip *c) +{ + return container_of(c, struct sifive_pwm_ptc_device, chip); +} + +static void sifive_pwm_ptc_get_state(struct pwm_chip *chip, struct pwm_device *dev, + struct pwm_state *state) +{ + struct sifive_pwm_ptc_device *pwm = chip_to_sifive_ptc(chip); + u32 data_lrc; + u32 data_hrc; + u32 pwm_clk_ns = 0; + + /* get lrc and hrc data from registe */ + data_lrc = ioread32(REG_PTC_RPTC_LRC(pwm->regs, dev->hwpwm)); + data_hrc = ioread32(REG_PTC_RPTC_HRC(pwm->regs, dev->hwpwm)); + + /* how many ns does apb clock elapse */ + pwm_clk_ns = NS_1 / clk_get_rate(pwm->clk); + + /* pwm period(ns) */ + state->period = data_lrc * pwm_clk_ns; + + /* duty cycle(ns) means high level eclapse ns if it is normal polarity */ + state->duty_cycle = data_hrc * pwm_clk_ns; + + /* polarity, we don't use it now because it is not in dts */ + state->polarity = PWM_POLARITY_NORMAL; + + /* enabled or not */ + state->enabled = 1; + + dev_dbg(pwm->chip.dev, "%s: no:%d\n", __func__, dev->hwpwm); + dev_dbg(pwm->chip.dev, "data_hrc:0x%x 0x%x\n", data_hrc, data_lrc); + dev_dbg(pwm->chip.dev, "period:%llu\n", state->period); + dev_dbg(pwm->chip.dev, "duty_cycle:%llu\n", state->duty_cycle); + dev_dbg(pwm->chip.dev, "polarity:%d\n", state->polarity); + dev_dbg(pwm->chip.dev, "enabled:%d\n", state->enabled); +} + +static int sifive_pwm_ptc_apply(struct pwm_chip *chip, struct pwm_device *dev, + const struct pwm_state *state) +{ + struct sifive_pwm_ptc_device *pwm = chip_to_sifive_ptc(chip); + void __iomem *reg_addr; + u32 pwm_clk_ns = 0; + u32 data_hrc = 0; + u32 data_lrc = 0; + u32 period_data = 0; + u32 duty_data = 0; + + dev_dbg(pwm->chip.dev, "%s: no:%d\n", __func__, dev->hwpwm); + dev_dbg(pwm->chip.dev, "period:%llu\n", state->period); + dev_dbg(pwm->chip.dev, "duty_cycle:%llu\n", state->duty_cycle); + dev_dbg(pwm->chip.dev, "polarity:%d\n", state->polarity); + dev_dbg(pwm->chip.dev, "enabled:%d\n", state->enabled); + + /* duty_cycle should be less or equal than period */ + if (state->duty_cycle > state->period) + return -EINVAL; + + /* calculate pwm real period (ns) */ + pwm_clk_ns = NS_1 / clk_get_rate(pwm->clk); + + dev_dbg(pwm->chip.dev, "pwm_clk_ns:%u\n", pwm_clk_ns); + + /* calculate period count */ + period_data = div_u64(state->period, pwm_clk_ns); + + if (!state->enabled) + /* if disabled, just set duty_data to 0, which means low level always */ + duty_data = 0; + else + /* calculate duty count */ + duty_data = div_u64(state->duty_cycle, pwm_clk_ns); + + dev_dbg(pwm->chip.dev, "period_data:%u, duty_data:%u\n", + period_data, duty_data); + + if (state->polarity == PWM_POLARITY_NORMAL) + /* calculate data_hrc */ + data_hrc = period_data - duty_data; + else + /* calculate data_hrc */ + data_hrc = duty_data; + + data_lrc = period_data; + + /* set hrc */ + reg_addr = REG_PTC_RPTC_HRC(pwm->regs, dev->hwpwm); + dev_dbg(pwm->chip.dev, "%s: reg_addr:%p, data:%u\n", + __func__, reg_addr, data_hrc); + + iowrite32(data_hrc, reg_addr); + + dev_dbg(pwm->chip.dev, "%s: hrc ok\n", __func__); + + /* set lrc */ + reg_addr = REG_PTC_RPTC_LRC(pwm->regs, dev->hwpwm); + dev_dbg(pwm->chip.dev, "%s: reg_addr:%p, data:%u\n", + __func__, reg_addr, data_lrc); + + iowrite32(data_lrc, reg_addr); + dev_dbg(pwm->chip.dev, "%s: lrc ok\n", __func__); + + /* Clear REG_RPTC_CNTR after setting period & duty_cycle */ + reg_addr = REG_PTC_RPTC_CNTR(pwm->regs, dev->hwpwm); + iowrite32(0, reg_addr); + return 0; +} + +static const struct pwm_ops sifive_pwm_ptc_ops = { + .get_state = sifive_pwm_ptc_get_state, + .apply = sifive_pwm_ptc_apply, + .owner = THIS_MODULE, +}; + +static void sifive_pwm_ptc_disable_action(void *data) +{ + clk_disable_unprepare(data); +} + +static int sifive_pwm_ptc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = pdev->dev.of_node; + struct sifive_pwm_ptc_device *pwm; + struct pwm_chip *chip; + struct reset_control *rst; + int ret; + + pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL); + if (!pwm) + return -ENOMEM; + + platform_set_drvdata(pdev, pwm); + + chip = &pwm->chip; + chip->dev = dev; + chip->ops = &sifive_pwm_ptc_ops; + + /* how many parameters can be transferred to ptc, need to fix */ + chip->of_pwm_n_cells = 3; + chip->base = -1; + + /* get pwm channels count, max value is 8 */ + ret = of_property_read_u32(node, "starfive,npwm", &chip->npwm); + if (ret < 0 || chip->npwm > MAX_PWM) + chip->npwm = MAX_PWM; + + dev_dbg(dev, "%s: npwm:0x%x\n", __func__, chip->npwm); + + /* get IO base address */ + pwm->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(pwm->regs)) + return dev_err_probe(dev, PTR_ERR(pwm->regs), + "Unable to map IO resources\n"); + + pwm->clk = devm_clk_get(dev, NULL); + if (IS_ERR(pwm->clk)) + return dev_err_probe(dev, PTR_ERR(pwm->clk), + "Unable to get controller clock\n"); + + ret = clk_prepare_enable(pwm->clk); + if (ret) + return dev_err_probe(dev, ret, "Unable to enable clock\n"); + + ret = devm_add_action_or_reset(dev, sifive_pwm_ptc_disable_action, pwm->clk); + if (ret) + return ret; + + rst = devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(rst)) + return dev_err_probe(dev, PTR_ERR(rst), "Unable to get reset\n"); + + ret = reset_control_deassert(rst); + if (ret) + return dev_err_probe(dev, ret, "Unable to deassert reset\n"); + + /* + * after pwmchip_add it will show up as /sys/class/pwm/pwmchip0, + * 0 is chip->base, pwm0 can be seen after running echo 0 > export + */ + ret = devm_pwmchip_add(dev, chip); + if (ret) + return dev_err_probe(dev, ret, "cannot register PTC: %d\n", ret); + + dev_dbg(dev, "SiFive PWM PTC chip registered %d PWMs\n", chip->npwm); + return 0; +} + +static const struct of_device_id sifive_pwm_ptc_of_match[] = { + { .compatible = "starfive,pwm0" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, sifive_pwm_ptc_of_match); + +static struct platform_driver sifive_pwm_ptc_driver = { + .probe = sifive_pwm_ptc_probe, + .driver = { + .name = "pwm-sifive-ptc", + .of_match_table = sifive_pwm_ptc_of_match, + }, +}; +module_platform_driver(sifive_pwm_ptc_driver); + +MODULE_DESCRIPTION("SiFive PWM PTC driver"); +MODULE_LICENSE("GPL v2"); From 3a70d82704fd267c1fc600bfeeb92d1e8a0d7cc4 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Thu, 27 May 2021 20:13:43 +0200 Subject: [PATCH 30/52] [WIP] dt-bindings: dma: dw-axi-dmac: Increase DMA channel limit to 16 The first DMAC instance in the StarFive JH7100 SoC supports 16 DMA channels. FIXME Given there are more changes to the driver than just increasing DMAC_MAX_CHANNELS, we probably need a new compatible value, too. Signed-off-by: Geert Uytterhoeven --- Documentation/devicetree/bindings/dma/snps,dw-axi-dmac.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/dma/snps,dw-axi-dmac.yaml b/Documentation/devicetree/bindings/dma/snps,dw-axi-dmac.yaml index 4324a94b26b24..957e4ce936f9b 100644 --- a/Documentation/devicetree/bindings/dma/snps,dw-axi-dmac.yaml +++ b/Documentation/devicetree/bindings/dma/snps,dw-axi-dmac.yaml @@ -51,7 +51,7 @@ properties: dma-channels: minimum: 1 - maximum: 8 + maximum: 16 resets: maxItems: 1 @@ -74,14 +74,14 @@ properties: Channel priority specifier associated with the DMA channels. $ref: /schemas/types.yaml#/definitions/uint32-array minItems: 1 - maxItems: 8 + maxItems: 16 snps,block-size: description: | Channel block size specifier associated with the DMA channels. $ref: /schemas/types.yaml#/definitions/uint32-array minItems: 1 - maxItems: 8 + maxItems: 16 snps,axi-max-burst-len: description: | From 436784aee26d8775ccb8d358a4c1a7e6148f5c21 Mon Sep 17 00:00:00 2001 From: Samin Guo Date: Wed, 17 Nov 2021 14:50:45 +0800 Subject: [PATCH 31/52] dmaengine: dw-axi-dmac: Handle xfer start while non-idle Signed-off-by: Samin Guo Signed-off-by: Curry Zhang --- drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c | 13 ++++++++++++- drivers/dma/dw-axi-dmac/dw-axi-dmac.h | 1 + 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c index c741da02b67e9..92a1d02bb60a8 100644 --- a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c +++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c @@ -377,11 +377,13 @@ static void axi_chan_block_xfer_start(struct axi_dma_chan *chan, u32 irq_mask; u8 lms = 0; /* Select AXI0 master for LLI fetching */ + chan->is_err = false; if (unlikely(axi_chan_is_hw_enable(chan))) { dev_err(chan2dev(chan), "%s is non-idle!\n", axi_chan_name(chan)); - return; + axi_chan_disable(chan); + chan->is_err = true; } axi_dma_enable(chan->chip); @@ -1013,6 +1015,14 @@ static noinline void axi_chan_handle_err(struct axi_dma_chan *chan, u32 status) /* The bad descriptor currently is in the head of vc list */ vd = vchan_next_desc(&chan->vc); + if (chan->is_err) { + struct axi_dma_desc *desc = vd_to_axi_desc(vd); + + axi_chan_block_xfer_start(chan, desc); + chan->is_err = false; + goto out; + } + /* Remove the completed descriptor from issued list */ list_del(&vd->node); @@ -1027,6 +1037,7 @@ static noinline void axi_chan_handle_err(struct axi_dma_chan *chan, u32 status) /* Try to restart the controller */ axi_chan_start_first_queued(chan); +out: spin_unlock_irqrestore(&chan->vc.lock, flags); } diff --git a/drivers/dma/dw-axi-dmac/dw-axi-dmac.h b/drivers/dma/dw-axi-dmac/dw-axi-dmac.h index e9d5eb0fd5948..4019a9a42d7ee 100644 --- a/drivers/dma/dw-axi-dmac/dw-axi-dmac.h +++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac.h @@ -49,6 +49,7 @@ struct axi_dma_chan { struct dma_slave_config config; enum dma_transfer_direction direction; bool cyclic; + bool is_err; /* these other elements are all protected by vc.lock */ bool is_paused; }; From 9ebd713099d573681e6f41bc6b0bc5328e3ff9cc Mon Sep 17 00:00:00 2001 From: Samin Guo Date: Wed, 17 Nov 2021 14:50:45 +0800 Subject: [PATCH 32/52] dmaengine: dw-axi-dmac: Add StarFive JH7100 support Signed-off-by: Samin Guo Signed-off-by: Emil Renner Berthing --- drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c | 11 ++++++++++- drivers/dma/dw-axi-dmac/dw-axi-dmac.h | 4 ++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c index 92a1d02bb60a8..bdc9d046409cf 100644 --- a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c +++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c @@ -86,7 +86,7 @@ static inline void axi_chan_config_write(struct axi_dma_chan *chan, cfg_lo = (config->dst_multblk_type << CH_CFG_L_DST_MULTBLK_TYPE_POS | config->src_multblk_type << CH_CFG_L_SRC_MULTBLK_TYPE_POS); - if (chan->chip->dw->hdata->reg_map_8_channels) { + if (!IS_ENABLED(CONFIG_SOC_STARFIVE) && chan->chip->dw->hdata->reg_map_8_channels) { cfg_hi = config->tt_fc << CH_CFG_H_TT_FC_POS | config->hs_sel_src << CH_CFG_H_HS_SEL_SRC_POS | config->hs_sel_dst << CH_CFG_H_HS_SEL_DST_POS | @@ -672,8 +672,13 @@ static int dw_axi_dma_set_hw_desc(struct axi_dma_chan *chan, hw_desc->lli->block_ts_lo = cpu_to_le32(block_ts - 1); +#ifdef CONFIG_SOC_STARFIVE + ctllo |= DWAXIDMAC_BURST_TRANS_LEN_16 << CH_CTL_L_DST_MSIZE_POS | + DWAXIDMAC_BURST_TRANS_LEN_16 << CH_CTL_L_SRC_MSIZE_POS; +#else ctllo |= DWAXIDMAC_BURST_TRANS_LEN_4 << CH_CTL_L_DST_MSIZE_POS | DWAXIDMAC_BURST_TRANS_LEN_4 << CH_CTL_L_SRC_MSIZE_POS; +#endif hw_desc->lli->ctl_lo = cpu_to_le32(ctllo); set_desc_src_master(hw_desc); @@ -1473,7 +1478,11 @@ static int dw_probe(struct platform_device *pdev) * Therefore, set constraint to 1024 * 4. */ dw->dma.dev->dma_parms = &dw->dma_parms; +#ifdef CONFIG_SOC_STARFIVE + dma_set_max_seg_size(&pdev->dev, DMAC_MAX_BLK_SIZE); +#else dma_set_max_seg_size(&pdev->dev, MAX_BLOCK_SIZE); +#endif platform_set_drvdata(pdev, chip); pm_runtime_enable(chip->dev); diff --git a/drivers/dma/dw-axi-dmac/dw-axi-dmac.h b/drivers/dma/dw-axi-dmac/dw-axi-dmac.h index 4019a9a42d7ee..d6ec88c45f483 100644 --- a/drivers/dma/dw-axi-dmac/dw-axi-dmac.h +++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac.h @@ -282,7 +282,11 @@ enum { #define CH_CTL_L_SRC_MAST BIT(0) /* CH_CFG_H */ +#ifdef CONFIG_SOC_STARFIVE +#define CH_CFG_H_PRIORITY_POS 15 +#else #define CH_CFG_H_PRIORITY_POS 17 +#endif #define CH_CFG_H_DST_PER_POS 12 #define CH_CFG_H_SRC_PER_POS 7 #define CH_CFG_H_HS_SEL_DST_POS 4 From 2396093129d884ee7fc77bcb8e6606fe202c0d14 Mon Sep 17 00:00:00 2001 From: Walker Chen Date: Wed, 17 Nov 2021 10:38:48 +0800 Subject: [PATCH 33/52] net: phy: motorcomm: Add YT8521 support This adds basic support for the Motorcomm YT8521 Gigabit Ethernet PHY. Signed-off-by: Walker Chen Signed-off-by: Emil Renner Berthing --- drivers/net/phy/Kconfig | 2 +- drivers/net/phy/motorcomm.c | 68 +++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 9fee639ee5c8b..bb263b74ad6dd 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -253,7 +253,7 @@ config MOTORCOMM_PHY tristate "Motorcomm PHYs" help Enables support for Motorcomm network PHYs. - Currently supports the YT8511 gigabit PHY. + Currently supports the YT8511 and YT8521 gigabit PHYs. config NATIONAL_PHY tristate "National Semiconductor PHYs" diff --git a/drivers/net/phy/motorcomm.c b/drivers/net/phy/motorcomm.c index 7e6ac2c5e27ef..2815e3c68bf83 100644 --- a/drivers/net/phy/motorcomm.c +++ b/drivers/net/phy/motorcomm.c @@ -10,6 +10,7 @@ #include #define PHY_ID_YT8511 0x0000010a +#define PHY_ID_YT8521 0x0000011a #define YT8511_PAGE_SELECT 0x1e #define YT8511_PAGE 0x1f @@ -17,6 +18,10 @@ #define YT8511_EXT_DELAY_DRIVE 0x0d #define YT8511_EXT_SLEEP_CTRL 0x27 +#define YT8521_EXT_SMI_SDS_PHY 0xa000 +#define YT8521_EXT_CHIP_CONFIG 0xa001 +#define YT8521_EXT_RGMII_CONFIG1 0xa003 + /* 2b00 25m from pll * 2b01 25m from xtl *default* * 2b10 62.m from pll @@ -38,6 +43,17 @@ #define YT8511_DELAY_FE_TX_EN (0xf << 12) #define YT8511_DELAY_FE_TX_DIS (0x2 << 12) +/* YT8521 SMI_SDS_PHY */ +#define YT8521_SMI_SDS_PHY_UTP 0 +#define YT8521_SMI_SDS_PHY_SDS BIT(1) + +/* YT8521 Chip_Config */ +#define YT8521_SW_RST_N_MODE BIT(15) +#define YT8521_RXC_DLY_EN BIT(8) + +/* YT8521 RGMII_Config1 */ +#define YT8521_TX_DELAY_SEL GENMASK(3, 0) + static int yt8511_read_page(struct phy_device *phydev) { return __phy_read(phydev, YT8511_PAGE_SELECT); @@ -111,6 +127,47 @@ static int yt8511_config_init(struct phy_device *phydev) return phy_restore_page(phydev, oldpage, ret); } +static int yt8521_soft_reset(struct phy_device *phydev) +{ + phy_modify_paged(phydev, YT8521_EXT_CHIP_CONFIG, YT8511_PAGE, + YT8521_SW_RST_N_MODE, 0); + + return genphy_soft_reset(phydev); +} + +static int yt8521_config_init(struct phy_device *phydev) +{ + int oldpage, ret = 0; + + oldpage = phy_select_page(phydev, YT8521_EXT_SMI_SDS_PHY); + if (oldpage < 0) + goto err_restore_page; + + /* switch to UTP access */ + ret = __phy_write(phydev, YT8511_PAGE, YT8521_SMI_SDS_PHY_UTP); + if (ret < 0) + goto err_restore_page; + + /* set tx delay to 3 * 150ps */ + ret = __phy_write(phydev, YT8511_PAGE_SELECT, YT8521_EXT_RGMII_CONFIG1); + if (ret < 0) + goto err_restore_page; + ret = __phy_modify(phydev, YT8511_PAGE, YT8521_TX_DELAY_SEL, 3); + if (ret < 0) + goto err_restore_page; + + /* disable rx delay */ + ret = __phy_write(phydev, YT8511_PAGE_SELECT, YT8521_EXT_CHIP_CONFIG); + if (ret < 0) + goto err_restore_page; + ret = __phy_modify(phydev, YT8511_PAGE, YT8521_RXC_DLY_EN, 0); + if (ret < 0) + goto err_restore_page; + +err_restore_page: + return phy_restore_page(phydev, oldpage, ret); +} + static struct phy_driver motorcomm_phy_drvs[] = { { PHY_ID_MATCH_EXACT(PHY_ID_YT8511), @@ -121,6 +178,16 @@ static struct phy_driver motorcomm_phy_drvs[] = { .read_page = yt8511_read_page, .write_page = yt8511_write_page, }, + { + PHY_ID_MATCH_EXACT(PHY_ID_YT8521), + .name = "YT8521 Gigabit Ethernet", + .soft_reset = yt8521_soft_reset, + .config_init = yt8521_config_init, + .suspend = genphy_suspend, + .resume = genphy_resume, + .read_page = yt8511_read_page, + .write_page = yt8511_write_page, + }, }; module_phy_driver(motorcomm_phy_drvs); @@ -131,6 +198,7 @@ MODULE_LICENSE("GPL"); static const struct mdio_device_id __maybe_unused motorcomm_tbl[] = { { PHY_ID_MATCH_EXACT(PHY_ID_YT8511) }, + { PHY_ID_MATCH_EXACT(PHY_ID_YT8521) }, { /* sentinal */ } }; From f671da90242954980b5dc5863efb740634b81f58 Mon Sep 17 00:00:00 2001 From: Walker Chen Date: Wed, 17 Nov 2021 10:38:48 +0800 Subject: [PATCH 34/52] net: phy: motorcomm: Add WIP YT8521 wake-on-lan code Signed-off-by: Walker Chen --- drivers/net/phy/motorcomm.c | 201 ++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) diff --git a/drivers/net/phy/motorcomm.c b/drivers/net/phy/motorcomm.c index 2815e3c68bf83..9c407fb0ea93d 100644 --- a/drivers/net/phy/motorcomm.c +++ b/drivers/net/phy/motorcomm.c @@ -12,6 +12,7 @@ #define PHY_ID_YT8511 0x0000010a #define PHY_ID_YT8521 0x0000011a +#define YT8511_INT_MASK 0x12 #define YT8511_PAGE_SELECT 0x1e #define YT8511_PAGE 0x1f #define YT8511_EXT_CLK_GATE 0x0c @@ -29,10 +30,14 @@ */ #define YT8511_CLK_125M (BIT(2) | BIT(1)) #define YT8511_PLLON_SLP BIT(14) +#define YT8511_EN_SLEEP_SW BIT(15) /* RX Delay enabled = 1.8ns 1000T, 8ns 10/100T */ #define YT8511_DELAY_RX BIT(0) +/* Enable rx_clk_gmii when link down */ +#define YT8511_EN_GATE_RX_CLK BIT(12) + /* TX Gig-E Delay is bits 7:4, default 0x5 * TX Fast-E Delay is bits 15:12, default 0xf * Delay = 150ps * N - 250ps @@ -43,6 +48,9 @@ #define YT8511_DELAY_FE_TX_EN (0xf << 12) #define YT8511_DELAY_FE_TX_DIS (0x2 << 12) +/* YT8521 Interrupt Mask Register */ +#define YT8521_WOL_INT BIT(6) + /* YT8521 SMI_SDS_PHY */ #define YT8521_SMI_SDS_PHY_UTP 0 #define YT8521_SMI_SDS_PHY_SDS BIT(1) @@ -54,6 +62,33 @@ /* YT8521 RGMII_Config1 */ #define YT8521_TX_DELAY_SEL GENMASK(3, 0) +/* to enable system WOL of phy, please define this macro to 1 + * otherwise, define it to 0. + */ +#define YTPHY_ENABLE_WOL 0 + +#if YTPHY_ENABLE_WOL +enum ytphy_wol_type { + YTPHY_WOL_TYPE_LEVEL, + YTPHY_WOL_TYPE_PULSE, + YTPHY_WOL_TYPE_MAX +}; + +enum ytphy_wol_width { + YTPHY_WOL_WIDTH_84MS, + YTPHY_WOL_WIDTH_168MS, + YTPHY_WOL_WIDTH_336MS, + YTPHY_WOL_WIDTH_672MS, + YTPHY_WOL_WIDTH_MAX +}; + +struct ytphy_wol_cfg { + int enable; + int type; + int width; +}; +#endif /* YTPHY_ENABLE_WOL */ + static int yt8511_read_page(struct phy_device *phydev) { return __phy_read(phydev, YT8511_PAGE_SELECT); @@ -164,10 +199,167 @@ static int yt8521_config_init(struct phy_device *phydev) if (ret < 0) goto err_restore_page; +#if YTPHY_ENABLE_WOL + phydev->irq = PHY_POLL; + + /* disable auto sleep */ + ret = __phy_write(phydev, YT8511_PAGE_SELECT, YT8511_EXT_SLEEP_CTRL); + if (ret < 0) + goto err_restore_page; + ret = __phy_modify(phydev, YT8511_PAGE, YT8511_EN_SLEEP_SW, 0); + if (ret < 0) + goto err_restore_page; + + /* enable rx_clk_gmii when no wire plugged */ + ret = __phy_write(phydev, YT8511_PAGE_SELECT, YT8511_EXT_CLK_GATE); + if (ret < 0) + goto err_restore_page; + ret = __phy_modify(phydev, YT8511_EXT_CLK_GATE, YT8511_EN_GATE_RX_CLK, 0); + if (ret < 0) + goto err_restore_page; +#endif /* YTPHY_ENABLE_WOL */ + err_restore_page: return phy_restore_page(phydev, oldpage, ret); } +#if YTPHY_ENABLE_WOL +static int ytphy_read_ext(struct phy_device *phydev, int page) +{ + return phy_read_paged(phydev, page, YT8511_PAGE); +} + +static int ytphy_write_ext(struct phy_device *phydev, int page, u16 val) +{ + return phy_write_paged(phydev, page, YT8511_PAGE, val); +} + +static int ytphy_wol_en_cfg(struct phy_device *phydev, struct ytphy_wol_cfg *wol_cfg) +{ + int val; + + val = ytphy_read_ext(phydev, YTPHY_WOL_CFG_REG); + if (val < 0) + return val; + + if (wol_cfg->enable) { + val |= YTPHY_WOL_CFG_EN; + + if (wol_cfg->type == YTPHY_WOL_TYPE_LEVEL) { + val &= ~YTPHY_WOL_CFG_TYPE; + val &= ~YTPHY_WOL_CFG_INTR_SEL; + } else if (wol_cfg->type == YTPHY_WOL_TYPE_PULSE) { + val |= YTPHY_WOL_CFG_TYPE; + val |= YTPHY_WOL_CFG_INTR_SEL; + + if (wol_cfg->width == YTPHY_WOL_WIDTH_84MS) { + val &= ~YTPHY_WOL_CFG_WIDTH1; + val &= ~YTPHY_WOL_CFG_WIDTH2; + } else if (wol_cfg->width == YTPHY_WOL_WIDTH_168MS) { + val |= YTPHY_WOL_CFG_WIDTH1; + val &= ~YTPHY_WOL_CFG_WIDTH2; + } else if (wol_cfg->width == YTPHY_WOL_WIDTH_336MS) { + val &= ~YTPHY_WOL_CFG_WIDTH1; + val |= YTPHY_WOL_CFG_WIDTH2; + } else if (wol_cfg->width == YTPHY_WOL_WIDTH_672MS) { + val |= YTPHY_WOL_CFG_WIDTH1; + val |= YTPHY_WOL_CFG_WIDTH2; + } + } + } else { + val &= ~YTPHY_WOL_CFG_EN; + val &= ~YTPHY_WOL_CFG_INTR_SEL; + } + + return ytphy_write_ext(phydev, YTPHY_WOL_CFG_REG, val); +} + +static void ytphy_get_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol) +{ + int val = 0; + + wol->supported = WAKE_MAGIC; + wol->wolopts = 0; + + val = ytphy_read_ext(phydev, YTPHY_WOL_CFG_REG); + if (val < 0) + return; + + if (val & YTPHY_WOL_CFG_EN) + wol->wolopts |= WAKE_MAGIC; +} + +static int ytphy_set_wol(struct phy_device *phydev, struct ethtool_wolinfo *wol) +{ + struct ytphy_wol_cfg wol_cfg = {}; + struct net_device *p_attached_dev = phydev->attached_dev; + int ret, pre_page, val; + + pre_page = ytphy_read_ext(phydev, YT8521_EXT_SMI_SDS_PHY); + if (pre_page < 0) + return pre_page; + + /* switch to UTP access */ + ret = ytphy_write_ext(phydev, YT8521_EXT_SMI_SDS_PHY, YT8521_SMI_SDS_PHY_UTP); + if (ret < 0) + return ret; + + if (wol->wolopts & WAKE_MAGIC) { + /* Enable the WOL interrupt */ + ret = phy_modify(phydev, YT8511_INT_MASK, YT8521_WOL_INT, YT8521_WOL_INT); + if (ret < 0) + return ret; + + /* Set the WOL config */ + wol_cfg.enable = 1; //enable + wol_cfg.type = YTPHY_WOL_TYPE_PULSE; + wol_cfg.width = YTPHY_WOL_WIDTH_672MS; + ret = ytphy_wol_en_cfg(phydev, &wol_cfg); + if (ret < 0) + return ret; + + /* Store the device address for the magic packet */ + ret = ytphy_write_ext(phydev, YTPHY_MAGIC_PACKET_MAC_ADDR2, + ((p_attached_dev->dev_addr[0] << 8) | + p_attached_dev->dev_addr[1])); + if (ret < 0) + return ret; + + ret = ytphy_write_ext(phydev, YTPHY_MAGIC_PACKET_MAC_ADDR1, + ((p_attached_dev->dev_addr[2] << 8) | + p_attached_dev->dev_addr[3])); + if (ret < 0) + return ret; + + ret = ytphy_write_ext(phydev, YTPHY_MAGIC_PACKET_MAC_ADDR0, + ((p_attached_dev->dev_addr[4] << 8) | + p_attached_dev->dev_addr[5])); + if (ret < 0) + return ret; + } else { + wol_cfg.enable = 0; //disable + wol_cfg.type = YTPHY_WOL_TYPE_MAX; + wol_cfg.width = YTPHY_WOL_WIDTH_MAX; + ret = ytphy_wol_en_cfg(phydev, &wol_cfg); + if (ret < 0) + return ret; + } + + /* Recover to previous register space page */ + return ytphy_write_ext(phydev, YT8521_EXT_SMI_SDS_PHY, pre_page); +} + +static int yt8521_suspend(struct phy_device *phydev) +{ + return 0; +} + +static int yt8521_resume(struct phy_device *phydev) +{ + return 0; +} +#endif /* YTPHY_ENABLE_WOL */ + static struct phy_driver motorcomm_phy_drvs[] = { { PHY_ID_MATCH_EXACT(PHY_ID_YT8511), @@ -183,8 +375,16 @@ static struct phy_driver motorcomm_phy_drvs[] = { .name = "YT8521 Gigabit Ethernet", .soft_reset = yt8521_soft_reset, .config_init = yt8521_config_init, +#if YTPHY_ENABLE_WOL + .flags = PHY_POLL; + .suspend = yt8521_suspend, + .resume = yt8521_resume, + .get_wol = ytphy_get_wol, + .set_wol = ytphy_set_wol, +#else .suspend = genphy_suspend, .resume = genphy_resume, +#endif .read_page = yt8511_read_page, .write_page = yt8511_write_page, }, @@ -194,6 +394,7 @@ module_phy_driver(motorcomm_phy_drvs); MODULE_DESCRIPTION("Motorcomm PHY driver"); MODULE_AUTHOR("Peter Geis"); +MODULE_AUTHOR("Walker Chen "); MODULE_LICENSE("GPL"); static const struct mdio_device_id __maybe_unused motorcomm_tbl[] = { From b44396401cbf41fe673c143836c5bf63571049d4 Mon Sep 17 00:00:00 2001 From: Tom Date: Tue, 6 Apr 2021 13:30:26 +0800 Subject: [PATCH 35/52] net: stmmac: Configure gtxclk based on speed --- .../ethernet/stmicro/stmmac/dwmac-generic.c | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-generic.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-generic.c index 5e731a72cce81..b38d3aae496e5 100644 --- a/drivers/net/ethernet/stmicro/stmmac/dwmac-generic.c +++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-generic.c @@ -16,6 +16,50 @@ #include "stmmac.h" #include "stmmac_platform.h" +/* + * GMAC_GTXCLK 为 gmac 的时钟分频寄存器,低8位为分频值 + * bit name access default descript + * [31] clk_gmac_gtxclk enable RW 0x0 "1:enable; 0:disable" + * [30] reserved - 0x0 reserved + * [29:8] reserved - 0x0 reserved + * [7:0] clk_gmac_gtxclk divide ratio RW 0x4 divide value + * + * gmac 的 root 时钟为500M, gtxclk 需求的时钟如下: + * 1000M: gtxclk为125M,分频值为500/125 = 0x4 + * 100M: gtxclk为25M, 分频值为500/25 = 0x14 + * 10M: gtxclk为2.5M,分频值为500/2.5 = 0xc8 + */ +#ifdef CONFIG_SOC_STARFIVE +#define CLKGEN_BASE 0x11800000 +#define CLKGEN_GMAC_GTXCLK_OFFSET 0x1EC +#define CLKGEN_GMAC_GTXCLK_ADDR (CLKGEN_BASE + CLKGEN_GMAC_GTXCLK_OFFSET) + +#define CLKGEN_125M_DIV 0x4 +#define CLKGEN_25M_DIV 0x14 +#define CLKGEN_2_5M_DIV 0xc8 + +static void dwmac_fixed_speed(void *priv, unsigned int speed) +{ + u32 value; + void *addr = ioremap(CLKGEN_GMAC_GTXCLK_ADDR, sizeof(value)); + if (!addr) { + pr_err("%s can't remap CLKGEN_GMAC_GTXCLK_ADDR\n", __func__); + return; + } + + value = readl(addr) & (~0x000000FF); + + switch (speed) { + case SPEED_1000: value |= CLKGEN_125M_DIV; break; + case SPEED_100: value |= CLKGEN_25M_DIV; break; + case SPEED_10: value |= CLKGEN_2_5M_DIV; break; + default: iounmap(addr); return; + } + writel(value, addr); /*set gmac gtxclk*/ + iounmap(addr); +} +#endif + static int dwmac_generic_probe(struct platform_device *pdev) { struct plat_stmmacenet_data *plat_dat; @@ -52,6 +96,9 @@ static int dwmac_generic_probe(struct platform_device *pdev) if (ret) goto err_remove_config_dt; } +#ifdef CONFIG_SOC_STARFIVE + plat_dat->fix_mac_speed = dwmac_fixed_speed; +#endif ret = stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res); if (ret) From 877a1c1c6662b2fe2cba1c9742391c32828cd1dd Mon Sep 17 00:00:00 2001 From: Matteo Croce Date: Fri, 21 May 2021 03:26:38 +0200 Subject: [PATCH 36/52] net: stmmac: use GFP_DMA32 Signed-off-by: Matteo Croce --- drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index c5f33630e7718..172a90aa4c860 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -1391,7 +1391,7 @@ static int stmmac_init_rx_buffers(struct stmmac_priv *priv, struct dma_desc *p, { struct stmmac_rx_queue *rx_q = &priv->rx_queue[queue]; struct stmmac_rx_buffer *buf = &rx_q->buf_pool[i]; - gfp_t gfp = (GFP_ATOMIC | __GFP_NOWARN); + gfp_t gfp = (GFP_ATOMIC | __GFP_NOWARN | GFP_DMA32); if (priv->dma_cap.addr64 <= 32) gfp |= GFP_DMA32; @@ -4442,7 +4442,7 @@ static inline void stmmac_rx_refill(struct stmmac_priv *priv, u32 queue) struct stmmac_rx_queue *rx_q = &priv->rx_queue[queue]; int dirty = stmmac_rx_dirty(priv, queue); unsigned int entry = rx_q->dirty_rx; - gfp_t gfp = (GFP_ATOMIC | __GFP_NOWARN); + gfp_t gfp = (GFP_ATOMIC | __GFP_NOWARN | GFP_DMA32); if (priv->dma_cap.addr64 <= 32) gfp |= GFP_DMA32; From ead5a504223f8e58c5622754a5c913e58482a40f Mon Sep 17 00:00:00 2001 From: Walker Chen Date: Wed, 17 Nov 2021 15:50:50 +0800 Subject: [PATCH 37/52] ASoC: starfive: Add StarFive JH7100 audio drivers Signed-off-by: Michael Yan Signed-off-by: Jenny Zhang Signed-off-by: Walker Chen Signed-off-by: Emil Renner Berthing --- sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/starfive/Kconfig | 59 ++ sound/soc/starfive/Makefile | 24 + sound/soc/starfive/i2svad-pcm.c | 249 ++++++ sound/soc/starfive/i2svad.c | 1042 +++++++++++++++++++++++ sound/soc/starfive/i2svad.h | 246 ++++++ sound/soc/starfive/pdm.c | 362 ++++++++ sound/soc/starfive/pdm.h | 43 + sound/soc/starfive/pwmdac-pcm.c | 233 +++++ sound/soc/starfive/pwmdac-transmitter.c | 82 ++ sound/soc/starfive/pwmdac.c | 862 +++++++++++++++++++ sound/soc/starfive/pwmdac.h | 161 ++++ sound/soc/starfive/spdif-pcm.c | 288 +++++++ sound/soc/starfive/spdif.c | 384 +++++++++ sound/soc/starfive/spdif.h | 154 ++++ 16 files changed, 4191 insertions(+) create mode 100644 sound/soc/starfive/Kconfig create mode 100644 sound/soc/starfive/Makefile create mode 100644 sound/soc/starfive/i2svad-pcm.c create mode 100644 sound/soc/starfive/i2svad.c create mode 100644 sound/soc/starfive/i2svad.h create mode 100644 sound/soc/starfive/pdm.c create mode 100644 sound/soc/starfive/pdm.h create mode 100644 sound/soc/starfive/pwmdac-pcm.c create mode 100644 sound/soc/starfive/pwmdac-transmitter.c create mode 100644 sound/soc/starfive/pwmdac.c create mode 100644 sound/soc/starfive/pwmdac.h create mode 100644 sound/soc/starfive/spdif-pcm.c create mode 100644 sound/soc/starfive/spdif.c create mode 100644 sound/soc/starfive/spdif.h diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 7d4747b6bab24..607a66c1aa422 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -90,6 +90,7 @@ source "sound/soc/sh/Kconfig" source "sound/soc/sof/Kconfig" source "sound/soc/spear/Kconfig" source "sound/soc/sprd/Kconfig" +source "sound/soc/starfive/Kconfig" source "sound/soc/sti/Kconfig" source "sound/soc/stm/Kconfig" source "sound/soc/sunxi/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index d4528962ac34e..dbaef4dd1b666 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -54,6 +54,7 @@ obj-$(CONFIG_SND_SOC) += pxa/ obj-$(CONFIG_SND_SOC) += qcom/ obj-$(CONFIG_SND_SOC) += rockchip/ obj-$(CONFIG_SND_SOC) += samsung/ +obj-$(CONFIG_SND_SOC) += starfive/ obj-$(CONFIG_SND_SOC) += sh/ obj-$(CONFIG_SND_SOC) += sof/ obj-$(CONFIG_SND_SOC) += spear/ diff --git a/sound/soc/starfive/Kconfig b/sound/soc/starfive/Kconfig new file mode 100644 index 0000000000000..2dfbf9d488864 --- /dev/null +++ b/sound/soc/starfive/Kconfig @@ -0,0 +1,59 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2021 StarFive Technology Co., Ltd. + +config SND_STARFIVE_SPDIF + tristate "starfive spdif" + depends on SOC_STARFIVE || COMPILE_TEST + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + help + Say Y or M if you want to add support for codecs attached to the + I2S interface on VIC vic_starlight board. You will also need to select + the drivers for the rest of VIC audio subsystem. + +config SND_STARFIVE_SPDIF_PCM + bool "PCM PIO extension for spdif driver" + depends on SND_STARFIVE_SPDIF + help + Say Y or N if you want to add a custom ALSA extension that registers + a PCM and uses PIO to transfer data. + +config SND_STARFIVE_PWMDAC + tristate "starfive pwmdac Device Driver" + depends on SOC_STARFIVE || COMPILE_TEST + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for sf pwmdac driver. + +config SND_STARFIVE_PWMDAC_PCM + bool "PCM PIO extension for pwmdac driver" + depends on SND_STARFIVE_PWMDAC + help + Say Y or N if you want to add a custom ALSA extension that registers + a PCM and uses PIO to transfer data. + +config SND_STARFIVE_PDM + tristate "starfive pdm Device Driver" + depends on SOC_STARFIVE || COMPILE_TEST + select REGMAP_MMIO + help + Say Y or M if you want to add support for sf pdm driver. + +config SND_STARFIVE_I2SVAD + tristate "starfive I2SVAD Device Driver" + depends on SOC_STARFIVE || COMPILE_TEST + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for I2SVAD driver for + starfive I2SVAD device. + +config SND_STARFIVE_I2SVAD_PCM + bool "PCM PIO extension for I2SVAD driver" + depends on SND_STARFIVE_I2SVAD + help + Say Y or N if you want to add a custom ALSA extension that registers + a PCM and uses PIO to transfer data. + + This functionality is specially suited for I2SVAD devices that don't have + DMA support. + diff --git a/sound/soc/starfive/Makefile b/sound/soc/starfive/Makefile new file mode 100644 index 0000000000000..60d85f424907f --- /dev/null +++ b/sound/soc/starfive/Makefile @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (C) 2021 StarFive Technology Co., Ltd. +# +snd-soc-starfive-spdif-y := spdif.o +snd-soc-starfive-spdif-$(CONFIG_SND_STARFIVE_SPDIF_PCM) += spdif-pcm.o + +obj-$(CONFIG_SND_STARFIVE_SPDIF) += snd-soc-starfive-spdif.o + +snd-soc-starfive-pwmdac-y := pwmdac.o +snd-soc-starfive-pwmdac-$(CONFIG_SND_STARFIVE_PWMDAC_PCM) += pwmdac-pcm.o +snd-soc-starfive-pwmdac-transmitter-y := pwmdac-transmitter.o + +obj-$(CONFIG_SND_STARFIVE_PWMDAC) += snd-soc-starfive-pwmdac.o +obj-$(CONFIG_SND_STARFIVE_PWMDAC) += snd-soc-starfive-pwmdac-transmitter.o + +snd-soc-starfive-pdm-y := pdm.o + +obj-$(CONFIG_SND_STARFIVE_PDM) += snd-soc-starfive-pdm.o + +snd-soc-starfive-i2svad-y := i2svad.o +snd-soc-starfive-i2svad-$(CONFIG_SND_STARFIVE_I2SVAD_PCM) += i2svad-pcm.o + +obj-$(CONFIG_SND_STARFIVE_I2SVAD) += snd-soc-starfive-i2svad.o diff --git a/sound/soc/starfive/i2svad-pcm.c b/sound/soc/starfive/i2svad-pcm.c new file mode 100644 index 0000000000000..61f6339d00d2d --- /dev/null +++ b/sound/soc/starfive/i2svad-pcm.c @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 StarFive Technology Co., Ltd. + */ +#include +#include +#include +#include + +#include "i2svad.h" + +#define BUFFER_BYTES_MAX (3 * 2 * 8 * PERIOD_BYTES_MIN) +#define PERIOD_BYTES_MIN 4096 +#define PERIODS_MIN 2 + +#define i2svad_pcm_tx_fn(sample_bits) \ +static unsigned int i2svad_pcm_tx_##sample_bits(struct i2svad_dev *dev, \ + struct snd_pcm_runtime *runtime, unsigned int tx_ptr, \ + bool *period_elapsed) \ +{ \ + const u##sample_bits (*p)[2] = (void *)runtime->dma_area; \ + unsigned int period_pos = tx_ptr % runtime->period_size; \ + int i; \ +\ + for (i = 0; i < dev->fifo_th; i++) { \ + iowrite32(p[tx_ptr][0], dev->i2s_base + LRBR_LTHR(0)); \ + iowrite32(p[tx_ptr][1], dev->i2s_base + RRBR_RTHR(0)); \ + period_pos++; \ + if (++tx_ptr >= runtime->buffer_size) \ + tx_ptr = 0; \ + } \ + *period_elapsed = period_pos >= runtime->period_size; \ + return tx_ptr; \ +} + +#define i2svad_pcm_rx_fn(sample_bits) \ +static unsigned int i2svad_pcm_rx_##sample_bits(struct i2svad_dev *dev, \ + struct snd_pcm_runtime *runtime, unsigned int rx_ptr, \ + bool *period_elapsed) \ +{ \ + u##sample_bits (*p)[2] = (void *)runtime->dma_area; \ + unsigned int period_pos = rx_ptr % runtime->period_size; \ + int i; \ +\ + for (i = 0; i < dev->fifo_th; i++) { \ + p[rx_ptr][0] = ioread32(dev->i2s_base + LRBR_LTHR(0)); \ + p[rx_ptr][1] = ioread32(dev->i2s_base + RRBR_RTHR(0)); \ + period_pos++; \ + if (++rx_ptr >= runtime->buffer_size) \ + rx_ptr = 0; \ + } \ + *period_elapsed = period_pos >= runtime->period_size; \ + return rx_ptr; \ +} + +i2svad_pcm_tx_fn(16); +i2svad_pcm_rx_fn(16); + +#undef i2svad_pcm_tx_fn +#undef i2svad_pcm_rx_fn + +static const struct snd_pcm_hardware i2svad_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000, + .rate_min = 32000, + .rate_max = 48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = BUFFER_BYTES_MAX, + .period_bytes_min = PERIOD_BYTES_MIN, + .period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN, + .periods_min = PERIODS_MIN, + .periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN, + .fifo_size = 16, +}; + +static void i2svad_pcm_transfer(struct i2svad_dev *dev, bool push) +{ + struct snd_pcm_substream *substream; + bool active, period_elapsed; + + rcu_read_lock(); + if (push) + substream = rcu_dereference(dev->tx_substream); + else + substream = rcu_dereference(dev->rx_substream); + active = substream && snd_pcm_running(substream); + if (active) { + unsigned int ptr; + unsigned int new_ptr; + + if (push) { + ptr = READ_ONCE(dev->tx_ptr); + new_ptr = dev->tx_fn(dev, substream->runtime, ptr, + &period_elapsed); + cmpxchg(&dev->tx_ptr, ptr, new_ptr); + } else { + ptr = READ_ONCE(dev->rx_ptr); + new_ptr = dev->rx_fn(dev, substream->runtime, ptr, + &period_elapsed); + cmpxchg(&dev->rx_ptr, ptr, new_ptr); + } + + if (period_elapsed) + snd_pcm_period_elapsed(substream); + } + rcu_read_unlock(); +} + +void i2svad_pcm_push_tx(struct i2svad_dev *dev) +{ + i2svad_pcm_transfer(dev, true); +} + +void i2svad_pcm_pop_rx(struct i2svad_dev *dev) +{ + i2svad_pcm_transfer(dev, false); +} + +static int i2svad_pcm_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct i2svad_dev *dev = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0)); + + snd_soc_set_runtime_hwparams(substream, &i2svad_pcm_hardware); + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + runtime->private_data = dev; + + return 0; +} + +static int i2svad_pcm_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + synchronize_rcu(); + return 0; +} + +static int i2svad_pcm_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct i2svad_dev *dev = runtime->private_data; + + switch (params_channels(hw_params)) { + case 2: + break; + default: + dev_err(dev->dev, "invalid channels number\n"); + return -EINVAL; + } + + switch (params_format(hw_params)) { + case SNDRV_PCM_FORMAT_S16_LE: + dev->tx_fn = i2svad_pcm_tx_16; + dev->rx_fn = i2svad_pcm_rx_16; + break; + default: + dev_err(dev->dev, "invalid format\n"); + return -EINVAL; + } + + return 0; +} + +static int i2svad_pcm_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct i2svad_dev *dev = runtime->private_data; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + WRITE_ONCE(dev->tx_ptr, 0); + rcu_assign_pointer(dev->tx_substream, substream); + } else { + WRITE_ONCE(dev->rx_ptr, 0); + rcu_assign_pointer(dev->rx_substream, substream); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rcu_assign_pointer(dev->tx_substream, NULL); + else + rcu_assign_pointer(dev->rx_substream, NULL); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static snd_pcm_uframes_t i2svad_pcm_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct i2svad_dev *dev = runtime->private_data; + snd_pcm_uframes_t pos; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + pos = READ_ONCE(dev->tx_ptr); + else + pos = READ_ONCE(dev->rx_ptr); + + return pos < runtime->buffer_size ? pos : 0; +} + +static int i2svad_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + size_t size = i2svad_pcm_hardware.buffer_bytes_max; + + snd_pcm_set_managed_buffer_all(rtd->pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + NULL, size, size); + return 0; +} + +static const struct snd_soc_component_driver i2svad_pcm_component = { + .open = i2svad_pcm_open, + .close = i2svad_pcm_close, + .hw_params = i2svad_pcm_hw_params, + .trigger = i2svad_pcm_trigger, + .pointer = i2svad_pcm_pointer, + .pcm_construct = i2svad_pcm_new, +}; + +int i2svad_pcm_register(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, &i2svad_pcm_component, + NULL, 0); +} diff --git a/sound/soc/starfive/i2svad.c b/sound/soc/starfive/i2svad.c new file mode 100644 index 0000000000000..74978d30702cf --- /dev/null +++ b/sound/soc/starfive/i2svad.c @@ -0,0 +1,1042 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 StarFive Technology Co., Ltd. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "i2svad.h" + +/* vad control function*/ +static void vad_start(struct vad_params *vad) +{ + regmap_update_bits(vad->vad_map, VAD_MEM_SW, + VAD_MEM_SW_MASK, VAD_MEM_SW_TO_VAD); + regmap_update_bits(vad->vad_map, VAD_SW, + VAD_SW_MASK, VAD_SW_VAD_XMEM_ENABLE|VAD_SW_ADC_ENABLE); + regmap_update_bits(vad->vad_map, VAD_SPINT_EN, + VAD_SPINT_EN_MASK, VAD_SPINT_EN_ENABLE); + regmap_update_bits(vad->vad_map, VAD_SLINT_EN, + VAD_SLINT_EN_MASK, VAD_SLINT_EN_ENABLE); +} + +static void vad_stop(struct vad_params *vad) +{ + regmap_update_bits(vad->vad_map, VAD_SPINT_EN, + VAD_SPINT_EN_MASK, VAD_SLINT_EN_DISABLE); + regmap_update_bits(vad->vad_map, VAD_SLINT_EN, + VAD_SLINT_EN_MASK, VAD_SLINT_EN_DISABLE); + regmap_update_bits(vad->vad_map, VAD_SW, + VAD_SW_MASK, VAD_SW_VAD_XMEM_DISABLE|VAD_SW_ADC_DISABLE); + regmap_update_bits(vad->vad_map, VAD_MEM_SW, + VAD_MEM_SW_MASK, VAD_MEM_SW_TO_AXI); +} + +static void vad_status(struct vad_params *vad) +{ + u32 sp_value,sp_en; + u32 sl_value,sl_en; + + regmap_read(vad->vad_map, VAD_SPINT,&sp_value); + regmap_read(vad->vad_map, VAD_SPINT_EN,&sp_en); + if (sp_value&sp_en){ + regmap_update_bits(vad->vad_map, VAD_SPINT_CLR, + VAD_SPINT_CLR_MASK, VAD_SPINT_CLR_VAD_SPINT); + vad->vstatus = VAD_STATUS_SPINT; + vad_stop(vad); + vad_start(vad); + } + + regmap_read(vad->vad_map, VAD_SLINT,&sl_value); + regmap_read(vad->vad_map, VAD_SLINT_EN,&sl_en); + if (sl_value&sl_en){ + regmap_update_bits(vad->vad_map, VAD_SLINT_CLR, + VAD_SLINT_CLR_MASK, VAD_SLINT_CLR_VAD_SLINT); + vad->vstatus = VAD_STATUS_SLINT; + } +} + +static int vad_trigger(struct vad_params *vad,int cmd) +{ + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if(vad->vswitch) + { + vad_start(vad); + } + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + vad_stop(vad); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static void vad_init(struct vad_params *vad) +{ + /* left_margin */ + regmap_update_bits(vad->vad_map, VAD_LEFT_MARGIN, + VAD_LEFT_MARGIN_MASK, 0x0); + /* right_margin */ + regmap_update_bits(vad->vad_map, VAD_RIGHT_MARGIN, + VAD_RIGHT_MARGIN_MASK, 0x0); + /*low-energy transition range threshold ——NL*/ + regmap_update_bits(vad->vad_map, VAD_N_LOW_CONT_FRAMES, + VAD_N_LOW_CONT_FRAMES_MASK, 0x3); + /* low-energy transition range */ + regmap_update_bits(vad->vad_map, VAD_N_LOW_SEEK_FRAMES, + VAD_N_LOW_SEEK_FRAMES_MASK, 0x8); + /* high-energy transition range threshold——NH */ + regmap_update_bits(vad->vad_map, VAD_N_HIGH_CONT_FRAMES, + VAD_N_HIGH_CONT_FRAMES_MASK, 0x5); + /* high-energy transition range */ + regmap_update_bits(vad->vad_map, VAD_N_HIGH_SEEK_FRAMES, + VAD_N_HIGH_SEEK_FRAMES_MASK, 0x1E); + /*low-energy voice range threshold——NVL*/ + regmap_update_bits(vad->vad_map, VAD_N_SPEECH_LOW_HIGH_FRAMES, + VAD_N_SPEECH_LOW_HIGH_FRAMES_MASK, 0x2); + /*low-energy voice range*/ + regmap_update_bits(vad->vad_map, VAD_N_SPEECH_LOW_SEEK_FRAMES, + VAD_N_SPEECH_LOW_SEEK_FRAMES_MASK, 0x12); + /*mean silence frame range*/ + regmap_update_bits(vad->vad_map, VAD_MEAN_SIL_FRAMES, + VAD_MEAN_SIL_FRAMES_MASK, 0xA); + /*low-energy threshold scaling factor,12bit(0~0xFFF)*/ + regmap_update_bits(vad->vad_map, VAD_N_ALPHA, + VAD_N_ALPHA_MASK, 0x1A); + /*high-energy threshold scaling factor,12bit(0~0xFFF)*/ + regmap_update_bits(vad->vad_map, VAD_N_BETA, + VAD_N_BETA_MASK, 0x34); + regmap_update_bits(vad->vad_map, VAD_LEFT_WD, + VAD_LEFT_WD_MASK, VAD_LEFT_WD_BIT_15_0); + regmap_update_bits(vad->vad_map, VAD_RIGHT_WD, + VAD_RIGHT_WD_MASK, VAD_RIGHT_WD_BIT_15_0); + regmap_update_bits(vad->vad_map, VAD_LR_SEL, + VAD_LR_SEL_MASK, VAD_LR_SEL_L); + regmap_update_bits(vad->vad_map, VAD_STOP_DELAY, + VAD_STOP_DELAY_MASK, VAD_STOP_DELAY_0_SAMPLE); + regmap_update_bits(vad->vad_map, VAD_ADDR_START, + VAD_ADDR_START_MASK, 0x0); + regmap_update_bits(vad->vad_map, VAD_ADDR_WRAP, + VAD_ADDR_WRAP_MASK, 0x2000); + regmap_update_bits(vad->vad_map, VAD_MEM_SW, + VAD_MEM_SW_MASK, VAD_MEM_SW_TO_AXI); + regmap_update_bits(vad->vad_map, VAD_SPINT_CLR, + VAD_SPINT_CLR_MASK, VAD_SPINT_CLR_VAD_SPINT); + regmap_update_bits(vad->vad_map, VAD_SLINT_CLR, + VAD_SLINT_CLR_MASK, VAD_SLINT_CLR_VAD_SLINT); +} + + +static int vad_switch_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + + return 0; +} + +static int vad_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct i2svad_dev *dev = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = dev->vad.vswitch; + + return 0; +} + +static int vad_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct i2svad_dev *dev = snd_soc_component_get_drvdata(component); + int val; + + val = ucontrol->value.integer.value[0]; + if (val && !dev->vad.vswitch) { + dev->vad.vswitch = true; + } else if (!val && dev->vad.vswitch) { + dev->vad.vswitch = false; + vad_stop(&(dev->vad)); + } + + return 0; +} + + +static int vad_status_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 2; + + return 0; +} + +static int vad_status_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct i2svad_dev *dev = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = dev->vad.vstatus; + dev->vad.vstatus = VAD_STATUS_NORMAL; + + return 0; +} + + +#define SOC_VAD_SWITCH_DECL(xname) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = vad_switch_info, .get = vad_switch_get, \ + .put = vad_switch_put, } + +#define SOC_VAD_STATUS_DECL(xname) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = vad_status_info, .get = vad_status_get, } + + +static const struct snd_kcontrol_new vad_snd_controls[] = { + SOC_VAD_SWITCH_DECL("vad switch"), + SOC_VAD_STATUS_DECL("vad status"), +}; + +static int vad_probe(struct snd_soc_component *component) +{ + struct i2svad_dev *priv = snd_soc_component_get_drvdata(component); + + snd_soc_component_init_regmap(component, priv->vad.vad_map); + snd_soc_add_component_controls(component, vad_snd_controls, + ARRAY_SIZE(vad_snd_controls)); + + return 0; +} + +/* i2s control function*/ +static inline void i2s_write_reg(void __iomem *io_base, int reg, u32 val) +{ + writel(val, io_base + reg); +} + +static inline u32 i2s_read_reg(void __iomem *io_base, int reg) +{ + return readl(io_base + reg); +} + +static inline void i2s_disable_channels(struct i2svad_dev *dev, u32 stream) +{ + u32 i = 0; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + for (i = 0; i < ALL_CHANNEL_NUM; i++) + i2s_write_reg(dev->i2s_base, TER(i), 0); + } else { + for (i = 0; i < ALL_CHANNEL_NUM; i++) + i2s_write_reg(dev->i2s_base, RER(i), 0); + } +} + +static inline void i2s_clear_irqs(struct i2svad_dev *dev, u32 stream) +{ + u32 i = 0; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + for (i = 0; i < ALL_CHANNEL_NUM; i++) + i2s_read_reg(dev->i2s_base, TOR(i)); + } else { + for (i = 0; i < ALL_CHANNEL_NUM; i++) + i2s_read_reg(dev->i2s_base, ROR(i)); + } +} + +static inline void i2s_disable_irqs(struct i2svad_dev *dev, u32 stream, + int chan_nr) +{ + u32 i, irq; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + for (i = 0; i < (chan_nr / 2); i++) { + irq = i2s_read_reg(dev->i2s_base, IMR(i)); + i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x30); + } + } else { + for (i = 0; i < (chan_nr / 2); i++) { + irq = i2s_read_reg(dev->i2s_base, IMR(i)); + i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x03); + } + } +} + +static inline void i2s_enable_irqs(struct i2svad_dev *dev, u32 stream, + int chan_nr) +{ + u32 i, irq; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + for (i = 0; i < (chan_nr / 2); i++) { + irq = i2s_read_reg(dev->i2s_base, IMR(i)); + i2s_write_reg(dev->i2s_base, IMR(i), irq & ~0x30); + } + } else { + for (i = 0; i < (chan_nr / 2); i++) { + irq = i2s_read_reg(dev->i2s_base, IMR(i)); + i2s_write_reg(dev->i2s_base, IMR(i), irq & ~0x03); + } + } +} + +static irqreturn_t i2s_irq_handler(int irq, void *dev_id) +{ + struct i2svad_dev *dev = dev_id; + bool irq_valid = false; + u32 isr[4]; + int i; + + for (i = 0; i < ALL_CHANNEL_NUM; i++) + isr[i] = i2s_read_reg(dev->i2s_base, ISR(i)); + + i2s_clear_irqs(dev, SNDRV_PCM_STREAM_PLAYBACK); + i2s_clear_irqs(dev, SNDRV_PCM_STREAM_CAPTURE); + + for (i = 0; i < 4; i++) { + /* + * Check if TX fifo is empty. If empty fill FIFO with samples + * NOTE: Only two channels supported + */ + if ((isr[i] & ISR_TXFE) && (i == 0) && dev->use_pio) { + i2svad_pcm_push_tx(dev); + irq_valid = true; + } + + /* + * Data available. Retrieve samples from FIFO + * NOTE: Only two channels supported + */ + if ((isr[i] & ISR_RXDA) && (i == 0) && dev->use_pio) { + i2svad_pcm_pop_rx(dev); + irq_valid = true; + } + + /* Error Handling: TX */ + if (isr[i] & ISR_TXFO) { + //dev_err(dev->dev, "TX overrun (ch_id=%d)\n", i); + irq_valid = true; + } + + /* Error Handling: TX */ + if (isr[i] & ISR_RXFO) { + //dev_err(dev->dev, "RX overrun (ch_id=%d)\n", i); + irq_valid = true; + } + } + + vad_status(&(dev->vad)); + + if (irq_valid) + return IRQ_HANDLED; + else + return IRQ_NONE; +} + +static void i2s_start(struct i2svad_dev *dev, + struct snd_pcm_substream *substream) +{ + struct i2s_clk_config_data *config = &dev->config; + + i2s_write_reg(dev->i2s_base, IER, 1); + i2s_enable_irqs(dev, substream->stream, config->chan_nr); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + i2s_write_reg(dev->i2s_base, ITER, 1); + else + i2s_write_reg(dev->i2s_base, IRER, 1); + + i2s_write_reg(dev->i2s_base, CER, 1); +} + +static void i2s_stop(struct i2svad_dev *dev, + struct snd_pcm_substream *substream) +{ + + i2s_clear_irqs(dev, substream->stream); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + i2s_write_reg(dev->i2s_base, ITER, 0); + else + i2s_write_reg(dev->i2s_base, IRER, 0); + + i2s_disable_irqs(dev, substream->stream, 8); + + if (!dev->active) { + i2s_write_reg(dev->i2s_base, CER, 0); + i2s_write_reg(dev->i2s_base, IER, 0); + } +} + +static int dw_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct i2svad_dev *dev = snd_soc_dai_get_drvdata(cpu_dai); + union dw_i2s_snd_dma_data *dma_data = NULL; + + + if (!(dev->capability & DWC_I2S_RECORD) && + (substream->stream == SNDRV_PCM_STREAM_CAPTURE)) + return -EINVAL; + + if (!(dev->capability & DWC_I2S_PLAY) && + (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)) + return -EINVAL; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_data = &dev->play_dma_data; + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + dma_data = &dev->capture_dma_data; + + snd_soc_dai_set_dma_data(cpu_dai, substream, (void *)dma_data); + + return 0; +} + +static void dw_i2s_config(struct i2svad_dev *dev, int stream) +{ + u32 ch_reg; + struct i2s_clk_config_data *config = &dev->config; + + + i2s_disable_channels(dev, stream); + + for (ch_reg = 0; ch_reg < (config->chan_nr / 2); ch_reg++) { + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + i2s_write_reg(dev->i2s_base, TCR(ch_reg), + dev->xfer_resolution); + i2s_write_reg(dev->i2s_base, TFCR(ch_reg), + dev->fifo_th - 1); + i2s_write_reg(dev->i2s_base, TER(ch_reg), 1); + } else { + i2s_write_reg(dev->i2s_base, RCR(ch_reg), + dev->xfer_resolution); + i2s_write_reg(dev->i2s_base, RFCR(ch_reg), + dev->fifo_th - 1); + i2s_write_reg(dev->i2s_base, RER(ch_reg), 1); + } + + } +} + +static int dw_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct i2svad_dev *dev = snd_soc_dai_get_drvdata(dai); + struct i2s_clk_config_data *config = &dev->config; + int ret; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + config->data_width = 16; + dev->ccr = 0x00; + dev->xfer_resolution = 0x02; + break; + + case SNDRV_PCM_FORMAT_S24_LE: + config->data_width = 24; + dev->ccr = 0x08; + dev->xfer_resolution = 0x04; + break; + + case SNDRV_PCM_FORMAT_S32_LE: + config->data_width = 32; + dev->ccr = 0x10; + dev->xfer_resolution = 0x05; + break; + + default: + dev_err(dev->dev, "designware-i2s: unsupported PCM fmt"); + return -EINVAL; + } + + config->chan_nr = params_channels(params); + + switch (config->chan_nr) { + case EIGHT_CHANNEL_SUPPORT: + case SIX_CHANNEL_SUPPORT: + case FOUR_CHANNEL_SUPPORT: + case TWO_CHANNEL_SUPPORT: + break; + default: + dev_err(dev->dev, "channel not supported\n"); + return -EINVAL; + } + + dw_i2s_config(dev, substream->stream); + + i2s_write_reg(dev->i2s_base, CCR, dev->ccr); + + config->sample_rate = params_rate(params); + + if (dev->capability & DW_I2S_MASTER) { + if (dev->i2s_clk_cfg) { + ret = dev->i2s_clk_cfg(config); + if (ret < 0) { + dev_err(dev->dev, "runtime audio clk config fail\n"); + return ret; + } + } else { + u32 bitclk = config->sample_rate * + config->data_width * 2; + + ret = clk_set_rate(dev->clk, bitclk); + if (ret) { + dev_err(dev->dev, "Can't set I2S clock rate: %d\n", + ret); + return ret; + } + } + } + return 0; +} + +static void dw_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + snd_soc_dai_set_dma_data(dai, substream, NULL); +} + +static int dw_i2s_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct i2svad_dev *dev = snd_soc_dai_get_drvdata(dai); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + i2s_write_reg(dev->i2s_base, TXFFR, 1); + else + i2s_write_reg(dev->i2s_base, RXFFR, 1); + + return 0; +} + +static int dw_i2s_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct i2svad_dev *dev = snd_soc_dai_get_drvdata(dai); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + dev->active++; + i2s_start(dev, substream); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dev->active--; + i2s_stop(dev, substream); + break; + default: + ret = -EINVAL; + break; + } + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + { + vad_trigger(&(dev->vad),cmd); + } + return ret; +} + +static int dw_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct i2svad_dev *dev = snd_soc_dai_get_drvdata(cpu_dai); + int ret = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + if (dev->capability & DW_I2S_SLAVE) + ret = 0; + else + ret = -EINVAL; + break; + case SND_SOC_DAIFMT_CBS_CFS: + if (dev->capability & DW_I2S_MASTER) + ret = 0; + else + ret = -EINVAL; + break; + case SND_SOC_DAIFMT_CBM_CFS: + case SND_SOC_DAIFMT_CBS_CFM: + ret = -EINVAL; + break; + default: + dev_dbg(dev->dev, "dwc : Invalid master/slave format\n"); + ret = -EINVAL; + break; + } + return ret; +} + +static const struct snd_soc_dai_ops dw_i2s_dai_ops = { + .startup = dw_i2s_startup, + .shutdown = dw_i2s_shutdown, + .hw_params = dw_i2s_hw_params, + .prepare = dw_i2s_prepare, + .trigger = dw_i2s_trigger, + .set_fmt = dw_i2s_set_fmt, +}; + +#ifdef CONFIG_PM +static int dw_i2s_runtime_suspend(struct device *dev) +{ + struct i2svad_dev *dw_dev = dev_get_drvdata(dev); + + if (dw_dev->capability & DW_I2S_MASTER) + clk_disable(dw_dev->clk); + return 0; +} + +static int dw_i2s_runtime_resume(struct device *dev) +{ + struct i2svad_dev *dw_dev = dev_get_drvdata(dev); + + if (dw_dev->capability & DW_I2S_MASTER) + clk_enable(dw_dev->clk); + return 0; +} + +static int dw_i2s_suspend(struct snd_soc_component *component) +{ + struct i2svad_dev *dev = snd_soc_component_get_drvdata(component); + + if (dev->capability & DW_I2S_MASTER) + clk_disable(dev->clk); + return 0; +} + +static int dw_i2s_resume(struct snd_soc_component *component) +{ + struct i2svad_dev *dev = snd_soc_component_get_drvdata(component); + struct snd_soc_dai *dai; + int stream; + + if (dev->capability & DW_I2S_MASTER) + clk_enable(dev->clk); + + for_each_component_dais(component, dai) { + for_each_pcm_streams(stream) + if (snd_soc_dai_stream_active(dai, stream)) + dw_i2s_config(dev, stream); + } + + return 0; +} + +#else +#define dw_i2s_suspend NULL +#define dw_i2s_resume NULL +#endif + +static int dw_i2svad_probe(struct snd_soc_component *component) +{ + vad_probe(component); + return 0; +} + +static const struct snd_soc_component_driver dw_i2s_component = { + .name = "dw-i2s", + .probe = dw_i2svad_probe, + .suspend = dw_i2s_suspend, + .resume = dw_i2s_resume, +}; + +/* + * The following tables allow a direct lookup of various parameters + * defined in the I2S block's configuration in terms of sound system + * parameters. Each table is sized to the number of entries possible + * according to the number of configuration bits describing an I2S + * block parameter. + */ + +/* Maximum bit resolution of a channel - not uniformly spaced */ +static const u32 fifo_width[COMP_MAX_WORDSIZE] = { + 12, 16, 20, 24, 32, 0, 0, 0 +}; + +/* Width of (DMA) bus */ +static const u32 bus_widths[COMP_MAX_DATA_WIDTH] = { + DMA_SLAVE_BUSWIDTH_1_BYTE, + DMA_SLAVE_BUSWIDTH_2_BYTES, + DMA_SLAVE_BUSWIDTH_4_BYTES, + DMA_SLAVE_BUSWIDTH_UNDEFINED +}; + +/* PCM format to support channel resolution */ +static const u32 formats[COMP_MAX_WORDSIZE] = { + SNDRV_PCM_FMTBIT_S16_LE, + SNDRV_PCM_FMTBIT_S16_LE, + SNDRV_PCM_FMTBIT_S24_LE, + SNDRV_PCM_FMTBIT_S24_LE, + SNDRV_PCM_FMTBIT_S32_LE, + 0, + 0, + 0 +}; + +static const struct regmap_config sf_i2s_regmap_cfg = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x1000, +}; + +static int dw_configure_dai(struct i2svad_dev *dev, + struct snd_soc_dai_driver *dw_i2s_dai, + unsigned int rates) +{ + /* + * Read component parameter registers to extract + * the I2S block's configuration. + */ + u32 comp1 = i2s_read_reg(dev->i2s_base, dev->i2s_reg_comp1); + u32 comp2 = i2s_read_reg(dev->i2s_base, dev->i2s_reg_comp2); + u32 fifo_depth = 1 << (1 + COMP1_FIFO_DEPTH_GLOBAL(comp1)); + u32 idx; + + if (dev->capability & DWC_I2S_RECORD && + dev->quirks & DW_I2S_QUIRK_COMP_PARAM1) + comp1 = comp1 & ~BIT(5); + + if (dev->capability & DWC_I2S_PLAY && + dev->quirks & DW_I2S_QUIRK_COMP_PARAM1) + comp1 = comp1 & ~BIT(6); + + if (COMP1_TX_ENABLED(comp1)) { + dev_dbg(dev->dev, " designware: play supported\n"); + idx = COMP1_TX_WORDSIZE_0(comp1); + if (WARN_ON(idx >= ARRAY_SIZE(formats))) + return -EINVAL; + if (dev->quirks & DW_I2S_QUIRK_16BIT_IDX_OVERRIDE) + idx = 1; + dw_i2s_dai->playback.channels_min = MIN_CHANNEL_NUM; + dw_i2s_dai->playback.channels_max = + 1 << (COMP1_TX_CHANNELS(comp1) + 1); + //dw_i2s_dai->playback.formats = formats[idx]; + dw_i2s_dai->playback.formats = SNDRV_PCM_FMTBIT_S16_LE; + dw_i2s_dai->playback.rates = rates; + } + + if (COMP1_RX_ENABLED(comp1)) { + dev_dbg(dev->dev, "designware: record supported\n"); + idx = COMP2_RX_WORDSIZE_0(comp2); + if (WARN_ON(idx >= ARRAY_SIZE(formats))) + return -EINVAL; + if (dev->quirks & DW_I2S_QUIRK_16BIT_IDX_OVERRIDE) + idx = 1; + dw_i2s_dai->capture.channels_min = MIN_CHANNEL_NUM; + dw_i2s_dai->capture.channels_max = + 1 << (COMP1_RX_CHANNELS(comp1) + 1); + //dw_i2s_dai->capture.formats = formats[idx]; + dw_i2s_dai->capture.formats = SNDRV_PCM_FMTBIT_S16_LE; + dw_i2s_dai->capture.rates = rates; + } + + if (COMP1_MODE_EN(comp1)) { + dev_dbg(dev->dev, "designware: i2s master mode supported\n"); + dev->capability |= DW_I2S_MASTER; + } else { + dev_dbg(dev->dev, "designware: i2s slave mode supported\n"); + dev->capability |= DW_I2S_SLAVE; + } + + dev->fifo_th = fifo_depth / 2; + return 0; +} + +static int dw_configure_dai_by_pd(struct i2svad_dev *dev, + struct snd_soc_dai_driver *dw_i2s_dai, + struct resource *res, + const struct i2s_platform_data *pdata) +{ + u32 comp1 = i2s_read_reg(dev->i2s_base, dev->i2s_reg_comp1); + u32 idx = COMP1_APB_DATA_WIDTH(comp1); + int ret; + + if (WARN_ON(idx >= ARRAY_SIZE(bus_widths))) + return -EINVAL; + + ret = dw_configure_dai(dev, dw_i2s_dai, pdata->snd_rates); + if (ret < 0) + return ret; + + if (dev->quirks & DW_I2S_QUIRK_16BIT_IDX_OVERRIDE) + idx = 1; + /* Set DMA slaves info */ + dev->play_dma_data.pd.data = pdata->play_dma_data; + dev->capture_dma_data.pd.data = pdata->capture_dma_data; + dev->play_dma_data.pd.addr = res->start + I2S_TXDMA; + dev->capture_dma_data.pd.addr = res->start + I2S_RXDMA; + dev->play_dma_data.pd.max_burst = 16; + dev->capture_dma_data.pd.max_burst = 16; + dev->play_dma_data.pd.addr_width = bus_widths[idx]; + dev->capture_dma_data.pd.addr_width = bus_widths[idx]; + dev->play_dma_data.pd.filter = pdata->filter; + dev->capture_dma_data.pd.filter = pdata->filter; + + return 0; +} + +static int dw_configure_dai_by_dt(struct i2svad_dev *dev, + struct snd_soc_dai_driver *dw_i2s_dai, + struct resource *res) +{ + u32 comp1 = i2s_read_reg(dev->i2s_base, I2S_COMP_PARAM_1); + u32 comp2 = i2s_read_reg(dev->i2s_base, I2S_COMP_PARAM_2); + u32 fifo_depth = 1 << (1 + COMP1_FIFO_DEPTH_GLOBAL(comp1)); + u32 idx = COMP1_APB_DATA_WIDTH(comp1); + u32 idx2; + int ret; + + if (WARN_ON(idx >= ARRAY_SIZE(bus_widths))) + return -EINVAL; + + ret = dw_configure_dai(dev, dw_i2s_dai, SNDRV_PCM_RATE_8000_192000); + if (ret < 0) + return ret; + + if (COMP1_TX_ENABLED(comp1)) { + idx2 = COMP1_TX_WORDSIZE_0(comp1); + + dev->capability |= DWC_I2S_PLAY; + dev->play_dma_data.dt.addr = res->start + I2S_TXDMA; + dev->play_dma_data.dt.addr_width = bus_widths[idx]; + dev->play_dma_data.dt.fifo_size = fifo_depth * + (fifo_width[idx2]) >> 8; + dev->play_dma_data.dt.maxburst = 16; + } + if (COMP1_RX_ENABLED(comp1)) { + idx2 = COMP2_RX_WORDSIZE_0(comp2); + + dev->capability |= DWC_I2S_RECORD; + dev->capture_dma_data.dt.addr = res->start + I2S_RXDMA; + dev->capture_dma_data.dt.addr_width = bus_widths[idx]; + dev->capture_dma_data.dt.fifo_size = fifo_depth * + (fifo_width[idx2] >> 8); + dev->capture_dma_data.dt.maxburst = 16; + } + + return 0; + +} + +static int dw_i2s_probe(struct platform_device *pdev) +{ + const struct i2s_platform_data *pdata = pdev->dev.platform_data; + struct i2svad_dev *dev; + struct resource *res; + int ret, irq; + struct snd_soc_dai_driver *dw_i2s_dai; + const char *clk_id; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dw_i2s_dai = devm_kzalloc(&pdev->dev, sizeof(*dw_i2s_dai), GFP_KERNEL); + if (!dw_i2s_dai) + return -ENOMEM; + + dw_i2s_dai->ops = &dw_i2s_dai_ops; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dev->i2s_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(dev->i2s_base)) + return PTR_ERR(dev->i2s_base); + + dev->vad.vad_base = dev->i2s_base; + dev->vad.vad_map = devm_regmap_init_mmio(&pdev->dev, dev->i2s_base, &sf_i2s_regmap_cfg); + if (IS_ERR(dev->vad.vad_map)) { + dev_err(&pdev->dev, "failed to init regmap: %ld\n", + PTR_ERR(dev->vad.vad_map)); + return PTR_ERR(dev->vad.vad_map); + } + + dev->dev = &pdev->dev; + + dev->clk_apb_i2svad = devm_clk_get(&pdev->dev, "i2svad_apb"); + if (IS_ERR(dev->clk_apb_i2svad)) + return dev_err_probe(&pdev->dev, PTR_ERR(dev->clk_apb_i2svad), + "failed to get apb clock\n"); + + dev->rst_apb_i2svad = devm_reset_control_get_exclusive(&pdev->dev, "apb_i2svad"); + if (IS_ERR(dev->rst_apb_i2svad)) + return dev_err_probe(&pdev->dev, PTR_ERR(dev->rst_apb_i2svad), + "failed to get apb reset\n"); + + dev->rst_i2svad_srst = devm_reset_control_get_exclusive(&pdev->dev, "i2svad_srst"); + if (IS_ERR(dev->rst_i2svad_srst)) + return dev_err_probe(&pdev->dev, PTR_ERR(dev->rst_i2svad_srst), + "failed to get source reset\n"); + + ret = clk_prepare_enable(dev->clk_apb_i2svad); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed to enable apb clock\n"); + + ret = reset_control_deassert(dev->rst_apb_i2svad); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed to deassert apb reset\n"); + + ret = reset_control_deassert(dev->rst_i2svad_srst); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed to deassert source reset\n"); + + irq = platform_get_irq(pdev, 0); + if (irq >= 0) { + ret = devm_request_irq(&pdev->dev, irq, i2s_irq_handler, 0, + pdev->name, dev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to request irq\n"); + return ret; + } + } + + dev->i2s_reg_comp1 = I2S_COMP_PARAM_1; + dev->i2s_reg_comp2 = I2S_COMP_PARAM_2; + if (pdata) { + dev->capability = pdata->cap; + clk_id = NULL; + dev->quirks = pdata->quirks; + if (dev->quirks & DW_I2S_QUIRK_COMP_REG_OFFSET) { + dev->i2s_reg_comp1 = pdata->i2s_reg_comp1; + dev->i2s_reg_comp2 = pdata->i2s_reg_comp2; + } + ret = dw_configure_dai_by_pd(dev, dw_i2s_dai, res, pdata); + } else { + clk_id = "i2sclk"; + ret = dw_configure_dai_by_dt(dev, dw_i2s_dai, res); + } + if (ret < 0) + return ret; + + if (dev->capability & DW_I2S_MASTER) { + if (pdata) { + dev->i2s_clk_cfg = pdata->i2s_clk_cfg; + if (!dev->i2s_clk_cfg) { + dev_err(&pdev->dev, "no clock configure method\n"); + return -ENODEV; + } + } + dev->clk = devm_clk_get(&pdev->dev, clk_id); + + if (IS_ERR(dev->clk)) + return PTR_ERR(dev->clk); + + ret = clk_prepare_enable(dev->clk); + if (ret < 0) + return ret; + } + + dev_set_drvdata(&pdev->dev, dev); + ret = devm_snd_soc_register_component(&pdev->dev, &dw_i2s_component, + dw_i2s_dai, 1); + if (ret != 0) { + dev_err(&pdev->dev, "not able to register dai\n"); + goto err_clk_disable; + } + + if (!pdata) { + if (irq >= 0) { + ret = i2svad_pcm_register(pdev); + dev->use_pio = true; + } else { + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, + 0); + dev->use_pio = false; + } + + if (ret) { + dev_err(&pdev->dev, "could not register pcm: %d\n", + ret); + goto err_clk_disable; + } + } + + vad_init(&(dev->vad)); + pm_runtime_enable(&pdev->dev); + + return 0; + +err_clk_disable: + if (dev->capability & DW_I2S_MASTER) + clk_disable_unprepare(dev->clk); + return ret; +} + +static int dw_i2s_remove(struct platform_device *pdev) +{ + struct i2svad_dev *dev = dev_get_drvdata(&pdev->dev); + + if (dev->capability & DW_I2S_MASTER) + clk_disable_unprepare(dev->clk); + + pm_runtime_disable(&pdev->dev); + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id dw_i2s_of_match[] = { + { .compatible = "starfive,sf-i2svad", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, dw_i2s_of_match); +#endif + +static const struct dev_pm_ops dwc_pm_ops = { + SET_RUNTIME_PM_OPS(dw_i2s_runtime_suspend, dw_i2s_runtime_resume, NULL) +}; + +static struct platform_driver dw_i2s_driver = { + .probe = dw_i2s_probe, + .remove = dw_i2s_remove, + .driver = { + .name = "sf-i2svad", + .of_match_table = of_match_ptr(dw_i2s_of_match), + .pm = &dwc_pm_ops, + }, +}; + +module_platform_driver(dw_i2s_driver); + +MODULE_AUTHOR("jenny zhang "); +MODULE_DESCRIPTION("starfive I2SVAD SoC Interface"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:sf-i2svad"); diff --git a/sound/soc/starfive/i2svad.h b/sound/soc/starfive/i2svad.h new file mode 100644 index 0000000000000..cd14cb4ce8138 --- /dev/null +++ b/sound/soc/starfive/i2svad.h @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 StarFive Technology Co., Ltd. + */ +#ifndef __SND_SOC_STARFIVE_I2SVAD_H +#define __SND_SOC_STARFIVE_I2SVAD_H + +#include +#include +#include +#include +#include +#include +#include + +/* common register for all channel */ +#define IER 0x000 +#define IRER 0x004 +#define ITER 0x008 +#define CER 0x00C +#define CCR 0x010 +#define RXFFR 0x014 +#define TXFFR 0x018 + +/* Interrupt status register fields */ +#define ISR_TXFO BIT(5) +#define ISR_TXFE BIT(4) +#define ISR_RXFO BIT(1) +#define ISR_RXDA BIT(0) + +/* I2STxRxRegisters for all channels */ +#define LRBR_LTHR(x) (0x40 * x + 0x020) +#define RRBR_RTHR(x) (0x40 * x + 0x024) +#define RER(x) (0x40 * x + 0x028) +#define TER(x) (0x40 * x + 0x02C) +#define RCR(x) (0x40 * x + 0x030) +#define TCR(x) (0x40 * x + 0x034) +#define ISR(x) (0x40 * x + 0x038) +#define IMR(x) (0x40 * x + 0x03C) +#define ROR(x) (0x40 * x + 0x040) +#define TOR(x) (0x40 * x + 0x044) +#define RFCR(x) (0x40 * x + 0x048) +#define TFCR(x) (0x40 * x + 0x04C) +#define RFF(x) (0x40 * x + 0x050) +#define TFF(x) (0x40 * x + 0x054) + +/* I2SCOMPRegisters */ +#define I2S_COMP_PARAM_2 0x01F0 +#define I2S_COMP_PARAM_1 0x01F4 +#define I2S_COMP_VERSION 0x01F8 +#define I2S_COMP_TYPE 0x01FC + +/* VAD Registers */ +#define VAD_LEFT_MARGIN 0x800 /* left_margin */ +#define VAD_RIGHT_MARGIN 0x804 /* right_margin */ +#define VAD_N_LOW_CONT_FRAMES 0x808 /* low-energy transition range threshold ——NL*/ +#define VAD_N_LOW_SEEK_FRAMES 0x80C /* low-energy transition range */ +#define VAD_N_HIGH_CONT_FRAMES 0x810 /* high-energy transition range threshold——NH */ +#define VAD_N_HIGH_SEEK_FRAMES 0x814 /* high-energy transition range */ +#define VAD_N_SPEECH_LOW_HIGH_FRAMES 0x818 /* low-energy voice range threshold——NVL*/ +#define VAD_N_SPEECH_LOW_SEEK_FRAMES 0x81C /* low-energy voice range*/ +#define VAD_MEAN_SIL_FRAMES 0x820 /* mean silence frame range*/ +#define VAD_N_ALPHA 0x824 /* low-energy threshold scaling factor,12bit(0~0xFFF)*/ +#define VAD_N_BETA 0x828 /* high-energy threshold scaling factor,12bit(0~0xFFF)*/ +#define VAD_FIFO_DEPTH 0x82C /* status register for VAD */ +#define VAD_LR_SEL 0x840 /* L/R channel data selection for processing */ +#define VAD_SW 0x844 /* push enable signal*/ +#define VAD_LEFT_WD 0x848 /* select left channel*/ +#define VAD_RIGHT_WD 0x84C /* select right channel*/ +#define VAD_STOP_DELAY 0x850 /* delay stop for 0-3 samples*/ +#define VAD_ADDR_START 0x854 /* vad memory start address, align with 64bit*/ +#define VAD_ADDR_WRAP 0x858 /* vad memory highest address for Push, align with 64bit,(addr_wrap-1) is the max physical address*/ +#define VAD_MEM_SW 0x85C /* xmem switch */ +#define VAD_SPINT_CLR 0x860 /* clear vad_spint interrup status*/ +#define VAD_SPINT_EN 0x864 /* disable/enable vad_spint from vad_flag rising edge*/ +#define VAD_SLINT_CLR 0x868 /* clear vad_slint interrup status*/ +#define VAD_SLINT_EN 0x86C /* disable/enable vad_slint from vad_flag falling edge*/ +#define VAD_RAW_SPINT 0x870 /* status of spint before vad_spint_en*/ +#define VAD_RAW_SLINT 0x874 /* status of slint before vad_slint_en*/ +#define VAD_SPINT 0x878 /* status of spint after vad_spint_en*/ +#define VAD_SLINT 0x87C /* status of slint before vad_slint_en*/ +#define VAD_XMEM_ADDR 0x880 /* next xmem address ,align to 16bi*/ +#define VAD_I2S_CTRL_REG_ADDR 0x884 + +/* + * vad parameter register fields + */ +#define VAD_LEFT_MARGIN_MASK GENMASK(4, 0) +#define VAD_RIGHT_MARGIN_MASK GENMASK(4, 0) +#define VAD_N_LOW_CONT_FRAMES_MASK GENMASK(4, 0) +#define VAD_N_LOW_SEEK_FRAMES_MASK GENMASK(4, 0) +#define VAD_N_HIGH_CONT_FRAMES_MASK GENMASK(4, 0) +#define VAD_N_HIGH_SEEK_FRAMES_MASK GENMASK(4, 0) +#define VAD_N_SPEECH_LOW_HIGH_FRAMES_MASK GENMASK(4, 0) +#define VAD_N_SPEECH_LOW_SEEK_FRAMES_MASK GENMASK(4, 0) +#define VAD_MEAN_SIL_FRAMES_MASK GENMASK(4, 0) +#define VAD_N_ALPHA_MASK GENMASK(11, 0) +#define VAD_N_BETA_MASK GENMASK(11, 0) +#define VAD_LR_SEL_MASK GENMASK(0, 0) +#define VAD_LR_SEL_L (0 << 0) +#define VAD_LR_SEL_R (1 << 0) + +#define VAD_SW_MASK GENMASK(1, 0) +#define VAD_SW_VAD_XMEM_ENABLE (1 << 0) +#define VAD_SW_VAD_XMEM_DISABLE (0 << 0) +#define VAD_SW_ADC_ENABLE (1 << 1) +#define VAD_SW_ADC_DISABLE (0 << 1) + + +#define VAD_LEFT_WD_MASK GENMASK(0, 0) +#define VAD_LEFT_WD_BIT_31_16 (1 << 1) +#define VAD_LEFT_WD_BIT_15_0 (0 << 1) + + +#define VAD_RIGHT_WD_MASK GENMASK(0, 0) +#define VAD_RIGHT_WD_BIT_31_16 (1 << 1) +#define VAD_RIGHT_WD_BIT_15_0 (0 << 1) + + +#define VAD_STOP_DELAY_MASK GENMASK(1, 0) +#define VAD_STOP_DELAY_0_SAMPLE 0 +#define VAD_STOP_DELAY_1_SAMPLE 1 +#define VAD_STOP_DELAY_2_SAMPLE 2 +#define VAD_STOP_DELAY_3_SAMPLE 3 + +#define VAD_ADDR_START_MASK GENMASK(12, 0) +#define VAD_ADDR_WRAP_MASK GENMASK(13, 0) +#define VAD_MEM_SW_MASK GENMASK(0, 0) +#define VAD_SPINT_CLR_MASK GENMASK(0, 0) +#define VAD_SPINT_EN_MASK GENMASK(0, 0) +#define VAD_SLINT_CLR_MASK GENMASK(0, 0) +#define VAD_SLINT_EN_MASK GENMASK(0, 0) +#define VAD_I2S_CTRL_REG_ADDR_MASK GENMASK(0, 0) + +#define VAD_MEM_SW_TO_VAD (1 << 0) +#define VAD_MEM_SW_TO_AXI (0 << 0) + +#define VAD_SPINT_CLR_VAD_SPINT (1 << 0) + +#define VAD_SPINT_EN_ENABLE (1 << 0) +#define VAD_SPINT_EN_DISABLE (0 << 0) + +#define VAD_SLINT_CLR_VAD_SLINT (1 << 0) + +#define VAD_SLINT_EN_ENABLE (1 << 0) +#define VAD_SLINT_EN_DISABLE (0 << 0) + +#define VAD_STATUS_NORMAL 0 +#define VAD_STATUS_SPINT 1 +#define VAD_STATUS_SLINT 2 + +/* + * Component parameter register fields - define the I2S block's + * configuration. + */ +#define COMP1_TX_WORDSIZE_3(r) (((r) & GENMASK(27, 25)) >> 25) +#define COMP1_TX_WORDSIZE_2(r) (((r) & GENMASK(24, 22)) >> 22) +#define COMP1_TX_WORDSIZE_1(r) (((r) & GENMASK(21, 19)) >> 19) +#define COMP1_TX_WORDSIZE_0(r) (((r) & GENMASK(18, 16)) >> 16) +#define COMP1_TX_CHANNELS(r) (((r) & GENMASK(10, 9)) >> 9) +#define COMP1_RX_CHANNELS(r) (((r) & GENMASK(8, 7)) >> 7) +#define COMP1_RX_ENABLED(r) (((r) & BIT(6)) >> 6) +#define COMP1_TX_ENABLED(r) (((r) & BIT(5)) >> 5) +#define COMP1_MODE_EN(r) (((r) & BIT(4)) >> 4) +#define COMP1_FIFO_DEPTH_GLOBAL(r) (((r) & GENMASK(3, 2)) >> 2) +#define COMP1_APB_DATA_WIDTH(r) (((r) & GENMASK(1, 0)) >> 0) + +#define COMP2_RX_WORDSIZE_3(r) (((r) & GENMASK(12, 10)) >> 10) +#define COMP2_RX_WORDSIZE_2(r) (((r) & GENMASK(9, 7)) >> 7) +#define COMP2_RX_WORDSIZE_1(r) (((r) & GENMASK(5, 3)) >> 3) +#define COMP2_RX_WORDSIZE_0(r) (((r) & GENMASK(2, 0)) >> 0) + +/* Number of entries in WORDSIZE and DATA_WIDTH parameter registers */ +#define COMP_MAX_WORDSIZE (1 << 3) +#define COMP_MAX_DATA_WIDTH (1 << 2) + +#define MAX_CHANNEL_NUM 8 +#define MIN_CHANNEL_NUM 2 +#define ALL_CHANNEL_NUM 4 + + +union dw_i2s_snd_dma_data { + struct i2s_dma_data pd; + struct snd_dmaengine_dai_dma_data dt; +}; + +struct vad_params { + void __iomem *vad_base; + struct regmap *vad_map; + unsigned int vswitch; + unsigned int vstatus; /*vad detect status: 1:SPINT 2:SLINT 0:normal*/ +}; + +struct i2svad_dev { + void __iomem *i2s_base; + struct clk *clk; + int active; + unsigned int capability; + unsigned int quirks; + unsigned int i2s_reg_comp1; + unsigned int i2s_reg_comp2; + struct device *dev; + u32 ccr; + u32 xfer_resolution; + u32 fifo_th; + + struct clk *clk_apb_i2svad; + struct reset_control *rst_apb_i2svad; + struct reset_control *rst_i2svad_srst; + + /* data related to DMA transfers b/w i2s and DMAC */ + union dw_i2s_snd_dma_data play_dma_data; + union dw_i2s_snd_dma_data capture_dma_data; + struct i2s_clk_config_data config; + int (*i2s_clk_cfg)(struct i2s_clk_config_data *config); + + /* data related to PIO transfers */ + bool use_pio; + struct snd_pcm_substream __rcu *tx_substream; + struct snd_pcm_substream __rcu *rx_substream; + unsigned int (*tx_fn)(struct i2svad_dev *dev, + struct snd_pcm_runtime *runtime, unsigned int tx_ptr, + bool *period_elapsed); + unsigned int (*rx_fn)(struct i2svad_dev *dev, + struct snd_pcm_runtime *runtime, unsigned int rx_ptr, + bool *period_elapsed); + unsigned int tx_ptr; + unsigned int rx_ptr; + + struct vad_params vad; +}; + +#if IS_ENABLED(CONFIG_SND_STARFIVE_I2SVAD_PCM) +void i2svad_pcm_push_tx(struct i2svad_dev *dev); +void i2svad_pcm_pop_rx(struct i2svad_dev *dev); +int i2svad_pcm_register(struct platform_device *pdev); +#else +static inline void i2svad_pcm_push_tx(struct i2svad_dev *dev) { } +static inline void i2svad_pcm_pop_rx(struct i2svad_dev *dev) { } +static inline int i2svad_pcm_register(struct platform_device *pdev) +{ + return -EINVAL; +} +#endif + +#endif diff --git a/sound/soc/starfive/pdm.c b/sound/soc/starfive/pdm.c new file mode 100644 index 0000000000000..af5efd8adfb7f --- /dev/null +++ b/sound/soc/starfive/pdm.c @@ -0,0 +1,362 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 StarFive Technology Co., Ltd. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pdm.h" + +#define AUDIOC_CLK (12288000) +#define PDM_MUL (128) + +struct sf_pdm { + struct regmap *pdm_map; + struct regmap *clk_map; + struct clk *clk; +}; + +static const DECLARE_TLV_DB_SCALE(volume_tlv, -9450, 150, 0); + +static const struct snd_kcontrol_new sf_pdm_snd_controls[] = { + SOC_SINGLE("DC compensation Control", PDM_DMIC_CTRL0, 30, 1, 0), + SOC_SINGLE("High Pass Filter Control", PDM_DMIC_CTRL0, 28, 1, 0), + SOC_SINGLE("Left Channel Volume Control", PDM_DMIC_CTRL0, 23, 1, 0), + SOC_SINGLE("Right Channel Volume Control", PDM_DMIC_CTRL0, 22, 1, 0), + SOC_SINGLE_TLV("Volume", PDM_DMIC_CTRL0, 16, 0x3F, 1, volume_tlv), + SOC_SINGLE("Data MSB Shift", PDM_DMIC_CTRL0, 1, 7, 0), + SOC_SINGLE("SCALE", PDM_DC_SCALE0, 0, 0x3F, 0), + SOC_SINGLE("DC offset", PDM_DC_SCALE0, 8, 0xFFFFF, 0), +}; + +static int sf_pdm_set_mclk(struct regmap *map, unsigned int clk, unsigned int weight) +{ + int mclk_div,bclk_div,lrclk_div; + u32 pdm_div; + + /* + audio source clk:12288000, mclk_div:4, mclk:3M + support 8K/16K/32K/48K sample reate + suapport 16/24/32 bit weight + bit weight 32 + mclk bclk lrclk + 3M 1.5M 48K + 3M 1M 32K + 3M 0.5M 16K + 3M 0.25M 8K + + bit weight 24,set lrclk_div as 32 + mclk bclk lrclk + 3M 1.5M 48K + 3M 1M 32K + 3M 0.5M 16K + 3M 0.25M 8K + + bit weight 16 + mclk bclk lrclk + 3M 0.75M 48K + 3M 0.5M 32K + 3M 0.25M 16K + 3M 0.125M 8K + */ + + switch (clk) { + case 8000: + case 16000: + case 32000: + case 48000: + break; + default: + printk(KERN_ERR "sample rate:%d\n", clk); + return -EINVAL; + } + + switch (weight) { + case 16: + case 24: + case 32: + break; + default: + printk(KERN_ERR "bit weight:%d\n", weight); + return -EINVAL; + } + + if (24 == weight) { + weight = 32; + } + + mclk_div = 4; + bclk_div = AUDIOC_CLK/mclk_div/(clk*weight); + lrclk_div = weight; + + /* PDM MCLK = 128*LRCLK */ + pdm_div = AUDIOC_CLK/(PDM_MUL*clk); + + regmap_update_bits(map, AUDIO_CLK_ADC_MCLK, 0x0F, mclk_div); + regmap_update_bits(map, AUDIO_CLK_I2SADC_BCLK, 0x1F, bclk_div); + regmap_update_bits(map, AUDIO_CLK_ADC_LRCLK, 0x3F, lrclk_div); + regmap_update_bits(map, AUDIO_CLK_PDM_CLK, 0x0F, pdm_div); + + return 0; +} + +static void sf_pdm_enable(struct regmap *map) +{ + /* Enable PDM */ + regmap_update_bits(map, PDM_DMIC_CTRL0, 0x01<pdm_map); + return 0; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + sf_pdm_disable(priv->pdm_map); + return 0; + + default: + return -EINVAL; + } +} + +static int sf_pdm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct sf_pdm *priv = snd_soc_dai_get_drvdata(dai); + unsigned int rate = params_rate(params); + unsigned int width; + int ret; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return 0; + + width = params_width(params); + switch (width) { + case 16: + case 24: + case 32: + break; + default: + dev_err(dai->dev, "unsupported sample width\n"); + return -EINVAL; + } + + ret = sf_pdm_set_mclk(priv->clk_map, rate, width); + if (ret < 0) { + dev_err(dai->dev, "unsupported sample rate\n"); + return -EINVAL; + } + + return 0; +} + +static const struct snd_soc_dai_ops sf_pdm_dai_ops = { + .trigger = sf_pdm_trigger, + .hw_params = sf_pdm_hw_params, +}; + +static int sf_pdm_dai_probe(struct snd_soc_dai *dai) +{ + struct sf_pdm *priv = snd_soc_dai_get_drvdata(dai); + + /* Reset */ + regmap_update_bits(priv->pdm_map, PDM_DMIC_CTRL0, + 0x01<pdm_map, PDM_DMIC_CTRL0, + 0x01<pdm_map); + + /* MUTE */ + regmap_update_bits(priv->pdm_map, PDM_DMIC_CTRL0, + 0x3F<pdm_map, PDM_DMIC_CTRL0, + 0x3F<pdm_map, PDM_DMIC_CTRL0, + 0x01<pdm_map, PDM_DMIC_CTRL0, + 0x01<pdm_map, PDM_DMIC_CTRL0, + 0x01<pdm_map, PDM_DMIC_CTRL0, + 0x01<pdm_map, PDM_DMIC_CTRL0, + 0x07<pdm_map, PDM_DC_SCALE0, 0x3F, 0x08); + + /* DC offset:0 */ + regmap_update_bits(priv->pdm_map, PDM_DC_SCALE0, + 0xFFFFF<pdm_map, PDM_DMIC_CTRL0, + 0x3F<pdm_map); + snd_soc_add_component_controls(component, sf_pdm_snd_controls, + ARRAY_SIZE(sf_pdm_snd_controls)); + + return 0; +} + +static const struct snd_soc_component_driver sf_pdm_component_drv = { + .name = "sf-pdm", + .probe = pdm_probe, +}; + +static const struct regmap_config sf_pdm_regmap_cfg = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x20, +}; + +static const struct regmap_config sf_audio_clk_regmap_cfg = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x100, +}; + +static int sf_pdm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct sf_pdm *priv; + struct resource *res; + void __iomem *regs; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + platform_set_drvdata(pdev, priv); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pdm"); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + priv->pdm_map = devm_regmap_init_mmio(dev, regs, &sf_pdm_regmap_cfg); + if (IS_ERR(priv->pdm_map)) { + dev_err(dev, "failed to init regmap: %ld\n", + PTR_ERR(priv->pdm_map)); + return PTR_ERR(priv->pdm_map); + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "audio-clk"); + regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + priv->clk_map = devm_regmap_init_mmio(dev, regs, &sf_audio_clk_regmap_cfg); + if (IS_ERR(priv->clk_map)) { + dev_err(dev, "failed to init regmap: %ld\n", + PTR_ERR(priv->clk_map)); + return PTR_ERR(priv->clk_map); + } + + return devm_snd_soc_register_component(dev, &sf_pdm_component_drv, + &sf_pdm_dai_drv, 1); +} + +static int sf_pdm_dev_remove(struct platform_device *pdev) +{ + return 0; +} +static const struct of_device_id sf_pdm_of_match[] = { + {.compatible = "starfive,sf-pdm",}, + {} +}; +MODULE_DEVICE_TABLE(of, sf_pdm_of_match); + +static struct platform_driver sf_pdm_driver = { + + .driver = { + .name = "sf-pdm", + .of_match_table = sf_pdm_of_match, + }, + .probe = sf_pdm_probe, + .remove = sf_pdm_dev_remove, +}; +module_platform_driver(sf_pdm_driver); + +MODULE_AUTHOR("michael.yan "); +MODULE_DESCRIPTION("starfive PDM Controller Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/starfive/pdm.h b/sound/soc/starfive/pdm.h new file mode 100644 index 0000000000000..cbc0a9ecd378d --- /dev/null +++ b/sound/soc/starfive/pdm.h @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 StarFive Technology Co., Ltd. + */ +#ifndef __SND_SOC_STARFIVE_PDM_H +#define __SND_SOC_STARFIVE_PDM_H + +#include +#include +#include +#include +#include +#include +#include + +#define PDM_DMIC_CTRL0 (0x00) +#define PDM_DC_SCALE0 (0x04) +#define PDM_DMIC_CTRL1 (0x10) +#define PDM_DC_SCALE1 (0x14) + +/* PDM CTRL OFFSET */ +#define PDM_DMIC_MSB_SHIFT_OFFSET (1) +#define PDM_DMIC_VOL_OFFSET (16) +#define PDM_DMIC_RVOL_OFFSET (22) +#define PDM_DMIC_LVOL_OFFSET (23) +#define PDM_DMIC_I2SMODE_OFFSET (24) +#define PDM_DMIC_ENHPF_OFFSET (28) +#define PDM_DMIC_FASTMODE_OFFSET (29) +#define PDM_DMIC_DCBPS_OFFSET (30) +#define PDM_DMIC_SW_RSTN_OFFSET (31) + +/* PDM SCALE OFFSET */ +#define PDM_DMIC_DCOFF3_OFFSET (24) +#define PDM_DMIC_DCOFF2_OFFSET (16) +#define PDM_DMIC_DCOFF1_OFFSET (8) +#define PDM_DMIC_SCALE_OFFSET (0) + +#define AUDIO_CLK_ADC_MCLK 0x0 +#define AUDIO_CLK_I2SADC_BCLK 0xC +#define AUDIO_CLK_ADC_LRCLK 0x14 +#define AUDIO_CLK_PDM_CLK 0x1C + +#endif /* __SND_SOC_STARFIVE_PDM_H */ diff --git a/sound/soc/starfive/pwmdac-pcm.c b/sound/soc/starfive/pwmdac-pcm.c new file mode 100644 index 0000000000000..decd08bc289e7 --- /dev/null +++ b/sound/soc/starfive/pwmdac-pcm.c @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 StarFive Technology Co., Ltd. + */ +#include +#include +#include +#include +#include "pwmdac.h" + +#define BUFFER_BYTES_MAX (3 * 2 * 8 * PERIOD_BYTES_MIN) +#define PERIOD_BYTES_MIN 4096 +#define PERIODS_MIN 2 + +static unsigned int sf_pwmdac_pcm_tx_8(struct sf_pwmdac_dev *dev, + struct snd_pcm_runtime *runtime, unsigned int tx_ptr, + bool *period_elapsed) +{ + const u8 (*p)[2] = (void *)runtime->dma_area; + unsigned int period_pos = tx_ptr % runtime->period_size; + int i; + u32 basedat = 0; + + for (i = 0; i < dev->fifo_th; i++) { + basedat = (p[tx_ptr][0]<<8)|(p[tx_ptr][1] << 24); + iowrite32(basedat,dev->pwmdac_base + PWMDAC_WDATA); + period_pos++; + if (++tx_ptr >= runtime->buffer_size) + tx_ptr = 0; + } + + *period_elapsed = period_pos >= runtime->period_size; + + return tx_ptr; +} + + +static unsigned int sf_pwmdac_pcm_tx_16(struct sf_pwmdac_dev *dev, + struct snd_pcm_runtime *runtime, unsigned int tx_ptr, + bool *period_elapsed) +{ + const u16 (*p)[2] = (void *)runtime->dma_area; + unsigned int period_pos = tx_ptr % runtime->period_size; + int i; + u32 basedat = 0; + + for (i = 0; i < dev->fifo_th; i++) { + basedat = (p[tx_ptr][0])|(p[tx_ptr][1] << 16); + iowrite32(basedat,dev->pwmdac_base + PWMDAC_WDATA); + period_pos++; + if (++tx_ptr >= runtime->buffer_size) + tx_ptr = 0; + } + + *period_elapsed = period_pos >= runtime->period_size; + return tx_ptr; +} + +static const struct snd_pcm_hardware sf_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .rates = SNDRV_PCM_RATE_16000, + .rate_min = 16000, + .rate_max = 16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = BUFFER_BYTES_MAX, + .period_bytes_min = PERIOD_BYTES_MIN, + .period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN, + .periods_min = PERIODS_MIN, + .periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN, + .fifo_size = 2, +}; + +static void sf_pcm_transfer(struct sf_pwmdac_dev *dev, bool push) +{ + struct snd_pcm_substream *substream = NULL; + bool period_elapsed = false; + bool active; + + rcu_read_lock(); + if (push) + substream = rcu_dereference(dev->tx_substream); + + active = substream && snd_pcm_running(substream); + if (active) { + unsigned int ptr; + unsigned int new_ptr; + + if (push) { + ptr = READ_ONCE(dev->tx_ptr); + new_ptr = dev->tx_fn(dev, substream->runtime, ptr, + &period_elapsed); + cmpxchg(&dev->tx_ptr, ptr, new_ptr); + } + + if (period_elapsed) + snd_pcm_period_elapsed(substream); + } + rcu_read_unlock(); +} + +void sf_pwmdac_pcm_push_tx(struct sf_pwmdac_dev *dev) +{ + sf_pcm_transfer(dev, true); +} + + +static int sf_pcm_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct sf_pwmdac_dev *dev = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0)); + + snd_soc_set_runtime_hwparams(substream, &sf_pcm_hardware); + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + runtime->private_data = dev; + + return 0; +} + + +static int sf_pcm_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + synchronize_rcu(); + return 0; +} + +static int sf_pcm_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sf_pwmdac_dev *dev = runtime->private_data; + + switch (params_channels(hw_params)) { + case 2: + break; + default: + dev_err(dev->dev, "invalid channels number\n"); + return -EINVAL; + } + + switch (params_format(hw_params)) { + case SNDRV_PCM_FORMAT_U8: + case SNDRV_PCM_FORMAT_S8: + dev->tx_fn = sf_pwmdac_pcm_tx_8; + break; + case SNDRV_PCM_FORMAT_S16_LE: + dev->tx_fn = sf_pwmdac_pcm_tx_16; + break; + default: + dev_err(dev->dev, "invalid format\n"); + return -EINVAL; + } + + return 0; +} + + +static int sf_pcm_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sf_pwmdac_dev *dev = runtime->private_data; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + WRITE_ONCE(dev->tx_ptr, 0); + rcu_assign_pointer(dev->tx_substream, substream); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rcu_assign_pointer(dev->tx_substream, NULL); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static snd_pcm_uframes_t sf_pcm_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sf_pwmdac_dev *dev = runtime->private_data; + snd_pcm_uframes_t pos = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + pos = READ_ONCE(dev->tx_ptr); + + return pos < runtime->buffer_size ? pos : 0; +} + +static int sf_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + size_t size = sf_pcm_hardware.buffer_bytes_max; + + snd_pcm_set_managed_buffer_all(rtd->pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + NULL, size, size); + return 0; +} + +static const struct snd_soc_component_driver dw_pcm_component = { + .open = sf_pcm_open, + .close = sf_pcm_close, + .hw_params = sf_pcm_hw_params, + .trigger = sf_pcm_trigger, + .pointer = sf_pcm_pointer, + .pcm_construct = sf_pcm_new, +}; + +int sf_pwmdac_pcm_register(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, &dw_pcm_component, + NULL, 0); +} diff --git a/sound/soc/starfive/pwmdac-transmitter.c b/sound/soc/starfive/pwmdac-transmitter.c new file mode 100644 index 0000000000000..c6e756ff135bf --- /dev/null +++ b/sound/soc/starfive/pwmdac-transmitter.c @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 StarFive Technology Co., Ltd. + */ +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "pwmdac-dit" + +#define STUB_RATES SNDRV_PCM_RATE_8000_192000 +#define STUB_FORMATS (SNDRV_PCM_FMTBIT_S8|\ + SNDRV_PCM_FMTBIT_U8 |\ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static const struct snd_soc_dapm_widget dit_widgets[] = { + SND_SOC_DAPM_OUTPUT("pwmdac-out"), +}; + +static const struct snd_soc_dapm_route dit_routes[] = { + { "pwmdac-out", NULL, "Playback" }, +}; + +static struct snd_soc_component_driver soc_codec_pwmdac_dit = { + .dapm_widgets = dit_widgets, + .num_dapm_widgets = ARRAY_SIZE(dit_widgets), + .dapm_routes = dit_routes, + .num_dapm_routes = ARRAY_SIZE(dit_routes), + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static struct snd_soc_dai_driver dit_stub_dai = { + .name = "pwmdac-dit-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 384, + .rates = STUB_RATES, + .formats = STUB_FORMATS, + }, +}; + +static int pwmdac_dit_probe(struct platform_device *pdev) +{ + + return devm_snd_soc_register_component(&pdev->dev, + &soc_codec_pwmdac_dit, + &dit_stub_dai, 1); +} + +#ifdef CONFIG_OF +static const struct of_device_id pwmdac_dit_dt_ids[] = { + { .compatible = "linux,pwmdac-dit", }, + { } +}; +MODULE_DEVICE_TABLE(of, pwmdac_dit_dt_ids); +#endif + +static struct platform_driver pwmdac_dit_driver = { + .probe = pwmdac_dit_probe, + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(pwmdac_dit_dt_ids), + }, +}; + +module_platform_driver(pwmdac_dit_driver); + +MODULE_AUTHOR("jenny.zhang "); +MODULE_DESCRIPTION("pwmdac dummy codec driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform: starfive-pwmdac dummy codec"); diff --git a/sound/soc/starfive/pwmdac.c b/sound/soc/starfive/pwmdac.c new file mode 100644 index 0000000000000..78f2f9c62ec98 --- /dev/null +++ b/sound/soc/starfive/pwmdac.c @@ -0,0 +1,862 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * PWMDAC driver for the StarFive JH7100 SoC + * + * Copyright (C) 2021 StarFive Technology Co., Ltd. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pwmdac.h" +#include + +struct ct_pwmdac { + char *name; + unsigned int vals; +}; + +static const struct ct_pwmdac pwmdac_ct_shift_bit[] = { + { .name = "8bit", .vals = PWMDAC_SHIFT_8 }, + { .name = "10bit", .vals = PWMDAC_SHIFT_10 } +}; + +static const struct ct_pwmdac pwmdac_ct_duty_cycle[] = { + { .name = "left", .vals = PWMDAC_CYCLE_LEFT }, + { .name = "right", .vals = PWMDAC_CYCLE_RIGHT }, + { .name = "center", .vals = PWMDAC_CYCLE_CENTER } +}; + +static const struct ct_pwmdac pwmdac_ct_data_mode[] = { + { .name = "unsinged", .vals = UNSINGED_DATA }, + { .name = "inverter", .vals = INVERTER_DATA_MSB } +}; + +static const struct ct_pwmdac pwmdac_ct_lr_change[] = { + { .name = "no_change", .vals = NO_CHANGE }, + { .name = "change", .vals = CHANGE } +}; + +static const struct ct_pwmdac pwmdac_ct_shift[] = { + { .name = "left 0 bit", .vals = PWMDAC_DATA_LEFT_SHIFT_BIT_0 }, + { .name = "left 1 bit", .vals = PWMDAC_DATA_LEFT_SHIFT_BIT_1 }, + { .name = "left 2 bit", .vals = PWMDAC_DATA_LEFT_SHIFT_BIT_2 }, + { .name = "left 3 bit", .vals = PWMDAC_DATA_LEFT_SHIFT_BIT_3 }, + { .name = "left 4 bit", .vals = PWMDAC_DATA_LEFT_SHIFT_BIT_4 }, + { .name = "left 5 bit", .vals = PWMDAC_DATA_LEFT_SHIFT_BIT_5 }, + { .name = "left 6 bit", .vals = PWMDAC_DATA_LEFT_SHIFT_BIT_6 } +}; + +static int pwmdac_shift_bit_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + unsigned int items = ARRAY_SIZE(pwmdac_ct_shift_bit); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = items; + if (uinfo->value.enumerated.item >= items) { + uinfo->value.enumerated.item = items - 1; + } + strcpy(uinfo->value.enumerated.name, + pwmdac_ct_shift_bit[uinfo->value.enumerated.item].name); + + return 0; +} +static int pwmdac_shift_bit_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component); + unsigned int item; + + if (dev->shift_bit == pwmdac_ct_shift_bit[0].vals) + item = 0; + else + item = 1; + + ucontrol->value.enumerated.item[0] = item; + + return 0; +} + +static int pwmdac_shift_bit_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component); + int sel = ucontrol->value.enumerated.item[0]; + unsigned int items = ARRAY_SIZE(pwmdac_ct_shift_bit); + + if (sel > items) + return 0; + + switch (sel) { + case 1: + dev->shift_bit = pwmdac_ct_shift_bit[1].vals; + break; + default: + dev->shift_bit = pwmdac_ct_shift_bit[0].vals; + break; + } + + return 0; +} + +static int pwmdac_duty_cycle_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + unsigned int items = ARRAY_SIZE(pwmdac_ct_duty_cycle); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = items; + if (uinfo->value.enumerated.item >= items) + uinfo->value.enumerated.item = items - 1; + strcpy(uinfo->value.enumerated.name, + pwmdac_ct_duty_cycle[uinfo->value.enumerated.item].name); + + return 0; +} + +static int pwmdac_duty_cycle_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component); + + ucontrol->value.enumerated.item[0] = dev->duty_cycle; + return 0; +} + +static int pwmdac_duty_cycle_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component); + int sel = ucontrol->value.enumerated.item[0]; + unsigned int items = ARRAY_SIZE(pwmdac_ct_duty_cycle); + + if (sel > items) + return 0; + + dev->duty_cycle = pwmdac_ct_duty_cycle[sel].vals; + return 0; +} + +/* +static int pwmdac_datan_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 1; + uinfo->value.integer.max = PWMDAC_SAMPLE_CNT_511; + uinfo->value.integer.step = 1; + return 0; +} + +static int pwmdac_datan_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] = dev->datan; + + return 0; +} + +static int pwmdac_datan_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component); + int sel = ucontrol->value.integer.value[0]; + + if (sel > PWMDAC_SAMPLE_CNT_511) + return 0; + + dev->datan = sel; + + return 0; +} +*/ + +static int pwmdac_data_mode_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + unsigned int items = ARRAY_SIZE(pwmdac_ct_data_mode); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = items; + if (uinfo->value.enumerated.item >= items) + uinfo->value.enumerated.item = items - 1; + strcpy(uinfo->value.enumerated.name, + pwmdac_ct_data_mode[uinfo->value.enumerated.item].name); + + return 0; +} + +static int pwmdac_data_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component); + + ucontrol->value.enumerated.item[0] = dev->data_mode; + return 0; +} + +static int pwmdac_data_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component); + int sel = ucontrol->value.enumerated.item[0]; + unsigned int items = ARRAY_SIZE(pwmdac_ct_data_mode); + + if (sel > items) + return 0; + + dev->data_mode = pwmdac_ct_data_mode[sel].vals; + return 0; +} + +static int pwmdac_shift_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + unsigned int items = ARRAY_SIZE(pwmdac_ct_shift); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = items; + if (uinfo->value.enumerated.item >= items) + uinfo->value.enumerated.item = items - 1; + strcpy(uinfo->value.enumerated.name, + pwmdac_ct_shift[uinfo->value.enumerated.item].name); + + return 0; +} + +static int pwmdac_shift_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component); + unsigned int item = dev->shift; + + ucontrol->value.enumerated.item[0] = pwmdac_ct_shift[item].vals; + return 0; +} + +static int pwmdac_shift_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component); + int sel = ucontrol->value.enumerated.item[0]; + unsigned int items = ARRAY_SIZE(pwmdac_ct_shift); + + if (sel > items) + return 0; + + dev->shift = pwmdac_ct_shift[sel].vals; + return 0; +} + +static int pwmdac_lr_change_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + unsigned int items = ARRAY_SIZE(pwmdac_ct_lr_change); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = items; + if (uinfo->value.enumerated.item >= items) + uinfo->value.enumerated.item = items - 1; + strcpy(uinfo->value.enumerated.name, + pwmdac_ct_lr_change[uinfo->value.enumerated.item].name); + + return 0; +} + +static int pwmdac_lr_change_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component); + + ucontrol->value.enumerated.item[0] = dev->lr_change; + return 0; +} + +static int pwmdac_lr_change_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct sf_pwmdac_dev *dev = snd_soc_component_get_drvdata(component); + int sel = ucontrol->value.enumerated.item[0]; + unsigned int items = ARRAY_SIZE(pwmdac_ct_lr_change); + + if (sel > items) + return 0; + + dev->lr_change = pwmdac_ct_lr_change[sel].vals; + return 0; +} + +static inline void pwmdc_write_reg(void __iomem *io_base, int reg, u32 val) +{ + writel(val, io_base + reg); +} + +static inline u32 pwmdc_read_reg(void __iomem *io_base, int reg) +{ + return readl(io_base + reg); +} + +/* + * 32bit-4byte +*/ +static void pwmdac_set_ctrl_enable(struct sf_pwmdac_dev *dev) +{ + u32 date; + date = pwmdc_read_reg(dev->pwmdac_base, PWMDAC_CTRL); + pwmdc_write_reg(dev->pwmdac_base, PWMDAC_CTRL, date | BIT(0) ); +} + +/* + * 32bit-4byte +*/ +static void pwmdac_set_ctrl_disable(struct sf_pwmdac_dev *dev) +{ + u32 date; + date = pwmdc_read_reg(dev->pwmdac_base, PWMDAC_CTRL); + pwmdc_write_reg(dev->pwmdac_base, PWMDAC_CTRL, date & ~ BIT(0)); +} + +/* + * 8:8-bit + * 10:10-bit +*/ +static void pwmdac_set_ctrl_shift(struct sf_pwmdac_dev *dev, u8 data) +{ + u32 value = 0; + + if (data == 8) { + value = (~((~value) | 0x02)); + pwmdc_write_reg(dev->pwmdac_base , PWMDAC_CTRL, value); + } + else if(data == 10){ + value |= 0x02; + pwmdc_write_reg(dev->pwmdac_base , PWMDAC_CTRL, value); + } +} + +/* + * 00:left + * 01:right + * 10:center +*/ +static void pwmdac_set_ctrl_dutyCycle(struct sf_pwmdac_dev *dev, u8 data) +{ + u32 value = 0; + + value = pwmdc_read_reg(dev->pwmdac_base , PWMDAC_CTRL); + if (data == 0) { //left + value = (~((~value) | (0x03<<2))); + pwmdc_write_reg(dev->pwmdac_base , PWMDAC_CTRL, value); + } + else if (data == 1) { //right + value = (~((~value) | (0x01<<3))) | (0x01<<2); + pwmdc_write_reg(dev->pwmdac_base , PWMDAC_CTRL, value); + } + else if (data == 2) { //center + value = (~((~value) | (0x01<<2))) | (0x01<<3); + pwmdc_write_reg(dev->pwmdac_base , PWMDAC_CTRL, value); + } +} + + +static void pwmdac_set_ctrl_N(struct sf_pwmdac_dev *dev, u16 data) +{ + u32 value = 0; + + value = pwmdc_read_reg(dev->pwmdac_base , PWMDAC_CTRL); + pwmdc_write_reg(dev->pwmdac_base , PWMDAC_CTRL, (value & 0xF) | ((data - 1) << 4)); +} + + +static void pwmdac_LR_data_change(struct sf_pwmdac_dev *dev, u8 data) +{ + u32 value = 0; + + value = pwmdc_read_reg(dev->pwmdac_base , PWMDAC_CTRL); + switch (data) { + case NO_CHANGE: + value &= (~SFC_PWMDAC_LEFT_RIGHT_DATA_CHANGE); + break; + case CHANGE: + value |= SFC_PWMDAC_LEFT_RIGHT_DATA_CHANGE; + break; + } + pwmdc_write_reg(dev->pwmdac_base, PWMDAC_CTRL, value); +} + +static void pwmdac_data_mode(struct sf_pwmdac_dev *dev, u8 data) +{ + u32 value = 0; + + value = pwmdc_read_reg(dev->pwmdac_base , PWMDAC_CTRL); + if (data == UNSINGED_DATA) { + value &= (~SFC_PWMDAC_DATA_MODE); + } + else if (data == INVERTER_DATA_MSB) { + value |= SFC_PWMDAC_DATA_MODE; + } + pwmdc_write_reg(dev->pwmdac_base,PWMDAC_CTRL, value); +} + +static int pwmdac_data_shift(struct sf_pwmdac_dev *dev,u8 data) +{ + u32 value = 0; + + if ((data < PWMDAC_DATA_LEFT_SHIFT_BIT_0) || (data > PWMDAC_DATA_LEFT_SHIFT_BIT_7)) { + return -1; + } + + value = pwmdc_read_reg(dev->pwmdac_base , PWMDAC_CTRL); + value &= ( ~ ( PWMDAC_DATA_LEFT_SHIFT_BIT_ALL << 15 ) ); + value |= (data<<15); + pwmdc_write_reg(dev->pwmdac_base , PWMDAC_CTRL, value); + return 0; +} + +static int get_pwmdac_fifo_state(struct sf_pwmdac_dev *dev) +{ + u32 value; + + value = pwmdc_read_reg(dev->pwmdac_base , PWMDAC_SATAE); + if ((value & 0x02) == 0) + return FIFO_UN_FULL; + + return FIFO_FULL; +} + + +static void pwmdac_set(struct sf_pwmdac_dev *dev) +{ + ///8-bit + left + N=16 + pwmdac_set_ctrl_shift(dev, dev->shift_bit); + pwmdac_set_ctrl_dutyCycle(dev, dev->duty_cycle); + pwmdac_set_ctrl_N(dev, dev->datan); + pwmdac_set_ctrl_enable(dev); + + pwmdac_LR_data_change(dev, dev->lr_change); + pwmdac_data_mode(dev, dev->data_mode); + if (dev->shift) { + pwmdac_data_shift(dev, dev->shift); + } +} + +static void pwmdac_stop(struct sf_pwmdac_dev *dev) +{ + pwmdac_set_ctrl_disable(dev); +} + +static int pwmdac_config(struct sf_pwmdac_dev *dev) +{ + switch (dev->mode) { + case shift_8Bit_unsigned: + case shift_8Bit_unsigned_dataShift: + /* 8 bit, unsigned */ + dev->shift_bit = PWMDAC_SHIFT_8; + dev->duty_cycle = PWMDAC_CYCLE_CENTER; + dev->datan = PWMDAC_SAMPLE_CNT_8; + dev->data_mode = UNSINGED_DATA; + break; + + case shift_8Bit_inverter: + case shift_8Bit_inverter_dataShift: + /* 8 bit, invert */ + dev->shift_bit = PWMDAC_SHIFT_8; + dev->duty_cycle = PWMDAC_CYCLE_CENTER; + dev->datan = PWMDAC_SAMPLE_CNT_8; + dev->data_mode = INVERTER_DATA_MSB; + break; + + case shift_10Bit_unsigned: + case shift_10Bit_unsigned_dataShift: + /* 10 bit, unsigend */ + dev->shift_bit = PWMDAC_SHIFT_10; + dev->duty_cycle = PWMDAC_CYCLE_CENTER; + dev->datan = PWMDAC_SAMPLE_CNT_8; + dev->data_mode = UNSINGED_DATA; + break; + + case shift_10Bit_inverter: + case shift_10Bit_inverter_dataShift: + /* 10 bit, invert */ + dev->shift_bit = PWMDAC_SHIFT_10; + dev->duty_cycle = PWMDAC_CYCLE_CENTER; + dev->datan = PWMDAC_SAMPLE_CNT_8; + dev->data_mode = INVERTER_DATA_MSB; + break; + + default: + return -1; + } + + if ((dev->mode == shift_8Bit_unsigned_dataShift) || (dev->mode == shift_8Bit_inverter_dataShift) + || (dev->mode == shift_10Bit_unsigned_dataShift) || (dev->mode == shift_10Bit_inverter_dataShift)) { + dev->shift = 4; /*0~7*/ + } else { + dev->shift = 0; + } + dev->lr_change = NO_CHANGE; + return 0; +} + +static int sf_pwmdac_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + //struct sf_pwmdac_dev *dev = snd_soc_dai_get_drvdata(dai); + //pwmdac_set(dev); + return 0; +} + +static int pwmdac_tx_thread(void *dev) +{ + struct sf_pwmdac_dev *pwmdac_dev = (struct sf_pwmdac_dev *)dev; + + set_current_state(TASK_INTERRUPTIBLE); + while (!schedule_timeout(usecs_to_jiffies(50))) { + if (pwmdac_dev->tx_thread_exit) + break; + if (get_pwmdac_fifo_state(pwmdac_dev)==0) { + sf_pwmdac_pcm_push_tx(pwmdac_dev); + } + + set_current_state(TASK_INTERRUPTIBLE); + } + + pwmdac_dev->tx_thread = NULL; + return 0; +} + +static int sf_pwmdac_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct sf_pwmdac_dev *dev = snd_soc_dai_get_drvdata(dai); + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + dev->active++; + pwmdac_set(dev); + if (dev->use_pio) { + dev->tx_thread = kthread_create(pwmdac_tx_thread, (void *)dev, "pwmdac"); + if (IS_ERR(dev->tx_thread)) { + return PTR_ERR(dev->tx_thread); + } + wake_up_process(dev->tx_thread); + dev->tx_thread_exit = 0; + } + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dev->active--; + pwmdac_stop(dev); + if (dev->use_pio) { + if(dev->tx_thread) { + dev->tx_thread_exit = 1; + } + } + break; + default: + ret = -EINVAL; + break; + } + return ret; + + return 0; +} + +static int sf_pwmdac_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct sf_pwmdac_dev *dev = dev_get_drvdata(dai->dev); + + dev->play_dma_data.addr = dev->mapbase + PWMDAC_WDATA; + + switch (params_channels(params)) { + case 2: + dev->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + break; + case 1: + dev->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + break; + default: + dev_err(dai->dev, "%d channels not supported\n", + params_channels(params)); + return -EINVAL; + } + + dev->play_dma_data.fifo_size = 1; + dev->play_dma_data.maxburst = 16; + + snd_soc_dai_init_dma_data(dai, &dev->play_dma_data, NULL); + snd_soc_dai_set_drvdata(dai, dev); + + return 0; +} + +static int sf_pwmdac_clks_get(struct platform_device *pdev, + struct sf_pwmdac_dev *dev) +{ + static const char *const clock_names[PWMDAC_CLK_NUM] = { + [PWMDAC_CLK_AUDIO_ROOT] = "audio_root", + [PWMDAC_CLK_AUDIO_SRC] = "audio_src", + [PWMDAC_CLK_AUDIO_12288] = "audio_12288", + [PWMDAC_CLK_DMA1P_AHB] = "dma1p_ahb", + [PWMDAC_CLK_PWMDAC_APB] = "pwmdac_apb", + [PWMDAC_CLK_DAC_MCLK] = "dac_mclk", + }; + int i; + + for (i = 0; i < PWMDAC_CLK_NUM; i++) + dev->clk[i].id = clock_names[i]; + + return devm_clk_bulk_get(&pdev->dev, ARRAY_SIZE(dev->clk), dev->clk); +} + +static int sf_pwmdac_resets_get(struct platform_device *pdev, + struct sf_pwmdac_dev *dev) +{ + static const char *const reset_names[PWMDAC_RST_NUM] = { + [PWMDAC_RST_APB_BUS] = "apb_bus", + [PWMDAC_RST_DMA1P_AHB] = "dma1p_ahb", + [PWMDAC_RST_APB_PWMDAC] = "apb_pwmdac", + }; + int i; + + for (i = 0; i < PWMDAC_RST_NUM; i++) + dev->rst[i].id = reset_names[i]; + + return devm_reset_control_bulk_get_exclusive(&pdev->dev, ARRAY_SIZE(dev->rst), dev->rst); +} + +static int sf_pwmdac_clk_init(struct platform_device *pdev, + struct sf_pwmdac_dev *dev) +{ + int ret; + int i; + + for (i = 0; i <= PWMDAC_CLK_DMA1P_AHB; i++) { + ret = clk_prepare_enable(dev->clk[i].clk); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "failed to enable %s\n", dev->clk[i].id); + } + + for (i = 0; i <= PWMDAC_RST_DMA1P_AHB; i++) { + ret = reset_control_deassert(dev->rst[i].rstc); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "failed to deassert %s\n", dev->rst[i].id); + } + + ret = clk_set_rate(dev->clk[PWMDAC_CLK_AUDIO_SRC].clk, 12288000); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "failed to set 12.288 MHz rate for clk_audio_src\n"); + + ret = reset_control_assert(dev->rst[PWMDAC_RST_APB_PWMDAC].rstc); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed to assert apb_pwmdac\n"); + + ret = clk_prepare_enable(dev->clk[PWMDAC_CLK_DAC_MCLK].clk); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed to prepare enable clk_dac_mclk\n"); + + /* we want 4096kHz but the clock driver always rounds down so add a little slack */ + ret = clk_set_rate(dev->clk[PWMDAC_CLK_DAC_MCLK].clk, 4096000 + 64); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed to set 4096kHz rate for clk_dac_mclk\n"); + + ret = clk_prepare_enable(dev->clk[PWMDAC_CLK_PWMDAC_APB].clk); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed to prepare enable clk_pwmdac_apb\n"); + + ret = reset_control_deassert(dev->rst[PWMDAC_RST_APB_PWMDAC].rstc); + if (ret) + return dev_err_probe(&pdev->dev, ret, "failed to deassert apb_pwmdac\n"); + + return 0; +} + +static int sf_pwmdac_dai_probe(struct snd_soc_dai *dai) +{ + struct sf_pwmdac_dev *dev = dev_get_drvdata(dai->dev); + + dev->play_dma_data.addr = dev->mapbase + PWMDAC_WDATA; + dev->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + dev->play_dma_data.fifo_size = 1; + dev->play_dma_data.maxburst = 16; + + snd_soc_dai_init_dma_data(dai, &dev->play_dma_data, NULL); + snd_soc_dai_set_drvdata(dai, dev); + + return 0; +} +#define SOC_PWMDAC_ENUM_DECL(xname, xinfo, xget, xput) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = xinfo, .get = xget, \ + .put = xput,} +static const struct snd_kcontrol_new pwmdac_snd_controls[] = { + SOC_PWMDAC_ENUM_DECL("shift_bit", pwmdac_shift_bit_info, + pwmdac_shift_bit_get, pwmdac_shift_bit_put), + SOC_PWMDAC_ENUM_DECL("duty_cycle", pwmdac_duty_cycle_info, + pwmdac_duty_cycle_get, pwmdac_duty_cycle_put), + SOC_PWMDAC_ENUM_DECL("data_mode", pwmdac_data_mode_info, + pwmdac_data_mode_get, pwmdac_data_mode_put), + SOC_PWMDAC_ENUM_DECL("shift", pwmdac_shift_info, + pwmdac_shift_get, pwmdac_shift_put), + SOC_PWMDAC_ENUM_DECL("lr_change", pwmdac_lr_change_info, + pwmdac_lr_change_get, pwmdac_lr_change_put), +}; +static int pwmdac_probe(struct snd_soc_component *component) +{ +// struct sf_pwmdac_dev *priv = snd_soc_component_get_drvdata(component); + snd_soc_add_component_controls(component, pwmdac_snd_controls, + ARRAY_SIZE(pwmdac_snd_controls)); + return 0; +} + + +static const struct snd_soc_dai_ops sf_pwmdac_dai_ops = { + .hw_params = sf_pwmdac_hw_params, + .prepare = sf_pwmdac_prepare, + .trigger = sf_pwmdac_trigger, +}; + +static const struct snd_soc_component_driver sf_pwmdac_component = { + .name = "sf-pwmdac", + .probe = pwmdac_probe, +}; + +static struct snd_soc_dai_driver pwmdac_dai = { + .name = "pwmdac", + .id = 0, + .probe = sf_pwmdac_dai_probe, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &sf_pwmdac_dai_ops, +}; + +static int sf_pwmdac_probe(struct platform_device *pdev) +{ + struct sf_pwmdac_dev *dev; + struct resource *res; + int ret; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dev->mapbase = res->start; + dev->pwmdac_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(dev->pwmdac_base)) + return PTR_ERR(dev->pwmdac_base); + + ret = sf_pwmdac_clks_get(pdev, dev); + if (ret) { + dev_err(&pdev->dev, "failed to get audio clock\n"); + return ret; + } + + ret = sf_pwmdac_resets_get(pdev, dev); + if (ret) { + dev_err(&pdev->dev, "failed to get audio reset controls\n"); + return ret; + } + + ret = sf_pwmdac_clk_init(pdev, dev); + if (ret) { + dev_err(&pdev->dev, "failed to enable audio clock\n"); + return ret; + } + + dev->dev = &pdev->dev; + dev->mode = shift_8Bit_inverter; + dev->fifo_th = 1;//8byte + pwmdac_config(dev); + + dev->use_pio = false; + dev_set_drvdata(&pdev->dev, dev); + ret = devm_snd_soc_register_component(&pdev->dev, &sf_pwmdac_component, + &pwmdac_dai, 1); + if (ret != 0) { + dev_err(&pdev->dev, "not able to register dai\n"); + return ret; + } + + if (dev->use_pio) { + ret = sf_pwmdac_pcm_register(pdev); + } else { + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, + 0); + } + return 0; +} + + +static int sf_pwmdac_remove(struct platform_device *pdev) +{ + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id sf_pwmdac_of_match[] = { + { .compatible = "starfive,pwmdac", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, sf_pwmdac_of_match); +#endif + + +static struct platform_driver sf_pwmdac_driver = { + .probe = sf_pwmdac_probe, + .remove = sf_pwmdac_remove, + .driver = { + .name = "sf-pwmdac", + .of_match_table = of_match_ptr(sf_pwmdac_of_match), + }, +}; + +module_platform_driver(sf_pwmdac_driver); + +MODULE_AUTHOR("jenny.zhang "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("starfive pwmdac SoC Interface"); +MODULE_ALIAS("platform:starfive-pwmdac"); diff --git a/sound/soc/starfive/pwmdac.h b/sound/soc/starfive/pwmdac.h new file mode 100644 index 0000000000000..b9f651fc59f12 --- /dev/null +++ b/sound/soc/starfive/pwmdac.h @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 StarFive Technology Co., Ltd. + */ +#ifndef __SND_SOC_STARFIVE_PWMDAC_H +#define __SND_SOC_STARFIVE_PWMDAC_H + +#include +#include +#include +#include +#include +#include + +#define PWMDAC_WDATA 0 // PWMDAC_BASE_ADDR +#define PWMDAC_CTRL 0x04 // PWMDAC_BASE_ADDR + 0x04 +#define PWMDAC_SATAE 0x08 // PWMDAC_BASE_ADDR + 0x08 +#define PWMDAC_RESERVED 0x0C // PWMDAC_BASE_ADDR + 0x0C + +#define SFC_PWMDAC_SHIFT BIT(1) +#define SFC_PWMDAC_DUTY_CYCLE BIT(2) +#define SFC_PWMDAC_CNT_N BIT(4) + +#define SFC_PWMDAC_LEFT_RIGHT_DATA_CHANGE BIT(13) +#define SFC_PWMDAC_DATA_MODE BIT(14) + +#define FIFO_UN_FULL 0 +#define FIFO_FULL 1 + +enum pwmdac_lr_change{ + NO_CHANGE = 0, + CHANGE, +}; + +enum pwmdac_d_mode{ + UNSINGED_DATA = 0, + INVERTER_DATA_MSB, +}; + +enum pwmdac_shift_bit{ + PWMDAC_SHIFT_8 = 8, /* pwmdac shift 8 bit */ + PWMDAC_SHIFT_10 = 10, /* pwmdac shift 10 bit */ +}; + +enum pwmdac_duty_cycle{ + PWMDAC_CYCLE_LEFT = 0, /* pwmdac duty cycle left */ + PWMDAC_CYCLE_RIGHT = 1, /* pwmdac duty cycle right */ + PWMDAC_CYCLE_CENTER = 2, /* pwmdac duty cycle center */ +}; + +/*sample count [12:4] <511*/ +enum pwmdac_sample_count{ + PWMDAC_SAMPLE_CNT_1 = 1, + PWMDAC_SAMPLE_CNT_2, + PWMDAC_SAMPLE_CNT_3, + PWMDAC_SAMPLE_CNT_4, + PWMDAC_SAMPLE_CNT_5, + PWMDAC_SAMPLE_CNT_6, + PWMDAC_SAMPLE_CNT_7, + PWMDAC_SAMPLE_CNT_8 = 1, //(32.468/8) == (12.288/3) == 4.096 + PWMDAC_SAMPLE_CNT_9, + PWMDAC_SAMPLE_CNT_10, + PWMDAC_SAMPLE_CNT_11, + PWMDAC_SAMPLE_CNT_12, + PWMDAC_SAMPLE_CNT_13, + PWMDAC_SAMPLE_CNT_14, + PWMDAC_SAMPLE_CNT_15, + PWMDAC_SAMPLE_CNT_16, + PWMDAC_SAMPLE_CNT_17, + PWMDAC_SAMPLE_CNT_18, + PWMDAC_SAMPLE_CNT_19, + PWMDAC_SAMPLE_CNT_20 = 20, + PWMDAC_SAMPLE_CNT_30 = 30, + PWMDAC_SAMPLE_CNT_511 = 511, +}; + + +enum data_shift{ + PWMDAC_DATA_LEFT_SHIFT_BIT_0 = 0, + PWMDAC_DATA_LEFT_SHIFT_BIT_1, + PWMDAC_DATA_LEFT_SHIFT_BIT_2, + PWMDAC_DATA_LEFT_SHIFT_BIT_3, + PWMDAC_DATA_LEFT_SHIFT_BIT_4, + PWMDAC_DATA_LEFT_SHIFT_BIT_5, + PWMDAC_DATA_LEFT_SHIFT_BIT_6, + PWMDAC_DATA_LEFT_SHIFT_BIT_7, + PWMDAC_DATA_LEFT_SHIFT_BIT_ALL, +}; + +enum pwmdac_config_list{ + shift_8Bit_unsigned = 0, + shift_8Bit_unsigned_dataShift, + shift_10Bit_unsigned, + shift_10Bit_unsigned_dataShift, + + shift_8Bit_inverter, + shift_8Bit_inverter_dataShift, + shift_10Bit_inverter, + shift_10Bit_inverter_dataShift, +}; + +enum pwmdac_clocks { + PWMDAC_CLK_AUDIO_ROOT, + PWMDAC_CLK_AUDIO_SRC, + PWMDAC_CLK_AUDIO_12288, + PWMDAC_CLK_DMA1P_AHB, + PWMDAC_CLK_PWMDAC_APB, + PWMDAC_CLK_DAC_MCLK, + PWMDAC_CLK_NUM, +}; + +enum pwmdac_resets { + PWMDAC_RST_APB_BUS, + PWMDAC_RST_DMA1P_AHB, + PWMDAC_RST_APB_PWMDAC, + PWMDAC_RST_NUM, +}; + +struct sf_pwmdac_dev { + void __iomem *pwmdac_base; + resource_size_t mapbase; + u8 mode; + u8 shift_bit; + u8 duty_cycle; + u8 datan; + u8 data_mode; + u8 lr_change; + u8 shift; + u8 fifo_th; + bool use_pio; + spinlock_t lock; + int active; + + struct clk_bulk_data clk[PWMDAC_CLK_NUM]; + struct reset_control_bulk_data rst[PWMDAC_RST_NUM]; + + struct device *dev; + struct snd_dmaengine_dai_dma_data play_dma_data; + struct snd_pcm_substream __rcu *tx_substream; + unsigned int (*tx_fn)(struct sf_pwmdac_dev *dev, + struct snd_pcm_runtime *runtime, unsigned int tx_ptr, + bool *period_elapsed); + unsigned int tx_ptr; + struct task_struct *tx_thread; + bool tx_thread_exit; +}; + + + +#if IS_ENABLED(CONFIG_SND_STARFIVE_PWMDAC_PCM) +void sf_pwmdac_pcm_push_tx(struct sf_pwmdac_dev *dev); +int sf_pwmdac_pcm_register(struct platform_device *pdev); +#else +static void sf_pwmdac_pcm_push_tx(struct sf_pwmdac_dev *dev) { } +static int sf_pwmdac_pcm_register(struct platform_device *pdev) +{ + return -EINVAL; +} +#endif + +#endif diff --git a/sound/soc/starfive/spdif-pcm.c b/sound/soc/starfive/spdif-pcm.c new file mode 100644 index 0000000000000..cd78ffd1e691a --- /dev/null +++ b/sound/soc/starfive/spdif-pcm.c @@ -0,0 +1,288 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 StarFive Technology Co., Ltd. + */ +#include +#include +#include +#include + +#include "spdif.h" + +#define BUFFER_BYTES_MAX (3 * 2 * 8 * PERIOD_BYTES_MIN) +#define PERIOD_BYTES_MIN 4096 +#define PERIODS_MIN 2 + +static unsigned int sf_spdif_pcm_tx(struct sf_spdif_dev *dev, + struct snd_pcm_runtime *runtime, unsigned int tx_ptr, + bool *period_elapsed, snd_pcm_format_t format) +{ + const u16 (*p16)[2] = (void *)runtime->dma_area; + const u32 (*p32)[2] = (void *)runtime->dma_area; + u32 data[2]; + unsigned int period_pos = tx_ptr % runtime->period_size; + int i; + + for (i = 0; i < dev->fifo_th; i++) { + if (SNDRV_PCM_FORMAT_S16_LE == format) { + data[0] = p16[tx_ptr][0]; + data[1] = p16[tx_ptr][1]; + data[0] = data[0]<<8; + data[1] = data[1]<<8; + } else if (SNDRV_PCM_FORMAT_S24_LE == format) { + data[0] = p32[tx_ptr][0]; + data[1] = p32[tx_ptr][1]; + } else if (SNDRV_PCM_FORMAT_S32_LE == format) { + data[0] = p32[tx_ptr][0]; + data[1] = p32[tx_ptr][1]; + data[0] = data[0]>>8; + data[1] = data[1]>>8; + } + + iowrite32(data[0], dev->spdif_base + SPDIF_FIFO_ADDR); + iowrite32(data[1], dev->spdif_base + SPDIF_FIFO_ADDR); + period_pos++; + if (++tx_ptr >= runtime->buffer_size) { + tx_ptr = 0; + } + } + + *period_elapsed = period_pos >= runtime->period_size; + return tx_ptr; +} + +static unsigned int sf_spdif_pcm_rx(struct sf_spdif_dev *dev, + struct snd_pcm_runtime *runtime, unsigned int rx_ptr, + bool *period_elapsed, snd_pcm_format_t format) +{ + u16 (*p16)[2] = (void *)runtime->dma_area; + u32 (*p32)[2] = (void *)runtime->dma_area; + u32 data[2]; + unsigned int period_pos = rx_ptr % runtime->period_size; + int i; + + for (i = 0; i < dev->fifo_th; i++) { + data[0] = ioread32(dev->spdif_base + SPDIF_FIFO_ADDR); + data[1] = ioread32(dev->spdif_base + SPDIF_FIFO_ADDR); + if (SNDRV_PCM_FORMAT_S16_LE == format) { + p16[rx_ptr][0] = data[0]>>8; + p16[rx_ptr][1] = data[1]>>8; + } else if (SNDRV_PCM_FORMAT_S24_LE == format) { + p32[rx_ptr][0] = data[0]; + p32[rx_ptr][1] = data[1]; + } else if (SNDRV_PCM_FORMAT_S32_LE == format) { + p32[rx_ptr][0] = data[0]<<8; + p32[rx_ptr][1] = data[1]<<8; + } + + period_pos++; + if (++rx_ptr >= runtime->buffer_size) + rx_ptr = 0; + } + + *period_elapsed = period_pos >= runtime->period_size; + return rx_ptr; +} + +static const struct snd_pcm_hardware sf_pcm_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_11025 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_22050 | + SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000, + .rate_min = 8000, + .rate_max = 48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = BUFFER_BYTES_MAX, + .period_bytes_min = PERIOD_BYTES_MIN, + .period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN, + .periods_min = PERIODS_MIN, + .periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN, + .fifo_size = 16, +}; + +static void sf_spdif_pcm_transfer(struct sf_spdif_dev *dev, bool push) +{ + struct snd_pcm_substream *substream; + bool active, period_elapsed; + + rcu_read_lock(); + if (push) + substream = rcu_dereference(dev->tx_substream); + else + substream = rcu_dereference(dev->rx_substream); + active = substream && snd_pcm_running(substream); + if (active) { + unsigned int ptr; + unsigned int new_ptr; + + if (push) { + ptr = READ_ONCE(dev->tx_ptr); + new_ptr = dev->tx_fn(dev, substream->runtime, ptr, + &period_elapsed, dev->format); + cmpxchg(&dev->tx_ptr, ptr, new_ptr); + } else { + ptr = READ_ONCE(dev->rx_ptr); + new_ptr = dev->rx_fn(dev, substream->runtime, ptr, + &period_elapsed, dev->format); + cmpxchg(&dev->rx_ptr, ptr, new_ptr); + } + + if (period_elapsed) + snd_pcm_period_elapsed(substream); + } + rcu_read_unlock(); +} + +void sf_spdif_pcm_push_tx(struct sf_spdif_dev *dev) +{ + sf_spdif_pcm_transfer(dev, true); +} + +void sf_spdif_pcm_pop_rx(struct sf_spdif_dev *dev) +{ + sf_spdif_pcm_transfer(dev, false); +} + +static int sf_pcm_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct sf_spdif_dev *dev = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0)); + + snd_soc_set_runtime_hwparams(substream, &sf_pcm_hardware); + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + runtime->private_data = dev; + + return 0; +} + +static int sf_pcm_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + synchronize_rcu(); + return 0; +} + +static int sf_pcm_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sf_spdif_dev *dev = runtime->private_data; + + switch (params_channels(hw_params)) { + case 2: + break; + default: + dev_err(dev->dev, "invalid channels number\n"); + return -EINVAL; + } + + dev->format = params_format(hw_params); + switch (dev->format) { + case SNDRV_PCM_FORMAT_S16_LE: + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S32_LE: + break; + default: + dev_err(dev->dev, "invalid format\n"); + return -EINVAL; + } + + dev->tx_fn = sf_spdif_pcm_tx; + dev->rx_fn = sf_spdif_pcm_rx; + + return 0; +} + +static int sf_pcm_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sf_spdif_dev *dev = runtime->private_data; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + WRITE_ONCE(dev->tx_ptr, 0); + rcu_assign_pointer(dev->tx_substream, substream); + } else { + WRITE_ONCE(dev->rx_ptr, 0); + rcu_assign_pointer(dev->rx_substream, substream); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rcu_assign_pointer(dev->tx_substream, NULL); + else + rcu_assign_pointer(dev->rx_substream, NULL); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static snd_pcm_uframes_t sf_pcm_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sf_spdif_dev *dev = runtime->private_data; + snd_pcm_uframes_t pos; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + pos = READ_ONCE(dev->tx_ptr); + } + else { + pos = READ_ONCE(dev->rx_ptr); + } + + return pos < runtime->buffer_size ? pos : 0; +} + +static int sf_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + size_t size = sf_pcm_hardware.buffer_bytes_max; + + snd_pcm_set_managed_buffer_all(rtd->pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + NULL, size, size); + + return 0; +} + +static const struct snd_soc_component_driver sf_pcm_component = { + .open = sf_pcm_open, + .close = sf_pcm_close, + .hw_params = sf_pcm_hw_params, + .trigger = sf_pcm_trigger, + .pointer = sf_pcm_pointer, + .pcm_construct = sf_pcm_new, +}; + +int sf_spdif_pcm_register(struct platform_device *pdev) +{ + return devm_snd_soc_register_component(&pdev->dev, &sf_pcm_component, + NULL, 0); +} + diff --git a/sound/soc/starfive/spdif.c b/sound/soc/starfive/spdif.c new file mode 100644 index 0000000000000..9103336380563 --- /dev/null +++ b/sound/soc/starfive/spdif.c @@ -0,0 +1,384 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 StarFive Technology Co., Ltd. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "spdif.h" + +static irqreturn_t spdif_irq_handler(int irq, void *dev_id) +{ + struct sf_spdif_dev *dev = dev_id; + bool irq_valid = false; + unsigned int intr; + unsigned int stat; + + regmap_read(dev->regmap, SPDIF_INT_REG, &intr); + regmap_read(dev->regmap, SPDIF_STAT_REG, &stat); + regmap_update_bits(dev->regmap, SPDIF_CTRL, + SPDIF_MASK_ENABLE, 0); + regmap_update_bits(dev->regmap, SPDIF_INT_REG, + SPDIF_INT_REG_BIT, 0); + + if ((stat & SPDIF_EMPTY_FLAG) || (stat & SPDIF_AEMPTY_FLAG)) { + sf_spdif_pcm_push_tx(dev); + irq_valid = true; + } + + if ((stat & SPDIF_FULL_FLAG) || (stat & SPDIF_AFULL_FLAG)) { + sf_spdif_pcm_pop_rx(dev); + irq_valid = true; + } + + if (stat & SPDIF_PARITY_FLAG) { + irq_valid = true; + } + + if (stat & SPDIF_UNDERR_FLAG) { + irq_valid = true; + } + + if (stat & SPDIF_OVRERR_FLAG) { + irq_valid = true; + } + + if (stat & SPDIF_SYNCERR_FLAG) { + irq_valid = true; + } + + if (stat & SPDIF_LOCK_FLAG) { + irq_valid = true; + } + + if (stat & SPDIF_BEGIN_FLAG) { + irq_valid = true; + } + + if (stat & SPDIF_RIGHT_LEFT) { + irq_valid = true; + } + + regmap_update_bits(dev->regmap, SPDIF_CTRL, + SPDIF_MASK_ENABLE, SPDIF_MASK_ENABLE); + + if (irq_valid) + return IRQ_HANDLED; + else + return IRQ_NONE; +} + +static int sf_spdif_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct sf_spdif_dev *spdif = snd_soc_dai_get_drvdata(dai); + bool tx; + + tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + if (tx) { + /* tx mode */ + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_TR_MODE, SPDIF_TR_MODE); + + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_MASK_FIFO, SPDIF_EMPTY_MASK | SPDIF_AEMPTY_MASK); + } else { + /* rx mode */ + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_TR_MODE, 0); + + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_MASK_FIFO, SPDIF_FULL_MASK | SPDIF_AFULL_MASK); + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* clock recovery form the SPDIF data stream 0:clk_enable */ + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_CLK_ENABLE, 0); + + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_ENABLE, SPDIF_ENABLE); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + /* clock recovery form the SPDIF data stream 1:power save mode */ + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_CLK_ENABLE, SPDIF_CLK_ENABLE); + + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_ENABLE, 0); + break; + default: + printk(KERN_ERR "%s L.%d cmd:%d\n", __func__, __LINE__, cmd); + return -EINVAL; + } + + return 0; +} + +static int sf_spdif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct sf_spdif_dev *spdif = snd_soc_dai_get_drvdata(dai); + unsigned int channels; + unsigned int rate; + unsigned int format; + unsigned int tsamplerate; + + channels = params_channels(params); + rate = params_rate(params); + format = params_format(params); + + switch (channels) { + case 2: + break; + default: + dev_err(dai->dev, "invalid channels number\n"); + return -EINVAL; + } + + switch (format) { + case SNDRV_PCM_FORMAT_S16_LE: + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S32_LE: + break; + default: + dev_err(spdif->dev, "invalid format\n"); + return -EINVAL; + } + + switch (rate) { + case 8000: + case 11025: + case 16000: + case 22050: + break; + default: + printk(KERN_ERR "channel:%d sample rate:%d\n", channels, rate); + return -EINVAL; + } + + /* 12288000/128=96000 */ + tsamplerate = (96000 + rate/2)/rate - 1; + + if (rate < 3) { + return -EINVAL; + } + + /* transmission sample rate */ + regmap_update_bits(spdif->regmap, SPDIF_CTRL, 0xFF, tsamplerate); + + return 0; +} + +static int sf_spdif_dai_probe(struct snd_soc_dai *dai) +{ + struct sf_spdif_dev *spdif = snd_soc_dai_get_drvdata(dai); + + #if 0 + spdif->play_dma_data.addr = (dma_addr_t)spdif->spdif_base + SPDIF_FIFO_ADDR; + spdif->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + spdif->play_dma_data.fifo_size = 16; + spdif->play_dma_data.maxburst = 16; + spdif->capture_dma_data.addr = (dma_addr_t)spdif->spdif_base + SPDIF_FIFO_ADDR; + spdif->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + spdif->capture_dma_data.fifo_size = 16; + spdif->capture_dma_data.maxburst = 16; + snd_soc_dai_init_dma_data(dai, &spdif->play_dma_data, &spdif->capture_dma_data); + snd_soc_dai_set_drvdata(dai, spdif); + #endif + + /* reset */ + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_ENABLE | SPDIF_SFR_ENABLE | SPDIF_FIFO_ENABLE, 0); + + /* clear irq */ + regmap_update_bits(spdif->regmap, SPDIF_INT_REG, + SPDIF_INT_REG_BIT, 0); + + /* power save mode */ + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_CLK_ENABLE, SPDIF_CLK_ENABLE); + + /* power save mode */ + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_CLK_ENABLE, SPDIF_CLK_ENABLE); + + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_PARITCHECK|SPDIF_VALIDITYCHECK|SPDIF_DUPLICATE, + SPDIF_PARITCHECK|SPDIF_VALIDITYCHECK|SPDIF_DUPLICATE); + + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_SETPREAMBB, SPDIF_SETPREAMBB); + + regmap_update_bits(spdif->regmap, SPDIF_INT_REG, + 0x1FFF<regmap, SPDIF_FIFO_CTRL, + 0xFFFFFFFF, 0x20|(0x20<regmap, SPDIF_CTRL, + SPDIF_PARITYGEN, SPDIF_PARITYGEN); + + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_MASK_ENABLE, SPDIF_MASK_ENABLE); + + /* APB access to FIFO enable, disable if use DMA/FIFO */ + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_USE_FIFO_IF, 0); + + /* two channel */ + regmap_update_bits(spdif->regmap, SPDIF_CTRL, + SPDIF_CHANNEL_MODE, 0); + + return 0; +} + +static const struct snd_soc_dai_ops sf_spdif_dai_ops = { + .trigger = sf_spdif_trigger, + .hw_params = sf_spdif_hw_params, +}; + +#define SF_PCM_RATE_44100_192000 (SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_96000 | \ + SNDRV_PCM_RATE_192000) + +#define SF_PCM_RATE_8000_22050 (SNDRV_PCM_RATE_8000 | \ + SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_22050) + +static struct snd_soc_dai_driver sf_spdif_dai = { + .name = "spdif", + .id = 0, + .probe = sf_spdif_dai_probe, + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SF_PCM_RATE_8000_22050, + .formats = SNDRV_PCM_FMTBIT_S16_LE \ + |SNDRV_PCM_FMTBIT_S24_LE \ + |SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = SF_PCM_RATE_8000_22050, + .formats = SNDRV_PCM_FMTBIT_S16_LE \ + |SNDRV_PCM_FMTBIT_S24_LE \ + |SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &sf_spdif_dai_ops, + .symmetric_rate = 1, +}; + +static const struct snd_soc_component_driver sf_spdif_component = { + .name = "sf-spdif", +}; + +static const struct regmap_config sf_spdif_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x200, +}; + +static int sf_spdif_probe(struct platform_device *pdev) +{ + struct sf_spdif_dev *spdif; + struct resource *res; + void __iomem *base; + int ret; + int irq; + + spdif = devm_kzalloc(&pdev->dev, sizeof(*spdif), GFP_KERNEL); + if (!spdif) + return -ENOMEM; + + platform_set_drvdata(pdev, spdif); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + spdif->spdif_base = base; + spdif->regmap = devm_regmap_init_mmio(&pdev->dev, spdif->spdif_base, + &sf_spdif_regmap_config); + if (IS_ERR(spdif->regmap)) + return PTR_ERR(spdif->regmap); + + spdif->dev = &pdev->dev; + spdif->fifo_th = 16; + + irq = platform_get_irq(pdev, 0); + if (irq >= 0) { + ret = devm_request_irq(&pdev->dev, irq, spdif_irq_handler, 0, + pdev->name, spdif); + if (ret < 0) { + dev_err(&pdev->dev, "failed to request irq\n"); + return ret; + } + } + + ret = devm_snd_soc_register_component(&pdev->dev, &sf_spdif_component, + &sf_spdif_dai, 1); + if (ret) + goto err_clk_disable; + + if (irq >= 0) { + ret = sf_spdif_pcm_register(pdev); + spdif->use_pio = true; + } else { + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, + 0); + spdif->use_pio = false; + } + + if (ret) + goto err_clk_disable; + + return 0; + +err_clk_disable: + return ret; +} + +static const struct of_device_id sf_spdif_of_match[] = { + { .compatible = "starfive,sf-spdif", }, + {}, +}; +MODULE_DEVICE_TABLE(of, sf_spdif_of_match); + +static struct platform_driver sf_spdif_driver = { + .driver = { + .name = "sf-spdif", + .of_match_table = sf_spdif_of_match, + }, + .probe = sf_spdif_probe, +}; +module_platform_driver(sf_spdif_driver); + +MODULE_AUTHOR("michael.yan "); +MODULE_DESCRIPTION("starfive SPDIF driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/starfive/spdif.h b/sound/soc/starfive/spdif.h new file mode 100644 index 0000000000000..b9b856db701b5 --- /dev/null +++ b/sound/soc/starfive/spdif.h @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 StarFive Technology Co., Ltd. + */ +#ifndef __SND_SOC_STARFIVE_SPDIF_H +#define __SND_SOC_STARFIVE_SPDIF_H + +#include +#include +#include +#include +#include +#include +#include + +#define SPDIF_CTRL (0x0) +#define SPDIF_INT_REG (0x4) +#define SPDIF_FIFO_CTRL (0x8) +#define SPDIF_STAT_REG (0xC) + +#define SPDIF_FIFO_ADDR (0x100) +#define DMAC_SPDIF_POLLING_LEN (256) + +///ctrl: sampled on the rising clock edge +#define SPDIF_TSAMPLERATE 0///[SRATEW-1:0] +#define SPDIF_SFR_ENABLE (1<<8) ///0:SFR reg reset to defualt value; auto set back to '1' after reset +#define SPDIF_ENABLE (1<<9) ///0:reset of SPDIF block, SRF bits are unchanged; 1:enables SPDIF module +#define SPDIF_FIFO_ENABLE (1<<10) ///0:FIFO pointers are reset to zero,threshold levels for FIFO are unchaned; auto set back to '1' +#define SPDIF_CLK_ENABLE (1<<11) ///1:blocked and the modules are in power save mode; 0:block feeds the modules +#define SPDIF_TR_MODE (1<<12) ///0:rx; 1:tx +#define SPDIF_PARITCHECK (1<<13) ///0:party bit rx in a sub-frame is repeated on the parity; 1:check on a parity error +#define SPDIF_PARITYGEN (1<<14) ///0:parity bit from FIFO is transmitted in sub-frame;1:parity bit generated inside the core and added to a transmitted sub-frame +#define SPDIF_VALIDITYCHECK (1<<15) ///0:validity bit in frame isn't checked and all frame are written; 1:validity bit rx is checked +#define SPDIF_CHANNEL_MODE (1<<16) ///0:two-channel; 1:single-channel +#define SPDIF_DUPLICATE (1<<17) ///only tx -single-channel mode; 0:secondary channel; 1: left(primary) channel +#define SPDIF_SETPREAMBB (1<<18) ///only tx; 0:first preamble B after reset tx valid sub-frame; 1:first preamble B is tx after preambleddel(INT_REG) +#define SPDIF_USE_FIFO_IF (1<<19) ///0:FIFO disabled ,APB accese FIFO; 1:FIFO enable, APB access to FIFO disable; +///#define RESERVED (1<<20) +#define SPDIF_PARITY_MASK (1<<21) +#define SPDIF_UNDERR_MASK (1<<22) +#define SPDIF_OVRERR_MASK (1<<23) +#define SPDIF_EMPTY_MASK (1<<24) +#define SPDIF_AEMPTY_MASK (1<<25) +#define SPDIF_FULL_MASK (1<<26) +#define SPDIF_AFULL_MASK (1<<27) +#define SPDIF_SYNCERR_MASK (1<<28) +#define SPDIF_LOCK_MASK (1<<29) +#define SPDIF_BEGIN_MASK (1<<30) +#define SPDIF_INTEREQ_MAKS (1<<31) + +#define SPDIF_MASK_ENABLE (SPDIF_PARITY_MASK | SPDIF_UNDERR_MASK | SPDIF_OVRERR_MASK | SPDIF_EMPTY_MASK | \ + SPDIF_AEMPTY_MASK | SPDIF_FULL_MASK | SPDIF_AFULL_MASK | SPDIF_SYNCERR_MASK | \ + SPDIF_LOCK_MASK | SPDIF_BEGIN_MASK | SPDIF_INTEREQ_MAKS) + +#define SPDIF_MASK_FIFO (SPDIF_EMPTY_MASK | SPDIF_AEMPTY_MASK | SPDIF_FULL_MASK | SPDIF_AFULL_MASK) + +////INT_REG +#define SPDIF_RSAMPLERATE 0 ///[SRATEW-1:0] +#define SPDIF_PREAMBLEDEL 8 ///[PDELAYW+7:8] first B delay +#define SPDIF_PARITYO (1<<21) ///0:clear parity error +#define SPDIF_TDATA_UNDERR (1<<22) ///tx data underrun error;0:clear +#define SPDIF_RDATA_OVRERR (1<<23) ///rx data overrun error; 0:clear +#define SPDIF_FIFO_EMPTY (1<<24) ///empty; 0:clear +#define SPDIF_FIOF_AEMPTY (1<<25) ///almost empty; 0:clear +#define SPDIF_FIFO_FULL (1<<26) ///FIFO full; 0:clear +#define SPDIF_FIFO_AFULL (1<<27) ///FIFO almost full; 0:clear +#define SPDIF_SYNCERR (1<<28) ///sync error; 0:clear +#define SPDIF_LOCK (1<<29) ///sync; 0:clear +#define SPDIF_BLOCK_BEGIN (1<<30) ///new start block rx data + +#define SPDIF_INT_REG_BIT (SPDIF_PARITYO | SPDIF_TDATA_UNDERR | SPDIF_RDATA_OVRERR | SPDIF_FIFO_EMPTY | \ + SPDIF_FIOF_AEMPTY | SPDIF_FIFO_FULL | SPDIF_FIFO_AFULL | SPDIF_SYNCERR | \ + SPDIF_LOCK | SPDIF_BLOCK_BEGIN) + +#define SPDIF_ERROR_INT_STATUS (SPDIF_PARITYO | SPDIF_TDATA_UNDERR | SPDIF_RDATA_OVRERR) +#define SPDIF_FIFO_INT_STATUS (SPDIF_FIFO_EMPTY | SPDIF_FIOF_AEMPTY | SPDIF_FIFO_FULL | SPDIF_FIFO_AFULL) + +#define SPDIF_INT_PARITY_ERROR (-1) +#define SPDIF_INT_TDATA_UNDERR (-2) +#define SPDIF_INT_RDATA_OVRERR (-3) +#define SPDIF_INT_FIFO_EMPTY 1 +#define SPDIF_INT_FIFO_AEMPTY 2 +#define SPDIF_INT_FIFO_FULL 3 +#define SPDIF_INT_FIFO_AFULL 4 +#define SPDIF_INT_SYNCERR (-4) +#define SPDIF_INT_LOCK 5 // reciever has become synchronized with input data stream +#define SPDIF_INT_BLOCK_BEGIN 6 // start a new block in recieve data, written into FIFO + +///FIFO_CTRL +#define SPDIF_AEMPTY_THRESHOLD 0 // [depth-1:0] +#define SPDIF_AFULL_THRESHOLD 16 // [depth+15:16] + +///STAT_REG +#define SPDIF_FIFO_LEVEL (1<<0) +#define SPDIF_PARITY_FLAG (1<<21) // 1:error; 0:repeated +#define SPDIF_UNDERR_FLAG (1<<22) // 1:error +#define SPDIF_OVRERR_FLAG (1<<23) // 1:error +#define SPDIF_EMPTY_FLAG (1<<24) // 1:fifo empty +#define SPDIF_AEMPTY_FLAG (1<<25) // 1:fifo almost empty +#define SPDIF_FULL_FLAG (1<<26) // 1:fifo full +#define SPDIF_AFULL_FLAG (1<<27) // 1:fifo almost full +#define SPDIF_SYNCERR_FLAG (1<<28) // 1:rx sync error +#define SPDIF_LOCK_FLAG (1<<29) // 1:RX sync +#define SPDIF_BEGIN_FLAG (1<<30) // 1:start a new block +#define SPDIF_RIGHT_LEFT (1<<31) // 1:left channel received and tx into FIFO; 0:right channel received and tx into FIFO + +#define SPDIF_STAT (SPDIF_PARITY_FLAG | SPDIF_UNDERR_FLAG | SPDIF_OVRERR_FLAG | SPDIF_EMPTY_FLAG | \ + SPDIF_AEMPTY_FLAG | SPDIF_FULL_FLAG | SPDIF_AFULL_FLAG | SPDIF_SYNCERR_FLAG | \ + SPDIF_LOCK_FLAG | SPDIF_BEGIN_FLAG | SPDIF_RIGHT_LEFT) +struct sf_spdif_dev { + void __iomem *spdif_base; + struct regmap *regmap; + struct device *dev; + u32 fifo_th; + int active; + + /* data related to DMA transfers b/w i2s and DMAC */ + struct snd_dmaengine_dai_dma_data play_dma_data; + struct snd_dmaengine_dai_dma_data capture_dma_data; + + bool use_pio; + struct snd_pcm_substream __rcu *tx_substream; + struct snd_pcm_substream __rcu *rx_substream; + + unsigned int (*tx_fn)(struct sf_spdif_dev *dev, + struct snd_pcm_runtime *runtime, unsigned int tx_ptr, + bool *period_elapsed, snd_pcm_format_t format); + unsigned int (*rx_fn)(struct sf_spdif_dev *dev, + struct snd_pcm_runtime *runtime, unsigned int rx_ptr, + bool *period_elapsed, snd_pcm_format_t format); + + snd_pcm_format_t format; + //unsigned int sample_bits; + unsigned int tx_ptr; + unsigned int rx_ptr; + + struct snd_dmaengine_dai_dma_data dma_data; +}; + +#if IS_ENABLED(CONFIG_SND_STARFIVE_SPDIF_PCM) +void sf_spdif_pcm_push_tx(struct sf_spdif_dev *dev); +void sf_spdif_pcm_pop_rx(struct sf_spdif_dev *dev); +int sf_spdif_pcm_register(struct platform_device *pdev); +#else +static inline void sf_spdif_pcm_push_tx(struct sf_spdif_dev *dev) { } +static inline void sf_spdif_pcm_pop_rx(struct sf_spdif_dev *dev) { } +static inline int sf_spdif_pcm_register(struct platform_device *pdev) +{ + return -EINVAL; +} +#endif + + +#endif /* __SND_SOC_STARFIVE_SPDIF_H */ From 797695f3e341762fcd9bf17f4db1b457102b125c Mon Sep 17 00:00:00 2001 From: "sw.multimedia" Date: Tue, 31 Aug 2021 16:48:57 +0800 Subject: [PATCH 38/52] drm/starfive: Add StarFive drm driver Add starfive DRM Display driver framework Signed-off-by: jack.zhu Signed-off-by: keith.zhao Signed-off-by: Geert Uytterhoeven Link: https://lore.kernel.org/r/a8ca722539672d6369d6e4092e1e08cb6b58c546.1645535955.git.geert@linux-m68k.org Signed-off-by: Emil Renner Berthing --- drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/starfive/Kconfig | 17 + drivers/gpu/drm/starfive/Makefile | 13 + drivers/gpu/drm/starfive/README.txt | 56 ++ drivers/gpu/drm/starfive/starfive_drm_crtc.c | 512 +++++++++++ drivers/gpu/drm/starfive/starfive_drm_crtc.h | 85 ++ drivers/gpu/drm/starfive/starfive_drm_drv.c | 265 ++++++ drivers/gpu/drm/starfive/starfive_drm_drv.h | 25 + .../gpu/drm/starfive/starfive_drm_encoder.c | 131 +++ .../gpu/drm/starfive/starfive_drm_encoder.h | 17 + drivers/gpu/drm/starfive/starfive_drm_gem.c | 347 ++++++++ drivers/gpu/drm/starfive/starfive_drm_gem.h | 39 + drivers/gpu/drm/starfive/starfive_drm_lcdc.c | 512 +++++++++++ drivers/gpu/drm/starfive/starfive_drm_lcdc.h | 160 ++++ drivers/gpu/drm/starfive/starfive_drm_plane.c | 227 +++++ drivers/gpu/drm/starfive/starfive_drm_plane.h | 12 + drivers/gpu/drm/starfive/starfive_drm_vpp.c | 802 ++++++++++++++++++ drivers/gpu/drm/starfive/starfive_drm_vpp.h | 202 +++++ 19 files changed, 3425 insertions(+) create mode 100644 drivers/gpu/drm/starfive/Kconfig create mode 100644 drivers/gpu/drm/starfive/Makefile create mode 100644 drivers/gpu/drm/starfive/README.txt create mode 100644 drivers/gpu/drm/starfive/starfive_drm_crtc.c create mode 100644 drivers/gpu/drm/starfive/starfive_drm_crtc.h create mode 100644 drivers/gpu/drm/starfive/starfive_drm_drv.c create mode 100644 drivers/gpu/drm/starfive/starfive_drm_drv.h create mode 100644 drivers/gpu/drm/starfive/starfive_drm_encoder.c create mode 100644 drivers/gpu/drm/starfive/starfive_drm_encoder.h create mode 100644 drivers/gpu/drm/starfive/starfive_drm_gem.c create mode 100644 drivers/gpu/drm/starfive/starfive_drm_gem.h create mode 100644 drivers/gpu/drm/starfive/starfive_drm_lcdc.c create mode 100644 drivers/gpu/drm/starfive/starfive_drm_lcdc.h create mode 100644 drivers/gpu/drm/starfive/starfive_drm_plane.c create mode 100644 drivers/gpu/drm/starfive/starfive_drm_plane.h create mode 100644 drivers/gpu/drm/starfive/starfive_drm_vpp.c create mode 100644 drivers/gpu/drm/starfive/starfive_drm_vpp.h diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index f65656df36199..3fbb7d8516365 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -316,6 +316,8 @@ source "drivers/gpu/drm/shmobile/Kconfig" source "drivers/gpu/drm/sun4i/Kconfig" +source "drivers/gpu/drm/starfive/Kconfig" + source "drivers/gpu/drm/omapdrm/Kconfig" source "drivers/gpu/drm/tilcdc/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 15fe3163f8220..baa463d06fda4 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -112,6 +112,7 @@ obj-y += rcar-du/ obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/ obj-y += omapdrm/ obj-$(CONFIG_DRM_SUN4I) += sun4i/ +obj-$(CONFIG_DRM_STARFIVE) += starfive/ obj-y += tilcdc/ obj-$(CONFIG_DRM_QXL) += qxl/ obj-$(CONFIG_DRM_VIRTIO_GPU) += virtio/ diff --git a/drivers/gpu/drm/starfive/Kconfig b/drivers/gpu/drm/starfive/Kconfig new file mode 100644 index 0000000000000..d096dca4aed0b --- /dev/null +++ b/drivers/gpu/drm/starfive/Kconfig @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2021 StarFive Technology Co., Ltd. + +config DRM_STARFIVE + tristate "DRM Support for StarFive SoCs" + depends on DRM + depends on SIFIVE_L2 + depends on SOC_STARFIVE || COMPILE_TEST + select DRM_GEM_CMA_HELPER + select DRM_KMS_HELPER + select DRM_MIPI_DSI + select DRM_PANEL + help + Choose this option if you have a StarFive SoCs. + The module will be called starfive-drm + This driver provides kernel mode setting and + buffer management to userspace. diff --git a/drivers/gpu/drm/starfive/Makefile b/drivers/gpu/drm/starfive/Makefile new file mode 100644 index 0000000000000..8ef9e5f469fd1 --- /dev/null +++ b/drivers/gpu/drm/starfive/Makefile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (C) 2021 StarFive Technology Co., Ltd. +# +starfive-drm-y := starfive_drm_drv.o \ + starfive_drm_gem.o \ + starfive_drm_crtc.o \ + starfive_drm_encoder.o \ + starfive_drm_plane.o \ + starfive_drm_lcdc.o \ + starfive_drm_vpp.o + +obj-$(CONFIG_DRM_STARFIVE) += starfive-drm.o diff --git a/drivers/gpu/drm/starfive/README.txt b/drivers/gpu/drm/starfive/README.txt new file mode 100644 index 0000000000000..dadec80c98bf5 --- /dev/null +++ b/drivers/gpu/drm/starfive/README.txt @@ -0,0 +1,56 @@ +Display Subsystem:(default FBdev) + +Steps switch to DRM: +1、Disable fbdev,close below config items: +CONFIG_FB_STARFIVE=y +CONFIG_FB_STARFIVE_HDMI_TDA998X=y +CONFIG_FB_STARFIVE_VIDEO=y + +2、open DRM hdmi pipeline,enable items: +CONFIG_DRM_I2C_NXP_TDA998X=y +CONFIG_DRM_I2C_NXP_TDA9950=y +CONFIG_DRM_STARFIVE=y +CONFIG_FRAMEBUFFER_CONSOLE=y + +Precautions:when use DRM hdmi pipeline,please make sure CONFIG_DRM_STARFIVE_MIPI_DSI is disable , + or will cause color abnormal. + +3、open DRM mipi pipeline + +enable items: + CONFIG_PHY_M31_DPHY_RX0=y + CONFIG_DRM_STARFIVE_MIPI_DSI=y + + +change jh7100.dtsi display-encoder as below: + + display-encoder { + compatible = "starfive,display-encoder"; + encoder-type = <6>; //2-TMDS, 3-LVDS, 6-DSI, 8-DPI + status = "okay"; + + ports { + port@0 { + endpoint { + remote-endpoint = <&dsi_out_port>; + }; + }; + + port@1 { + endpoint { + remote-endpoint = <&crtc_0_out>; + }; + }; + }; + }; + +install libdrm: +make buildroot_initramfs-menuconfig +choose: +BR2_PACKAGE_LIBDRM=y +BR2_PACKAGE_LIBDRM_RADEON=y +BR2_PACKAGE_LIBDRM_AMDGPU=y +BR2_PACKAGE_LIBDRM_NOUVEAU=y +BR2_PACKAGE_LIBDRM_ETNAVIV=y +BR2_PACKAGE_LIBDRM_INSTALL_TESTS=y + diff --git a/drivers/gpu/drm/starfive/starfive_drm_crtc.c b/drivers/gpu/drm/starfive/starfive_drm_crtc.c new file mode 100644 index 0000000000000..66923f8302681 --- /dev/null +++ b/drivers/gpu/drm/starfive/starfive_drm_crtc.c @@ -0,0 +1,512 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 StarFive Technology Co., Ltd. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "starfive_drm_drv.h" +#include "starfive_drm_crtc.h" +#include "starfive_drm_plane.h" +#include "starfive_drm_lcdc.h" +#include "starfive_drm_vpp.h" +//#include