diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig index 77de7a1370f7e0ed2ba378be461fdbc947e4402e..0436e79e3928ca738e8cb8e34e7e046b2f445b0e 100644 --- a/arch/loongarch/Kconfig +++ b/arch/loongarch/Kconfig @@ -426,6 +426,13 @@ config NODES_SHIFT default "6" depends on NUMA +config VA_BITS_40 + bool "40-bits" + default y + depends on 64BIT + help + Support a maximum at least 40 bits of application virtual memory. + config FORCE_MAX_ZONEORDER int "Maximum zone order" range 14 64 if PAGE_SIZE_64KB @@ -490,6 +497,9 @@ config ARCH_SPARSEMEM_ENABLE or have huge holes in the physical address space for other reasons. See for more. +config SYS_SUPPORTS_HUGETLBFS + def_bool y + config ARCH_ENABLE_THP_MIGRATION def_bool y depends on TRANSPARENT_HUGEPAGE @@ -510,6 +520,14 @@ config ARCH_MMAP_RND_BITS_MAX menu "Power management options" +config ARCH_HIBERNATION_POSSIBLE + def_bool y + +config ARCH_SUSPEND_POSSIBLE + def_bool y + +source "kernel/power/Kconfig" + source "drivers/acpi/Kconfig" endmenu diff --git a/arch/loongarch/Makefile b/arch/loongarch/Makefile index 3ab3625946a900dd68d80b536d7e02a843abbcc1..cb14e7f9640174132f30578f8e453a3a8600aca7 100644 --- a/arch/loongarch/Makefile +++ b/arch/loongarch/Makefile @@ -54,6 +54,7 @@ LDFLAGS_vmlinux += -G0 -static -n -nostdlib # upgrade the compiler or downgrade the assembler. ifdef CONFIG_AS_HAS_EXPLICIT_RELOCS cflags-y += -mexplicit-relocs +KBUILD_CFLAGS_KERNEL += -mdirect-extern-access else cflags-y += $(call cc-option,-mno-explicit-relocs) KBUILD_AFLAGS_KERNEL += -Wa,-mla-global-with-pcrel @@ -96,9 +97,13 @@ endif head-y := arch/loongarch/kernel/head.o core-y += arch/loongarch/ + libs-y += arch/loongarch/lib/ libs-$(CONFIG_EFI_STUB) += $(objtree)/drivers/firmware/efi/libstub/lib.a +# suspend and hibernation support +drivers-$(CONFIG_PM) += arch/loongarch/power/ + ifeq ($(KBUILD_EXTMOD),) prepare: vdso_prepare vdso_prepare: prepare0 diff --git a/arch/loongarch/configs/loongson3_defconfig b/arch/loongarch/configs/loongson3_defconfig index 3b4b63235fa88964987d906a0dbb817da2854f93..8e15593b052a62e9ecde247fe39321ad5dbd8239 100644 --- a/arch/loongarch/configs/loongson3_defconfig +++ b/arch/loongarch/configs/loongson3_defconfig @@ -37,8 +37,8 @@ CONFIG_PERF_EVENTS=y # CONFIG_COMPAT_BRK is not set CONFIG_CPU_HAS_LSX=y CONFIG_CPU_HAS_LASX=y -CONFIG_HOTPLUG_CPU=y CONFIG_NUMA=y +CONFIG_HIBERNATION=y CONFIG_ACPI_SPCR_TABLE=y CONFIG_ACPI_DOCK=y CONFIG_ACPI_IPMI=m @@ -733,6 +733,7 @@ CONFIG_FAT_DEFAULT_IOCHARSET="gb2312" CONFIG_PROC_KCORE=y CONFIG_TMPFS=y CONFIG_TMPFS_POSIX_ACL=y +CONFIG_HUGETLBFS=y CONFIG_CONFIGFS_FS=y CONFIG_HFS_FS=m CONFIG_HFSPLUS_FS=m diff --git a/arch/loongarch/include/asm/acpi.h b/arch/loongarch/include/asm/acpi.h index 825c2519b9d1f7c0eedabf118f20375b4493604e..31f140d4ef26234dcf89c8ff4d9889005e668507 100644 --- a/arch/loongarch/include/asm/acpi.h +++ b/arch/loongarch/include/asm/acpi.h @@ -7,7 +7,7 @@ #ifndef _ASM_LOONGARCH_ACPI_H #define _ASM_LOONGARCH_ACPI_H - +#include #ifdef CONFIG_ACPI extern int acpi_strict; extern int acpi_disabled; @@ -35,4 +35,10 @@ extern struct list_head acpi_wakeup_device_list; #define ACPI_TABLE_UPGRADE_MAX_PHYS ARCH_LOW_ADDRESS_LIMIT +static inline unsigned long acpi_get_wakeup_address(void) +{ + return (unsigned long)loongarch_wakeup_start; +} +extern int loongarch_acpi_suspend(void); +extern int (*acpi_suspend_lowlevel)(void); #endif /* _ASM_LOONGARCH_ACPI_H */ diff --git a/arch/loongarch/include/asm/atomic.h b/arch/loongarch/include/asm/atomic.h index 98a24f2217464f05eb8799f08bc1946c51ed27da..fed68810a4991f24f19def87b1dbab9e54fe81ea 100644 --- a/arch/loongarch/include/asm/atomic.h +++ b/arch/loongarch/include/asm/atomic.h @@ -162,8 +162,10 @@ static inline int arch_atomic_sub_if_positive(int i, atomic_t *v) " bltz %0, 2f \n" " sc.w %1, %2 \n" " beqz %1, 1b \n" + " b 3f " "2: \n" __WEAK_LLSC_MB + "3: " : "=&r" (result), "=&r" (temp), "+ZC" (v->counter) : "I" (-i)); } else { @@ -174,8 +176,10 @@ static inline int arch_atomic_sub_if_positive(int i, atomic_t *v) " bltz %0, 2f \n" " sc.w %1, %2 \n" " beqz %1, 1b \n" + " b 3f " "2: \n" __WEAK_LLSC_MB + "3: " : "=&r" (result), "=&r" (temp), "+ZC" (v->counter) : "r" (i)); } @@ -323,8 +327,10 @@ static inline long arch_atomic64_sub_if_positive(long i, atomic64_t *v) " bltz %0, 2f \n" " sc.d %1, %2 \n" " beqz %1, 1b \n" + " b 3f " "2: \n" __WEAK_LLSC_MB + "3: " : "=&r" (result), "=&r" (temp), "+ZC" (v->counter) : "I" (-i)); } else { @@ -335,8 +341,10 @@ static inline long arch_atomic64_sub_if_positive(long i, atomic64_t *v) " bltz %0, 2f \n" " sc.d %1, %2 \n" " beqz %1, 1b \n" + " b 3f " "2: \n" __WEAK_LLSC_MB + "3: " : "=&r" (result), "=&r" (temp), "+ZC" (v->counter) : "r" (i)); } diff --git a/arch/loongarch/include/asm/bootinfo.h b/arch/loongarch/include/asm/bootinfo.h index 8e5881bc5ad19c57b426f5e0b97a1bcc940a008b..068ea523260e1ec94651b08a1d8f18e73f166872 100644 --- a/arch/loongarch/include/asm/bootinfo.h +++ b/arch/loongarch/include/asm/bootinfo.h @@ -33,6 +33,10 @@ struct loongson_system_configuration { int cores_per_package; unsigned long cores_io_master; const char *cpuname; + u64 suspend_addr; + u64 gpe0_ena_reg; + u8 pcie_wake_enabled; + u8 is_soc_cpu; }; extern u64 efi_system_table; diff --git a/arch/loongarch/include/asm/cmpxchg.h b/arch/loongarch/include/asm/cmpxchg.h index ae19e33c77548aed5f94c59f0c727c1d535eb647..c2b3472d49ec73e89995f4033b9954ca7c578a8d 100644 --- a/arch/loongarch/include/asm/cmpxchg.h +++ b/arch/loongarch/include/asm/cmpxchg.h @@ -102,8 +102,10 @@ static inline unsigned long __xchg(volatile void *ptr, unsigned long x, " move $t0, %z4 \n" \ " " st " $t0, %1 \n" \ " beqz $t0, 1b \n" \ + " b 3f " \ "2: \n" \ __WEAK_LLSC_MB \ + "3: " \ : "=&r" (__ret), "=ZB"(*m) \ : "ZB"(*m), "Jr" (old), "Jr" (new) \ : "t0", "memory"); \ diff --git a/arch/loongarch/include/asm/efi.h b/arch/loongarch/include/asm/efi.h index 85176fc629b0d88790dd828eac3015c47252f363..9499abaa6baf300f2a79ac0d21fc075e9b4bc353 100644 --- a/arch/loongarch/include/asm/efi.h +++ b/arch/loongarch/include/asm/efi.h @@ -18,6 +18,23 @@ void __init efi_runtime_init(void); #define EFI_ALLOC_ALIGN SZ_64K #define EFI_RT_VIRTUAL_OFFSET CSR_DMW0_BASE +#define LINUX_EFI_INITRD_MEDIA_GUID EFI_GUID(0x5568e427, 0x68fc, 0x4f3d, 0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68) +#define LINUX_EFI_NEW_MEMMAP_GUID EFI_GUID(0x800f683f, 0xd08b, 0x423a, 0xa2, 0x93, 0x96, 0x5c, 0x3c, 0x6f, 0xe2, 0xb4) + +struct linux_efi_initrd { + unsigned long base; + unsigned long size; +}; + +struct efi_new_memmap { + unsigned long map_size; + unsigned long desc_size; + u32 desc_ver; + unsigned long map_key; + unsigned long buff_size; + efi_memory_desc_t map[]; +}; + static inline struct screen_info *alloc_screen_info(void) { return &screen_info; diff --git a/arch/loongarch/include/asm/futex.h b/arch/loongarch/include/asm/futex.h index feb6658c84ff8b04e18a684ee9cddca18bfb1c33..19811ebafbbd642b9ee9a1ffcef9bebc19bc9c23 100644 --- a/arch/loongarch/include/asm/futex.h +++ b/arch/loongarch/include/asm/futex.h @@ -84,8 +84,10 @@ futex_atomic_cmpxchg_inatomic(u32 *uval, u32 __user *uaddr, u32 oldval, u32 newv " move $t0, %z5 \n" "2: sc.w $t0, %2 \n" " beqz $t0, 1b \n" + " b 5f " "3: \n" __WEAK_LLSC_MB + "5: " " .section .fixup,\"ax\" \n" "4: li.d %0, %6 \n" " b 3b \n" diff --git a/arch/loongarch/include/asm/irqflags.h b/arch/loongarch/include/asm/irqflags.h index 319a8c616f1f5b60097c30a24f9f24682b9282cc..53eb33b2c258e5a2efb7e52ae71fc1213d0d3c4b 100644 --- a/arch/loongarch/include/asm/irqflags.h +++ b/arch/loongarch/include/asm/irqflags.h @@ -17,16 +17,15 @@ static inline void arch_local_irq_enable(void) __asm__ __volatile__( "csrxchg %[val], %[mask], %[reg]\n\t" : [val] "+r" (flags) - : [mask] "r" (CSR_CRMD_IE), [reg] "i" (LOONGARCH_CSR_CRMD) + : [mask] "r" (flags), [reg] "i" (LOONGARCH_CSR_CRMD) : "memory"); } static inline void arch_local_irq_disable(void) { - u32 flags = 0; __asm__ __volatile__( - "csrxchg %[val], %[mask], %[reg]\n\t" - : [val] "+r" (flags) + "csrxchg $zero, %[mask], %[reg]\n\t" + : : [mask] "r" (CSR_CRMD_IE), [reg] "i" (LOONGARCH_CSR_CRMD) : "memory"); } diff --git a/arch/loongarch/include/asm/loongson.h b/arch/loongarch/include/asm/loongson.h index 6e8f6972ceb614c9cd96b3814063006507d71064..e4108f674c4e8ebccfb07a2e880e0d94278b2d76 100644 --- a/arch/loongarch/include/asm/loongson.h +++ b/arch/loongarch/include/asm/loongson.h @@ -70,8 +70,6 @@ static inline void xconf_writeq(u64 val64, volatile void __iomem *addr) #define LS7A_CHIPCFG_REG_BASE (LS7A_PCH_REG_BASE + 0x00010000) /* MISC reg base */ #define LS7A_MISC_REG_BASE (LS7A_PCH_REG_BASE + 0x00080000) -/* ACPI regs */ -#define LS7A_ACPI_REG_BASE (LS7A_MISC_REG_BASE + 0x00050000) /* RTC regs */ #define LS7A_RTC_REG_BASE (LS7A_MISC_REG_BASE + 0x00050100) @@ -93,36 +91,6 @@ static inline void xconf_writeq(u64 val64, volatile void __iomem *addr) #define LS7A_LPC_INT_CLR (volatile void *)TO_UNCACHE(LS7A_PCH_REG_BASE + 0x200c) #define LS7A_LPC_INT_POL (volatile void *)TO_UNCACHE(LS7A_PCH_REG_BASE + 0x2010) -#define LS7A_PMCON_SOC_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x000) -#define LS7A_PMCON_RESUME_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x004) -#define LS7A_PMCON_RTC_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x008) -#define LS7A_PM1_EVT_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x00c) -#define LS7A_PM1_ENA_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x010) -#define LS7A_PM1_CNT_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x014) -#define LS7A_PM1_TMR_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x018) -#define LS7A_P_CNT_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x01c) -#define LS7A_GPE0_STS_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x028) -#define LS7A_GPE0_ENA_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x02c) -#define LS7A_RST_CNT_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x030) -#define LS7A_WD_SET_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x034) -#define LS7A_WD_TIMER_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x038) -#define LS7A_THSENS_CNT_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x04c) -#define LS7A_GEN_RTC_1_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x050) -#define LS7A_GEN_RTC_2_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x054) -#define LS7A_DPM_CFG_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x400) -#define LS7A_DPM_STS_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x404) -#define LS7A_DPM_CNT_REG (volatile void *)TO_UNCACHE(LS7A_ACPI_REG_BASE + 0x408) - -typedef enum { - ACPI_PCI_HOTPLUG_STATUS = 1 << 1, - ACPI_CPU_HOTPLUG_STATUS = 1 << 2, - ACPI_MEM_HOTPLUG_STATUS = 1 << 3, - ACPI_POWERBUTTON_STATUS = 1 << 8, - ACPI_RTC_WAKE_STATUS = 1 << 10, - ACPI_PCI_WAKE_STATUS = 1 << 14, - ACPI_ANY_WAKE_STATUS = 1 << 15, -} AcpiEventStatusBits; - #define HT1LO_OFFSET 0xe0000000000UL /* PCI Configuration Space Base */ diff --git a/arch/loongarch/include/asm/pgtable.h b/arch/loongarch/include/asm/pgtable.h index e5ed54fce4027945ead8372d607a76f27cb1eea9..b56a476bc5480baae6b01cc5976ec031604fbbba 100644 --- a/arch/loongarch/include/asm/pgtable.h +++ b/arch/loongarch/include/asm/pgtable.h @@ -42,7 +42,11 @@ #define PGDIR_SIZE (1UL << PGDIR_SHIFT) #define PGDIR_MASK (~(PGDIR_SIZE-1)) +#ifdef CONFIG_VA_BITS_40 +#define VA_BITS 40 +#else #define VA_BITS (PGDIR_SHIFT + (PAGE_SHIFT - 3)) +#endif #define PTRS_PER_PGD (PAGE_SIZE >> 3) #if CONFIG_PGTABLE_LEVELS > 3 @@ -294,9 +298,10 @@ static inline void set_pte(pte_t *ptep, pte_t pteval) " or %[tmp], %[tmp], %[global] \n" __SC "%[tmp], %[buddy] \n" " beqz %[tmp], 1b \n" - " nop \n" + " b 3f " "2: \n" __WEAK_LLSC_MB + "3: " : [buddy] "+m" (buddy->pte), [tmp] "=&r" (tmp) : [global] "r" (page_global)); #else /* !CONFIG_SMP */ diff --git a/arch/loongarch/include/asm/sparsemem.h b/arch/loongarch/include/asm/sparsemem.h index ee55cdf933c6b91e354d608d56e1f5a7fb4b6fe0..0f6e76fb31740f62a7a1b17698375bb792897f0c 100644 --- a/arch/loongarch/include/asm/sparsemem.h +++ b/arch/loongarch/include/asm/sparsemem.h @@ -8,7 +8,7 @@ * SECTION_SIZE_BITS 2^N: how big each section will be * MAX_PHYSMEM_BITS 2^N: how much memory we can have in that space */ -#define SECTION_SIZE_BITS 29 /* 2^29 = Largest Huge Page Size */ +#define SECTION_SIZE_BITS 28 #define MAX_PHYSMEM_BITS 48 #ifndef CONFIG_SPARSEMEM_VMEMMAP diff --git a/arch/loongarch/include/asm/stackframe.h b/arch/loongarch/include/asm/stackframe.h index 4ca953062b5be2c2a76f5176c7e4977284981e6b..733dc9e962410b2676ea602bb98ac1371e9db1fa 100644 --- a/arch/loongarch/include/asm/stackframe.h +++ b/arch/loongarch/include/asm/stackframe.h @@ -114,14 +114,6 @@ LONG_S zero, sp, PT_R0 csrrd t0, LOONGARCH_CSR_PRMD LONG_S t0, sp, PT_PRMD - csrrd t0, LOONGARCH_CSR_CRMD - LONG_S t0, sp, PT_CRMD - csrrd t0, LOONGARCH_CSR_EUEN - LONG_S t0, sp, PT_EUEN - csrrd t0, LOONGARCH_CSR_ECFG - LONG_S t0, sp, PT_ECFG - csrrd t0, LOONGARCH_CSR_ESTAT - PTR_S t0, sp, PT_ESTAT cfi_st ra, PT_R1, \docfi cfi_st a0, PT_R4, \docfi cfi_st a1, PT_R5, \docfi @@ -140,7 +132,6 @@ cfi_st fp, PT_R22, \docfi /* Set thread_info if we're coming from user mode */ - csrrd t0, LOONGARCH_CSR_PRMD andi t0, t0, 0x3 /* extract pplv bit */ beqz t0, 9f diff --git a/arch/loongarch/include/asm/suspend.h b/arch/loongarch/include/asm/suspend.h new file mode 100644 index 0000000000000000000000000000000000000000..82b73fe2bde5b07e3584ab21e0c99445ee836614 --- /dev/null +++ b/arch/loongarch/include/asm/suspend.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ASM_LOONGARCH_SUSPEND_H +#define _ASM_LOONGARCH_SUSPEND_H + +extern unsigned long eentry; +extern unsigned long tlbrentry; + +void arch_common_resume(void); +void arch_common_suspend(void); +extern void loongarch_suspend_enter(void); +extern void loongarch_wakeup_start(void); +void enable_pcie_wakeup(void); +extern void swsusp_arch_save(void); + +#endif /* _ASM_LOONGARCH_SUSPEND_H */ diff --git a/arch/loongarch/include/asm/thread_info.h b/arch/loongarch/include/asm/thread_info.h index c9030464cbb59272c2eb1e02f896382e9f46b731..43e74b37df60d823c03d58bebbc0ea58d915d1d2 100644 --- a/arch/loongarch/include/asm/thread_info.h +++ b/arch/loongarch/include/asm/thread_info.h @@ -112,6 +112,7 @@ static inline unsigned long current_stack_pointer(void) #define _TIF_LASX_CTX_LIVE (1< #include #include +#include #include #include @@ -27,11 +28,18 @@ #include #include "legacy_boot.h" +static unsigned long new_memmap __initdata = EFI_INVALID_TABLE_ADDR; +static unsigned long initrd __initdata = EFI_INVALID_TABLE_ADDR; + static unsigned long efi_nr_tables; static unsigned long efi_config_table; static efi_system_table_t *efi_systab; -static efi_config_table_type_t arch_tables[] __initdata = {{},}; +static efi_config_table_type_t arch_tables[] __initdata = { + {LINUX_EFI_NEW_MEMMAP_GUID, &new_memmap, "NEWMEM"}, + {LINUX_EFI_INITRD_MEDIA_GUID, &initrd, "INITRD"}, + {}, +}; static __initdata pgd_t *pgd_efi; static int __init efimap_populate_hugepages( @@ -185,6 +193,9 @@ static int __init set_virtual_map(void) (efi_memory_desc_t *)TO_PHYS((unsigned long)runtime_map)); efi_unmap_pgt(); + if (status != EFI_SUCCESS) + return -1; + return 0; } @@ -214,6 +225,44 @@ void __init efi_runtime_init(void) set_bit(EFI_RUNTIME_SERVICES, &efi.flags); } +static void __init get_initrd(void) +{ + if (IS_ENABLED(CONFIG_BLK_DEV_INITRD) && + initrd != EFI_INVALID_TABLE_ADDR && phys_initrd_size == 0) { + struct linux_efi_initrd *tbl; + + tbl = early_memremap(initrd, sizeof(*tbl)); + if (tbl) { + phys_initrd_start = tbl->base; + phys_initrd_size = tbl->size; + early_memunmap(tbl, sizeof(*tbl)); + } + } +} + +static void __init init_new_memmap(void) +{ + struct efi_new_memmap *tbl; + + if (new_memmap == EFI_INVALID_TABLE_ADDR) + return; + + tbl = early_memremap_ro(new_memmap, sizeof(*tbl)); + if (tbl) { + struct efi_memory_map_data data; + + data.phys_map = new_memmap + sizeof(*tbl); + data.size = tbl->map_size; + data.desc_size = tbl->desc_size; + data.desc_version = tbl->desc_ver; + + if (efi_memmap_init_early(&data) < 0) + panic("Unable to map EFI memory map.\n"); + + early_memunmap(tbl, sizeof(*tbl)); + } +} + void __init loongson_efi_init(void) { int size; @@ -240,6 +289,10 @@ void __init loongson_efi_init(void) efi_config_parse_tables(config_tables, efi_systab->nr_tables, arch_tables); early_memunmap(config_tables, efi_nr_tables * size); + get_initrd(); + + init_new_memmap(); + if (screen_info.orig_video_isVGA == VIDEO_TYPE_EFI) memblock_reserve(screen_info.lfb_base, screen_info.lfb_size); } diff --git a/arch/loongarch/kernel/entry.S b/arch/loongarch/kernel/entry.S index d53b631c90227cc4bbaf06192b90404afcc53493..8670e9d128ab25b91089a6c4645c25435b73d546 100644 --- a/arch/loongarch/kernel/entry.S +++ b/arch/loongarch/kernel/entry.S @@ -14,13 +14,14 @@ #include #include #include +#include .text .cfi_sections .debug_frame .align 5 SYM_FUNC_START(handle_syscall) csrrd t0, PERCPU_BASE_KS - la.abs t1, kernelsp + la.pcrel t1, kernelsp add.d t1, t1, t0 move t2, sp ld.d sp, t1, 0 @@ -28,19 +29,10 @@ SYM_FUNC_START(handle_syscall) addi.d sp, sp, -PT_SIZE cfi_st t2, PT_R3 cfi_rel_offset sp, PT_R3 - st.d zero, sp, PT_R0 csrrd t2, LOONGARCH_CSR_PRMD st.d t2, sp, PT_PRMD - csrrd t2, LOONGARCH_CSR_CRMD - st.d t2, sp, PT_CRMD - csrrd t2, LOONGARCH_CSR_EUEN - st.d t2, sp, PT_EUEN - csrrd t2, LOONGARCH_CSR_ECFG - st.d t2, sp, PT_ECFG - csrrd t2, LOONGARCH_CSR_ESTAT - st.d t2, sp, PT_ESTAT cfi_st ra, PT_R1 - cfi_st a0, PT_R4 + cfi_st a0, PT_ORIG_A0 cfi_st a1, PT_R5 cfi_st a2, PT_R6 cfi_st a3, PT_R7 @@ -49,6 +41,7 @@ SYM_FUNC_START(handle_syscall) cfi_st a6, PT_R10 cfi_st a7, PT_R11 csrrd ra, LOONGARCH_CSR_ERA + addi.d ra, ra, 4 st.d ra, sp, PT_ERA cfi_rel_offset ra, PT_ERA @@ -63,9 +56,17 @@ SYM_FUNC_START(handle_syscall) and tp, tp, sp move a0, sp + move a1, a7 bl do_syscall - RESTORE_ALL_AND_RET + addi.w t0, zero, __NR_rt_sigreturn + bne a0, t0, 1f + + RESTORE_STATIC + RESTORE_TEMP +1: + RESTORE_SOME + RESTORE_SP_AND_RET SYM_FUNC_END(handle_syscall) SYM_CODE_START(ret_from_fork) diff --git a/arch/loongarch/kernel/env.c b/arch/loongarch/kernel/env.c index 596e6635368ed8a0f2bda6d37a481cc93a4521a3..64490a2764d2e3e796ed1e72f96c5e8186263963 100644 --- a/arch/loongarch/kernel/env.c +++ b/arch/loongarch/kernel/env.c @@ -22,7 +22,8 @@ void __init init_environ(void) { int efi_boot = fw_arg0; struct efi_memory_map_data data; - void *fdt_ptr = early_memremap_ro(fw_arg1, SZ_64K); + char *cmdline; + void *fdt_ptr; if (efi_bp) return; @@ -32,6 +33,20 @@ void __init init_environ(void) else clear_bit(EFI_BOOT, &efi.flags); + if (fw_arg2 == 0) + goto parse_fdt; + + cmdline = early_memremap_ro(fw_arg1, COMMAND_LINE_SIZE); + strscpy(boot_command_line, cmdline, COMMAND_LINE_SIZE); + early_memunmap(cmdline, COMMAND_LINE_SIZE); + + efi_system_table = fw_arg2; + + return; + +parse_fdt: + fdt_ptr = early_memremap_ro(fw_arg1, SZ_64K); + early_init_dt_scan(fdt_ptr); early_init_fdt_reserve_self(); efi_system_table = efi_get_fdt_params(&data); diff --git a/arch/loongarch/kernel/head.S b/arch/loongarch/kernel/head.S index e2074cd4fff4dd4ba1d7cfcd5077318fbc3ec37c..58254fd1999d47a59f21ad78d7bf6a3c64b4e4ac 100644 --- a/arch/loongarch/kernel/head.S +++ b/arch/loongarch/kernel/head.S @@ -34,6 +34,7 @@ SYM_DATA(kernel_offset, .long kernel_offset - _text); __REF +.align 12 SYM_CODE_START(kernel_entry) # kernel entry point /* Config direct window and set PG */ diff --git a/arch/loongarch/kernel/legacy_boot.c b/arch/loongarch/kernel/legacy_boot.c index c670b5ea4ce5b01093082a5fe8567b9a383444cb..9c2aaf6317c6589527a0dbb52bc58643a826ac9a 100644 --- a/arch/loongarch/kernel/legacy_boot.c +++ b/arch/loongarch/kernel/legacy_boot.c @@ -239,7 +239,11 @@ int setup_legacy_IRQ(void) pr_err("CPU domain init error!\n"); return -1; } - cpu_domain = get_cpudomain(); + cpu_domain = irq_find_matching_fwnode(cpuintc_handle, DOMAIN_BUS_ANY); + if (!cpu_domain) { + pr_info("CPU domain error!\n"); + return -1; + } ret = liointc_acpi_init(cpu_domain, acpi_liointc); if (ret) { pr_err("Liointc domain init error!\n"); @@ -269,7 +273,11 @@ int setup_legacy_IRQ(void) pch_msi_parse_madt((union acpi_subtable_headers *)acpi_pchmsi[0], 0); } - pic_domain = get_pchpic_irq_domain(); + pic_domain = irq_find_matching_fwnode(pch_pic_handle[0], DOMAIN_BUS_ANY); + if (!pic_domain) { + pr_info("Pic domain error!\n"); + return -1; + } if (pic_domain) pch_lpc_acpi_init(pic_domain, acpi_pchlpc); @@ -517,7 +525,7 @@ unsigned long legacy_boot_init(unsigned long argc, unsigned long cmdptr, unsigne { int ret; - if (!bpi) + if (!bpi || (argc < 2)) return -1; efi_bp = (struct boot_params *)bpi; bpi_version = get_bpi_version(&efi_bp->signature); diff --git a/arch/loongarch/kernel/platform.c b/arch/loongarch/kernel/platform.c new file mode 100644 index 0000000000000000000000000000000000000000..da158221fae1a796e8f8fc004289e4310ea5b60f --- /dev/null +++ b/arch/loongarch/kernel/platform.c @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2009 Lemote Inc. + * Author: Wu Zhangjin, wuzhangjin@gmail.com + * Copyright (C) 2020 Loongson Technology Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +extern int loongson_acpi_init(void); + +static int __init loongson3_acpi_suspend_init(void) +{ +#ifdef CONFIG_ACPI + acpi_status status; + unsigned long long suspend_addr = 0; + + if (acpi_disabled || acpi_gbl_reduced_hardware) + return 0; + + acpi_write_bit_register(ACPI_BITREG_SCI_ENABLE, 1); + + status = acpi_evaluate_integer(NULL, "\\SADR", NULL, &suspend_addr); + if (ACPI_FAILURE(status) || !suspend_addr) { + pr_err("ACPI S3 is not support!\n"); + return -1; + } + loongson_sysconf.suspend_addr = (u64)phys_to_virt(TO_PHYS(suspend_addr)); +#endif + return 0; +} + +device_initcall(loongson3_acpi_suspend_init); diff --git a/arch/loongarch/kernel/setup.c b/arch/loongarch/kernel/setup.c index 957e720e12c8a67d915f88dcae3b75ea731c5be9..018cccc41ffcc2cf3161c5ebf2097aa10672d0e1 100644 --- a/arch/loongarch/kernel/setup.c +++ b/arch/loongarch/kernel/setup.c @@ -199,10 +199,24 @@ static int __init early_parse_mem(char *p) return 0; } early_param("mem", early_parse_mem); +static void __init set_pcie_wakeup(void) +{ + acpi_status status; + u32 value; + + if (loongson_sysconf.is_soc_cpu || acpi_gbl_reduced_hardware) + return; + + status = acpi_read_bit_register(ACPI_BITREG_PCIEXP_WAKE_DISABLE, &value); + if (ACPI_FAILURE(status)) + return; + + loongson_sysconf.pcie_wake_enabled = !value; +} + void __init platform_init(void) { - loongson_efi_init(); #ifdef CONFIG_ACPI_TABLE_UPGRADE acpi_table_upgrade(); #endif @@ -211,6 +225,7 @@ void __init platform_init(void) acpi_boot_table_init(); acpi_boot_init(); #endif + set_pcie_wakeup(); #ifdef CONFIG_NUMA init_numa_memory(); @@ -363,6 +378,7 @@ void __init setup_arch(char **cmdline_p) legacy_boot_init(fw_arg0, fw_arg1, fw_arg2); init_environ(); + loongson_efi_init(); memblock_init(); pagetable_init(); parse_early_param(); diff --git a/arch/loongarch/kernel/smp.c b/arch/loongarch/kernel/smp.c index 883d8d540f67186dd689800bab920752b3cb599f..7e6dde83e6acbf767978ed14be1fe9b713156e3b 100644 --- a/arch/loongarch/kernel/smp.c +++ b/arch/loongarch/kernel/smp.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include diff --git a/arch/loongarch/kernel/switch.S b/arch/loongarch/kernel/switch.S index 43ebbc3990f73afd5fc05a357ec5882a0f63d48c..d0363437d25f92a5a9058255d6e5cdaeee776928 100644 --- a/arch/loongarch/kernel/switch.S +++ b/arch/loongarch/kernel/switch.S @@ -16,9 +16,6 @@ */ .align 5 SYM_FUNC_START(__switch_to) - csrrd t1, LOONGARCH_CSR_PRMD - stptr.d t1, a0, THREAD_CSRPRMD - cpu_save_nonscratch a0 stptr.d ra, a0, THREAD_REG01 stptr.d a3, a0, THREAD_SCHED_RA @@ -30,8 +27,5 @@ SYM_FUNC_START(__switch_to) PTR_ADD t0, t0, tp set_saved_sp t0, t1, t2 - ldptr.d t1, a1, THREAD_CSRPRMD - csrwr t1, LOONGARCH_CSR_PRMD - jr ra SYM_FUNC_END(__switch_to) diff --git a/arch/loongarch/kernel/syscall.c b/arch/loongarch/kernel/syscall.c index 3fc4211db9895f71bc356b075fbb79f6779bbbe6..796fcdcaa6a7cfd183a949d5906695e1fa2c76dc 100644 --- a/arch/loongarch/kernel/syscall.c +++ b/arch/loongarch/kernel/syscall.c @@ -37,18 +37,16 @@ void *sys_call_table[__NR_syscalls] = { typedef long (*sys_call_fn)(unsigned long, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long); -void noinstr do_syscall(struct pt_regs *regs) +unsigned long noinstr do_syscall(struct pt_regs *regs, unsigned long nr) { - unsigned long nr; sys_call_fn syscall_fn; - nr = regs->regs[11]; /* Set for syscall restarting */ if (nr < NR_syscalls) regs->regs[0] = nr + 1; + else + regs->regs[0] = 0; - regs->csr_era += 4; - regs->orig_a0 = regs->regs[4]; regs->regs[4] = -ENOSYS; nr = syscall_enter_from_user_mode(regs, nr); @@ -60,4 +58,6 @@ void noinstr do_syscall(struct pt_regs *regs) } syscall_exit_to_user_mode(regs); + + return nr; } diff --git a/arch/loongarch/kernel/time.c b/arch/loongarch/kernel/time.c index 786735dcc8d678ecc6022ca8e8060a0bd4a4153a..8d331a5fae5a03801a05062204c50850f03334b6 100644 --- a/arch/loongarch/kernel/time.c +++ b/arch/loongarch/kernel/time.c @@ -115,7 +115,12 @@ static unsigned long __init get_loops_per_jiffy(void) return lpj; } -static long init_timeval; +static long init_timeval __nosavedata; + +void save_counter(void) +{ + init_timeval = drdtime(); +} void sync_counter(void) { diff --git a/arch/loongarch/mm/hugetlbpage.c b/arch/loongarch/mm/hugetlbpage.c index ba138117b1247e450431b54c4afe562a56acb312..97ed6f1d1c9ba291e7ebf76968718f12ee631cf6 100644 --- a/arch/loongarch/mm/hugetlbpage.c +++ b/arch/loongarch/mm/hugetlbpage.c @@ -13,8 +13,8 @@ #include #include -pte_t *huge_pte_alloc(struct mm_struct *mm, struct vm_area_struct *vma, - unsigned long addr, unsigned long sz) +pte_t *huge_pte_alloc(struct mm_struct *mm, + unsigned long addr, unsigned long sz) { pgd_t *pgd; p4d_t *p4d; diff --git a/arch/loongarch/power/Makefile b/arch/loongarch/power/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..a77c31a961783787bf4930bdc01c72fd3f5f8bde --- /dev/null +++ b/arch/loongarch/power/Makefile @@ -0,0 +1,4 @@ +OBJECT_FILES_NON_STANDARD_suspend_asm.o := y + +obj-$(CONFIG_SUSPEND) += suspend.o suspend_asm.o +obj-$(CONFIG_HIBERNATION) += cpu.o hibernate.o hibernate_asm.o diff --git a/arch/loongarch/power/cpu.c b/arch/loongarch/power/cpu.c new file mode 100644 index 0000000000000000000000000000000000000000..e3d8fc1099e2682ce7e2adefe661edd23d15639b --- /dev/null +++ b/arch/loongarch/power/cpu.c @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Suspend support specific for loongarch. + * + * Licensed under the GPLv2 + * Copyright (C) 2020 Loongson Technology Co., Ltd. + */ +#include +#include + +static u64 saved_crmd; +static u64 saved_prmd; +static u64 saved_euen; +static u64 saved_ecfg; +struct pt_regs saved_regs; + +void save_processor_state(void) +{ + saved_crmd = csr_read32(LOONGARCH_CSR_CRMD); + saved_prmd = csr_read32(LOONGARCH_CSR_PRMD); + saved_euen = csr_read32(LOONGARCH_CSR_EUEN); + saved_ecfg = csr_read32(LOONGARCH_CSR_ECFG); + + if (is_fpu_owner()) + save_fp(current); +} + +void restore_processor_state(void) +{ + csr_write32(saved_crmd, LOONGARCH_CSR_CRMD); + csr_write32(saved_prmd, LOONGARCH_CSR_PRMD); + csr_write32(saved_euen, LOONGARCH_CSR_EUEN); + csr_write32(saved_ecfg, LOONGARCH_CSR_ECFG); + + if (is_fpu_owner()) + restore_fp(current); +} + +int pfn_is_nosave(unsigned long pfn) +{ + unsigned long nosave_begin_pfn = PFN_DOWN(__pa(&__nosave_begin)); + unsigned long nosave_end_pfn = PFN_UP(__pa(&__nosave_end)); + + return (pfn >= nosave_begin_pfn) && (pfn < nosave_end_pfn); +} diff --git a/arch/loongarch/power/hibernate.c b/arch/loongarch/power/hibernate.c new file mode 100644 index 0000000000000000000000000000000000000000..f79bfdea707be4abe94c76a87b4ae85230bfb6e2 --- /dev/null +++ b/arch/loongarch/power/hibernate.c @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include + +extern int restore_image(void); + +int swsusp_arch_suspend(void) +{ + enable_pcie_wakeup(); + swsusp_arch_save(); + + return 0; +} + +int swsusp_arch_resume(void) +{ + /* Avoid TLB mismatch during and after kernel resume */ + local_flush_tlb_all(); + return restore_image(); +} diff --git a/arch/loongarch/power/hibernate_asm.S b/arch/loongarch/power/hibernate_asm.S new file mode 100644 index 0000000000000000000000000000000000000000..1874e473b293d734053a7817e578139e3ff1a244 --- /dev/null +++ b/arch/loongarch/power/hibernate_asm.S @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Hibernation support specific for loongarch - temporary page tables + * + * Licensed under the GPLv2 + * + * Copyright (C) 2009 Lemote Inc. + * Author: Hu Hongbing + * Wu Zhangjin + * Copyright (C) 2020 Loongson Technology Co., Ltd. + */ +#include +#include +#include +#include + +.text +SYM_FUNC_START(swsusp_arch_save) + la.abs t0, saved_regs + PTR_S ra, t0, PT_R1 + PTR_S sp, t0, PT_R3 + PTR_S fp, t0, PT_R22 + PTR_S tp, t0, PT_R2 + PTR_S s0, t0, PT_R23 + PTR_S s1, t0, PT_R24 + PTR_S s2, t0, PT_R25 + PTR_S s3, t0, PT_R26 + PTR_S s4, t0, PT_R27 + PTR_S s5, t0, PT_R28 + PTR_S s6, t0, PT_R29 + PTR_S s7, t0, PT_R30 + PTR_S s8, t0, PT_R31 + b swsusp_save +SYM_FUNC_END(swsusp_arch_save) + +SYM_FUNC_START(restore_image) + la.pcrel t0, restore_pblist + PTR_L t0, t0, 0 +0: + PTR_L t1, t0, PBE_ADDRESS /* source */ + PTR_L t2, t0, PBE_ORIG_ADDRESS /* destination */ + PTR_LI t3, _PAGE_SIZE + PTR_ADD t3, t3, t1 +1: + REG_L t8, t1, 0 + REG_S t8, t2, 0 + PTR_ADDI t1, t1, SZREG + PTR_ADDI t2, t2, SZREG + bne t1, t3, 1b + PTR_L t0, t0, PBE_NEXT + bnez t0, 0b + la.pcrel t0, saved_regs + PTR_L ra, t0, PT_R1 + PTR_L sp, t0, PT_R3 + PTR_L fp, t0, PT_R22 + PTR_L tp, t0, PT_R2 + PTR_L s0, t0, PT_R23 + PTR_L s1, t0, PT_R24 + PTR_L s2, t0, PT_R25 + PTR_L s3, t0, PT_R26 + PTR_L s4, t0, PT_R27 + PTR_L s5, t0, PT_R28 + PTR_L s6, t0, PT_R29 + PTR_L s7, t0, PT_R30 + PTR_L s8, t0, PT_R31 + PTR_LI a0, 0x0 + jirl zero, ra, 0 +SYM_FUNC_END(restore_image) diff --git a/arch/loongarch/power/suspend.c b/arch/loongarch/power/suspend.c new file mode 100644 index 0000000000000000000000000000000000000000..2895fe660450d8dcbcd3eb4243ea73556d2059cf --- /dev/null +++ b/arch/loongarch/power/suspend.c @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * loongson-specific suspend support + * + * Copyright (C) 2020 Loongson Technology Co., Ltd. + * Author: Huacai Chen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +u64 loongarch_suspend_addr; + +struct saved_registers { + u32 ecfg; + u64 pgd; + u64 kpgd; + u32 pwctl0; + u32 pwctl1; + u32 euen; +}; +static struct saved_registers saved_regs; + +void arch_common_suspend(void) +{ + save_counter(); + saved_regs.pgd = csr_read64(LOONGARCH_CSR_PGDL); + saved_regs.kpgd = csr_read64(LOONGARCH_CSR_PGDH); + saved_regs.pwctl0 = csr_read32(LOONGARCH_CSR_PWCTL0); + saved_regs.pwctl1 = csr_read32(LOONGARCH_CSR_PWCTL1); + saved_regs.ecfg = csr_read32(LOONGARCH_CSR_ECFG); + saved_regs.euen = csr_read32(LOONGARCH_CSR_EUEN); + + loongarch_suspend_addr = loongson_sysconf.suspend_addr; +} + +void arch_common_resume(void) +{ + sync_counter(); + local_flush_tlb_all(); + csr_write64(per_cpu_offset(0), PERCPU_BASE_KS); + + csr_write64(saved_regs.pgd, LOONGARCH_CSR_PGDL); + csr_write64(saved_regs.kpgd, LOONGARCH_CSR_PGDH); + csr_write32(saved_regs.pwctl0, LOONGARCH_CSR_PWCTL0); + csr_write32(saved_regs.pwctl1, LOONGARCH_CSR_PWCTL1); + csr_write32(saved_regs.ecfg, LOONGARCH_CSR_ECFG); + csr_write32(saved_regs.euen, LOONGARCH_CSR_EUEN); + csr_write64(eentry, LOONGARCH_CSR_EENTRY); + csr_write64(tlbrentry, LOONGARCH_CSR_TLBRENTRY); + csr_write64(eentry, LOONGARCH_CSR_MERRENTRY); +} + +static void enable_gpe_wakeup(void) +{ + struct list_head *node, *next; + u32 data = 0; + + data = readl((void *)loongson_sysconf.gpe0_ena_reg); + + list_for_each_safe(node, next, &acpi_wakeup_device_list) { + struct acpi_device *dev = + container_of(node, struct acpi_device, wakeup_list); + + if (!dev->wakeup.flags.valid + || ACPI_STATE_S3 > (u32) dev->wakeup.sleep_state + || !(device_may_wakeup(&dev->dev) + || dev->wakeup.prepare_count)) + continue; + + data |= (1 << dev->wakeup.gpe_number); + } + writel(data, (void *)loongson_sysconf.gpe0_ena_reg); +} + +void enable_pcie_wakeup(void) +{ + u16 value; + + if (loongson_sysconf.is_soc_cpu || acpi_gbl_reduced_hardware) + return; + + acpi_write_bit_register(ACPI_BITREG_PCIEXP_WAKE_STATUS, 1); + + if (loongson_sysconf.pcie_wake_enabled) + acpi_write_bit_register(ACPI_BITREG_PCIEXP_WAKE_DISABLE, 0); +} +EXPORT_SYMBOL_GPL(enable_pcie_wakeup); + +int loongarch_acpi_suspend(void) +{ + arch_common_suspend(); + enable_gpe_wakeup(); + enable_pcie_wakeup(); + /* processor specific suspend */ + loongarch_suspend_enter(); + arch_common_resume(); + + return 0; +} + +static int plat_pm_callback(struct notifier_block *nb, unsigned long action, void *ptr) +{ + int ret = 0; + + switch (action) { + case PM_POST_SUSPEND: + enable_gpe_wakeup(); + break; + default: + break; + } + + return notifier_from_errno(ret); +} + +static int __init plat_pm_post_init(void) +{ + if (loongson_sysconf.is_soc_cpu || acpi_gbl_reduced_hardware) + return 0; + + enable_gpe_wakeup(); + pm_notifier(plat_pm_callback, -INT_MAX); + return 0; +} + +late_initcall_sync(plat_pm_post_init); diff --git a/arch/loongarch/power/suspend_asm.S b/arch/loongarch/power/suspend_asm.S new file mode 100644 index 0000000000000000000000000000000000000000..ceac577c3794a73df7ececdc7faf12b63e05568a --- /dev/null +++ b/arch/loongarch/power/suspend_asm.S @@ -0,0 +1,124 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Sleep helper for Loongson-3 sleep mode. + * + * Copyright (C) 2020 Loongson Technology Co., Ltd. + * Author: Huacai Chen + */ + +#include +#include +#include +#include +#include + + .extern loongarch_nr_nodes + .extern loongarch_suspend_addr + .extern loongarch_pcache_ways + .extern loongarch_pcache_sets + .extern loongarch_pcache_linesz + .extern loongarch_scache_ways + .extern loongarch_scache_sets + .extern loongarch_scache_linesz + + .text + .align 5 + +/* preparatory stuff */ +.macro SETUP_SLEEP + addi.d sp, sp, -PT_SIZE + st.d $r1, sp, PT_R1 + st.d $r2, sp, PT_R2 + st.d $r3, sp, PT_R3 + st.d $r4, sp, PT_R4 + st.d $r5, sp, PT_R5 + st.d $r6, sp, PT_R6 + st.d $r7, sp, PT_R7 + st.d $r8, sp, PT_R8 + st.d $r9, sp, PT_R9 + st.d $r10, sp, PT_R10 + st.d $r11, sp, PT_R11 + st.d $r20, sp, PT_R20 + st.d $r21, sp, PT_R21 + st.d $r22, sp, PT_R22 + st.d $r23, sp, PT_R23 + st.d $r24, sp, PT_R24 + st.d $r25, sp, PT_R25 + st.d $r26, sp, PT_R26 + st.d $r27, sp, PT_R27 + st.d $r28, sp, PT_R28 + st.d $r29, sp, PT_R29 + st.d $r30, sp, PT_R30 + st.d $r31, sp, PT_R31 + +#ifdef CONFIG_ACPI + la.pcrel t0, acpi_saved_sp + st.d sp, t0, 0 +#endif +.endm +/* Sleep code for Loongson-3 */ +SYM_CODE_START(loongarch_suspend_enter) + SETUP_SLEEP + bl cpu_flush_caches + + /* Pass RA and SP to BIOS, for machines without CMOS RAM */ + addi.d a1, sp, 0 + la.pcrel a0, loongarch_wakeup_start + + la.pcrel t0, loongarch_suspend_addr + ld.d t0, t0, 0 /* Call BIOS's STR sleep routine */ + jr t0 + nop +SYM_CODE_END(loongarch_suspend_enter) + +.macro SETUP_WAKEUP + + nop + ld.d $r1, sp, PT_R1 + ld.d $r2, sp, PT_R2 + ld.d $r3, sp, PT_R3 + ld.d $r4, sp, PT_R4 + ld.d $r5, sp, PT_R5 + ld.d $r6, sp, PT_R6 + ld.d $r7, sp, PT_R7 + ld.d $r8, sp, PT_R8 + ld.d $r9, sp, PT_R9 + ld.d $r10, sp, PT_R10 + ld.d $r11, sp, PT_R11 + ld.d $r20, sp, PT_R20 + ld.d $r21, sp, PT_R21 + ld.d $r22, sp, PT_R22 + ld.d $r23, sp, PT_R23 + ld.d $r24, sp, PT_R24 + ld.d $r25, sp, PT_R25 + ld.d $r26, sp, PT_R26 + ld.d $r27, sp, PT_R27 + ld.d $r28, sp, PT_R28 + ld.d $r29, sp, PT_R29 + ld.d $r30, sp, PT_R30 + ld.d $r31, sp, PT_R31 +.endm + + /* This is where we return upon wakeup. + * Reload all of the registers and return. + */ + .align 12 +SYM_CODE_START(loongarch_wakeup_start) + li.d t0, CSR_DMW0_INIT # UC, PLV0 + csrwr t0, LOONGARCH_CSR_DMWIN0 + li.d t0, CSR_DMW1_INIT # CA, PLV0 + csrwr t0, LOONGARCH_CSR_DMWIN1 + + la.abs t0, 0f + jirl zero, t0, 0 +0: + la.pcrel t0, acpi_saved_sp + ld.d sp, t0, 0 + SETUP_WAKEUP + addi.d sp, sp, PT_SIZE + jr ra +SYM_CODE_END(loongarch_wakeup_start) diff --git a/drivers/acpi/pci_irq.c b/drivers/acpi/pci_irq.c index 14ee631cb7cf121b673829120bb221ee369d8b6f..0684ab9b8f623c7bfa06965bcb32b3cf0646b452 100644 --- a/drivers/acpi/pci_irq.c +++ b/drivers/acpi/pci_irq.c @@ -405,8 +405,14 @@ int acpi_pci_irq_enable(struct pci_dev *dev) * controller and must therefore be considered active high * as default. */ +#ifdef CONFIG_LOONGARCH + int polarity = acpi_irq_model == ACPI_IRQ_MODEL_GIC || + acpi_irq_model == ACPI_IRQ_MODEL_LPIC ? + ACPI_ACTIVE_HIGH : ACPI_ACTIVE_LOW; +#else int polarity = acpi_irq_model == ACPI_IRQ_MODEL_GIC ? ACPI_ACTIVE_HIGH : ACPI_ACTIVE_LOW; +#endif char *link = NULL; char link_desc[16]; int rc; diff --git a/drivers/firmware/efi/libstub/Makefile b/drivers/firmware/efi/libstub/Makefile index 6d4add16e9a309c6b01c75ebb8a744a432224695..fc5c4f0b14a53af3978e3510ffc9635e7e8f0f57 100644 --- a/drivers/firmware/efi/libstub/Makefile +++ b/drivers/firmware/efi/libstub/Makefile @@ -27,7 +27,7 @@ cflags-$(CONFIG_ARM) := $(subst $(CC_FLAGS_FTRACE),,$(KBUILD_CFLAGS)) \ cflags-$(CONFIG_RISCV) := $(subst $(CC_FLAGS_FTRACE),,$(KBUILD_CFLAGS)) \ -fpic cflags-$(CONFIG_LOONGARCH) := $(subst $(CC_FLAGS_FTRACE),,$(KBUILD_CFLAGS)) \ - -fpic + -fpie cflags-$(CONFIG_EFI_GENERIC_STUB) += -I$(srctree)/scripts/dtc/libfdt diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index e1993d17e22cd2ed39c91c05a4bd6af4d0f0576c..c561d00a3bf64ebdf24e348185a463176399de4f 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -359,7 +359,8 @@ config GPIO_LOGICVC config GPIO_LOONGSON bool "Loongson-2/3 GPIO support" - depends on CPU_LOONGSON2EF || CPU_LOONGSON64 + depends on CPU_LOONGSON2EF || CPU_LOONGSON64 || LOONGARCH + default m help driver for GPIO functionality on Loongson-2F/3A/3B processors. diff --git a/drivers/gpio/gpio-loongson.c b/drivers/gpio/gpio-loongson.c index a42145873cc92ab1628ed11b354cb648f5a09150..1262ca0ad12cc4ee031c8a172935ec38fb469552 100644 --- a/drivers/gpio/gpio-loongson.c +++ b/drivers/gpio/gpio-loongson.c @@ -1,13 +1,13 @@ -// SPDX-License-Identifier: GPL-2.0-or-later /* - * Loongson-2F/3A/3B GPIO Support + * Loongson-3A/3B/3C/7A GPIO Support * - * Copyright (c) 2008 Richard Liu, STMicroelectronics - * Copyright (c) 2008-2010 Arnaud Patard - * Copyright (c) 2013 Hongbing Hu - * Copyright (c) 2014 Huacai Chen + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. */ +#include #include #include #include @@ -17,119 +17,373 @@ #include #include #include -#include -#define STLS2F_N_GPIO 4 -#define STLS3A_N_GPIO 16 +/* ============== Data structrues =============== */ -#ifdef CONFIG_CPU_LOONGSON64 -#define LOONGSON_N_GPIO STLS3A_N_GPIO -#else -#define LOONGSON_N_GPIO STLS2F_N_GPIO -#endif +/* gpio data */ +struct platform_gpio_data { + u32 gpio_conf; + u32 gpio_out; + u32 gpio_in; + u32 in_start_bit; + u32 support_irq; + char *label; + int gpio_base; + int ngpio; +}; + +#define GPIO_IO_CONF(x) (x->base + x->conf_offset) +#define GPIO_OUT(x) (x->base + x->out_offset) +#define GPIO_IN(x) (x->base + x->in_offset) + +#define LS7A_GPIO_OEN_BYTE(x, gpio) (x->base + x->conf_offset + gpio) +#define LS7A_GPIO_OUT_BYTE(x, gpio) (x->base + x->out_offset + gpio) +#define LS7A_GPIO_IN_BYTE(x, gpio) (x->base + x->in_offset + gpio) + +struct loongson_gpio_chip { + struct gpio_chip chip; + spinlock_t lock; + void __iomem *base; + int conf_offset; + int out_offset; + int in_offset; + int in_start_bit; + u16 *gsi_idx_map; + u16 mapsize; + bool support_irq; +}; /* - * Offset into the register where we read lines, we write them from offset 0. - * This offset is the only thing that stand between us and using - * GPIO_GENERIC. + * GPIO primitives. */ -#define LOONGSON_GPIO_IN_OFFSET 16 +static int loongson_gpio_request(struct gpio_chip *chip, unsigned int pin) +{ + if (pin >= chip->ngpio) + return -EINVAL; + else + return 0; +} -static DEFINE_SPINLOCK(gpio_lock); +static inline void +__set_direction(struct loongson_gpio_chip *lgpio, unsigned int pin, int input) +{ + u64 temp; + u8 value; -static int loongson_gpio_get_value(struct gpio_chip *chip, unsigned gpio) + if (!strcmp(lgpio->chip.label, "loongson,loongson3-gpio") || + !strncmp(lgpio->chip.label, "LOON0007", 8)) { + temp = readq(GPIO_IO_CONF(lgpio)); + if (input) + temp |= 1ULL << pin; + else + temp &= ~(1ULL << pin); + writeq(temp, GPIO_IO_CONF(lgpio)); + return; + } + if (!strcmp(lgpio->chip.label, "loongson,ls7a-gpio") || + !strncmp(lgpio->chip.label, "LOON0002", 8)) { + if (input) + value = 1; + else + value = 0; + writeb(value, LS7A_GPIO_OEN_BYTE(lgpio, pin)); + return; + } +} + +static void __set_level(struct loongson_gpio_chip *lgpio, unsigned int pin, int high) { - u32 val; + u64 temp; + u8 value; - spin_lock(&gpio_lock); - val = LOONGSON_GPIODATA; - spin_unlock(&gpio_lock); + /* If GPIO controller is on 3A,then... */ + if (!strcmp(lgpio->chip.label, "loongson,loongson3-gpio") || + !strncmp(lgpio->chip.label, "LOON0007", 8)) { + temp = readq(GPIO_OUT(lgpio)); + if (high) + temp |= 1ULL << pin; + else + temp &= ~(1ULL << pin); + writeq(temp, GPIO_OUT(lgpio)); + return; + } - return !!(val & BIT(gpio + LOONGSON_GPIO_IN_OFFSET)); + if (!strcmp(lgpio->chip.label, "loongson,ls7a-gpio") || + !strncmp(lgpio->chip.label, "LOON0002", 8)) { + if (high) + value = 1; + else + value = 0; + writeb(value, LS7A_GPIO_OUT_BYTE(lgpio, pin)); + return; + } } -static void loongson_gpio_set_value(struct gpio_chip *chip, - unsigned gpio, int value) +static int loongson_gpio_direction_input(struct gpio_chip *chip, unsigned int pin) { - u32 val; + unsigned long flags; + struct loongson_gpio_chip *lgpio = + container_of(chip, struct loongson_gpio_chip, chip); - spin_lock(&gpio_lock); - val = LOONGSON_GPIODATA; - if (value) - val |= BIT(gpio); - else - val &= ~BIT(gpio); - LOONGSON_GPIODATA = val; - spin_unlock(&gpio_lock); + spin_lock_irqsave(&lgpio->lock, flags); + __set_direction(lgpio, pin, 1); + spin_unlock_irqrestore(&lgpio->lock, flags); + + return 0; } -static int loongson_gpio_direction_input(struct gpio_chip *chip, unsigned gpio) +static int loongson_gpio_direction_output(struct gpio_chip *chip, + unsigned int pin, int value) { - u32 temp; + struct loongson_gpio_chip *lgpio = + container_of(chip, struct loongson_gpio_chip, chip); + unsigned long flags; - spin_lock(&gpio_lock); - temp = LOONGSON_GPIOIE; - temp |= BIT(gpio); - LOONGSON_GPIOIE = temp; - spin_unlock(&gpio_lock); + spin_lock_irqsave(&lgpio->lock, flags); + __set_level(lgpio, pin, value); + __set_direction(lgpio, pin, 0); + spin_unlock_irqrestore(&lgpio->lock, flags); return 0; } -static int loongson_gpio_direction_output(struct gpio_chip *chip, - unsigned gpio, int level) +static int loongson_gpio_get(struct gpio_chip *chip, unsigned int pin) +{ + struct loongson_gpio_chip *lgpio = + container_of(chip, struct loongson_gpio_chip, chip); + u64 temp; + u8 value; + + /* GPIO controller in 3A is different for 7A */ + if (!strcmp(lgpio->chip.label, "loongson,loongson3-gpio") || + !strncmp(lgpio->chip.label, "LOON0007", 8)) { + temp = readq(GPIO_IN(lgpio)); + return ((temp & (1ULL << (pin + lgpio->in_start_bit))) != 0); + } + + if (!strcmp(lgpio->chip.label, "loongson,ls7a-gpio") || + !strncmp(lgpio->chip.label, "LOON0002", 8)) { + value = readb(LS7A_GPIO_IN_BYTE(lgpio, pin)); + return (value & 1); + } + + return -ENXIO; +} + +static void loongson_gpio_set(struct gpio_chip *chip, unsigned int pin, int value) +{ + struct loongson_gpio_chip *lgpio = + container_of(chip, struct loongson_gpio_chip, chip); + unsigned long flags; + + spin_lock_irqsave(&lgpio->lock, flags); + __set_level(lgpio, pin, value); + spin_unlock_irqrestore(&lgpio->lock, flags); +} + +static int loongson_gpio_to_irq(struct gpio_chip *chip, unsigned int offset) +{ + struct platform_device *pdev = + container_of(chip->parent, struct platform_device, dev); + struct loongson_gpio_chip *lgpio = + container_of(chip, struct loongson_gpio_chip, chip); + + if (offset >= chip->ngpio) + return -EINVAL; + + if ((lgpio->gsi_idx_map != NULL) && (offset < lgpio->mapsize)) + offset = lgpio->gsi_idx_map[offset]; + + return platform_get_irq(pdev, offset); +} + +static int loongson_gpio_init(struct device *dev, struct loongson_gpio_chip *lgpio, + struct device_node *np, void __iomem *base) { - u32 temp; + lgpio->chip.request = loongson_gpio_request; + lgpio->chip.direction_input = loongson_gpio_direction_input; + lgpio->chip.get = loongson_gpio_get; + lgpio->chip.direction_output = loongson_gpio_direction_output; + lgpio->chip.set = loongson_gpio_set; + lgpio->chip.can_sleep = 0; + lgpio->chip.of_node = np; + lgpio->chip.parent = dev; + spin_lock_init(&lgpio->lock); + lgpio->base = (void __iomem *)base; - loongson_gpio_set_value(chip, gpio, level); - spin_lock(&gpio_lock); - temp = LOONGSON_GPIOIE; - temp &= ~BIT(gpio); - LOONGSON_GPIOIE = temp; - spin_unlock(&gpio_lock); + if (!strcmp(lgpio->chip.label, "loongson,ls7a-gpio") || + !strncmp(lgpio->chip.label, "LOON0002", 8) || + !strcmp(lgpio->chip.label, "loongson,loongson3-gpio") || + !strncmp(lgpio->chip.label, "LOON0007", 8)) { + + lgpio->chip.to_irq = loongson_gpio_to_irq; + } + gpiochip_add(&lgpio->chip); return 0; } + +static void of_loongson_gpio_get_props(struct device_node *np, + struct loongson_gpio_chip *lgpio) +{ + const char *name; + + of_property_read_u32(np, "ngpios", (u32 *)&lgpio->chip.ngpio); + of_property_read_u32(np, "gpio_base", (u32 *)&lgpio->chip.base); + of_property_read_u32(np, "conf_offset", (u32 *)&lgpio->conf_offset); + of_property_read_u32(np, "out_offset", (u32 *)&lgpio->out_offset); + of_property_read_u32(np, "in_offset", (u32 *)&lgpio->in_offset); + of_property_read_string(np, "compatible", &name); + if (!strcmp(name, "loongson,loongson3-gpio")) { + of_property_read_u32(np, "in_start_bit", + (u32 *)&lgpio->in_start_bit); + if (of_property_read_bool(np, "support_irq")) + lgpio->support_irq = true; + } + lgpio->chip.label = kstrdup(name, GFP_KERNEL); +} + +static void acpi_loongson_gpio_get_props(struct platform_device *pdev, + struct loongson_gpio_chip *lgpio) +{ + + struct device *dev = &pdev->dev; + int rval; + + device_property_read_u32(dev, "ngpios", (u32 *)&lgpio->chip.ngpio); + device_property_read_u32(dev, "gpio_base", (u32 *)&lgpio->chip.base); + device_property_read_u32(dev, "conf_offset", (u32 *)&lgpio->conf_offset); + device_property_read_u32(dev, "out_offset", (u32 *)&lgpio->out_offset); + device_property_read_u32(dev, "in_offset", (u32 *)&lgpio->in_offset); + rval = device_property_read_u16_array(dev, "gsi_idx_map", NULL, 0); + if (rval > 0) { + lgpio->gsi_idx_map = + kmalloc_array(rval, sizeof(*lgpio->gsi_idx_map), + GFP_KERNEL); + if (unlikely(!lgpio->gsi_idx_map)) { + dev_err(dev, "Alloc gsi_idx_map fail!\n"); + } else { + lgpio->mapsize = rval; + device_property_read_u16_array(dev, "gsi_idx_map", + lgpio->gsi_idx_map, lgpio->mapsize); + } + } + if (!strcmp(pdev->name, "LOON0007")) { + device_property_read_u32(dev, "in_start_bit", + (u32 *)&lgpio->in_start_bit); + if (device_property_read_bool(dev, "support_irq")) + lgpio->support_irq = true; + } + lgpio->chip.label = kstrdup(pdev->name, GFP_KERNEL); +} + +static void platform_loongson_gpio_get_props(struct platform_device *pdev, + struct loongson_gpio_chip *lgpio) +{ + struct platform_gpio_data *gpio_data = + (struct platform_gpio_data *)pdev->dev.platform_data; + + lgpio->chip.ngpio = gpio_data->ngpio; + lgpio->chip.base = gpio_data->gpio_base; + lgpio->conf_offset = gpio_data->gpio_conf; + lgpio->out_offset = gpio_data->gpio_out; + lgpio->in_offset = gpio_data->gpio_in; + if (!strcmp(gpio_data->label, "loongson,loongson3-gpio")) { + lgpio->in_start_bit = gpio_data->in_start_bit; + lgpio->support_irq = gpio_data->support_irq; + } + lgpio->chip.label = kstrdup(gpio_data->label, GFP_KERNEL); +} + static int loongson_gpio_probe(struct platform_device *pdev) { - struct gpio_chip *gc; + struct resource *iores; + void __iomem *base; + struct loongson_gpio_chip *lgpio; + struct device_node *np = pdev->dev.of_node; struct device *dev = &pdev->dev; + int ret = 0; - gc = devm_kzalloc(dev, sizeof(*gc), GFP_KERNEL); - if (!gc) + lgpio = kzalloc(sizeof(struct loongson_gpio_chip), GFP_KERNEL); + if (!lgpio) return -ENOMEM; - gc->label = "loongson-gpio-chip"; - gc->base = 0; - gc->ngpio = LOONGSON_N_GPIO; - gc->get = loongson_gpio_get_value; - gc->set = loongson_gpio_set_value; - gc->direction_input = loongson_gpio_direction_input; - gc->direction_output = loongson_gpio_direction_output; + if (np) + of_loongson_gpio_get_props(np, lgpio); + else if (ACPI_COMPANION(&pdev->dev)) + acpi_loongson_gpio_get_props(pdev, lgpio); + else + platform_loongson_gpio_get_props(pdev, lgpio); + + iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!iores) { + ret = -ENODEV; + goto out; + } + if (!request_mem_region(iores->start, resource_size(iores), + pdev->name)) { + ret = -EBUSY; + goto out; + } + base = ioremap(iores->start, resource_size(iores)); + if (!base) { + ret = -ENOMEM; + goto out; + } + platform_set_drvdata(pdev, lgpio); + loongson_gpio_init(dev, lgpio, np, base); - return gpiochip_add_data(gc, NULL); + return 0; +out: + pr_err("%s: %s: missing mandatory property\n", __func__, np->name); + return ret; } -static struct platform_driver loongson_gpio_driver = { +static int loongson_gpio_remove(struct platform_device *pdev) +{ + struct loongson_gpio_chip *lgpio = platform_get_drvdata(pdev); + struct resource *mem; + + platform_set_drvdata(pdev, NULL); + gpiochip_remove(&lgpio->chip); + iounmap(lgpio->base); + kfree(lgpio->gsi_idx_map); + kfree(lgpio); + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(mem->start, resource_size(mem)); + return 0; +} + +static const struct acpi_device_id loongson_gpio_acpi_match[] = { + {"LOON0002"}, + {"LOON0007"}, + {} +}; +MODULE_DEVICE_TABLE(acpi, loongson_gpio_acpi_match); + +static struct platform_driver ls_gpio_driver = { .driver = { .name = "loongson-gpio", + .owner = THIS_MODULE, + .acpi_match_table = ACPI_PTR(loongson_gpio_acpi_match), }, .probe = loongson_gpio_probe, + .remove = loongson_gpio_remove, }; static int __init loongson_gpio_setup(void) { - struct platform_device *pdev; - int ret; - - ret = platform_driver_register(&loongson_gpio_driver); - if (ret) { - pr_err("error registering loongson GPIO driver\n"); - return ret; - } + return platform_driver_register(&ls_gpio_driver); +} +subsys_initcall(loongson_gpio_setup); - pdev = platform_device_register_simple("loongson-gpio", -1, NULL, 0); - return PTR_ERR_OR_ZERO(pdev); +static void __exit loongson_gpio_driver(void) +{ + platform_driver_unregister(&ls_gpio_driver); } -postcore_initcall(loongson_gpio_setup); +module_exit(loongson_gpio_driver); +MODULE_AUTHOR("Loongson Technology Corporation Limited"); +MODULE_DESCRIPTION("LOONGSON GPIO"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:loongson_gpio"); diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index b3775463d3ed35cea534caeff1f62ea6d5e069d4..49bc665e12cffa14eccdb23183180b2dbe2fc66a 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -855,6 +855,14 @@ config I2C_OWL Say Y here if you want to use the I2C bus controller on the Actions Semiconductor Owl SoC's. +config I2C_LOONGSON + tristate "Loongson I2C adapter" + depends on LOONGARCH + default m + help + If you say yes to this option, support will be included for the + I2C interface on the Loongson's LS7A Platform-Bridge. + config I2C_PASEMI tristate "PA Semi SMBus interface" depends on PPC_PASEMI && PCI diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 54d8e33b5726780c3c6730911aac1d344c5f801b..50caa14ef3fbc6c378a9f03db5c39ac45861455e 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -88,6 +88,7 @@ obj-$(CONFIG_I2C_NPCM7XX) += i2c-npcm7xx.o obj-$(CONFIG_I2C_OCORES) += i2c-ocores.o obj-$(CONFIG_I2C_OMAP) += i2c-omap.o obj-$(CONFIG_I2C_OWL) += i2c-owl.o +obj-$(CONFIG_I2C_LOONGSON) += i2c-loongson.o obj-$(CONFIG_I2C_PASEMI) += i2c-pasemi.o obj-$(CONFIG_I2C_PCA_PLATFORM) += i2c-pca-platform.o obj-$(CONFIG_I2C_PMCMSP) += i2c-pmcmsp.o diff --git a/drivers/i2c/busses/i2c-loongson.c b/drivers/i2c/busses/i2c-loongson.c new file mode 100644 index 0000000000000000000000000000000000000000..58d7f8ba17bb3dbdb879c78ce0967efc100dff4c --- /dev/null +++ b/drivers/i2c/busses/i2c-loongson.c @@ -0,0 +1,577 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Loongson-7A I2C master mode driver + * + * Copyright (C) 2013 Loongson Technology Corporation Limited + * Copyright (C) 2014-2017 Lemote, Inc. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "loongson_i2c" + +#define LOONGSON_I2C_PRER_LO_REG 0x0 +#define LOONGSON_I2C_PRER_HI_REG 0x1 +#define LOONGSON_I2C_CTR_REG 0x2 +#define LOONGSON_I2C_TXR_REG 0x3 +#define LOONGSON_I2C_RXR_REG 0x3 +#define LOONGSON_I2C_CR_REG 0x4 +#define LOONGSON_I2C_SR_REG 0x4 +#define LOONGSON_I2C_BLTOP_REG 0x5 +#define LOONGSON_I2C_SADDR_REG 0x7 + +#define CTR_EN 0x80 +#define CTR_IEN 0x40 +#define CTR_TXROK 0x90 +#define CTR_RXROK 0x88 + +#define CR_START 0x81 +#define CR_STOP 0x41 +#define CR_READ 0x21 +#define CR_WRITE 0x11 +#define CR_ACK 0x8 +#define CR_IACK 0x1 + +#define SR_NOACK 0x80 +#define SR_BUSY 0x40 +#define SR_AL 0x20 +#define SR_SLAVE_ADDRESSED 0x10 +#define SR_SLAVE_RW 0x8 +#define SR_TIP 0x2 +#define SR_IF 0x1 + +#define i2c_readb(addr) readb(dev->base + addr) +#define i2c_writeb(val, addr) writeb(val, dev->base + addr) + +#ifdef LOONGSON_I2C_DEBUG +#define i2c_debug(fmt, args...) dev_crit(fmt, ##args) +#else +#define i2c_debug(fmt, args...) +#endif + +static bool repeated_start = 1; +module_param(repeated_start, bool, 0600); +MODULE_PARM_DESC(repeated_start, "Compatible with devices that support repeated start"); + +enum loongson_i2c_slave_state { + LOONGSON_I2C_SLAVE_STOP, + LOONGSON_I2C_SLAVE_START, + LOONGSON_I2C_SLAVE_READ_REQUESTED, + LOONGSON_I2C_SLAVE_READ_PROCESSED, + LOONGSON_I2C_SLAVE_WRITE_REQUESTED, + LOONGSON_I2C_SLAVE_WRITE_RECEIVED, +}; + +struct loongson_i2c_dev { + spinlock_t lock; + unsigned int suspended:1; + struct device *dev; + void __iomem *base; + int irq; + struct completion cmd_complete; + struct resource *ioarea; + struct i2c_adapter adapter; +#if IS_ENABLED(CONFIG_I2C_SLAVE) + struct i2c_client *slave; + enum loongson_i2c_slave_state slave_state; +#endif /* CONFIG_I2C_SLAVE */ +}; + +static int i2c_stop(struct loongson_i2c_dev *dev) +{ + unsigned long time_left; + +again: + i2c_writeb(CR_STOP, LOONGSON_I2C_CR_REG); + time_left = wait_for_completion_timeout( + &dev->cmd_complete, + (&dev->adapter)->timeout); + if (!time_left) { + pr_info("Timeout abort message cmd\n"); + return -1; + } + + i2c_readb(LOONGSON_I2C_SR_REG); + while (i2c_readb(LOONGSON_I2C_SR_REG) & SR_BUSY) + goto again; + + return 0; +} + +static int i2c_start(struct loongson_i2c_dev *dev, + int dev_addr, int flags) +{ + unsigned long time_left; + int retry = 5; + unsigned char addr = (dev_addr & 0x7f) << 1; + + addr |= (flags & I2C_M_RD) ? 1 : 0; +start: + mdelay(1); + i2c_writeb(addr, LOONGSON_I2C_TXR_REG); + i2c_debug("%s : i2c device address: 0x%x\n", + __func__, __LINE__, addr); + i2c_writeb((CR_START | CR_WRITE), LOONGSON_I2C_CR_REG); + time_left = wait_for_completion_timeout( + &dev->cmd_complete, + (&dev->adapter)->timeout); + if (!time_left) { + pr_info("Timeout abort message cmd\n"); + return -1; + } + + if (i2c_readb(LOONGSON_I2C_SR_REG) & SR_NOACK) { + if (i2c_stop(dev) < 0) + return -1; + while (retry--) + goto start; + pr_debug("There is no i2c device ack\n"); + return 0; + } + return 1; +} + +#if IS_ENABLED(CONFIG_I2C_SLAVE) +static void __loongson_i2c_reg_slave(struct loongson_i2c_dev *dev, u16 slave_addr) +{ + /* Set slave addr. */ + i2c_writeb(slave_addr & 0x7f, LOONGSON_I2C_SADDR_REG); + + /* Turn on slave mode. */ + i2c_writeb(0xc0, LOONGSON_I2C_CTR_REG); +} + +static int loongson_i2c_reg_slave(struct i2c_client *client) +{ + struct loongson_i2c_dev *dev = i2c_get_adapdata(client->adapter); + unsigned long flags; + + if (dev->slave) + return -EINVAL; + + __loongson_i2c_reg_slave(dev, client->addr); + + dev->slave = client; + dev->slave_state = LOONGSON_I2C_SLAVE_STOP; + + return 0; +} + +static int loongson_i2c_unreg_slave(struct i2c_client *client) +{ + struct loongson_i2c_dev *dev = i2c_get_adapdata(client->adapter); + unsigned long flags; + + if (!dev->slave) + return -EINVAL; + + /* Turn off slave mode. */ + i2c_writeb(0xa0, LOONGSON_I2C_CTR_REG); + + dev->slave = NULL; + + return 0; +} +#endif /* CONFIG_I2C_SLAVE */ + +static void loongson_i2c_reginit(struct loongson_i2c_dev *dev) +{ +#if IS_ENABLED(CONFIG_I2C_SLAVE) + if (dev->slave) { + __loongson_i2c_reg_slave(dev, dev->slave->addr); + return; + } +#endif /* CONFIG_I2C_SLAVE */ + i2c_writeb(i2c_readb(LOONGSON_I2C_CR_REG) | 0x01, LOONGSON_I2C_CR_REG); + i2c_writeb(i2c_readb(LOONGSON_I2C_CTR_REG) & ~0x80, LOONGSON_I2C_CTR_REG); + i2c_writeb(0x2c, LOONGSON_I2C_PRER_LO_REG); + i2c_writeb(0x1, LOONGSON_I2C_PRER_HI_REG); + i2c_writeb(i2c_readb(LOONGSON_I2C_CTR_REG) | 0xe0, LOONGSON_I2C_CTR_REG); +} + +static int i2c_read(struct loongson_i2c_dev *dev, + unsigned char *buf, int count) +{ + int i; + unsigned long time_left; + + for (i = 0; i < count; i++) { + i2c_writeb((i == count - 1) ? + (CR_READ | CR_ACK) : CR_READ, + LOONGSON_I2C_CR_REG); + time_left = wait_for_completion_timeout( + &dev->cmd_complete, + (&dev->adapter)->timeout); + if (!time_left) { + pr_info("Timeout abort message cmd\n"); + return -1; + } + + buf[i] = i2c_readb(LOONGSON_I2C_RXR_REG); + i2c_debug("%s : read buf[%d] <= %02x\n", + __func__, __LINE__, i, buf[i]); + } + + return i; +} + +static int i2c_write(struct loongson_i2c_dev *dev, + unsigned char *buf, int count) +{ + int i; + unsigned long time_left; + + for (i = 0; i < count; i++) { + i2c_writeb(buf[i], LOONGSON_I2C_TXR_REG); + i2c_debug("%s : write buf[%d] => %02x\n", + __func__, __LINE__, i, buf[i]); + i2c_writeb(CR_WRITE, LOONGSON_I2C_CR_REG); + time_left = wait_for_completion_timeout( + &dev->cmd_complete, + (&dev->adapter)->timeout); + if (!time_left) { + pr_info("Timeout abort message cmd\n"); + return -1; + } + + if (i2c_readb(LOONGSON_I2C_SR_REG) & SR_NOACK) { + i2c_debug("%s : device no ack\n", + __func__, __LINE__); + if (i2c_stop(dev) < 0) + return -1; + return 0; + } + } + + return i; +} + +static int i2c_doxfer(struct loongson_i2c_dev *dev, + struct i2c_msg *msgs, int num) +{ + struct i2c_msg *m = msgs; + int i, err; + + for (i = 0; i < num; i++) { + reinit_completion(&dev->cmd_complete); + err = i2c_start(dev, m->addr, m->flags); + if (err <= 0) + return err; + + if (m->flags & I2C_M_RD) { + if (i2c_read(dev, m->buf, m->len) < 0) + return -1; + } else { + if (i2c_write(dev, m->buf, m->len) < 0) + return -1; + } + ++m; + if (!repeated_start && i2c_stop(dev) < 0) + return -1; + } + if (repeated_start && i2c_stop(dev) < 0) + return -1; + return i; +} + +static int i2c_xfer(struct i2c_adapter *adap, + struct i2c_msg *msgs, int num) +{ + int ret; + int retry; + struct loongson_i2c_dev *dev; + + dev = i2c_get_adapdata(adap); + for (retry = 0; retry < adap->retries; retry++) { + ret = i2c_doxfer(dev, msgs, num); + if (ret != -EAGAIN) + return ret; + + udelay(100); + } + + return -EREMOTEIO; +} + +static unsigned int i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm loongson_i2c_algo = { + .master_xfer = i2c_xfer, + .functionality = i2c_func, +#if IS_ENABLED(CONFIG_I2C_SLAVE) + .reg_slave = loongson_i2c_reg_slave, + .unreg_slave = loongson_i2c_unreg_slave, +#endif /* CONFIG_I2C_SLAVE */ +}; + +#if IS_ENABLED(CONFIG_I2C_SLAVE) +static bool loongson_i2c_slave_irq(struct loongson_i2c_dev *dev) +{ + u32 stat; + struct i2c_client *slave = dev->slave; + u8 value; + + stat = i2c_readb(LOONGSON_I2C_SR_REG); + + /* Slave was requested, restart state machine. */ + if (stat & SR_SLAVE_ADDRESSED) { + dev->slave_state = LOONGSON_I2C_SLAVE_START; + i2c_writeb(CTR_RXROK | CTR_IEN, LOONGSON_I2C_CTR_REG); + } + + /* Slave is not currently active, irq was for someone else. */ + if (dev->slave_state == LOONGSON_I2C_SLAVE_STOP) + return IRQ_NONE; + + /* Handle address frame. */ + if (dev->slave_state == LOONGSON_I2C_SLAVE_START) { + if (stat & SR_SLAVE_RW) //slave be read + dev->slave_state = + LOONGSON_I2C_SLAVE_READ_REQUESTED; + else + dev->slave_state = + LOONGSON_I2C_SLAVE_WRITE_REQUESTED; + } + + /* Slave was asked to stop. */ + if (stat & SR_NOACK) + dev->slave_state = LOONGSON_I2C_SLAVE_STOP; + + value = i2c_readb(LOONGSON_I2C_RXR_REG); + switch (dev->slave_state) { + case LOONGSON_I2C_SLAVE_READ_REQUESTED: + dev->slave_state = LOONGSON_I2C_SLAVE_READ_PROCESSED; + i2c_slave_event(slave, I2C_SLAVE_READ_REQUESTED, &value); + i2c_writeb(value, LOONGSON_I2C_TXR_REG); + i2c_writeb(CTR_TXROK | CTR_IEN, LOONGSON_I2C_CTR_REG); + break; + case LOONGSON_I2C_SLAVE_READ_PROCESSED: + i2c_slave_event(slave, I2C_SLAVE_READ_PROCESSED, &value); + i2c_writeb(value, LOONGSON_I2C_TXR_REG); + i2c_writeb(CTR_TXROK | CTR_IEN, LOONGSON_I2C_CTR_REG); + break; + case LOONGSON_I2C_SLAVE_WRITE_REQUESTED: + dev->slave_state = LOONGSON_I2C_SLAVE_WRITE_RECEIVED; + i2c_slave_event(slave, I2C_SLAVE_WRITE_REQUESTED, &value); + break; + case LOONGSON_I2C_SLAVE_WRITE_RECEIVED: + i2c_slave_event(slave, I2C_SLAVE_WRITE_RECEIVED, &value); + i2c_writeb(CTR_RXROK | CTR_IEN, LOONGSON_I2C_CTR_REG); + break; + case LOONGSON_I2C_SLAVE_STOP: + i2c_slave_event(slave, I2C_SLAVE_STOP, &value); + i2c_writeb(0, LOONGSON_I2C_TXR_REG); + i2c_writeb(CTR_TXROK | CTR_IEN, LOONGSON_I2C_CTR_REG); + break; + default: + dev_err(dev->dev, "unhandled slave_state: %d\n", + dev->slave_state); + break; + } + +out: + return IRQ_HANDLED; +} +#endif /* CONFIG_I2C_SLAVE */ + +/* + * Interrupt service routine. This gets called whenever an I2C interrupt + * occurs. + */ +static irqreturn_t i2c_loongson_isr(int this_irq, void *dev_id) +{ + unsigned char iflag; + struct loongson_i2c_dev *dev = dev_id; + + iflag = i2c_readb(LOONGSON_I2C_SR_REG); + + if (iflag & SR_IF) { + i2c_writeb(CR_IACK, LOONGSON_I2C_CR_REG); +#if IS_ENABLED(CONFIG_I2C_SLAVE) + if (dev->slave) + loongson_i2c_slave_irq(dev); +#endif + if (!(iflag & SR_TIP)) + complete(&dev->cmd_complete); + } else + return IRQ_NONE; + + return IRQ_HANDLED; +} + +static int loongson_i2c_probe(struct platform_device *pdev) +{ + struct loongson_i2c_dev *dev; + struct i2c_adapter *adap; + struct resource *mem, *ioarea; + int r, irq; + + /* NOTE: driver uses the static register mapping */ + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "no mem resource?\n"); + return -ENODEV; + } + irq = platform_get_irq(pdev, 0); + if (irq <= 0) { + dev_err(&pdev->dev, "no irq resource?\n"); + return -ENODEV; + } + + ioarea = request_mem_region(mem->start, resource_size(mem), + pdev->name); + if (!ioarea) { + dev_err(&pdev->dev, "I2C region already claimed\n"); + return -EBUSY; + } + + dev = kzalloc(sizeof(struct loongson_i2c_dev), GFP_KERNEL); + if (!dev) { + r = -ENOMEM; + goto err_release_region; + } + + init_completion(&dev->cmd_complete); + + dev->dev = &pdev->dev; + dev->irq = irq; + dev->base = ioremap(mem->start, resource_size(mem)); + if (!dev->base) { + r = -ENOMEM; + goto err_free_mem; + } + + platform_set_drvdata(pdev, dev); + + loongson_i2c_reginit(dev); + + r = request_irq(dev->irq, i2c_loongson_isr, IRQF_SHARED, DRIVER_NAME, dev); + if (r) + dev_err(&pdev->dev, "failure requesting irq %i\n", dev->irq); + + adap = &dev->adapter; + i2c_set_adapdata(adap, dev); + adap->nr = pdev->id; + strlcpy(adap->name, pdev->name, sizeof(adap->name)); + adap->owner = THIS_MODULE; + adap->class = I2C_CLASS_HWMON; + adap->retries = 5; + adap->algo = &loongson_i2c_algo; + adap->dev.parent = &pdev->dev; + adap->dev.of_node = pdev->dev.of_node; + ACPI_COMPANION_SET(&adap->dev, ACPI_COMPANION(&pdev->dev)); + adap->timeout = msecs_to_jiffies(100); + + /* i2c device drivers may be active on return from add_adapter() */ + r = i2c_add_adapter(adap); + if (r) { + dev_err(dev->dev, "failure adding adapter\n"); + goto err_iounmap; + } + + return 0; + +err_iounmap: + iounmap(dev->base); +err_free_mem: + platform_set_drvdata(pdev, NULL); + kfree(dev); +err_release_region: + release_mem_region(mem->start, resource_size(mem)); + + return r; +} + +static int loongson_i2c_remove(struct platform_device *pdev) +{ + struct loongson_i2c_dev *dev = platform_get_drvdata(pdev); + struct resource *mem; + + platform_set_drvdata(pdev, NULL); + i2c_del_adapter(&dev->adapter); + iounmap(dev->base); + kfree(dev); + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(mem->start, resource_size(mem)); + return 0; +} + +#ifdef CONFIG_PM +static int loongson_i2c_suspend_noirq(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct loongson_i2c_dev *i2c_dev = platform_get_drvdata(pdev); + + i2c_dev->suspended = 1; + + return 0; +} + +static int loongson_i2c_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct loongson_i2c_dev *i2c_dev = platform_get_drvdata(pdev); + + i2c_dev->suspended = 0; + loongson_i2c_reginit(i2c_dev); + + return 0; +} + +static const struct dev_pm_ops loongson_i2c_dev_pm_ops = { + .suspend_noirq = loongson_i2c_suspend_noirq, + .resume = loongson_i2c_resume, +}; + +#define LOONGSON_DEV_PM_OPS (&loongson_i2c_dev_pm_ops) +#else +#define LOONGSON_DEV_PM_OPS NULL +#endif + +static const struct acpi_device_id loongson_i2c_acpi_match[] = { + {"LOON0004"}, + {} +}; +MODULE_DEVICE_TABLE(acpi, loongson_i2c_acpi_match); + +static struct platform_driver loongson_i2c_driver = { + .probe = loongson_i2c_probe, + .remove = loongson_i2c_remove, + .driver = { + .name = "loongson-i2c", + .owner = THIS_MODULE, + .pm = LOONGSON_DEV_PM_OPS, + .acpi_match_table = ACPI_PTR(loongson_i2c_acpi_match), + }, +}; + +static int __init loongson_i2c_init_driver(void) +{ + return platform_driver_register(&loongson_i2c_driver); +} +subsys_initcall(loongson_i2c_init_driver); + +static void __exit loongson_i2c_exit_driver(void) +{ + platform_driver_unregister(&loongson_i2c_driver); +} +module_exit(loongson_i2c_exit_driver); + +MODULE_AUTHOR("Loongson Technology Corporation Limited"); +MODULE_DESCRIPTION("Loongson LOONGSON I2C bus adapter"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:loongson-i2c"); diff --git a/drivers/irqchip/irq-loongarch-cpu.c b/drivers/irqchip/irq-loongarch-cpu.c index d3a0bbe4a9f747e731967bfbb7b493b7a1a9e31b..adbd32b58f77b7a05c6fb2963f10f00fefabcf93 100644 --- a/drivers/irqchip/irq-loongarch-cpu.c +++ b/drivers/irqchip/irq-loongarch-cpu.c @@ -119,11 +119,6 @@ static int __init acpi_cascade_irqdomain_init(void) return 0; } -struct irq_domain *get_cpudomain(void) -{ - return irq_domain; -} - int __init cpuintc_acpi_init(union acpi_subtable_headers *header, const unsigned long end) { diff --git a/drivers/irqchip/irq-loongson-liointc.c b/drivers/irqchip/irq-loongson-liointc.c index 1ba889a165f2362ea1c2ca4dcb039fe1589d022e..9f0b2ec2a2d22dd26f57510016c5f3bac9350ce0 100644 --- a/drivers/irqchip/irq-loongson-liointc.c +++ b/drivers/irqchip/irq-loongson-liointc.c @@ -28,7 +28,7 @@ #define LIOINTC_INTC_CHIP_START 0x20 -#define LIOINTC_REG_INTC_STATUS (LIOINTC_INTC_CHIP_START + 0x20) +#define LIOINTC_REG_INTC_STATUS(cpuid) (LIOINTC_INTC_CHIP_START + 0x20 + (cpuid) * 8) #define LIOINTC_REG_INTC_EN_STATUS (LIOINTC_INTC_CHIP_START + 0x04) #define LIOINTC_REG_INTC_ENABLE (LIOINTC_INTC_CHIP_START + 0x08) #define LIOINTC_REG_INTC_DISABLE (LIOINTC_INTC_CHIP_START + 0x0c) @@ -196,7 +196,7 @@ static int liointc_init(phys_addr_t addr, unsigned long size, int revision, goto out_free_priv; for (i = 0; i < LIOINTC_NUM_CORES; i++) - priv->core_isr[i] = base + LIOINTC_REG_INTC_STATUS; + priv->core_isr[i] = base + LIOINTC_REG_INTC_STATUS(i); for (i = 0; i < LIOINTC_NUM_PARENT; i++) priv->handler[i].parent_int_map = parent_int_map[i]; diff --git a/drivers/irqchip/irq-loongson-pch-pic.c b/drivers/irqchip/irq-loongson-pch-pic.c index cd8b16293f39cbef200433275d597bd0f0f62ca3..e09078e1ac0161dce262983fde4136fa2c1b057e 100644 --- a/drivers/irqchip/irq-loongson-pch-pic.c +++ b/drivers/irqchip/irq-loongson-pch-pic.c @@ -52,11 +52,6 @@ static struct pch_pic *pch_pic_priv[MAX_IO_PICS]; struct fwnode_handle *pch_pic_handle[MAX_IO_PICS]; -struct irq_domain *get_pchpic_irq_domain(void) -{ - return pch_pic_priv[0]->pic_domain; -} - static void pch_pic_bitset(struct pch_pic *priv, int offset, int bit) { u32 reg; diff --git a/drivers/parisc/Makefile b/drivers/parisc/Makefile index 99fa6a89e0b96d0d4db6e4b70cbce6bd69cb0f39..91c912b093313b13c4b8f8894163aad518f24dbd 100644 --- a/drivers/parisc/Makefile +++ b/drivers/parisc/Makefile @@ -22,4 +22,3 @@ obj-$(CONFIG_SUPERIO) += superio.o obj-$(CONFIG_CHASSIS_LCD_LED) += led.o obj-$(CONFIG_PDC_STABLE) += pdc_stable.o obj-y += power.o - diff --git a/drivers/platform/Kconfig b/drivers/platform/Kconfig index 2abee71e8e5413d349276c09bad31630da9e841c..9d77c17de528c90f3ffe78cdbb99e283f9f87831 100644 --- a/drivers/platform/Kconfig +++ b/drivers/platform/Kconfig @@ -15,3 +15,4 @@ source "drivers/platform/mellanox/Kconfig" source "drivers/platform/mpam/Kconfig" source "drivers/platform/olpc/Kconfig" +source "drivers/platform/loongarch/Kconfig" diff --git a/drivers/platform/Makefile b/drivers/platform/Makefile index 8c67ef9a3bd74484ad89bb0563707fba6e005f7a..05d1c74982ddd964a8349d15bc46619220647f43 100644 --- a/drivers/platform/Makefile +++ b/drivers/platform/Makefile @@ -11,3 +11,4 @@ obj-$(CONFIG_OLPC_EC) += olpc/ obj-$(CONFIG_GOLDFISH) += goldfish/ obj-$(CONFIG_CHROME_PLATFORMS) += chrome/ obj-$(CONFIG_ARM_CPU_RESCTRL) += mpam/ +obj-$(CONFIG_LOONGARCH) += loongarch/ diff --git a/drivers/platform/loongarch/Kconfig b/drivers/platform/loongarch/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..8925fe9b24d40b3ab7b47df40e0c12ff77d7471b --- /dev/null +++ b/drivers/platform/loongarch/Kconfig @@ -0,0 +1,34 @@ +# +# LOONGARCH Platform Specific Drivers +# + +menuconfig LOONGARCH_PLATFORM_DEVICES + bool "LOONGARCH Platform Specific Device Drivers" + default y + help + Say Y here to get to see options for device drivers of various + LOONGARCH platforms, including vendor-specific netbook/laptop/desktop + extension and hardware monitor drivers. This option itself does + not add any kernel code. + + If you say N, all options in this submenu will be skipped and disabled. + +if LOONGARCH_PLATFORM_DEVICES + +config LOONGSON_GENERIC_LAPTOP + tristate "Generic Loongson-3 Laptop Driver" + depends on ACPI + depends on BACKLIGHT_CLASS_DEVICE + depends on INPUT + depends on MACH_LOONGSON64 + select ACPI_VIDEO + select INPUT_SPARSEKMAP + default y + help + This add an ACPI-based Loongson-3 family laptops generic driver, + used to support backlight and touchpad. Set it to N if you are not + sure. + + To compile this driver as a module, choose M here. + +endif # LOONGARCH_PLATFORM_DEVICES diff --git a/drivers/platform/loongarch/Makefile b/drivers/platform/loongarch/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..f912a53b66a7c44d23261f8d63510b0b8243c78d --- /dev/null +++ b/drivers/platform/loongarch/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_LOONGSON_GENERIC_LAPTOP) += loongson_generic_laptop.o diff --git a/drivers/platform/loongarch/loongson_generic_laptop.c b/drivers/platform/loongarch/loongson_generic_laptop.c new file mode 100644 index 0000000000000000000000000000000000000000..dbeda9b7ae193c6c6016f8ed0e8737f0fbffde50 --- /dev/null +++ b/drivers/platform/loongarch/loongson_generic_laptop.c @@ -0,0 +1,668 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * loongson_generic_laptop.c - Loongson processor + * based LAPTOP/ALL-IN-ONE driver + * + * lvjianmin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#define LSACPI_VERSION "1.0" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ACPI HIDs */ +#define ACPI_LOONGSON_HKEY_HID "LOON0000" +#define ACPI_EC_HID "PNP0C09" + +/**************************************************************************** + * Main driver + */ + +#define LSACPI_NAME "loongson-laptop" +#define LSACPI_DESC "Loongson Laptop/all-in-one ACPI Driver" +#define LSACPI_FILE LSACPI_NAME "_acpi" +#define LSACPI_DRVR_NAME LSACPI_FILE +#define LSACPI_ACPI_EVENT_PREFIX "loongson_generic" +/**************************************************************************** + * Driver-wide structs and misc. variables + */ + +struct generic_sub_driver { + u32 type; + char *name; + acpi_handle *handle; + struct acpi_device *device; + struct platform_driver *driver; + int (*init)(struct generic_sub_driver *sub_driver); + void (*notify)(struct generic_sub_driver *sub_driver, u32 event); + u8 acpi_notify_installed; +}; + +static u32 input_device_registered; + +static int hotkey_status_get(int *status); + +static int loongson_laptop_backlight_update(struct backlight_device *bd); +/**************************************************************************** + **************************************************************************** + * + * ACPI Helpers and device model + * + **************************************************************************** + ****************************************************************************/ + +/************************************************************************* + * ACPI basic handles + */ + +static int acpi_evalf(acpi_handle handle, + int *res, char *method, char *fmt, ...); +static acpi_handle hkey_handle; + +static void dispatch_acpi_notify(acpi_handle handle, u32 event, void *data) +{ + struct generic_sub_driver *sub_driver = data; + + if (!sub_driver || !sub_driver->notify) + return; + sub_driver->notify(sub_driver, event); +} + +static int __init setup_acpi_notify(struct generic_sub_driver *sub_driver) +{ + acpi_status status; + int rc; + + if (!*sub_driver->handle) + return 0; + + rc = acpi_bus_get_device(*sub_driver->handle, &sub_driver->device); + if (rc < 0) { + pr_err("acpi_bus_get_device(%s) failed: %d\n", + sub_driver->name, rc); + return -ENODEV; + } + + sub_driver->device->driver_data = sub_driver; + sprintf(acpi_device_class(sub_driver->device), "%s/%s", + LSACPI_ACPI_EVENT_PREFIX, sub_driver->name); + + status = acpi_install_notify_handler(*sub_driver->handle, + sub_driver->type, dispatch_acpi_notify, sub_driver); + if (ACPI_FAILURE(status)) { + if (status == AE_ALREADY_EXISTS) { + pr_notice("another device driver is already handling %s events\n", + sub_driver->name); + } else { + pr_err("acpi_install_notify_handler(%s) failed: %s\n", + sub_driver->name, acpi_format_exception(status)); + } + return -ENODEV; + } + sub_driver->acpi_notify_installed = 1; + return 0; +} + +static struct input_dev *generic_inputdev; + +#ifdef CONFIG_PM +static int loongson_hkey_suspend(struct device *dev) +{ + return 0; +} +static int loongson_hkey_resume(struct device *dev) +{ + int status = 0; + struct key_entry ke; + struct backlight_device *bd; + + bd = backlight_device_get_by_type(BACKLIGHT_PLATFORM); + if (bd) { + loongson_laptop_backlight_update(bd) ? + pr_warn("Loongson_backlight:resume brightness failed") : + pr_info("Loongson_backlight:resume brightness %d\n", bd->props.brightness); + } + /* + * Only if the firmware supports SW_LID event model, we can handle the + * event. This is for the consideration of development board without + * EC. + */ + if (test_bit(SW_LID, generic_inputdev->swbit)) { + if (hotkey_status_get(&status)) + return -EIO; + /* + * The input device sw element records the last lid status. + * When the system is awakened by other wake-up sources, + * the lid event will also be reported. The judgment of + * adding SW_LID bit which in sw element can avoid this + * case. + * + * input system will drop lid event when current lid event + * value and last lid status in the same data set,which + * data set inclue zero set and no zero set. so laptop + * driver doesn't report repeated events. + * + * Lid status is generally 0, but hardware exception is + * considered. So add lid status confirmation. + */ + if (test_bit(SW_LID, generic_inputdev->sw) && !(status & (1 << SW_LID))) { + ke.type = KE_SW; + ke.sw.value = (u8)status; + ke.sw.code = SW_LID; + sparse_keymap_report_entry(generic_inputdev, &ke, + 1, true); + } + } + + return 0; +} + +static const struct dev_pm_ops loongson_hkey_dev_pm_ops = { + .suspend_noirq = loongson_hkey_suspend, + .resume = loongson_hkey_resume, +}; + +#define LOONGSON_HKEY_DEV_PM_OPS (&loongson_hkey_dev_pm_ops) +#else +#define LOONGSON_HKEY_DEV_PM_OPS NULL +#endif +static int loongson_hkey_probe(struct platform_device *pdev) +{ + hkey_handle = ACPI_HANDLE(&pdev->dev); + + if (!hkey_handle) + return -ENODEV; + + return 0; +} + +static const struct acpi_device_id loongson_htk_device_ids[] = { + {ACPI_LOONGSON_HKEY_HID, 0}, + {"", 0}, +}; + +static struct platform_driver loongson_hkey_driver = { + .probe = loongson_hkey_probe, + .driver = { + .name = "loongson-hkey", + .owner = THIS_MODULE, + .pm = LOONGSON_HKEY_DEV_PM_OPS, + .acpi_match_table = ACPI_PTR(loongson_htk_device_ids), + }, +}; + +/* + * Loongson generic laptop firmware event model + * + */ + +#define GENERIC_HOTKEY_MAP_MAX 64 +#define METHOD_NAME__KMAP "KMAP" +static struct key_entry hotkey_keycode_map[GENERIC_HOTKEY_MAP_MAX]; +static int hkey_map(void) +{ + struct acpi_buffer buf; + union acpi_object *pack; + acpi_status status; + u32 index; + + buf.length = ACPI_ALLOCATE_BUFFER; + status = acpi_evaluate_object_typed(hkey_handle, + METHOD_NAME__KMAP, NULL, &buf, ACPI_TYPE_PACKAGE); + if (status != AE_OK) { + dev_err(": ACPI exception: %s\n", + acpi_format_exception(status)); + return -1; + } + pack = buf.pointer; + for (index = 0; index < pack->package.count; index++) { + union acpi_object *sub_pack = &pack->package.elements[index]; + union acpi_object *element = &sub_pack->package.elements[0]; + + hotkey_keycode_map[index].type = element->integer.value; + element = &sub_pack->package.elements[1]; + hotkey_keycode_map[index].code = element->integer.value; + element = &sub_pack->package.elements[2]; + hotkey_keycode_map[index].keycode = element->integer.value; + } + return 0; +} + +static int hotkey_backlight_set(bool enable) +{ + if (!acpi_evalf(hkey_handle, NULL, "VCBL", "vd", enable ? 1 : 0)) + return -EIO; + + return 0; +} +static int event_init(struct generic_sub_driver *sub_driver) +{ + int ret; + + ret = hkey_map(); + if (ret) { + dev_err("Fail to parse keymap from DSDT.\n"); + return ret; + } + + ret = sparse_keymap_setup(generic_inputdev, hotkey_keycode_map, NULL); + if (ret) { + dev_err("Fail to setup input device keymap\n"); + input_free_device(generic_inputdev); + + return ret; + } + + /* + * This hotkey driver handle backlight event when + * acpi_video_get_backlight_type() gets acpi_backlight_vendor + */ + if (acpi_video_get_backlight_type() != acpi_backlight_vendor) + hotkey_backlight_set(false); + else + hotkey_backlight_set(true); + + pr_info("ACPI:enabling firmware HKEY event interface...\n"); + return ret; + +} + +#define GENERIC_EVENT_TYPE_OFF 12 +#define GENERIC_EVENT_MASK 0xFFF +#define TPACPI_MAX_ACPI_ARGS 3 +static int acpi_evalf(acpi_handle handle, + int *res, char *method, char *fmt, ...) +{ + char *fmt0 = fmt; + struct acpi_object_list params; + union acpi_object in_objs[TPACPI_MAX_ACPI_ARGS]; + struct acpi_buffer result, *resultp; + union acpi_object out_obj; + acpi_status status; + va_list ap; + char res_type; + int success; + int quiet; + + if (!*fmt) { + pr_err("acpi_evalf() called with empty format\n"); + return 0; + } + + if (*fmt == 'q') { + quiet = 1; + fmt++; + } else + quiet = 0; + + res_type = *(fmt++); + + params.count = 0; + params.pointer = &in_objs[0]; + + va_start(ap, fmt); + while (*fmt) { + char c = *(fmt++); + + switch (c) { + case 'd': /* int */ + in_objs[params.count].integer.value = va_arg(ap, int); + in_objs[params.count++].type = ACPI_TYPE_INTEGER; + break; + /* add more types as needed */ + default: + pr_err("acpi_evalf() called with invalid format character '%c'\n", + c); + va_end(ap); + return 0; + } + } + va_end(ap); + + if (res_type != 'v') { + result.length = sizeof(out_obj); + result.pointer = &out_obj; + resultp = &result; + } else + resultp = NULL; + + status = acpi_evaluate_object(handle, method, ¶ms, resultp); + + switch (res_type) { + case 'd': /* int */ + success = (status == AE_OK && + out_obj.type == ACPI_TYPE_INTEGER); + if (success && res) + *res = out_obj.integer.value; + break; + case 'v': /* void */ + success = status == AE_OK; + break; + /* add more types as needed */ + default: + pr_err("acpi_evalf() called with invalid format character '%c'\n", + res_type); + return 0; + } + + if (!success && !quiet) + pr_err("acpi_evalf(%s, %s, ...) failed: %s\n", + method, fmt0, acpi_format_exception(status)); + + return success; +} + +int ec_get_brightness(void) +{ + int status = 0; + + if (!hkey_handle) + return -ENXIO; + + if (!acpi_evalf(hkey_handle, &status, "ECBG", "d")) + return -EIO; + + if (status < 0) + return status; + + return status; +} +EXPORT_SYMBOL(ec_get_brightness); + +int ec_set_brightness(int level) +{ + int ret = 0; + + if (!hkey_handle) + return -ENXIO; + + if (!acpi_evalf(hkey_handle, NULL, "ECBS", "vd", level)) + ret = -EIO; + + return ret; +} +EXPORT_SYMBOL(ec_set_brightness); + +int ec_bl_level(u8 level) +{ + int status = 0; + + if (!hkey_handle) + return -ENXIO; + + if (!acpi_evalf(hkey_handle, &status, "ECLL", "d")) + return -EIO; + if (status < 0 || level > status) + return status; + + if (!acpi_evalf(hkey_handle, &status, "ECSL", "d")) + return -EIO; + + if (status < 0 || level < status) + return status; + + return level; +} +EXPORT_SYMBOL(ec_bl_level); + +static int loongson_laptop_backlight_update(struct backlight_device *bd) +{ + int lvl = ec_bl_level(bd->props.brightness); + + if (lvl < 0) + return -EIO; + if (ec_set_brightness(lvl)) + return -EIO; + return 0; +} + +static int loongson_laptop_get_brightness(struct backlight_device *bd) +{ + u8 __maybe_unused level; + + level = ec_get_brightness(); + if (level >= 0) + return level; + return -EIO; +} + +static const struct backlight_ops ls_backlight_laptop_ops = { + .update_status = loongson_laptop_backlight_update, + .get_brightness = loongson_laptop_get_brightness, +}; + +static int ls_laptop_backlight_register(void) +{ + struct backlight_properties props; + int status = 0; + + memset(&props, 0, sizeof(props)); + props.type = BACKLIGHT_PLATFORM; + + if (!acpi_evalf(hkey_handle, &status, "ECLL", "d")) + return -EIO; + props.max_brightness = status; + props.brightness = 1; + + if (backlight_device_register("loongson_laptop", + NULL, NULL, + &ls_backlight_laptop_ops, &props)) + return 0; + + return -EIO; +} + +static int hotkey_status_get(int *status) +{ + if (!acpi_evalf(hkey_handle, status, "GSWS", "d")) + return -EIO; + + return 0; +} +int turn_off_lvds(void) +{ + int status; + union acpi_object arg0 = { ACPI_TYPE_INTEGER }; + struct acpi_object_list args = { 1, &arg0 }; + + arg0.integer.value = 0; + status = acpi_evaluate_object(NULL, "\\BLSW", &args, NULL); + if (ACPI_FAILURE(status)) { + pr_info("Loongson lvds error:0x%x\n", status); + return -ENODEV; + } + return 0; +} + +int turn_on_lvds(void) +{ + int status; + union acpi_object arg0 = { ACPI_TYPE_INTEGER }; + struct acpi_object_list args = { 1, &arg0 }; + + arg0.integer.value = 1; + status = acpi_evaluate_object(NULL, "\\BLSW", &args, NULL); + if (ACPI_FAILURE(status)) { + pr_info("Loongson lvds error:0x%x\n", status); + return -ENODEV; + } + return 0; +} +static void event_notify(struct generic_sub_driver *sub_driver, u32 event) +{ + struct key_entry *ke = NULL; + int scan_code = event & GENERIC_EVENT_MASK; + int type = (event >> GENERIC_EVENT_TYPE_OFF) & 0xF; + + ke = sparse_keymap_entry_from_scancode(generic_inputdev, scan_code); + if (ke) { + if (type == KE_SW) { + int status = 0; + + if (hotkey_status_get(&status)) + return; + ke->sw.value = !!(status & (1 << ke->sw.code)); + } + sparse_keymap_report_entry(generic_inputdev, ke, 1, true); + } +} + +/**************************************************************************** + **************************************************************************** + * + * Infrastructure + * + **************************************************************************** + ****************************************************************************/ +static void generic_exit(struct generic_sub_driver *sub_driver) +{ + + if (sub_driver->acpi_notify_installed) { + acpi_remove_notify_handler(*sub_driver->handle, + sub_driver->type, dispatch_acpi_notify); + sub_driver->acpi_notify_installed = 0; + } +} + +static int __init generic_subdriver_init(struct generic_sub_driver *sub_driver) +{ + int ret; + + if (!sub_driver || !sub_driver->driver) + return -EINVAL; + + ret = platform_driver_register(sub_driver->driver); + if (ret) + return -EINVAL; + + if (sub_driver->init) + sub_driver->init(sub_driver); + + if (sub_driver->notify) { + ret = setup_acpi_notify(sub_driver); + if (ret == -ENODEV) { + ret = 0; + goto err_out; + } + if (ret < 0) + goto err_out; + } + + return 0; + +err_out: + generic_exit(sub_driver); + return (ret < 0) ? ret : 0; +} + +/* Module init, exit, parameters */ +static struct generic_sub_driver generic_sub_drivers[] = { + { + .name = "hkey", + .init = event_init, + .notify = event_notify, + .handle = &hkey_handle, + .type = ACPI_DEVICE_NOTIFY, + .driver = &loongson_hkey_driver, + }, +}; + +static void generic_acpi_module_exit(void) +{ + if (generic_inputdev) { + if (input_device_registered) + input_unregister_device(generic_inputdev); + else + input_free_device(generic_inputdev); + } +} + +static int __init generic_acpi_module_init(void) +{ + int ret, i; + int status; + bool ec_found; + + if (acpi_disabled) + return -ENODEV; + + /* The EC device is required */ + ec_found = acpi_dev_found(ACPI_EC_HID); + if (!ec_found) + return -ENODEV; + + generic_inputdev = input_allocate_device(); + if (!generic_inputdev) { + pr_err("unable to allocate input device\n"); + generic_acpi_module_exit(); + return -ENOMEM; + } + + /* Prepare input device, but don't register */ + generic_inputdev->name = + "Loongson Generic Laptop/All-in-one Extra Buttons"; + generic_inputdev->phys = LSACPI_DRVR_NAME "/input0"; + generic_inputdev->id.bustype = BUS_HOST; + generic_inputdev->dev.parent = NULL; + + /* Init subdrivers */ + for (i = 0; i < ARRAY_SIZE(generic_sub_drivers); i++) { + ret = generic_subdriver_init(&generic_sub_drivers[i]); + if (ret < 0) { + generic_acpi_module_exit(); + return ret; + } + } + + ret = input_register_device(generic_inputdev); + if (ret < 0) { + pr_err("unable to register input device\n"); + generic_acpi_module_exit(); + return ret; + } + + input_device_registered = 1; + + if (acpi_evalf(hkey_handle, &status, "ECBG", "d")) { + pr_info("Loongson Laptop used, init brightness is 0x%x\n", status); + ret = ls_laptop_backlight_register(); + if (ret < 0) + pr_err("Loongson Laptop:laptop-backlight device register failed\n"); + } else + pr_info("Loongson Laptop :laptop-backlight device is not in use\n"); + return 0; +} + +MODULE_ALIAS("platform:ls-laptop"); +MODULE_AUTHOR("lvjianmin "); +MODULE_DESCRIPTION(LSACPI_DESC); +MODULE_VERSION(LSACPI_VERSION); +MODULE_LICENSE("GPL"); + +module_init(generic_acpi_module_init); +module_exit(generic_acpi_module_exit); diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 72385f424d4dfc898f723c5ac266a8ce135dde18..425d2fc3cf336c38f017a3430b7fa3ce7d025a44 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -1020,6 +1020,13 @@ config SPI_TLE62X0 sysfs interface, with each line presented as a kind of GPIO exposing both switch control and diagnostic feedback. +config SPI_LOONGSON + tristate "Loongson SPI Controller Support" + depends on LOONGARCH + default m + help + This is the driver for Loongson spi master controller. + # # Add new SPI protocol masters in alphabetical order above this line # diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 737f6b21a97ab95319ee8609e89c8b1276978f6b..a87e4688c6792979bbd41fdcca213e9db9e75d64 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -136,3 +136,4 @@ obj-$(CONFIG_SPI_AMD) += spi-amd.o # SPI slave protocol handlers obj-$(CONFIG_SPI_SLAVE_TIME) += spi-slave-time.o obj-$(CONFIG_SPI_SLAVE_SYSTEM_CONTROL) += spi-slave-system-control.o +obj-$(CONFIG_SPI_LOONGSON) += spi-loongson.o diff --git a/drivers/spi/spi-loongson.c b/drivers/spi/spi-loongson.c new file mode 100644 index 0000000000000000000000000000000000000000..1d951d5f9159d81ad922246b0c0dfe16c7a588d5 --- /dev/null +++ b/drivers/spi/spi-loongson.c @@ -0,0 +1,530 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Loongson SPI driver + * + * Copyright (C) 2013 Loongson Technology Corporation Limited + * + * 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 +#include +#include + +/*define spi register */ +#define SPCR 0x00 +#define SPSR 0x01 +#define FIFO 0x02 +#define SPER 0x03 +#define PARA 0x04 +#define SPCS 0x04 +#define SFCS 0x05 +#define TIMI 0x06 + +#define PARA_MEM_EN 0x01 +#define SPSR_SPIF 0x80 +#define SPSR_WCOL 0x40 +#define SPCR_SPE 0x40 + +struct loongson_spi { + struct work_struct work; + spinlock_t lock; + + struct list_head msg_queue; + struct spi_master *master; + void __iomem *base; + int cs_active; + unsigned int hz; + unsigned char spcr, sper, spsr; + unsigned char para, sfcs, timi; + struct workqueue_struct *wq; + unsigned int mode; +} *loongson_spi_dev; + +static inline int set_cs(struct loongson_spi *loongson_spi, struct spi_device *spi, int val); + +static void loongson_spi_write_reg(struct loongson_spi *spi, + unsigned char reg, unsigned char data) +{ + writeb(data, spi->base + reg); +} + +static char loongson_spi_read_reg(struct loongson_spi *spi, + unsigned char reg) +{ + return readb(spi->base + reg); +} + +static int loongson_spi_update_state(struct loongson_spi *loongson_spi, struct spi_device *spi, + struct spi_transfer *t) +{ + unsigned int hz; + unsigned int div, div_tmp; + unsigned int bit; + unsigned long clk; + unsigned char val; + const char rdiv[12] = {0, 1, 4, 2, 3, 5, 6, 7, 8, 9, 10, 11}; + + hz = t ? t->speed_hz : spi->max_speed_hz; + + if (!hz) + hz = spi->max_speed_hz; + + if ((hz && loongson_spi->hz != hz) || + ((spi->mode ^ loongson_spi->mode) & (SPI_CPOL | SPI_CPHA))) { + clk = 100000000; + div = DIV_ROUND_UP(clk, hz); + + if (div < 2) + div = 2; + + if (div > 4096) + div = 4096; + + bit = fls(div) - 1; + if ((1<dev, "clk = %ld hz = %d div_tmp = %d bit = %d\n", + clk, hz, div_tmp, bit); + + loongson_spi->hz = hz; + loongson_spi->spcr = div_tmp & 3; + loongson_spi->sper = (div_tmp >> 2) & 3; + + val = loongson_spi_read_reg(loongson_spi, SPCR); + val &= ~0xc; + if (spi->mode & SPI_CPOL) + val |= 8; + if (spi->mode & SPI_CPHA) + val |= 4; + loongson_spi_write_reg(loongson_spi, SPCR, (val & ~3) | loongson_spi->spcr); + val = loongson_spi_read_reg(loongson_spi, SPER); + loongson_spi_write_reg(loongson_spi, SPER, (val & ~3) | loongson_spi->sper); + loongson_spi->mode &= SPI_NO_CS; + loongson_spi->mode |= spi->mode; + } + + return 0; +} + + + +static int loongson_spi_setup(struct spi_device *spi) +{ + struct loongson_spi *loongson_spi; + + loongson_spi = spi_master_get_devdata(spi->master); + if (spi->bits_per_word % 8) + return -EINVAL; + + if (spi->chip_select >= spi->master->num_chipselect) + return -EINVAL; + + loongson_spi_update_state(loongson_spi, spi, NULL); + + set_cs(loongson_spi, spi, 1); + + return 0; +} + +static int loongson_spi_write_read_8bit(struct spi_device *spi, + const u8 **tx_buf, u8 **rx_buf, unsigned int num) +{ + struct loongson_spi *loongson_spi; + + loongson_spi = spi_master_get_devdata(spi->master); + +#define __WAIT(cond) do {} while ((cond)) + if (tx_buf && *tx_buf) { + loongson_spi_write_reg(loongson_spi, FIFO, *((*tx_buf)++)); + __WAIT((loongson_spi_read_reg(loongson_spi, SPSR) & 0x1) == 1); + } else { + loongson_spi_write_reg(loongson_spi, FIFO, 0); + __WAIT((loongson_spi_read_reg(loongson_spi, SPSR) & 0x1) == 1); + } +#undef __WAIT + + if (rx_buf && *rx_buf) + *(*rx_buf)++ = loongson_spi_read_reg(loongson_spi, FIFO); + else + loongson_spi_read_reg(loongson_spi, FIFO); + + return 1; +} + + +static unsigned int loongson_spi_write_read(struct spi_device *spi, struct spi_transfer *xfer) +{ + struct loongson_spi *loongson_spi; + unsigned int count; + const u8 *tx = xfer->tx_buf; + u8 *rx = xfer->rx_buf; + + loongson_spi = spi_master_get_devdata(spi->master); + count = xfer->len; + + do { + if (loongson_spi_write_read_8bit(spi, &tx, &rx, count) < 0) + goto out; + count--; + } while (count); + +out: + return xfer->len - count; + +} + +static inline int set_cs(struct loongson_spi *loongson_spi, struct spi_device *spi, int val) +{ + if (spi->mode & SPI_CS_HIGH) + val = !val; + if (loongson_spi->mode & SPI_NO_CS) { + loongson_spi_write_reg(loongson_spi, SPCS, val); + } else { + int cs = loongson_spi_read_reg(loongson_spi, SFCS) & ~(0x11 << spi->chip_select); + + loongson_spi_write_reg(loongson_spi, SFCS, + (val ? (0x11 << spi->chip_select) : (0x1 << spi->chip_select)) | cs); + } + return 0; +} + +static void loongson_spi_work(struct work_struct *work) +{ + struct loongson_spi *loongson_spi = + container_of(work, struct loongson_spi, work); + int param; + + spin_lock(&loongson_spi->lock); + param = loongson_spi_read_reg(loongson_spi, PARA); + loongson_spi_write_reg(loongson_spi, PARA, param&~1); + while (!list_empty(&loongson_spi->msg_queue)) { + + struct spi_message *m; + struct spi_device *spi; + struct spi_transfer *t = NULL; + + m = container_of(loongson_spi->msg_queue.next, struct spi_message, queue); + + list_del_init(&m->queue); + spin_unlock(&loongson_spi->lock); + + spi = m->spi; + + /*in here set cs*/ + set_cs(loongson_spi, spi, 0); + + list_for_each_entry(t, &m->transfers, transfer_list) { + + /*setup spi clock*/ + loongson_spi_update_state(loongson_spi, spi, t); + + if (t->len) + m->actual_length += + loongson_spi_write_read(spi, t); + } + + set_cs(loongson_spi, spi, 1); + m->complete(m->context); + + + spin_lock(&loongson_spi->lock); + } + + loongson_spi_write_reg(loongson_spi, PARA, param); + spin_unlock(&loongson_spi->lock); +} + + + +static int loongson_spi_transfer(struct spi_device *spi, struct spi_message *m) +{ + struct loongson_spi *loongson_spi; + struct spi_transfer *t = NULL; + + m->actual_length = 0; + m->status = 0; + if (list_empty(&m->transfers) || !m->complete) + return -EINVAL; + + loongson_spi = spi_master_get_devdata(spi->master); + + list_for_each_entry(t, &m->transfers, transfer_list) { + + if (t->tx_buf == NULL && t->rx_buf == NULL && t->len) { + dev_err(&spi->dev, + "message rejected : invalid transfer data buffers\n"); + goto msg_rejected; + } + /*other things not check*/ + } + + spin_lock(&loongson_spi->lock); + list_add_tail(&m->queue, &loongson_spi->msg_queue); + queue_work(loongson_spi->wq, &loongson_spi->work); + spin_unlock(&loongson_spi->lock); + + return 0; +msg_rejected: + + m->status = -EINVAL; + if (m->complete) + m->complete(m->context); + return -EINVAL; +} + +static void loongson_spi_reginit(void) +{ + unsigned char val; + + val = loongson_spi_read_reg(loongson_spi_dev, SPCR); + val &= ~SPCR_SPE; + loongson_spi_write_reg(loongson_spi_dev, SPCR, val); + + loongson_spi_write_reg(loongson_spi_dev, SPSR, (SPSR_SPIF | SPSR_WCOL)); + + val = loongson_spi_read_reg(loongson_spi_dev, SPCR); + val |= SPCR_SPE; + loongson_spi_write_reg(loongson_spi_dev, SPCR, val); +} + +static int loongson_spi_probe(struct platform_device *pdev) +{ + struct spi_master *master; + struct loongson_spi *spi; + struct resource *res; + int ret; + + master = spi_alloc_master(&pdev->dev, sizeof(struct loongson_spi)); + if (master == NULL) { + dev_dbg(&pdev->dev, "master allocation failed\n"); + return -ENOMEM; + } + + if (pdev->id != -1) + master->bus_num = pdev->id; + + master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; + master->setup = loongson_spi_setup; + master->transfer = loongson_spi_transfer; + master->num_chipselect = 4; +#ifdef CONFIG_OF + master->dev.of_node = of_node_get(pdev->dev.of_node); +#endif + dev_set_drvdata(&pdev->dev, master); + + spi = spi_master_get_devdata(master); + + loongson_spi_dev = spi; + + spi->wq = create_singlethread_workqueue(pdev->name); + + spi->master = master; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "Cannot get IORESOURCE_MEM\n"); + ret = -ENOENT; + goto free_master; + } + + spi->base = ioremap(res->start, (res->end - res->start)+1); + if (spi->base == NULL) { + dev_err(&pdev->dev, "Cannot map IO\n"); + ret = -ENXIO; + goto unmap_io; + } + + loongson_spi_reginit(); + + spi->mode = 0; + if (of_get_property(pdev->dev.of_node, "spi-nocs", NULL)) + spi->mode |= SPI_NO_CS; + + INIT_WORK(&spi->work, loongson_spi_work); + + spin_lock_init(&spi->lock); + INIT_LIST_HEAD(&spi->msg_queue); + + ret = spi_register_master(master); + if (ret < 0) + goto unmap_io; + + return ret; + +unmap_io: + iounmap(spi->base); +free_master: + kfree(master); + spi_master_put(master); + return ret; + +} + +#ifdef CONFIG_PM +static int loongson_spi_suspend(struct device *dev) +{ + struct loongson_spi *loongson_spi; + struct spi_master *master; + + master = dev_get_drvdata(dev); + loongson_spi = spi_master_get_devdata(master); + + loongson_spi->spcr = loongson_spi_read_reg(loongson_spi, SPCR); + loongson_spi->sper = loongson_spi_read_reg(loongson_spi, SPER); + loongson_spi->spsr = loongson_spi_read_reg(loongson_spi, SPSR); + loongson_spi->para = loongson_spi_read_reg(loongson_spi, PARA); + loongson_spi->sfcs = loongson_spi_read_reg(loongson_spi, SFCS); + loongson_spi->timi = loongson_spi_read_reg(loongson_spi, TIMI); + + return 0; +} + +static int loongson_spi_resume(struct device *dev) +{ + struct loongson_spi *loongson_spi; + struct spi_master *master; + + master = dev_get_drvdata(dev); + loongson_spi = spi_master_get_devdata(master); + + loongson_spi_write_reg(loongson_spi, SPCR, loongson_spi->spcr); + loongson_spi_write_reg(loongson_spi, SPER, loongson_spi->sper); + loongson_spi_write_reg(loongson_spi, SPSR, loongson_spi->spsr); + loongson_spi_write_reg(loongson_spi, PARA, loongson_spi->para); + loongson_spi_write_reg(loongson_spi, SFCS, loongson_spi->sfcs); + loongson_spi_write_reg(loongson_spi, TIMI, loongson_spi->timi); + + return 0; +} + +static const struct dev_pm_ops loongson_spi_dev_pm_ops = { + .suspend = loongson_spi_suspend, + .resume = loongson_spi_resume, +}; + +#define LS_DEV_PM_OPS (&loongson_spi_dev_pm_ops) +#else +#define LS_DEV_PM_OPS NULL +#endif + +static struct platform_driver loongson_spi_driver = { + .probe = loongson_spi_probe, + .driver = { + .name = "loongson-spi", + .owner = THIS_MODULE, + .bus = &platform_bus_type, + .pm = LS_DEV_PM_OPS, + }, +}; + +#ifdef CONFIG_PCI +static struct resource loongson_spi_resources[] = { + [0] = { + .flags = IORESOURCE_MEM, + }, + [1] = { + .flags = IORESOURCE_IRQ, + }, +}; + +static struct platform_device loongson_spi_device = { + .name = "loongson-spi", + .id = 0, + .num_resources = ARRAY_SIZE(loongson_spi_resources), + .resource = loongson_spi_resources, +}; + +static int loongson_spi_pci_register(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + int ret; + unsigned char v8; + + pr_debug("%s BEGIN\n", __func__); + /* Enable device in PCI config */ + ret = pci_enable_device(pdev); + if (ret < 0) { + dev_err("loongson-pci (%s): Cannot enable PCI device\n", + ci_name(pdev)); + goto err_out; + } + + /* request the mem regions */ + ret = pci_request_region(pdev, 0, "loongson-spi io"); + if (ret < 0) { + dev_err("loongson-spi (%s): cannot request region 0.\n", + pci_name(pdev)); + goto err_out; + } + + loongson_spi_resources[0].start = pci_resource_start(pdev, 0); + loongson_spi_resources[0].end = pci_resource_end(pdev, 0); + /* need api from pci irq */ + ret = pci_read_config_byte(pdev, PCI_INTERRUPT_LINE, &v8); + + if (ret == PCIBIOS_SUCCESSFUL) { + loongson_spi_resources[1].start = v8; + loongson_spi_resources[1].end = v8; + platform_device_register(&loongson_spi_device); + } + +err_out: + return ret; +} + +static void loongson_spi_pci_unregister(struct pci_dev *pdev) +{ + pci_release_region(pdev, 0); +} + +static struct pci_device_id loongson_spi_devices[] = { + {PCI_DEVICE(0x14, 0x7a0b)}, + {0, 0, 0, 0, 0, 0, 0} +}; + +static struct pci_driver loongson_spi_pci_driver = { + .name = "loongson-spi-pci", + .id_table = loongson_spi_devices, + .probe = loongson_spi_pci_register, + .remove = loongson_spi_pci_unregister, +}; +#endif + +static int __init loongson_spi_init(void) +{ + int ret; + + ret = platform_driver_register(&loongson_spi_driver); +#ifdef CONFIG_PCI + if (!ret) + ret = pci_register_driver(&loongson_spi_pci_driver); +#endif + return ret; +} + +static void __exit loongson_spi_exit(void) +{ + platform_driver_unregister(&loongson_spi_driver); +#ifdef CONFIG_PCI + pci_unregister_driver(&loongson_spi_pci_driver); +#endif +} + +subsys_initcall(loongson_spi_init); +module_exit(loongson_spi_exit); + +MODULE_AUTHOR("Loongson Technology Corporation Limited"); +MODULE_DESCRIPTION("Loongson SPI driver"); +MODULE_LICENSE("GPL"); diff --git a/include/acpi/platform/aclinux.h b/include/acpi/platform/aclinux.h index 72f52a1342a0f6d818575d49902c30342d0a368e..1a60441ccc16daddb2676eb31eef7857c228b8e6 100644 --- a/include/acpi/platform/aclinux.h +++ b/include/acpi/platform/aclinux.h @@ -194,7 +194,7 @@ #if defined(__ia64__) || (defined(__x86_64__) && !defined(__ILP32__)) ||\ defined(__aarch64__) || defined(__PPC64__) ||\ - defined(__s390x__) ||\ + defined(__s390x__) || defined(__loongarch__)\ (defined(__riscv) && (defined(__LP64__) || defined(_LP64))) #define ACPI_MACHINE_WIDTH 64 #define COMPILER_DEPENDENT_INT64 long