diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 9ce75836fc73c..dd2b51fd67929 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -1933,7 +1933,7 @@ for the device. By default it is set to false (0). ieee754= [MIPS] Select IEEE Std 754 conformance mode - Format: { strict | legacy | 2008 | relaxed } + Format: { strict | legacy | 2008 | relaxed | emulated } Default: strict Choose which programs will be accepted for execution @@ -1953,6 +1953,8 @@ by the FPU relaxed accept any binaries regardless of whether supported by the FPU + emulated accept any binaries but enable FPU emulator + if binary mode is unsupported by the FPU. The FPU emulator is always able to support both NaN encodings, so if no FPU hardware is present or it has diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig index 91c3a502156b3..84b673e713c05 100644 --- a/arch/mips/Kconfig +++ b/arch/mips/Kconfig @@ -16,6 +16,7 @@ config MIPS select ARCH_HAS_TICK_BROADCAST if GENERIC_CLOCKEVENTS_BROADCAST select ARCH_HAS_UBSAN_SANITIZE_ALL select ARCH_HAS_GCOV_PROFILE_ALL + select ARCH_SUPPORTS_NUMA_BALANCING if 64BIT select ARCH_KEEP_MEMBLOCK select ARCH_USE_BUILTIN_BSWAP select ARCH_USE_CMPXCHG_LOCKREF if 64BIT @@ -29,7 +30,7 @@ config MIPS select BUILDTIME_TABLE_SORT select CLONE_BACKWARDS select CPU_NO_EFFICIENT_FFS if (TARGET_ISA_REV < 1) - select CPU_PM if CPU_IDLE + select CPU_PM if CPU_IDLE || SUSPEND select GENERIC_ATOMIC64 if !64BIT select GENERIC_CMOS_UPDATE select GENERIC_CPU_AUTOPROBE @@ -102,6 +103,11 @@ config MIPS select TRACE_IRQFLAGS_SUPPORT select ARCH_HAS_ELFCORE_COMPAT select HAVE_ARCH_KCSAN if 64BIT + select HAVE_ARCH_AUDITSYSCALL + select AUDIT_ARCH + +config AUDIT_ARCH + bool config MIPS_FIXUP_BIGPHYS_ADDR bool @@ -492,6 +498,7 @@ config MACH_LOONGSON64 select CSRC_R4K select CEVT_R4K select FORCE_PCI + select HAVE_CLK select ISA select I8259 select IRQ_MIPS_CPU @@ -515,6 +522,8 @@ config MACH_LOONGSON64 select BUILTIN_DTB select PCI_HOST_GENERIC select HAVE_ARCH_NODEDATA_EXTENSION if NUMA + select CPU_SUPPORTS_CPUFREQ + select MIPS_EXTERNAL_TIMER help This enables the support of Loongson-2/3 family of machines. @@ -1013,6 +1022,12 @@ config FW_ARC config ARCH_MAY_HAVE_PC_FDC bool +config AUDITSYSCALL_O32 + bool + +config AUDITSYSCALL_N32 + bool + config BOOT_RAW bool @@ -1284,6 +1299,7 @@ config CPU_LOONGSON64 select GPIOLIB select SWIOTLB select HAVE_KVM + select HAVE_PLAT_MEMCPY help The Loongson GSx64(GS264/GS464/GS464E/GS464V) series of processor cores implements the MIPS64R2 instruction set with many extensions, @@ -2106,9 +2122,9 @@ endchoice config ARCH_FORCE_MAX_ORDER int "Maximum zone order" - default "13" if MIPS_HUGE_TLB_SUPPORT && PAGE_SIZE_64KB - default "12" if MIPS_HUGE_TLB_SUPPORT && PAGE_SIZE_32KB - default "11" if MIPS_HUGE_TLB_SUPPORT && PAGE_SIZE_16KB + default "13" if (NUMA_BALANCING || MIPS_HUGE_TLB_SUPPORT) && PAGE_SIZE_64KB + default "12" if (NUMA_BALANCING || MIPS_HUGE_TLB_SUPPORT) && PAGE_SIZE_32KB + default "11" if (NUMA_BALANCING || MIPS_HUGE_TLB_SUPPORT) && PAGE_SIZE_16KB default "10" help The kernel memory allocator divides physically contiguous memory @@ -3087,6 +3103,7 @@ config MIPS32_O32 select ARCH_WANT_OLD_COMPAT_IPC select COMPAT select MIPS32_COMPAT + select AUDITSYSCALL_O32 if AUDITSYSCALL help Select this option if you want to run o32 binaries. These are pure 32-bit binaries as used by the 32-bit Linux/MIPS port. Most of @@ -3100,6 +3117,7 @@ config MIPS32_N32 select ARCH_WANT_COMPAT_IPC_PARSE_VERSION select COMPAT select MIPS32_COMPAT + select AUDITSYSCALL_N32 if AUDITSYSCALL help Select this option if you want to run n32 binaries. These are 64-bit binaries using 32-bit quantities for addressing and certain diff --git a/arch/mips/Makefile b/arch/mips/Makefile index f49807e1f19bc..0888074f4dfef 100644 --- a/arch/mips/Makefile +++ b/arch/mips/Makefile @@ -299,7 +299,7 @@ drivers-$(CONFIG_PCI) += arch/mips/pci/ ifdef CONFIG_64BIT ifndef KBUILD_SYM32 ifeq ($(shell expr $(load-y) \< 0xffffffff80000000), 0) - KBUILD_SYM32 = y + KBUILD_SYM32 = $(call cc-option-yn, -msym32) endif endif diff --git a/arch/mips/configs/deepin_loongson3_desktop_defconfig b/arch/mips/configs/deepin_loongson3_desktop_defconfig new file mode 100644 index 0000000000000..7ea3575c96ca6 --- /dev/null +++ b/arch/mips/configs/deepin_loongson3_desktop_defconfig @@ -0,0 +1,406 @@ +# CONFIG_LOCALVERSION_AUTO is not set +CONFIG_BUILD_SALT="-loongson3-desktop-hwe" +CONFIG_KERNEL_ZSTD=y +CONFIG_SYSVIPC=y +CONFIG_POSIX_MQUEUE=y +CONFIG_AUDIT=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_BPF_SYSCALL=y +CONFIG_BPF_JIT=y +CONFIG_PREEMPT=y +CONFIG_BSD_PROCESS_ACCT=y +CONFIG_BSD_PROCESS_ACCT_V3=y +CONFIG_TASKSTATS=y +CONFIG_TASK_DELAY_ACCT=y +CONFIG_TASK_XACCT=y +CONFIG_TASK_IO_ACCOUNTING=y +CONFIG_MEMCG=y +CONFIG_BLK_CGROUP=y +CONFIG_CGROUP_PIDS=y +CONFIG_CGROUP_FREEZER=y +CONFIG_CPUSETS=y +CONFIG_CGROUP_DEVICE=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_NAMESPACES=y +CONFIG_USER_NS=y +CONFIG_SCHED_AUTOGROUP=y +CONFIG_RELAY=y +CONFIG_BLK_DEV_INITRD=y +CONFIG_EXPERT=y +CONFIG_PERF_EVENTS=y +CONFIG_KEXEC=y +CONFIG_MACH_LOONGSON64=y +CONFIG_ARCH_FORCE_MAX_ORDER=11 +CONFIG_CPU_HAS_MSA=y +CONFIG_NUMA=y +CONFIG_NR_CPUS=16 +CONFIG_HZ_256=y +CONFIG_MIPS32_O32=y +CONFIG_MIPS32_N32=y +CONFIG_VIRTUALIZATION=y +CONFIG_KVM=m +CONFIG_KPROBES=y +CONFIG_MODULES=y +CONFIG_MODULE_FORCE_LOAD=y +CONFIG_MODULE_UNLOAD=y +CONFIG_MODULE_FORCE_UNLOAD=y +CONFIG_MODVERSIONS=y +CONFIG_PARTITION_ADVANCED=y +CONFIG_MQ_IOSCHED_DEADLINE=m +CONFIG_IOSCHED_BFQ=y +CONFIG_BINFMT_MISC=m +CONFIG_KSM=y +CONFIG_NET=y +CONFIG_PACKET=y +CONFIG_XFRM_USER=y +CONFIG_NET_KEY=y +CONFIG_IP_MULTICAST=y +CONFIG_IP_ADVANCED_ROUTER=y +CONFIG_IP_MULTIPLE_TABLES=y +CONFIG_IP_ROUTE_MULTIPATH=y +CONFIG_IP_ROUTE_VERBOSE=y +CONFIG_NETFILTER=y +CONFIG_BRIDGE_NETFILTER=m +CONFIG_NETFILTER_NETLINK_LOG=m +CONFIG_NF_CONNTRACK=m +CONFIG_NF_CONNTRACK_SNMP=m +CONFIG_NF_TABLES=m +CONFIG_NFT_CT=m +CONFIG_NFT_NAT=m +CONFIG_NETFILTER_XT_TARGET_CHECKSUM=m +CONFIG_NETFILTER_XT_TARGET_CLASSIFY=m +CONFIG_NETFILTER_XT_TARGET_MARK=m +CONFIG_NETFILTER_XT_TARGET_NFQUEUE=m +CONFIG_NETFILTER_XT_MATCH_COMMENT=m +CONFIG_NETFILTER_XT_MATCH_CONNTRACK=m +CONFIG_NETFILTER_XT_MATCH_DCCP=m +CONFIG_NETFILTER_XT_MATCH_ESP=m +CONFIG_NETFILTER_XT_MATCH_LENGTH=m +CONFIG_NETFILTER_XT_MATCH_LIMIT=m +CONFIG_NETFILTER_XT_MATCH_MAC=m +CONFIG_NETFILTER_XT_MATCH_MARK=m +CONFIG_NETFILTER_XT_MATCH_MULTIPORT=m +CONFIG_NETFILTER_XT_MATCH_PKTTYPE=m +CONFIG_NETFILTER_XT_MATCH_QUOTA=m +CONFIG_NETFILTER_XT_MATCH_REALM=m +CONFIG_NETFILTER_XT_MATCH_STATISTIC=m +CONFIG_NETFILTER_XT_MATCH_STRING=m +CONFIG_NETFILTER_XT_MATCH_TCPMSS=m +CONFIG_IP_SET=m +CONFIG_IP_VS=m +CONFIG_NF_TABLES_IPV4=y +CONFIG_NF_TABLES_ARP=y +CONFIG_IP_NF_IPTABLES=m +CONFIG_IP_NF_MATCH_AH=m +CONFIG_IP_NF_MATCH_ECN=m +CONFIG_IP_NF_MATCH_RPFILTER=m +CONFIG_IP_NF_MATCH_TTL=m +CONFIG_IP_NF_FILTER=m +CONFIG_IP_NF_TARGET_REJECT=m +CONFIG_IP_NF_NAT=m +CONFIG_IP_NF_TARGET_MASQUERADE=m +CONFIG_IP_NF_MANGLE=m +CONFIG_IP_NF_TARGET_ECN=m +CONFIG_IP_NF_TARGET_TTL=m +CONFIG_IP_NF_RAW=m +CONFIG_IP_NF_SECURITY=m +CONFIG_IP_NF_ARPTABLES=m +CONFIG_IP_NF_ARPFILTER=m +CONFIG_IP_NF_ARP_MANGLE=m +CONFIG_NF_TABLES_IPV6=y +CONFIG_IP6_NF_IPTABLES=m +CONFIG_IP6_NF_MATCH_RPFILTER=m +CONFIG_IP6_NF_FILTER=m +CONFIG_IP6_NF_TARGET_REJECT=m +CONFIG_IP6_NF_MANGLE=m +CONFIG_IP6_NF_RAW=m +CONFIG_IP6_NF_SECURITY=m +CONFIG_IP6_NF_NAT=m +CONFIG_IP6_NF_TARGET_MASQUERADE=m +CONFIG_NF_TABLES_BRIDGE=m +CONFIG_BRIDGE_NF_EBTABLES=m +CONFIG_BRIDGE_EBT_T_FILTER=m +CONFIG_BRIDGE_EBT_T_NAT=m +CONFIG_IP_SCTP=m +CONFIG_L2TP=m +CONFIG_BRIDGE=m +CONFIG_VSOCKETS=m +CONFIG_VIRTIO_VSOCKETS=m +CONFIG_CFG80211=m +CONFIG_CFG80211_WEXT=y +CONFIG_MAC80211=m +CONFIG_RFKILL=m +CONFIG_RFKILL_INPUT=y +CONFIG_NET_9P=m +CONFIG_NET_9P_VIRTIO=m +CONFIG_PCIEPORTBUS=y +# CONFIG_PCIEASPM is not set +CONFIG_HOTPLUG_PCI=y +CONFIG_DEVTMPFS=y +CONFIG_DEVTMPFS_MOUNT=y +CONFIG_MTD=m +CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_RAM=y +CONFIG_BLK_DEV_RAM_SIZE=8192 +CONFIG_VIRTIO_BLK=y +CONFIG_BLK_DEV_NVME=y +CONFIG_NVME_MULTIPATH=y +CONFIG_NVME_HWMON=y +CONFIG_RAID_ATTRS=m +CONFIG_BLK_DEV_SD=y +CONFIG_BLK_DEV_SR=y +CONFIG_CHR_DEV_SG=y +CONFIG_CHR_DEV_SCH=m +CONFIG_SCSI_CONSTANTS=y +CONFIG_SCSI_LOGGING=y +CONFIG_SCSI_SPI_ATTRS=m +CONFIG_SCSI_FC_ATTRS=m +CONFIG_ISCSI_TCP=m +CONFIG_MEGARAID_NEWGEN=y +CONFIG_MEGARAID_MM=y +CONFIG_MEGARAID_MAILBOX=y +CONFIG_MEGARAID_LEGACY=y +CONFIG_MEGARAID_SAS=y +CONFIG_SCSI_VIRTIO=y +CONFIG_ATA=y +CONFIG_SATA_AHCI=y +CONFIG_PATA_ATIIXP=y +CONFIG_MD=y +CONFIG_BLK_DEV_MD=m +CONFIG_MD_LINEAR=m +CONFIG_MD_RAID0=m +CONFIG_MD_RAID1=m +CONFIG_MD_RAID10=m +CONFIG_MD_RAID456=m +CONFIG_MD_MULTIPATH=m +CONFIG_BLK_DEV_DM=m +CONFIG_DM_CRYPT=m +CONFIG_DM_SNAPSHOT=m +CONFIG_DM_MIRROR=m +CONFIG_DM_ZERO=m +CONFIG_TARGET_CORE=m +CONFIG_TCM_IBLOCK=m +CONFIG_TCM_FILEIO=m +CONFIG_TCM_PSCSI=m +CONFIG_LOOPBACK_TARGET=m +CONFIG_ISCSI_TARGET=m +CONFIG_NETDEVICES=y +CONFIG_DUMMY=m +CONFIG_TUN=m +CONFIG_VETH=m +CONFIG_VIRTIO_NET=m +# CONFIG_NET_VENDOR_3COM is not set +# CONFIG_NET_VENDOR_ADAPTEC is not set +# CONFIG_NET_VENDOR_ALTEON is not set +# CONFIG_NET_VENDOR_AMD is not set +# CONFIG_NET_VENDOR_ARC is not set +# CONFIG_NET_VENDOR_ATHEROS is not set +# CONFIG_NET_VENDOR_BROADCOM is not set +# CONFIG_NET_VENDOR_CHELSIO is not set +# CONFIG_NET_VENDOR_CIRRUS is not set +# CONFIG_NET_VENDOR_CISCO is not set +# CONFIG_NET_VENDOR_DEC is not set +# CONFIG_NET_VENDOR_DLINK is not set +# CONFIG_NET_VENDOR_EMULEX is not set +# CONFIG_NET_VENDOR_I825XX is not set +CONFIG_E1000=y +CONFIG_E1000E=y +CONFIG_IGB=y +CONFIG_IXGBE=y +# CONFIG_NET_VENDOR_MARVELL is not set +# CONFIG_NET_VENDOR_MELLANOX is not set +# CONFIG_NET_VENDOR_MICREL is not set +# CONFIG_NET_VENDOR_MYRI is not set +# CONFIG_NET_VENDOR_NATSEMI is not set +# CONFIG_NET_VENDOR_NVIDIA is not set +# CONFIG_NET_VENDOR_OKI is not set +# CONFIG_NET_VENDOR_QLOGIC is not set +# CONFIG_NET_VENDOR_BROCADE is not set +# CONFIG_NET_VENDOR_RDC is not set +CONFIG_8139CP=m +CONFIG_8139TOO=m +CONFIG_R8169=y +# CONFIG_NET_VENDOR_SEEQ is not set +# CONFIG_NET_VENDOR_SILAN is not set +# CONFIG_NET_VENDOR_SIS is not set +# CONFIG_NET_VENDOR_SMSC is not set +# CONFIG_NET_VENDOR_STMICRO is not set +# CONFIG_NET_VENDOR_SUN is not set +# CONFIG_NET_VENDOR_TEHUTI is not set +# CONFIG_NET_VENDOR_TI is not set +# CONFIG_NET_VENDOR_TOSHIBA is not set +# CONFIG_NET_VENDOR_VIA is not set +# CONFIG_NET_VENDOR_WIZNET is not set +CONFIG_PPP=m +CONFIG_PPP_BSDCOMP=m +CONFIG_PPP_DEFLATE=m +CONFIG_PPP_FILTER=y +CONFIG_PPP_MPPE=m +CONFIG_PPP_MULTILINK=y +CONFIG_PPPOE=m +CONFIG_PPPOL2TP=m +CONFIG_PPP_ASYNC=m +CONFIG_PPP_SYNC_TTY=m +CONFIG_ATH9K=m +CONFIG_HOSTAP=m +CONFIG_INPUT_MOUSEDEV=y +CONFIG_INPUT_MOUSEDEV_PSAUX=y +CONFIG_KEYBOARD_XTKBD=m +CONFIG_MOUSE_PS2_SENTELIC=y +CONFIG_MOUSE_SERIAL=m +CONFIG_INPUT_MISC=y +CONFIG_INPUT_UINPUT=m +CONFIG_SERIO_SERPORT=m +CONFIG_SERIO_RAW=m +CONFIG_LEGACY_PTY_COUNT=16 +CONFIG_SERIAL_8250=y +CONFIG_SERIAL_8250_CONSOLE=y +CONFIG_SERIAL_8250_NR_UARTS=16 +CONFIG_SERIAL_8250_EXTENDED=y +CONFIG_SERIAL_8250_MANY_PORTS=y +CONFIG_SERIAL_8250_SHARE_IRQ=y +CONFIG_SERIAL_8250_RSA=y +CONFIG_SERIAL_OF_PLATFORM=y +CONFIG_SERIAL_NONSTANDARD=y +CONFIG_VIRTIO_CONSOLE=y +CONFIG_HW_RANDOM=y +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_PIIX4=y +CONFIG_GPIO_LOONGSON=y +CONFIG_SENSORS_LM75=m +CONFIG_SENSORS_LM93=m +CONFIG_SENSORS_W83627HF=m +CONFIG_MEDIA_SUPPORT=m +CONFIG_MEDIA_USB_SUPPORT=y +CONFIG_USB_VIDEO_CLASS=m +CONFIG_DRM=y +CONFIG_DRM_RADEON=m +CONFIG_DRM_AMDGPU=m +CONFIG_DRM_AMDGPU_SI=y +CONFIG_DRM_AMDGPU_CIK=y +CONFIG_DRM_AMDGPU_USERPTR=y +CONFIG_DRM_AMD_ACP=y +CONFIG_DRM_AMD_DC_SI=y +CONFIG_DRM_AST=m +CONFIG_DRM_QXL=y +CONFIG_DRM_VIRTIO_GPU=y +# CONFIG_DRM_ARISE is not set +CONFIG_FB=y +CONFIG_FB_RADEON=y +CONFIG_LCD_PLATFORM=m +# CONFIG_VGA_CONSOLE is not set +CONFIG_FRAMEBUFFER_CONSOLE_ROTATION=y +CONFIG_LOGO=y +CONFIG_SOUND=y +CONFIG_SND=m +CONFIG_SND_SEQUENCER=m +CONFIG_SND_SEQ_DUMMY=m +# CONFIG_SND_ISA is not set +CONFIG_SND_HDA_INTEL=m +CONFIG_SND_HDA_PATCH_LOADER=y +CONFIG_SND_HDA_CODEC_REALTEK=m +CONFIG_SND_HDA_CODEC_SIGMATEL=m +CONFIG_SND_HDA_CODEC_HDMI=m +CONFIG_SND_HDA_CODEC_CONEXANT=m +# CONFIG_SND_USB is not set +CONFIG_HIDRAW=y +CONFIG_HID_A4TECH=m +CONFIG_HID_SUNPLUS=m +CONFIG_HID_PID=y +CONFIG_USB_HIDDEV=y +CONFIG_USB=y +CONFIG_USB_MON=y +CONFIG_USB_XHCI_HCD=m +CONFIG_USB_EHCI_HCD=y +CONFIG_USB_EHCI_ROOT_HUB_TT=y +CONFIG_USB_OHCI_HCD=y +CONFIG_USB_UHCI_HCD=m +CONFIG_USB_STORAGE=m +CONFIG_USB_SERIAL=m +CONFIG_USB_SERIAL_PL2303=m +CONFIG_USB_SERIAL_OPTION=m +CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_CMOS=y +CONFIG_RTC_DRV_LOONGSON=y +CONFIG_RTC_DRV_GOLDFISH=y +CONFIG_DMADEVICES=y +CONFIG_VIRTIO_PCI=y +CONFIG_VIRTIO_BALLOON=m +CONFIG_VIRTIO_INPUT=y +CONFIG_VIRTIO_MMIO=y +CONFIG_VHOST_NET=m +CONFIG_VHOST_SCSI=m +CONFIG_VHOST_VSOCK=m +CONFIG_GOLDFISH=y +CONFIG_PM_DEVFREQ=y +CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND=y +CONFIG_DEVFREQ_GOV_PERFORMANCE=y +CONFIG_DEVFREQ_GOV_POWERSAVE=y +CONFIG_DEVFREQ_GOV_USERSPACE=y +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_POSIX_ACL=y +CONFIG_EXT4_FS_SECURITY=y +CONFIG_XFS_FS=y +CONFIG_XFS_POSIX_ACL=y +CONFIG_BTRFS_FS=y +CONFIG_BTRFS_FS_POSIX_ACL=y +CONFIG_QUOTA=y +CONFIG_QFMT_V1=m +CONFIG_QFMT_V2=m +CONFIG_AUTOFS_FS=y +CONFIG_FUSE_FS=m +CONFIG_VIRTIO_FS=m +CONFIG_FSCACHE=m +CONFIG_ISO9660_FS=m +CONFIG_JOLIET=y +CONFIG_MSDOS_FS=m +CONFIG_VFAT_FS=m +CONFIG_FAT_DEFAULT_CODEPAGE=936 +CONFIG_FAT_DEFAULT_IOCHARSET="gb2312" +CONFIG_PROC_KCORE=y +CONFIG_TMPFS=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_CONFIGFS_FS=y +CONFIG_CRAMFS=m +CONFIG_SQUASHFS=y +CONFIG_SQUASHFS_XATTR=y +CONFIG_NFS_FS=m +CONFIG_NFS_V3_ACL=y +CONFIG_NFS_V4=m +CONFIG_NFSD=m +CONFIG_NFSD_V3_ACL=y +CONFIG_NFSD_V4=y +CONFIG_CIFS=m +CONFIG_9P_FS=m +CONFIG_9P_FSCACHE=y +CONFIG_9P_FS_POSIX_ACL=y +CONFIG_NLS_CODEPAGE_437=y +CONFIG_NLS_CODEPAGE_936=y +CONFIG_NLS_ASCII=y +CONFIG_NLS_UTF8=y +CONFIG_SECURITY=y +CONFIG_SECURITYFS=y +CONFIG_SECURITY_NETWORK=y +CONFIG_SECURITY_PATH=y +CONFIG_SECURITY_SELINUX=y +CONFIG_SECURITY_SELINUX_BOOTPARAM=y +CONFIG_DEFAULT_SECURITY_DAC=y +CONFIG_CRYPTO_BLOWFISH=m +CONFIG_CRYPTO_CAST5=m +CONFIG_CRYPTO_CAST6=m +CONFIG_CRYPTO_SERPENT=m +CONFIG_CRYPTO_TWOFISH=m +CONFIG_CRYPTO_HMAC=y +CONFIG_CRYPTO_MD5=y +CONFIG_CRYPTO_WP512=m +CONFIG_CRYPTO_DEFLATE=m +CONFIG_PRINTK_TIME=y +CONFIG_STRIP_ASM_SYMS=y +CONFIG_MAGIC_SYSRQ=y +CONFIG_DEBUG_FS=y +# CONFIG_SCHED_DEBUG is not set +CONFIG_FUNCTION_TRACER=y +CONFIG_FTRACE_SYSCALLS=y +CONFIG_CMDLINE_BOOL=y +CONFIG_CMDLINE="ieee754=relaxed" diff --git a/arch/mips/include/asm/abi.h b/arch/mips/include/asm/abi.h index dba7f4b6bebfa..6e717a4a13ceb 100644 --- a/arch/mips/include/asm/abi.h +++ b/arch/mips/include/asm/abi.h @@ -21,6 +21,7 @@ struct mips_abi { int (* const setup_rt_frame)(void *sig_return, struct ksignal *ksig, struct pt_regs *regs, sigset_t *set); const unsigned long restart; + const int audit_arch; unsigned off_sc_fpregs; unsigned off_sc_fpc_csr; diff --git a/arch/mips/include/asm/clock.h b/arch/mips/include/asm/clock.h new file mode 100644 index 0000000000000..db72b78e0b2f4 --- /dev/null +++ b/arch/mips/include/asm/clock.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __ASM_MIPS_CLOCK_H +#define __ASM_MIPS_CLOCK_H + +#include +#include +#include +#include + +struct clk; + +struct clk_ops { + void (*init) (struct clk *clk); + void (*enable) (struct clk *clk); + void (*disable) (struct clk *clk); + void (*recalc) (struct clk *clk); + int (*set_rate) (struct clk *clk, unsigned long rate, int algo_id); + long (*round_rate) (struct clk *clk, unsigned long rate); +}; + +struct clk { + struct list_head node; + const char *name; + int id; + struct module *owner; + + struct clk *parent; + struct clk_ops *ops; + + struct kref kref; + + unsigned long rate; + unsigned long flags; +}; + +#define CLK_ALWAYS_ENABLED (1 << 0) +#define CLK_RATE_PROPAGATES (1 << 1) + +int clk_init(void); + +int __clk_enable(struct clk *); +void __clk_disable(struct clk *); + +void clk_recalc_rate(struct clk *); + +int clk_register(struct clk *); +void clk_unregister(struct clk *); + +struct clk *gs464_cpu_clk_get(int cpu); +struct clk *gs464_clk_get(struct device *dev, const char *id); +void gs464_propagate_rate(struct clk *clk); +unsigned long gs464_clk_get_rate(struct clk *clk); +int gs464_clk_set_rate(struct clk *clk, unsigned long rate); +long gs464_clk_round_rate(struct clk *clk, unsigned long rate); +int gs464_clock_init(void); + +#endif /* __ASM_MIPS_CLOCK_H */ diff --git a/arch/mips/include/asm/cpu-features.h b/arch/mips/include/asm/cpu-features.h index 404390bb87eaf..e522efa1b53d0 100644 --- a/arch/mips/include/asm/cpu-features.h +++ b/arch/mips/include/asm/cpu-features.h @@ -659,6 +659,10 @@ # define cpu_has_mm_full __opt(MIPS_CPU_MM_FULL) #endif +#ifndef cpu_has_constant_timer +# define cpu_has_constant_timer __opt(MIPS_CPU_CONST_TIMER) +#endif + /* * Guest capabilities */ diff --git a/arch/mips/include/asm/cpu-info.h b/arch/mips/include/asm/cpu-info.h index a600670d00e97..d2ea1476aaae8 100644 --- a/arch/mips/include/asm/cpu-info.h +++ b/arch/mips/include/asm/cpu-info.h @@ -125,7 +125,9 @@ extern void cpu_probe(void); extern void cpu_report(void); extern const char *__cpu_name[]; +extern const char *__cpu_full_name[]; #define cpu_name_string() __cpu_name[raw_smp_processor_id()] +#define cpu_full_name_string() __cpu_full_name[raw_smp_processor_id()] struct seq_file; struct notifier_block; diff --git a/arch/mips/include/asm/cpu.h b/arch/mips/include/asm/cpu.h index ecb9854cb4324..3c1299b718e10 100644 --- a/arch/mips/include/asm/cpu.h +++ b/arch/mips/include/asm/cpu.h @@ -421,6 +421,7 @@ enum cpu_type_enum { #define MIPS_CPU_MAC_2008_ONLY BIT_ULL(60) /* CPU Only support MAC2008 Fused multiply-add instruction */ #define MIPS_CPU_FTLBPAREX BIT_ULL(61) /* CPU has FTLB parity exception */ #define MIPS_CPU_GSEXCEX BIT_ULL(62) /* CPU has GSExc exception */ +#define MIPS_CPU_CONST_TIMER BIT_ULL(63) /* CPU has constant timer */ /* * CPU ASE encodings diff --git a/arch/mips/include/asm/fpu.h b/arch/mips/include/asm/fpu.h index 86310d6e10352..bc5ac9887d097 100644 --- a/arch/mips/include/asm/fpu.h +++ b/arch/mips/include/asm/fpu.h @@ -129,6 +129,18 @@ static inline int __own_fpu(void) if (ret) return ret; + if (current->thread.fpu.fcr31 & FPU_CSR_NAN2008) { + if (!cpu_has_nan_2008) { + ret = SIGFPE; + goto failed; + } + } else { + if (!cpu_has_nan_legacy) { + ret = SIGFPE; + goto failed; + } + } + KSTK_STATUS(current) |= ST0_CU1; if (mode == FPU_64BIT || mode == FPU_HYBRID) KSTK_STATUS(current) |= ST0_FR; @@ -137,6 +149,9 @@ static inline int __own_fpu(void) set_thread_flag(TIF_USEDFPU); return 0; +failed: + __disable_fpu(); + return ret; } static inline int own_fpu_inatomic(int restore) diff --git a/arch/mips/include/asm/mach-loongson64/boot_param.h b/arch/mips/include/asm/mach-loongson64/boot_param.h index 9218b3ae33832..d1f064d885662 100644 --- a/arch/mips/include/asm/mach-loongson64/boot_param.h +++ b/arch/mips/include/asm/mach-loongson64/boot_param.h @@ -66,6 +66,7 @@ struct efi_cpuinfo_loongson { u16 reserved_cores_mask; u32 cpu_clock_freq; /* cpu_clock */ u32 nr_cpus; + char cpuname[64]; } __packed; #define MAX_UARTS 64 diff --git a/arch/mips/include/asm/mach-loongson64/ec_wpce775l.h b/arch/mips/include/asm/mach-loongson64/ec_wpce775l.h new file mode 100644 index 0000000000000..e1f8577a6ed61 --- /dev/null +++ b/arch/mips/include/asm/mach-loongson64/ec_wpce775l.h @@ -0,0 +1,400 @@ +/* + * EC (Embedded Controller) WPCE775L device driver header for Linux + * + * Copyright (C) 2011 Lemote Inc. + * Author : Wang Rui + * Author : Huang Wei + * Date : 2011-02-21 + * + * EC relative header file. All the EC registers should be defined here. + * + * 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 you option) and later version. + */ + +#ifndef __EC_WPCE775L_H__ +#define __EC_WPCE775L_H__ + +#define EC_VERSION "1.11" + +/* + * The following registers are determined by the EC index configureation. + * 1. fill the PORT_INDEX as EC register. + * 2. fill the PORT_DATA as EC register write data or get the data from it. + */ +/* base address for io access for Loongson3A+rs780e Notebook platform */ +#define EC_BASE_ADDR_PORT (0xb8000000) +#define SIO_INDEX_PORT 0x2E +#define SIO_DATA_PORT 0x2F + +/* + * EC delay time 500us for register and status access + * Unit : us + */ +#define EC_REG_DELAY 30000 +#define EC_CMD_TIMEOUT 0x1000 +#define EC_SEND_TIMEOUT 0xffff +#define EC_RECV_TIMEOUT 0xffff + +/* + * EC access port for with Host communication. + */ +#define EC_CMD_PORT 0x66 +#define EC_STS_PORT 0x66 +#define EC_DAT_PORT 0x62 + +/* + * ACPI legacy commands. + */ +#define CMD_READ_EC 0x80 /* Read EC command. */ +#define CMD_WRITE_EC 0x81 /* Write EC command. */ +#define CMD_GET_EVENT_NUM 0x84 /* Query EC command, for get SCI event number. */ + +/* + * ACPI OEM commands. + */ +#define CMD_RESET 0x4E /* Reset and poweroff the machine auto-clear: rd/wr */ +enum +{ + RESET_OFF = 0, + RESET_ON, + PWROFF_ON, + SUSPEND_ON, + STANDBY_ON +}; + +#define CMD_EC_VERSION 0x4F /* EC Version OEM command: 36 Bytes */ + +/* + * Used ACPI legacy command 80h to do active. + */ +/* >>> Read/Write temperature & fan index for ACPI 80h/81h command. */ +#define INDEX_TEMPERATURE_VALUE 0x1B /* Current CPU temperature value, Read and Write(81h command). */ +#define INDEX_FAN_MAXSPEED_LEVEL 0x5B /* Fan speed maxinum levels supported. Defaut is 6. */ +#define INDEX_FAN_SPEED_LEVEL 0x5C /* FAn speed level. [0,5] or [0x06, 0x38]*/ +#define INDEX_FAN_CTRLMOD 0x5D /* Fan control mode, 0 = by EC, 1 = by Host.*/ +enum +{ + FAN_CTRL_BYEC = 0, + FAN_CTRL_BYHOST +}; +#define INDEX_FAN_STSCTRL 0x5E /* Fan status/control, 0 = stop, 1 = run. */ +enum +{ + FAN_STSCTRL_OFF = 0, + FAN_STSCTRL_ON +}; +#define INDEX_FAN_ERRSTS 0x5F /* Fan error status, 0 = no error, 1 = has error. */ +enum +{ + FAN_ERRSTS_NO = 0, + FAN_ERRSTS_HAS +}; +#define INDEX_FAN_SPEED_LOW 0x08 /* Fan speed low byte.*/ +#define INDEX_FAN_SPEED_HIGH 0x09 /* Fan speed high byte. */ +/* <<< End Temp & Fan */ + +/* >>> Read/Write LCD backlight information/control index for ACPI 80h/81h command. */ +#define INDEX_BACKLIGHT_CTRLMODE 0x57 /* LCD backlight control mode: 0 = by EC, 1 = by HOST */ +enum +{ + BACKLIGHT_CTRL_BYEC = 0, + BACKLIGHT_CTRL_BYHOST +}; +#define INDEX_BACKLIGHT_STSCTRL 0x58 /* LCD backlight status or control: 0 = turn off, 1 = turn on */ +enum +{ + BACKLIGHT_OFF = 0, + BACKLIGHT_ON +}; +#define INDEX_DISPLAY_MAXBRIGHTNESS_LEVEL 0x59 /* LCD backlight brightness max level */ +#define INDEX_DISPLAY_BRIGHTNESS 0x5A /* 10 stages (0~9) LCD backlight brightness adjust */ +enum +{ + FLAG_DISPLAY_BRIGHTNESS_LEVEL_0 = 0, /* This level is backlight turn off. */ + FLAG_DISPLAY_BRIGHTNESS_LEVEL_1, + FLAG_DISPLAY_BRIGHTNESS_LEVEL_2, + FLAG_DISPLAY_BRIGHTNESS_LEVEL_3, + FLAG_DISPLAY_BRIGHTNESS_LEVEL_4, + FLAG_DISPLAY_BRIGHTNESS_LEVEL_5, + FLAG_DISPLAY_BRIGHTNESS_LEVEL_6, + FLAG_DISPLAY_BRIGHTNESS_LEVEL_7, + FLAG_DISPLAY_BRIGHTNESS_LEVEL_8, + FLAG_DISPLAY_BRIGHTNESS_LEVEL_9, + FLAG_DISPLAY_BRIGHTNESS_LEVEL_10 +}; +/* <<< End Backlight */ + +#define INDEX_TOUCHPAD_ENABLE_LED 0x56 +enum +{ + TP_EN_LED_OFF, + TP_EN_LED_ON +}; + +/* >>> Read battery(BQ3060) index for ACPI 80h command */ +/* + * The reported battery die temperature. + * The temperature is expressed in units of 0.25 seconds and is updated every 2.56 seconds. + * The equation to calculate reported pack temperature is: + * Temperature = 0.1 * (256 * TEMPH + TEMPL) Kelvin + * Temperature -= 273 Degrees Celsius + * The host sytem has read-only access to this register pair. + */ +#define INDEX_BATTERY_TEMP_LOW 0x20 /* Battery temperature low byte. */ +#define INDEX_BATTERY_TEMP_HIGH 0x21 /* Battery temperature high byte. */ +#define INDEX_BATTERY_VOL_LOW 0x22 /* Battery Voltage Low byte. */ +#define INDEX_BATTERY_VOL_HIGH 0x23 /* Battery Voltage High byte. */ +#define INDEX_BATTERY_CURRENT_LOW 0x24 /* Battery Current Low byte. */ +#define INDEX_BATTERY_CURRENT_HIGH 0x25 /* Battery Current High byte. */ +#define INDEX_BATTERY_AC_LOW 0x26 /* Battery AverageCurrent Low byte. */ +#define INDEX_BATTERY_AC_HIGH 0x27 /* Battery AverageCurrent High byte. */ +#define INDEX_BATTERY_CAPACITY 0x2A /* Battery RemainingCapacity percent. */ +#define INDEX_BATTERY_STATUS_LOW 0x2C /* Battery Status low byte. */ +enum +{ + BIT_BATTERY_STATUS_FD = 4, /* Battery Fully Discharged Notify. 1 = Fully Discharged */ + BIT_BATTERY_STATUS_FC, /* Battery Fully Charged Notify. 1 = Fully Charged. */ + BIT_BATTERY_STATUS_DSG, /* Battery Discharging mode. 0 = in charging mode, 1 = in discharging mode, + relaxation mode, or valid charge termination has occurred. */ + BIT_BATTERY_STATUS_INIT /* Battery Initialization. 1 = Initialization */ +}; +#define INDEX_BATTERY_STATUS_HIGH 0x2D /* Battery Status high byte. */ +enum +{ + BIT_BATTERT_STATUS_RTA = 0, /* Battery Remaining Time Alarm. <= 10min */ + BIT_BATTERY_STATUS_RCA, /* Battery Remaining Capacity Alarm. <= 430mAh */ + BIT_BATTERY_STATUS_TDA = 3, /* Battery Terminate Discharge Alarm. */ + BIT_BATTERY_STATUS_OTA, /* Battery Over Temperature Alarm. */ + BIT_BATTERY_STATUS_TCA = 6, /* Battery Terminate Charge Alarm. */ + BIT_BATTERY_STATUS_OCA /* Battery Over Charged Alarm. */ +}; +#define INDEX_BATTERY_RC_LOW 0x2E /* Battery RemainingCapacity Low byte. */ +#define INDEX_BATTERY_RC_HIGH 0x2F /* Battery RemainingCapacity High byte. */ +#define INDEX_BATTERY_ATTE_LOW 0x30 /* Battery AverageTimeToEmpty Low byte. */ +#define INDEX_BATTERY_ATTE_HIGH 0x31 /* Battery AverageTimeToEmpty High byte. */ +#define INDEX_BATTERY_ATTF_LOW 0x32 /* Battery AverageTimeToFull Low byte. */ +#define INDEX_BATTERY_ATTF_HIGH 0x33 /* Battery AverageTimeToFull High byte. */ +#define INDEX_BATTERY_FCC_LOW 0x34 /* Battery FullChargeCapacity Low byte. */ +#define INDEX_BATTERY_FCC_HIGH 0x35 /* Battery FullChargeCapacity High byte. */ +#define INDEX_BATTERY_CC_LOW 0x36 /* Battery ChargingCurrent Low byte. */ +#define INDEX_BATTERY_CC_HIGH 0x37 /* Battery ChargingCurrent High byte. */ +#define INDEX_BATTERY_CV_LOW 0x38 /* Battery ChargingVoltage Low byte. */ +#define INDEX_BATTERY_CV_HIGH 0x39 /* Battery ChargingVoltage High byte. */ +#define INDEX_BATTERY_CHGSTS_LOW 0x3A /* Battery ChargingStatus Low byte. */ +enum +{ + BIT_BATTERY_CHGSTS_XCHGLV = 0, /* 1 = Battery is depleted */ + BIT_BATTERY_CHGSTS_OC, /* 1 = Overcharge fault */ + BIT_BATTERY_CHGSTS_OCHGI, /* 1 = Overcharge current fault */ + BIT_BATTERY_CHGSTS_OCHGV, /* 1 = Overcharge voltage fault */ + BIT_BATTERY_CHGSTS_FCMTO, /* 1 = Fast-charge timeout fault */ + BIT_BATTERY_CHGSTS_PCMTO, /* 1 = Precharge timeout fault */ + BIT_BATTERY_CHGSTS_CB /* 1 = Cell balancing in progress */ +}; +#define INDEX_BATTERY_CHGSTS_HIGH 0x3B /* Battery ChargingStatus High byte. */ +enum +{ + BIT_BATTERY_CHGSTS_HTCHG = 0, /* 1 = Low temperature charging */ + BIT_BATTERY_CHGSTS_ST2CHG, /* 1 = Standard temperature charging 2 */ + BIT_BATTERY_CHGSTS_ST1CHG, /* 1 = Standard temperature charging 1 */ + BIT_BATTERY_CHGSTS_LTCHG, /* 1 = Low temperature charging */ + BIT_BATTERY_CHGSTS_PCHG = 5, /* 1 = Precharging conditions exist */ + BIT_BATTERY_CHGSTS_CHGSUSP, /* 1 = Charging suspended */ + BIT_BATTERY_CHGSTS_XCHG /* 1 = Charging disabled */ +}; +#define INDEX_BATTERY_CYCLECNT_LOW 0x3C /* Battery CycleCount Low byte. */ +#define INDEX_BATTERY_CYCLECNT_HIGH 0x3D /* Battery CycleCount High byte. */ + +/* Battery static information. */ +#define INDEX_BATTERY_DC_LOW 0x60 /* Battery DesignCapacity Low byte. */ +#define INDEX_BATTERY_DC_HIGH 0x61 /* Battery DesignCapacity High byte. */ +#define INDEX_BATTERY_DV_LOW 0x62 /* Battery DesignVoltage Low byte. */ +#define INDEX_BATTERY_DV_HIGH 0x63 /* Battery DesignVoltage High byte. */ +#define INDEX_BATTERY_MFD_LOW 0x64 /* Battery ManufactureDate Low byte. */ +#define INDEX_BATTERY_MFD_HIGH 0x65 /* Battery ManufactureDate High byte. */ +#define INDEX_BATTERY_SN_LOW 0x66 /* Battery SerialNumber Low byte. */ +#define INDEX_BATTERY_SN_HIGH 0x67 /* Battery SerialNumber High byte. */ +#define INDEX_BATTERY_MFN_LENG 0x68 /* Battery ManufacturerName string length. */ +#define INDEX_BATTERY_MFN_START 0x69 /* Battery ManufacturerName string start byte. */ + +#define INDEX_BATTERY_DEVNAME_LENG 0x74 /* Battery DeviceName string length. */ +#define INDEX_BATTERY_DEVNAME_START 0x75 /* Battery DeviceName string start byte. */ +#define INDEX_BATTERY_DEVCHEM_LENG 0x7C /* Battery DeviceChemitry string length. */ +#define INDEX_BATTERY_DEVCHEM_START 0x7D /* Battery DeviceChemitry string start byte. */ +#define INDEX_BATTERY_MFINFO_LENG 0x81 /* Battery ManufacturerInfo string length. */ +#define INDEX_BATTERY_MFINFO_START 0x82 /* Battery ManufacturerInfo string start byte. */ +#define INDEX_BATTERY_CELLCNT_START 0x95 /* Battery packaging fashion string start byte(=4). Unit: ASCII. */ +#define BATTERY_CELLCNT_LENG 4 /* Battery packaging fashion string size. */ +#define FLAG_BAT_CELL_3S1P "3S1P" + +#define BIT_BATTERY_CURRENT_PN 7 /* Battery current sign is positive or negative */ +#define BIT_BATTERY_CURRENT_PIN 0x07 /* Battery current sign is positive or negative.*/ +/* <<< End Battery */ + +#define MASK(x) (1 << x) + +#define INDEX_STOPCHG_STATUS 0xA1 /* Read currently stop charge status. */ +enum +{ + BIT_STOPCHG_FULLYCHG = 0, + BIT_STOPCHG_TIMEOUT, + BIT_STOPCHG_OVERTEMP, + BIT_STOPCHG_OVERVOLT, + BIT_STOPCHG_OVERCURRENT, + BIT_STOPCHG_TERMINATE +}; +#define INDEX_POWER_STATUS 0xA2 /* Read current power status. */ +enum +{ + BIT_POWER_PWRON = 0, /* Power-on start status, 1 = on power-on, 0 = power-on complete. */ + BIT_POWER_BATVL, /* Battery in very low status. */ + BIT_POWER_BATL, /* Battery in low status. */ + BIT_POWER_BATFCHG, /* Battery in fully charging status. */ + BIT_POWER_BATCHG, /* Battery in charging status. */ + BIT_POWER_TERMINATE, /* Battery in terminate charging status. */ + BIT_POWER_BATPRES, /* Battery present. */ + BIT_POWER_ACPRES /* AC present. */ +}; + +#define INDEX_DEVICE_STATUS 0xA3 /* Read Current Device Status */ +enum +{ + BIT_DEVICE_TP = 0, /* TouchPad status: 0 = close, 1 = open */ + BIT_DEVICE_WLAN, /* WLAN status: 0 = close, 1 = open */ + BIT_DEVICE_3G, /* 3G status: 0 = close, 1 = open */ + BIT_DEVICE_CAM, /* Camera status: 0 = close, 1 = open */ + BIT_DEVICE_MUTE, /* Mute status: 0 = close, 1 = open */ + BIT_DEVICE_LID, /* LID status: 0 = close, 1 = open */ + BIT_DEVICE_BKLIGHT, /* BackLight status: 0 = close, 1 = open */ + BIT_DEVICE_SIM /* SIM Card status: 0 = pull out, 1 = insert */ +}; + +#define INDEX_SHUTDOWN_ID 0xA4 /* Read Shutdown ID */ +enum +{ + BIT_SHUTDNID_S45 = 0, /* in S4 or S5 */ + BIT_SHUTDNID_BATDEAD, /* Battery Dead */ + BIT_SHUTDNID_OVERHEAT, /* Over Heat */ + BIT_SHUTDNID_SYSCMD, /* System command */ + BIT_SHUTDNID_LPRESSPWN, /* Long press power button */ + BIT_SHUTDNID_PWRUNDER9V,/* Batery voltage low under 9V */ + BIT_SHUTDNID_S3, /* Entry S3 state */ + BIT_SHUTDNID_S1 /* Entry S1 state */ +}; + +#define INDEX_SYSTEM_CFG 0xA5 /* Read System config */ +#define BIT_SYSCFG_TPSWITCH (1 << 0) /* TouchPad switch */ +#define BIT_SYSCFG_WLANPRES (1 << 1) /* WLAN present */ +#define BIT_SYSCFG_NB3GPRES (1 << 2) /* 3G present */ +#define BIT_SYSCFG_CAMERAPRES (1 << 3) /* Camera Present */ +#define BIT_SYSCFG_VOLCTRLEC (1 << 4) /* Volume control by EC */ +#define BIT_SYSCFG_BLCTRLEC (1 << 5) /* Backlight control by EC */ +#define BIT_SYSCFG_AUTOBRIGHT (1 << 7) /* Auto brightness */ + +#define INDEX_VOLUME_LEVEL 0xA6 /* Read Volume Level command */ +#define INDEX_VOLUME_MAXLEVEL 0xA7 /* Volume MaxLevel */ +#define VOLUME_MAX_LEVEL 0x0A /* Volume level max is 11 */ +enum +{ + FLAG_VOLUME_LEVEL_0 = 0, + FLAG_VOLUME_LEVEL_1, + FLAG_VOLUME_LEVEL_2, + FLAG_VOLUME_LEVEL_3, + FLAG_VOLUME_LEVEL_4, + FLAG_VOLUME_LEVEL_5, + FLAG_VOLUME_LEVEL_6, + FLAG_VOLUME_LEVEL_7, + FLAG_VOLUME_LEVEL_8, + FLAG_VOLUME_LEVEL_9, + FLAG_VOLUME_LEVEL_10 +}; + +/* Camera control */ +#define INDEX_CAM_STSCTRL 0xAA +enum +{ + CAM_STSCTRL_OFF = 0, + CAM_STSCTRL_ON +}; + +/* data destroy led control */ +#define INDEX_DATA_DESTROY 0xB0 +enum +{ + DATA_DESTROY_OFF = 0, + DATA_DESTROY_ON +}; + +/* The led of board healthy */ +#define INDEX_BOARD_HEALTHY 0xB1 + +/* EC_SC input */ +/* EC Status query, by direct read 66h port. */ +#define EC_SMI_EVT (1 << 6) /* 1 = SMI event padding */ +#define EC_SCI_EVT (1 << 5) /* 1 = SCI event padding */ +#define EC_BURST (1 << 4) /* 1 = Controller is in burst mode */ +#define EC_CMD (1 << 3) /* 1 = Byte in data register is command */ + +#define EC_IBF (1 << 1) /* 1 = Input buffer full (data ready for ec) */ +#define EC_OBF (1 << 0) /* 1 = Output buffer full (data ready for host) */ + +/* SCI Event Number from EC */ +enum +{ + SCI_EVENT_NUM_WLAN = 0x21, /* 0x21, Fn+F1, Wlan is on or off */ + SCI_EVENT_NUM_3G, /* 0x22, Fn+F9 for 3G switch */ + SCI_EVENT_NUM_LID, /* 0x23, press the lid or not */ + SCI_EVENT_NUM_DISPLAY_TOGGLE, /* 0x24, Fn+F8 for display switch */ + SCI_EVENT_NUM_SLEEP, /* 0x25, Fn+ESC for entering sleep mode */ + SCI_EVENT_NUM_BRIGHTNESS_UP, /* 0x26, Fn+F3, LCD backlight brightness up adjust */ + SCI_EVENT_NUM_BRIGHTNESS_DN, /* 0x27, Fn+F2, LCD backlight brightness down adjust */ + SCI_EVENT_NUM_CAMERA, /* 0x28, Fn+F10, Camera is on or off */ + SCI_EVENT_NUM_TP, /* 0x29, Fn+F11, TouchPad is on */ + SCI_EVENT_NUM_AUDIO_MUTE, /* 0x2A, Fn+F4, Mute is on or off */ + SCI_EVENT_NUM_BLACK_SCREEN, /* 0x2B, Fn+F7, Black screen is on or off */ + SCI_EVENT_NUM_VOLUME_UP, /* 0x2C, Fn+F6, Volume up adjust */ + SCI_EVENT_NUM_VOLUME_DN, /* 0x2D, Fn+F5, Volume down adjust */ + SCI_EVENT_NUM_OVERTEMP, /* 0x2E, Over-temperature happened */ + SCI_EVENT_NUM_SIM, /* 0x2F, SIM Card Detect */ + SCI_EVENT_NUM_AC, /* 0x30, AC in/out */ + SCI_EVENT_NUM_BAT, /* 0x31, BAT in/out */ + SCI_EVENT_NUM_BATL, /* 0x32, Battery Low capacity alarm, < 10% */ + SCI_EVENT_NUM_BATVL, /* 0x33, Battery VeryLow capacity alarm, < 5% */ + SCI_EVENT_NUM_THROT, /* 0x34, CPU Throttling event alarm, CPU Temperature > 85 or < 80. */ + SCI_EVENT_NUM_POWER = 0x37, /* 0x37, Power button */ + SCI_EVENT_RESOLUTION_SETTING, /* 0x38, Resolution Setting */ + SCI_EVENT_MEDIA_RUN_PAUSE, /* 0x39, Media Play or Pause */ + SCI_EVENT_MEDIA_STOP, /* 0x3A, Media Stop */ + SCI_EVENT_MEDIA_LAST, /* 0x3B, Media Play last one */ + SCI_EVENT_MEDIA_NEXT, /* 0x3C, Media Play next one */ + SCI_EVENT_RECOVERY = 0x3D /* 0x3D, Recovery Event */ +}; + +#define SCI_EVENT_NUM_START SCI_EVENT_NUM_WLAN +#define SCI_EVENT_NUM_END SCI_EVENT_RECOVERY + +extern unsigned char app_access_ec_flag; + +typedef int (*sci_handler)(int status); + +/* The general ec index-io port read action */ +extern unsigned char ec_read(unsigned char index); +extern unsigned char ec_read_all(unsigned char command, unsigned char index); +extern unsigned char ec_read_noindex(unsigned char command); + +/* The general ec index-io port write action */ +extern int ec_write(unsigned char index, unsigned char data); +extern int ec_write_all(unsigned char command, unsigned char index, unsigned char data); +extern int ec_write_noindex(unsigned char command, unsigned char data); + +/* Query sequence of 62/66 port access routine. */ +extern int ec_query_seq(unsigned char command); +extern int ec_query_get_event_num(void); +extern int ec_get_event_num(void); + +extern void clean_ec_event_status(void); + +#endif /* __EC_WPCE775L_H__ */ diff --git a/arch/mips/include/asm/mach-loongson64/ioremap.h b/arch/mips/include/asm/mach-loongson64/ioremap.h new file mode 100644 index 0000000000000..155dafe2ef346 --- /dev/null +++ b/arch/mips/include/asm/mach-loongson64/ioremap.h @@ -0,0 +1,44 @@ +/* + * include/asm-mips/mach-loongson64/ioremap.h + * + * 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. + */ +#ifndef __ASM_MACH_LOONGSON_IOREMAP_H +#define __ASM_MACH_LOONGSON_IOREMAP_H + +#include + +/* + * Allow physical addresses to be fixed up to help peripherals located + * outside the low 32-bit range -- generic pass-through version. + */ +static inline phys_addr_t fixup_bigphys_addr(phys_addr_t phys_addr, phys_addr_t size) +{ + return phys_addr; +} + +/* Add support for uncached accelerate */ +static inline void __iomem *plat_ioremap(phys_addr_t offset, unsigned long size, + unsigned long flags) +{ +#ifdef CONFIG_64BIT +#define LOONGSON_UNCACHED_ACCEL_BASE _AC(0xb800000000000000, UL) + u64 base = LOONGSON_UNCACHED_ACCEL_BASE; + + if (flags == _CACHE_UNCACHED_ACCELERATED && + offset + size < XKSEG - base) { + return (void __iomem *)(unsigned long) (base + offset); + } +#endif + return NULL; +} + +static inline int plat_iounmap(const volatile void __iomem *addr) +{ + return 0; +} + +#endif /* __ASM_MACH_LOONGSON_IOREMAP_H */ diff --git a/arch/mips/include/asm/mach-loongson64/loongson.h b/arch/mips/include/asm/mach-loongson64/loongson.h index f7c3ab6d724e2..70399b6cd2aee 100644 --- a/arch/mips/include/asm/mach-loongson64/loongson.h +++ b/arch/mips/include/asm/mach-loongson64/loongson.h @@ -263,4 +263,9 @@ extern u64 loongson_freqctrl[MAX_PACKAGES]; #define LOONGSON_PCIMAP_WIN(WIN, ADDR) \ ((((ADDR)>>26) & LOONGSON_PCIMAP_PCIMAP_LO0) << ((WIN)*6)) +#ifdef CONFIG_CPU_SUPPORTS_CPUFREQ +#include +extern struct cpufreq_frequency_table gs464_clockmod_table[]; +#endif + #endif /* __ASM_MACH_LOONGSON64_LOONGSON_H */ diff --git a/arch/mips/include/asm/mach-loongson64/loongson_hwmon.h b/arch/mips/include/asm/mach-loongson64/loongson_hwmon.h index 545f91f2ae16a..c1fa61ad483b9 100644 --- a/arch/mips/include/asm/mach-loongson64/loongson_hwmon.h +++ b/arch/mips/include/asm/mach-loongson64/loongson_hwmon.h @@ -36,7 +36,7 @@ struct temp_range { struct loongson_fan_policy { u8 type; - /* percent only used when type is CONSTANT_SPEED_POLICY */ + /* CONSTANT_SPEED_POLICY: actual perenct, STEP_SPEED_POLICY: maximum percent */ u8 percent; /* period between two check. (Unit: S) */ @@ -53,4 +53,8 @@ struct loongson_fan_policy { struct delayed_work work; }; +extern struct loongson_fan_policy kernel_helper_policy; +extern struct loongson_fan_policy step_speed_policy; +extern struct loongson_fan_policy constant_speed_policy; + #endif /* __LOONGSON_HWMON_H_*/ diff --git a/arch/mips/include/asm/mach-loongson64/loongson_regs.h b/arch/mips/include/asm/mach-loongson64/loongson_regs.h index b5be7511f6cde..e7412535e6408 100644 --- a/arch/mips/include/asm/mach-loongson64/loongson_regs.h +++ b/arch/mips/include/asm/mach-loongson64/loongson_regs.h @@ -131,6 +131,9 @@ static inline u32 read_cpucfg(u32 reg) #define LOONGSON_CFG7_GCCAEQRP BIT(0) #define LOONGSON_CFG7_UCAWINP BIT(1) +#define CSR_TO_RAW_ADDR(cpu, csr) ((0x900000003ff00000 | csr | \ + (((u64)cpu & 0xc) << 42)) | (((u64)cpu & 0x3) << 8)) + static inline bool cpu_has_csr(void) { if (cpu_has_cfg()) @@ -225,7 +228,9 @@ static inline void csr_writeq(u64 val, u32 reg) #define LOONGSON_CSR_VENDOR 0x10 /* Vendor name string, should be "Loongson" */ #define LOONGSON_CSR_CPUNAME 0x20 /* Processor name string */ #define LOONGSON_CSR_NODECNT 0x408 +#define LOONGSON_CSR_SMCINT 0x420 #define LOONGSON_CSR_CPUTEMP 0x428 +#define LOONGSON_CSR_SMCMBX 0x51c /* PerCore CSR, only accessable by local cores */ #define LOONGSON_CSR_IPI_STATUS 0x1000 @@ -247,6 +252,12 @@ static inline void csr_writeq(u64 val, u32 reg) #define CSR_MAIL_SEND_BUF_SHIFT 32 #define CSR_MAIL_SEND_H32_MASK 0xFFFFFFFF00000000ULL +#define LOONGSON_CSR_TIMER_CFG 0x1060 +#define LOONGSON_CSR_TIMER_TICK 0x1070 +#define CONSTANT_TIMER_CFG_PERIODIC (_ULCAST_(1) << 62) +#define CONSTANT_TIMER_CFG_EN (_ULCAST_(1) << 61) +#define CONSTANT_TIMER_INITVAL_RESET (_ULCAST_(0xffff) << 48) + static inline u64 drdtime(void) { int rID = 0; @@ -265,4 +276,25 @@ static inline u64 drdtime(void) return val; } +static inline unsigned int calc_const_freq(void) +{ + unsigned int res; + unsigned int base_freq; + unsigned int cfm, cfd; + + res = read_cpucfg(LOONGSON_CFG2); + if (!(res & LOONGSON_CFG2_LLFTP)) + return 0; + + res = read_cpucfg(LOONGSON_CFG5); + cfm = res & 0xffff; + cfd = (res >> 16) & 0xffff; + base_freq = read_cpucfg(LOONGSON_CFG4); + + if (!base_freq || !cfm || !cfd) + return 0; + else + return (base_freq * cfm / cfd); +} + #endif diff --git a/arch/mips/include/asm/mach-loongson64/loongson_smc.h b/arch/mips/include/asm/mach-loongson64/loongson_smc.h new file mode 100644 index 0000000000000..c5af9cd25aee0 --- /dev/null +++ b/arch/mips/include/asm/mach-loongson64/loongson_smc.h @@ -0,0 +1,149 @@ +#ifndef _LOONGSON_SMC_H_ +#define _LOONGSON_SMC_H_ + +union smc_message { + u32 value; + struct { + u32 arg : 24; + u8 cmd : 7; /* Return 0x7f if command failed */ + u8 complete : 1; + }; +}; + +/* Commands in SMC mailbox registers */ + +#define CMD_GET_VERSION 0x1 +/* Interface Version, input none, return version */ + +/* Features */ +#define CMD_GET_FEATURES 0x2 +/* Get features that SMC implemented, input index, output feature flags */ +#define CMD_GET_ENABLED_FEATURES 0x3 +/* Get currently enabled features, input index, output feature flags */ +#define CMD_SET_ENABLED_FEATURES 0x4 +/* Set features enabled state, input index and flags, output sucessfully enabled flags */ +struct feature_args { + u16 flags : 16; + u8 index : 8; +}; + +#define FEATURE_INDEX_GENERAL 0x0 +#define FEATURE_INDEX_ADVANCED 0x1 + +/* General Feature Flags */ +#define FEATURE_FREQ_SCALE (1 << 0) +#define FEATURE_VOLTAGE_SCALE (1 << 1) +#define FEATURE_BOOST (1 << 2) /* Enable Boost means set PLL from normal to higher */ +#define FEATURE_SENSORS (1 << 3) /* Temperature/Voltage sensors */ +#define FEATURE_FAN_CONTROL (1 << 4) + +/* Advanced Features Flags */ +#define FEATURE_ADJUST_DVFS_PARAM (1 << 0) /* Allow OS adjust FreqScale/VID of each level. */ + +/* Freqscale Related */ +#define CMD_GET_CPU_FREQUENCY 0x5 +/* Input CPUNum, output frequency, in MHz */ +#define CMD_GET_FREQ_LEVELS 0x6 +/* Input none, output freq levels */ +struct freq_level_args { + u8 min_level : 8; + u8 max_normal_level : 8; + u8 max_boost_level : 8; +}; + +#define CMD_GET_FREQ_INFO 0x7 +/* Input index and level, output info */ +#define CMD_SET_FREQ_INFO 0x8 +/* Input index and info, output none, available only with FEATURE_ADJUST_DVFS_PARAM */ +#define FREQ_INFO_INDEX_FREQ 0x0 /* Freq in MHz */ +struct freq_info_args { + u16 info : 16; + u8 index : 8; +}; + +#define CMD_SET_CPU_LEVEL 0x9 +/* Input cpu mask and level, output none */ +/* + * Note: This command return as completed only means + * SMC already knows the request, does not means the + * CPU freqency have changed. SMC should ensure constant + * counter frequency unchanged. + */ +struct freq_level_setting_args { + u16 cpumask : 16; + u8 level : 8; +}; + +/* TEMP Sensors */ +#define CMD_GET_SENSOR_NUM 0x10 +/* Input none, output Number of sensors in u4 */ + +#define CMD_GET_SENSOR_STATUS 0x11 +/* Input sensor_id and info_type, output info */ +#define SENSOR_INFO_TYPE_TEMP 0x0 +#define SENSOR_INFO_TYPE_VOLTAGE 0x1 +#define SENSOR_INFO_TYPE_FLAGS 0xf +#define SENSOR_FLAG_TEMP (1 << 0) +#define SENSOR_FLAG_VOLTAGE (1 << 1) +struct sensor_info_args { + union { + u16 val; + s16 temp; /* Signed 16bit, in Celsius */ + }; + u8 info_type : 4; + u8 sensor_id : 4; +}; + +/* Fan Control */ +#define CMD_GET_FAN_NUM 0x12 +/* Input none, output Number of fans in u4 */ + +#define CMD_GET_FAN_INFO 0x13 +/* Input sensor_id and info_type, output info */ +#define CMD_SET_FAN_INFO 0x14 +/* Input sensor_id and info_type info, output none */ +#define FAN_INFO_RPM 0x0 /* Return RPM, can not set */ +#define FAN_INFO_LEVEL 0x1 /* PWM Level, 0~255, only set with manual mode */ +#define FAN_INFO_FLAGS 0xf /* Determine Mode */ +#define FAN_FLAG_AUTO (1 << 0) +#define FAN_FLAG_MANUAL (1 << 1) + +struct fan_info_args { + u16 val; + u8 info_type : 4; + u8 fan_id : 4; +}; + +static inline int do_service_request(u8 cmd, void *arg) +{ + int retries; + union smc_message msg; + + msg.value = csr_readl(LOONGSON_CSR_SMCMBX); + if (!msg.complete) + return -1; + + msg.cmd = cmd; + msg.arg = *(u32 *)arg; + msg.complete = 0x0; + + csr_writel(msg.value, LOONGSON_CSR_SMCMBX); + csr_writel(csr_readl(LOONGSON_CSR_SMCINT) | 1<<10, LOONGSON_CSR_SMCINT); + + for (retries = 0; retries < 10000; retries++) { + msg.value = csr_readl(LOONGSON_CSR_SMCMBX); + if (msg.complete) + break; + + usleep_range(2, 5); + } + + if (!msg.complete || msg.cmd == 0x7f) + return -1; + + *(u32 *)arg = msg.arg; + + return 0; +} + +#endif diff --git a/arch/mips/include/asm/mach-loongson64/workarounds.h b/arch/mips/include/asm/mach-loongson64/workarounds.h index 17b71172a0971..296a97fdd0c00 100644 --- a/arch/mips/include/asm/mach-loongson64/workarounds.h +++ b/arch/mips/include/asm/mach-loongson64/workarounds.h @@ -4,5 +4,10 @@ #define WORKAROUND_CPUFREQ 0x00000001 #define WORKAROUND_CPUHOTPLUG 0x00000002 +#define WORKAROUND_USB_TMCS 0x00000010 + +void gpio_lvds_off(void); +void turn_off_lvds(void); +void turn_on_lvds(void); #endif diff --git a/arch/mips/include/asm/mipsregs.h b/arch/mips/include/asm/mipsregs.h index 2d53704d9f246..879ab0ad156da 100644 --- a/arch/mips/include/asm/mipsregs.h +++ b/arch/mips/include/asm/mipsregs.h @@ -693,6 +693,7 @@ #define MTI_CONF6_SYND (_ULCAST_(1) << 13) /* Sleep state performance counter disable */ #define MTI_CONF6_SPCD (_ULCAST_(1) << 14) + /* proAptiv FTLB on/off bit */ #define MTI_CONF6_FTLBEN (_ULCAST_(1) << 15) /* Disable load/store bonding */ @@ -2078,7 +2079,14 @@ do { \ _ASM_INSN_IF_MIPS(0x4200000c) \ _ASM_INSN32_IF_MM(0x0000517c) #else /* !TOOLCHAIN_SUPPORTS_VIRT */ -#define _ASM_SET_VIRT ".set\tvirt\n\t" +#if MIPS_ISA_REV >= 5 +#define _ASM_SET_VIRT_ISA +#elif defined(CONFIG_64BIT) +#define _ASM_SET_VIRT_ISA ".set\tmips64r5\n\t" +#else +#define _ASM_SET_VIRT_ISA ".set\tmips32r5\n\t" +#endif +#define _ASM_SET_VIRT _ASM_SET_VIRT_ISA ".set\tvirt\n\t" #define _ASM_SET_MFGC0 _ASM_SET_VIRT #define _ASM_SET_DMFGC0 _ASM_SET_VIRT #define _ASM_SET_MTGC0 _ASM_SET_VIRT @@ -2099,7 +2107,6 @@ do { \ ({ int __res; \ __asm__ __volatile__( \ ".set\tpush\n\t" \ - ".set\tmips32r5\n\t" \ _ASM_SET_MFGC0 \ "mfgc0\t%0, " #source ", %1\n\t" \ _ASM_UNSET_MFGC0 \ @@ -2113,7 +2120,6 @@ do { \ ({ unsigned long long __res; \ __asm__ __volatile__( \ ".set\tpush\n\t" \ - ".set\tmips64r5\n\t" \ _ASM_SET_DMFGC0 \ "dmfgc0\t%0, " #source ", %1\n\t" \ _ASM_UNSET_DMFGC0 \ @@ -2127,7 +2133,6 @@ do { \ do { \ __asm__ __volatile__( \ ".set\tpush\n\t" \ - ".set\tmips32r5\n\t" \ _ASM_SET_MTGC0 \ "mtgc0\t%z0, " #register ", %1\n\t" \ _ASM_UNSET_MTGC0 \ @@ -2140,7 +2145,6 @@ do { \ do { \ __asm__ __volatile__( \ ".set\tpush\n\t" \ - ".set\tmips64r5\n\t" \ _ASM_SET_DMTGC0 \ "dmtgc0\t%z0, " #register ", %1\n\t" \ _ASM_UNSET_DMTGC0 \ diff --git a/arch/mips/include/asm/pgtable-64.h b/arch/mips/include/asm/pgtable-64.h index 20ca48c1b6063..91ba82e605ecc 100644 --- a/arch/mips/include/asm/pgtable-64.h +++ b/arch/mips/include/asm/pgtable-64.h @@ -260,7 +260,7 @@ static inline int pmd_present(pmd_t pmd) { #ifdef CONFIG_MIPS_HUGE_TLB_SUPPORT if (unlikely(pmd_val(pmd) & _PAGE_HUGE)) - return pmd_val(pmd) & _PAGE_PRESENT; + return pmd_val(pmd) & (_PAGE_PRESENT | _PAGE_PROTNONE); #endif return pmd_val(pmd) != (unsigned long) invalid_pte_table; diff --git a/arch/mips/include/asm/pgtable-bits.h b/arch/mips/include/asm/pgtable-bits.h index 421e78c30253c..a5302fdd39267 100644 --- a/arch/mips/include/asm/pgtable-bits.h +++ b/arch/mips/include/asm/pgtable-bits.h @@ -52,6 +52,9 @@ enum pgtable_bits { _PAGE_WRITE_SHIFT, _PAGE_ACCESSED_SHIFT, _PAGE_MODIFIED_SHIFT, +#if defined(CONFIG_ARCH_SUPPORTS_NUMA_BALANCING) + _PAGE_PROTNONE_SHIFT, +#endif #if defined(CONFIG_ARCH_HAS_PTE_SPECIAL) _PAGE_SPECIAL_SHIFT, #endif @@ -84,6 +87,9 @@ enum pgtable_bits { _PAGE_WRITE_SHIFT, _PAGE_ACCESSED_SHIFT, _PAGE_MODIFIED_SHIFT, +#if defined(CONFIG_ARCH_SUPPORTS_NUMA_BALANCING) + _PAGE_PROTNONE_SHIFT, +#endif #if defined(CONFIG_ARCH_HAS_PTE_SPECIAL) _PAGE_SPECIAL_SHIFT, #endif @@ -102,6 +108,9 @@ enum pgtable_bits { _PAGE_WRITE_SHIFT, _PAGE_ACCESSED_SHIFT, _PAGE_MODIFIED_SHIFT, +#if defined(CONFIG_ARCH_SUPPORTS_NUMA_BALANCING) + _PAGE_PROTNONE_SHIFT, +#endif #if defined(CONFIG_ARCH_HAS_PTE_SPECIAL) _PAGE_SPECIAL_SHIFT, #endif @@ -131,6 +140,9 @@ enum pgtable_bits { #if defined(CONFIG_MIPS_HUGE_TLB_SUPPORT) _PAGE_HUGE_SHIFT, #endif +#if defined(CONFIG_ARCH_SUPPORTS_NUMA_BALANCING) + _PAGE_PROTNONE_SHIFT, +#endif #if defined(CONFIG_ARCH_HAS_PTE_SPECIAL) _PAGE_SPECIAL_SHIFT, #endif @@ -158,6 +170,11 @@ enum pgtable_bits { #if defined(CONFIG_MIPS_HUGE_TLB_SUPPORT) # define _PAGE_HUGE (1 << _PAGE_HUGE_SHIFT) #endif +#if defined(CONFIG_ARCH_SUPPORTS_NUMA_BALANCING) +# define _PAGE_PROTNONE (1 <<_PAGE_PROTNONE_SHIFT) +#else +# define _PAGE_PROTNONE 0 +#endif #if defined(CONFIG_ARCH_HAS_PTE_SPECIAL) # define _PAGE_SPECIAL (1 << _PAGE_SPECIAL_SHIFT) #else diff --git a/arch/mips/include/asm/pgtable.h b/arch/mips/include/asm/pgtable.h index 430b208c01307..d44e51d5d9e9c 100644 --- a/arch/mips/include/asm/pgtable.h +++ b/arch/mips/include/asm/pgtable.h @@ -27,6 +27,8 @@ struct vm_area_struct; #define PAGE_SHARED vm_get_page_prot(VM_READ|VM_WRITE|VM_SHARED) +#define PAGE_NONE __pgprot(_PAGE_PROTNONE | _PAGE_NO_READ | \ + _page_cachable_default) #define PAGE_KERNEL __pgprot(_PAGE_PRESENT | __READABLE | __WRITEABLE | \ _PAGE_GLOBAL | _page_cachable_default) #define PAGE_KERNEL_NC __pgprot(_PAGE_PRESENT | __READABLE | __WRITEABLE | \ @@ -160,7 +162,7 @@ static inline void pte_clear(struct mm_struct *mm, unsigned long addr, pte_t *pt #else #define pte_none(pte) (!(pte_val(pte) & ~_PAGE_GLOBAL)) -#define pte_present(pte) (pte_val(pte) & _PAGE_PRESENT) +#define pte_present(pte) (pte_val(pte) & (_PAGE_PRESENT | _PAGE_PROTNONE)) #define pte_no_exec(pte) (pte_val(pte) & _PAGE_NO_EXEC) /* @@ -730,7 +732,7 @@ static inline pmd_t pmd_modify(pmd_t pmd, pgprot_t newprot) static inline pmd_t pmd_mkinvalid(pmd_t pmd) { - pmd_val(pmd) &= ~(_PAGE_PRESENT | _PAGE_VALID | _PAGE_DIRTY); + pmd_val(pmd) &= ~(_PAGE_PRESENT | _PAGE_VALID | _PAGE_PROTNONE | _PAGE_DIRTY); return pmd; } @@ -752,6 +754,18 @@ static inline pmd_t pmdp_huge_get_and_clear(struct mm_struct *mm, #endif /* CONFIG_TRANSPARENT_HUGEPAGE */ +#ifdef CONFIG_NUMA_BALANCING +static inline long pte_protnone(pte_t pte) +{ + return (pte_val(pte) & _PAGE_PROTNONE); +} + +static inline long pmd_protnone(pmd_t pmd) +{ + return (pmd_val(pmd) & _PAGE_PROTNONE); +} +#endif /* CONFIG_NUMA_BALANCING */ + #ifdef _PAGE_HUGE #define pmd_leaf(pmd) ((pmd_val(pmd) & _PAGE_HUGE) != 0) #define pud_leaf(pud) ((pud_val(pud) & _PAGE_HUGE) != 0) diff --git a/arch/mips/include/asm/pm.h b/arch/mips/include/asm/pm.h index 10bb7b6407383..7ecd4dfe38461 100644 --- a/arch/mips/include/asm/pm.h +++ b/arch/mips/include/asm/pm.h @@ -17,7 +17,7 @@ /* Save CPU state to stack for suspend to RAM */ .macro SUSPEND_SAVE_REGS - subu sp, PT_SIZE + PTR_SUBU sp, PT_SIZE /* Call preserved GPRs */ LONG_S $16, PT_R16(sp) LONG_S $17, PT_R17(sp) @@ -56,13 +56,13 @@ LONG_L $31, PT_R31(sp) /* Pop and return */ jr ra - addiu sp, PT_SIZE + PTR_ADDIU sp, PT_SIZE .set pop .endm /* Get address of static suspend state into t1 */ .macro LA_STATIC_SUSPEND - la t1, mips_static_suspend_state + PTR_LA t1, mips_static_suspend_state .endm /* Save important CPU state for early restoration to global data */ @@ -72,11 +72,11 @@ * Segment configuration is saved in global data where it can be easily * reloaded without depending on the segment configuration. */ - mfc0 k0, CP0_PAGEMASK, 2 /* SegCtl0 */ + mfc0 k0, CP0_SEGCTL0 LONG_S k0, SSS_SEGCTL0(t1) - mfc0 k0, CP0_PAGEMASK, 3 /* SegCtl1 */ + mfc0 k0, CP0_SEGCTL1 LONG_S k0, SSS_SEGCTL1(t1) - mfc0 k0, CP0_PAGEMASK, 4 /* SegCtl2 */ + mfc0 k0, CP0_SEGCTL2 LONG_S k0, SSS_SEGCTL2(t1) #endif /* save stack pointer (pointing to GPRs) */ @@ -92,11 +92,11 @@ * segments. */ LONG_L k0, SSS_SEGCTL0(t1) - mtc0 k0, CP0_PAGEMASK, 2 /* SegCtl0 */ + mtc0 k0, CP0_SEGCTL0 LONG_L k0, SSS_SEGCTL1(t1) - mtc0 k0, CP0_PAGEMASK, 3 /* SegCtl1 */ + mtc0 k0, CP0_SEGCTL1 LONG_L k0, SSS_SEGCTL2(t1) - mtc0 k0, CP0_PAGEMASK, 4 /* SegCtl2 */ + mtc0 k0, CP0_SEGCTL2 tlbw_use_hazard #endif /* restore stack pointer (pointing to GPRs) */ @@ -105,10 +105,10 @@ /* flush caches to make sure context has reached memory */ .macro SUSPEND_CACHE_FLUSH - .extern __wback_cache_all + .extern __flush_cache_all .set push .set noreorder - la t1, __wback_cache_all + PTR_LA t1, __flush_cache_all LONG_L t0, 0(t1) jalr t0 nop diff --git a/arch/mips/include/asm/serial.h b/arch/mips/include/asm/serial.h new file mode 100644 index 0000000000000..2777148dbfc5a --- /dev/null +++ b/arch/mips/include/asm/serial.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2017 MIPS Tech, LLC + */ +#ifndef __ASM__SERIAL_H +#define __ASM__SERIAL_H + +#ifdef CONFIG_MIPS_GENERIC +/* + * Generic kernels cannot know a correct value for all platforms at + * compile time. Set it to 0 to prevent 8250_early using it + */ +#define BASE_BAUD 0 +#else +#include +#endif + +#endif /* __ASM__SERIAL_H */ diff --git a/arch/mips/include/asm/time.h b/arch/mips/include/asm/time.h index e855a3611d922..8a91ad684fc87 100644 --- a/arch/mips/include/asm/time.h +++ b/arch/mips/include/asm/time.h @@ -22,6 +22,8 @@ extern spinlock_t rtc_lock; */ extern void plat_time_init(void); +extern unsigned int mips_cpu_frequency; + /* * mips_hpt_frequency - must be set if you intend to use an R4k-compatible * counter as a timer interrupt source. @@ -40,11 +42,19 @@ extern int __weak get_c0_perfcount_int(void); */ extern unsigned int get_c0_compare_int(void); extern int r4k_clockevent_init(void); +#ifdef CONFIG_CPU_LOONGSON64 +extern int constant_clockevent_init(void); +#else +static inline int constant_clockevent_init(void) { return 0; } +#endif static inline int mips_clockevent_init(void) { #ifdef CONFIG_CEVT_R4K - return r4k_clockevent_init(); + if (!cpu_has_constant_timer) + return r4k_clockevent_init(); + else + return constant_clockevent_init(); #else return -ENXIO; #endif @@ -54,11 +64,19 @@ static inline int mips_clockevent_init(void) * Initialize the count register as a clocksource */ extern int init_r4k_clocksource(void); +#ifdef CONFIG_CPU_LOONGSON64 +extern int init_constant_clocksource(void); +#else +static inline int init_constant_clocksource(void) { return 0; } +#endif static inline int init_mips_clocksource(void) { #ifdef CONFIG_CSRC_R4K - return init_r4k_clocksource(); + if (!cpu_has_constant_timer) + return init_r4k_clocksource(); + else + return init_constant_clocksource(); #else return 0; #endif diff --git a/arch/mips/include/asm/uasm.h b/arch/mips/include/asm/uasm.h index 296bcf31abb57..06612a106ab4d 100644 --- a/arch/mips/include/asm/uasm.h +++ b/arch/mips/include/asm/uasm.h @@ -42,6 +42,10 @@ void uasm_i##op(u32 **buf, unsigned int a, signed int b, unsigned int c) #define Ip_s3s1s2(op) \ void uasm_i##op(u32 **buf, int a, int b, int c) +#define Ip_u4u2u1s3(op) \ +void uasm_i##op(u32 **buf, unsigned int a, unsigned int b, signed int c, \ + unsigned int d) + #define Ip_u2u1s3(op) \ void uasm_i##op(u32 **buf, unsigned int a, unsigned int b, signed int c) @@ -159,6 +163,7 @@ Ip_u2s3u1(_sd); Ip_u3u1u2(_seleqz); Ip_u3u1u2(_selnez); Ip_u2s3u1(_sh); +Ip_u4u2u1s3(_gssq); Ip_u2u1u3(_sll); Ip_u3u2u1(_sllv); Ip_s3s1s2(_slt); diff --git a/arch/mips/include/asm/vdso/clocksource.h b/arch/mips/include/asm/vdso/clocksource.h index 510e1671d8985..7fd43ca06eb11 100644 --- a/arch/mips/include/asm/vdso/clocksource.h +++ b/arch/mips/include/asm/vdso/clocksource.h @@ -4,6 +4,7 @@ #define VDSO_ARCH_CLOCKMODES \ VDSO_CLOCKMODE_R4K, \ - VDSO_CLOCKMODE_GIC + VDSO_CLOCKMODE_GIC, \ + VDSO_CLOCKMODE_CONST #endif /* __ASM_VDSOCLOCKSOURCE_H */ diff --git a/arch/mips/include/asm/vdso/gettimeofday.h b/arch/mips/include/asm/vdso/gettimeofday.h index 44a45f3fa4b01..c1b15b0f07689 100644 --- a/arch/mips/include/asm/vdso/gettimeofday.h +++ b/arch/mips/include/asm/vdso/gettimeofday.h @@ -183,6 +183,22 @@ static __always_inline u64 read_gic_count(const struct vdso_data *data) #endif +#ifdef CONFIG_CPU_LOONGSON64 +static __always_inline u64 read_const_count(void) +{ + unsigned long count; + + __asm__ __volatile__( + " .set push\n" + " .set mips32r2\n" + " rdhwr %0, $30\n" + " .set pop\n" + : "=r" (count)); + + return count; +} +#endif + static __always_inline u64 __arch_get_hw_counter(s32 clock_mode, const struct vdso_data *vd) { @@ -193,6 +209,10 @@ static __always_inline u64 __arch_get_hw_counter(s32 clock_mode, #ifdef CONFIG_CLKSRC_MIPS_GIC if (clock_mode == VDSO_CLOCKMODE_GIC) return read_gic_count(vd); +#endif +#ifdef CONFIG_CPU_LOONGSON64 + if (clock_mode == VDSO_CLOCKMODE_CONST) + return read_const_count(); #endif /* * Core checks mode already. So this raced against a concurrent diff --git a/arch/mips/include/uapi/asm/inst.h b/arch/mips/include/uapi/asm/inst.h index c29dbc8c1d491..6d41c8bbbdec2 100644 --- a/arch/mips/include/uapi/asm/inst.h +++ b/arch/mips/include/uapi/asm/inst.h @@ -137,6 +137,13 @@ enum ddivu_op { ddivu_dmodu_op = 0x3, }; +/* + * func field of spec opcode. + */ +enum swc2_op { + gssq_op = 0x20, +}; + /* * rt field of bcond opcodes. */ diff --git a/arch/mips/include/uapi/asm/unistd.h b/arch/mips/include/uapi/asm/unistd.h index 4abe387549ad2..251d02e4111be 100644 --- a/arch/mips/include/uapi/asm/unistd.h +++ b/arch/mips/include/uapi/asm/unistd.h @@ -15,25 +15,31 @@ #include -#if _MIPS_SIM == _MIPS_SIM_ABI32 +#if (defined(__WANT_SYSCALL_NUMBERS) && \ + (__WANT_SYSCALL_NUMBERS == _MIPS_SIM_ABI32)) || \ + (!defined(__WANT_SYSCALL_NUMBERS) && _MIPS_SIM == _MIPS_SIM_ABI32) #define __NR_Linux 4000 #include -#endif /* _MIPS_SIM == _MIPS_SIM_ABI32 */ +#endif /* Want O32 || _MIPS_SIM == _MIPS_SIM_ABI32 */ -#if _MIPS_SIM == _MIPS_SIM_ABI64 +#if (defined(__WANT_SYSCALL_NUMBERS) && \ + (__WANT_SYSCALL_NUMBERS == _MIPS_SIM_ABI64)) || \ + (!defined(__WANT_SYSCALL_NUMBERS) && _MIPS_SIM == _MIPS_SIM_ABI64) #define __NR_Linux 5000 #include -#endif /* _MIPS_SIM == _MIPS_SIM_ABI64 */ +#endif /* Want N64 || _MIPS_SIM == _MIPS_SIM_ABI64 */ -#if _MIPS_SIM == _MIPS_SIM_NABI32 +#if (defined(__WANT_SYSCALL_NUMBERS) && \ + (__WANT_SYSCALL_NUMBERS == _MIPS_SIM_NABI32)) || \ + (!defined(__WANT_SYSCALL_NUMBERS) && _MIPS_SIM == _MIPS_SIM_NABI32) #define __NR_Linux 6000 #include -#endif /* _MIPS_SIM == _MIPS_SIM_NABI32 */ +#endif /* Want N32 || _MIPS_SIM == _MIPS_SIM_NABI32 */ #endif /* _UAPI_ASM_UNISTD_H */ diff --git a/arch/mips/kernel/Makefile b/arch/mips/kernel/Makefile index 853a43ee4b446..e7c5ffd504d06 100644 --- a/arch/mips/kernel/Makefile +++ b/arch/mips/kernel/Makefile @@ -111,4 +111,8 @@ obj-$(CONFIG_MIPS_CPC) += mips-cpc.o obj-$(CONFIG_CPU_PM) += pm.o obj-$(CONFIG_MIPS_CPS_PM) += pm-cps.o +obj-$(CONFIG_AUDITSYSCALL_O32) += audit-o32.o +obj-$(CONFIG_AUDITSYSCALL_N32) += audit-n32.o +obj-$(CONFIG_AUDITSYSCALL) += audit-native.o + CPPFLAGS_vmlinux.lds := $(KBUILD_CFLAGS) diff --git a/arch/mips/kernel/audit-n32.c b/arch/mips/kernel/audit-n32.c new file mode 100644 index 0000000000000..76c95ff924f83 --- /dev/null +++ b/arch/mips/kernel/audit-n32.c @@ -0,0 +1,59 @@ +#define __WANT_SYSCALL_NUMBERS _MIPS_SIM_NABI32 + +#include +#include +#include +#include +#include "audit-n32.h" + +static unsigned dir_class_n32[] = { +#include +~0U +}; + +static unsigned read_class_n32[] = { +#include +~0U +}; + +static unsigned write_class_n32[] = { +#include +~0U +}; + +static unsigned chattr_class_n32[] = { +#include +~0U +}; + +static unsigned signal_class_n32[] = { +#include +~0U +}; + +int audit_classify_syscall_n32(int abi, unsigned syscall) +{ + switch (syscall) { + case __NR_open: + return AUDITSC_OPEN; + case __NR_openat: + return AUDITSC_OPENAT; + case __NR_execve: + return AUDITSC_EXECVE; + default: + return AUDITSC_NATIVE; + } +} + +static int __init audit_classes_n32_init(void) +{ + audit_register_class(AUDIT_CLASS_WRITE_N32, write_class_n32); + audit_register_class(AUDIT_CLASS_READ_N32, read_class_n32); + audit_register_class(AUDIT_CLASS_DIR_WRITE_N32, dir_class_n32); + audit_register_class(AUDIT_CLASS_CHATTR_N32, chattr_class_n32); + audit_register_class(AUDIT_CLASS_SIGNAL_N32, signal_class_n32); + + return 0; +} + +__initcall(audit_classes_n32_init); diff --git a/arch/mips/kernel/audit-n32.h b/arch/mips/kernel/audit-n32.h new file mode 100644 index 0000000000000..781de2e9ad66b --- /dev/null +++ b/arch/mips/kernel/audit-n32.h @@ -0,0 +1,5 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifdef CONFIG_AUDITSYSCALL_N32 +int audit_classify_syscall_n32(int abi, unsigned syscall); +#endif /* CONFIG_AUDITSYSCALL_N32 */ diff --git a/arch/mips/kernel/audit-native.c b/arch/mips/kernel/audit-native.c new file mode 100644 index 0000000000000..763f0e6b3636a --- /dev/null +++ b/arch/mips/kernel/audit-native.c @@ -0,0 +1,77 @@ +#include +#include +#include +#include +#include "audit-n32.h" +#include "audit-o32.h" + +static unsigned dir_class[] = { +#include +~0U +}; + +static unsigned read_class[] = { +#include +~0U +}; + +static unsigned write_class[] = { +#include +~0U +}; + +static unsigned chattr_class[] = { +#include +~0U +}; + +static unsigned signal_class[] = { +#include +~0U +}; + + +/* + * Pretend to be a single architecture + */ +int audit_classify_arch(int arch) +{ + return 0; +} + +int audit_classify_syscall(int abi, unsigned syscall) +{ + switch (syscall) { + case __NR_open: + return AUDITSC_OPEN; + case __NR_openat: + return AUDITSC_OPEN; +#ifdef __NR_socketcall /* Only exists on O32 */ + case __NR_socketcall: + return AUDITSC_SOCKETCALL; +#endif + case __NR_execve: + return AUDITSC_EXECVE; + default: +#ifdef CONFIG_AUDITSYSCALL_O32 + return audit_classify_syscall_o32(abi, syscall); +#endif +#ifdef CONFIG_AUDITSYSCALL_N32 + return audit_classify_syscall_n32(abi, syscall); +#endif + return AUDITSC_NATIVE; + } +} + +static int __init audit_classes_init(void) +{ + audit_register_class(AUDIT_CLASS_WRITE, write_class); + audit_register_class(AUDIT_CLASS_READ, read_class); + audit_register_class(AUDIT_CLASS_DIR_WRITE, dir_class); + audit_register_class(AUDIT_CLASS_CHATTR, chattr_class); + audit_register_class(AUDIT_CLASS_SIGNAL, signal_class); + + return 0; +} + +__initcall(audit_classes_init); diff --git a/arch/mips/kernel/audit-o32.c b/arch/mips/kernel/audit-o32.c new file mode 100644 index 0000000000000..818f91ce14acf --- /dev/null +++ b/arch/mips/kernel/audit-o32.c @@ -0,0 +1,61 @@ +#define __WANT_SYSCALL_NUMBERS _MIPS_SIM_ABI32 + +#include +#include +#include +#include +#include "audit-o32.h" + +static unsigned dir_class_o32[] = { +#include +~0U +}; + +static unsigned read_class_o32[] = { +#include +~0U +}; + +static unsigned write_class_o32[] = { +#include +~0U +}; + +static unsigned chattr_class_o32[] = { +#include +~0U +}; + +static unsigned signal_class_o32[] = { +#include +~0U +}; + +int audit_classify_syscall_o32(int abi, unsigned syscall) +{ + switch (syscall) { + case __NR_open: + return AUDITSC_OPEN; + case __NR_openat: + return AUDITSC_OPENAT; + case __NR_socketcall: + return AUDITSC_SOCKETCALL; + case __NR_execve: + return AUDITSC_EXECVE; + default: + return AUDITSC_NATIVE; + } +} + +static int __init audit_classes_o32_init(void) +{ + audit_register_class(AUDIT_CLASS_WRITE_32, write_class_o32); + audit_register_class(AUDIT_CLASS_READ_32, read_class_o32); + audit_register_class(AUDIT_CLASS_DIR_WRITE_32, dir_class_o32); + audit_register_class(AUDIT_CLASS_CHATTR_32, chattr_class_o32); + audit_register_class(AUDIT_CLASS_SIGNAL_32, signal_class_o32); + + return 0; +} + +__initcall(audit_classes_o32_init); diff --git a/arch/mips/kernel/audit-o32.h b/arch/mips/kernel/audit-o32.h new file mode 100644 index 0000000000000..8a782fdd1c6e7 --- /dev/null +++ b/arch/mips/kernel/audit-o32.h @@ -0,0 +1,5 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifdef CONFIG_AUDITSYSCALL_O32 +int audit_classify_syscall_o32(int abi, unsigned syscall); +#endif /* CONFIG_AUDITSYSCALL_O32 */ diff --git a/arch/mips/kernel/cpu-probe.c b/arch/mips/kernel/cpu-probe.c index c7fee72ea6067..7f748be9f35de 100644 --- a/arch/mips/kernel/cpu-probe.c +++ b/arch/mips/kernel/cpu-probe.c @@ -33,6 +33,10 @@ #include +#ifdef CONFIG_CPU_LOONGSON64 +#include +#endif + /* Hardware capabilities */ unsigned int elf_hwcap __read_mostly; EXPORT_SYMBOL_GPL(elf_hwcap); @@ -1249,32 +1253,44 @@ static inline void cpu_probe_legacy(struct cpuinfo_mips *c, unsigned int cpu) switch (c->processor_id & PRID_REV_MASK) { case PRID_REV_LOONGSON2E: c->cputype = CPU_LOONGSON2EF; - __cpu_name[cpu] = "ICT Loongson-2"; + __cpu_name[cpu] = "Loongson-2"; set_elf_platform(cpu, "loongson2e"); set_isa(c, MIPS_CPU_ISA_III); c->fpu_msk31 |= FPU_CSR_CONDX; + __cpu_full_name[cpu] = "Loongson-2E"; break; case PRID_REV_LOONGSON2F: c->cputype = CPU_LOONGSON2EF; - __cpu_name[cpu] = "ICT Loongson-2"; + __cpu_name[cpu] = "Loongson-2"; set_elf_platform(cpu, "loongson2f"); set_isa(c, MIPS_CPU_ISA_III); c->fpu_msk31 |= FPU_CSR_CONDX; + __cpu_full_name[cpu] = "Loongson-2F"; break; case PRID_REV_LOONGSON3A_R1: c->cputype = CPU_LOONGSON64; - __cpu_name[cpu] = "ICT Loongson-3"; + __cpu_name[cpu] = "Loongson-3"; set_elf_platform(cpu, "loongson3a"); set_isa(c, MIPS_CPU_ISA_M64R1); + __cpu_full_name[cpu] = "Loongson-3A R1 (Loongson-3A1000)"; c->ases |= (MIPS_ASE_LOONGSON_MMI | MIPS_ASE_LOONGSON_CAM | MIPS_ASE_LOONGSON_EXT); break; case PRID_REV_LOONGSON3B_R1: + c->cputype = CPU_LOONGSON64; + __cpu_name[cpu] = "Loongson-3"; + set_elf_platform(cpu, "loongson3b"); + set_isa(c, MIPS_CPU_ISA_M64R1); + __cpu_full_name[cpu] = "Loongson-3B R1 (Loongson-3B1000)"; + c->ases |= (MIPS_ASE_LOONGSON_MMI | MIPS_ASE_LOONGSON_CAM | + MIPS_ASE_LOONGSON_EXT); + break; case PRID_REV_LOONGSON3B_R2: c->cputype = CPU_LOONGSON64; - __cpu_name[cpu] = "ICT Loongson-3"; + __cpu_name[cpu] = "Loongson-3"; set_elf_platform(cpu, "loongson3b"); set_isa(c, MIPS_CPU_ISA_M64R1); + __cpu_full_name[cpu] = "Loongson-3B R2 (Loongson-3B1500)"; c->ases |= (MIPS_ASE_LOONGSON_MMI | MIPS_ASE_LOONGSON_CAM | MIPS_ASE_LOONGSON_EXT); break; @@ -1675,6 +1691,17 @@ static inline void decode_cpucfg(struct cpuinfo_mips *c) c->ases |= MIPS_ASE_LOONGSON_CAM; } +static void decode_loongson_cpucfg(struct cpuinfo_mips *c) +{ +#ifdef CONFIG_CPU_LOONGSON64 + unsigned int cpucfg; + + cpucfg = read_cpucfg(LOONGSON_CFG2); + if (cpucfg & LOONGSON_CFG2_LLFTP) + c->options |= MIPS_CPU_CONST_TIMER; +#endif +} + static inline void cpu_probe_loongson(struct cpuinfo_mips *c, unsigned int cpu) { c->cputype = CPU_LOONGSON64; @@ -1702,15 +1729,17 @@ static inline void cpu_probe_loongson(struct cpuinfo_mips *c, unsigned int cpu) switch (c->processor_id & PRID_REV_MASK) { case PRID_REV_LOONGSON3A_R2_0: case PRID_REV_LOONGSON3A_R2_1: - __cpu_name[cpu] = "ICT Loongson-3"; + __cpu_name[cpu] = "Loongson-3"; set_elf_platform(cpu, "loongson3a"); set_isa(c, MIPS_CPU_ISA_M64R2); + __cpu_full_name[cpu] = "Loongson-3A R2 (Loongson-3A2000)"; break; case PRID_REV_LOONGSON3A_R3_0: case PRID_REV_LOONGSON3A_R3_1: - __cpu_name[cpu] = "ICT Loongson-3"; + __cpu_name[cpu] = "Loongson-3"; set_elf_platform(cpu, "loongson3a"); set_isa(c, MIPS_CPU_ISA_M64R2); + __cpu_full_name[cpu] = "Loongson-3A R3 (Loongson-3A3000)"; break; } /* @@ -1729,10 +1758,12 @@ static inline void cpu_probe_loongson(struct cpuinfo_mips *c, unsigned int cpu) LOONGSON_CONF6_INTIMER); break; case PRID_IMP_LOONGSON_64G: - __cpu_name[cpu] = "ICT Loongson-3"; + __cpu_name[cpu] = "Loongson-3"; set_elf_platform(cpu, "loongson3a"); set_isa(c, MIPS_CPU_ISA_M64R2); + __cpu_full_name[cpu] = "Loongson-3A R4 (Loongson-3A4000)"; decode_cpucfg(c); + decode_loongson_cpucfg(c); change_c0_config6(LOONGSON_CONF6_EXTIMER | LOONGSON_CONF6_INTIMER, LOONGSON_CONF6_INTIMER); break; @@ -1837,6 +1868,7 @@ EXPORT_SYMBOL(__ua_limit); #endif const char *__cpu_name[NR_CPUS]; +const char *__cpu_full_name[NR_CPUS]; const char *__elf_platform; const char *__elf_base_platform; diff --git a/arch/mips/kernel/elf.c b/arch/mips/kernel/elf.c index 7aa2c2360ff60..f0e7fe85a42a7 100644 --- a/arch/mips/kernel/elf.c +++ b/arch/mips/kernel/elf.c @@ -318,6 +318,10 @@ void mips_set_personality_nan(struct arch_elf_state *state) t->thread.fpu.fcr31 = c->fpu_csr31; switch (state->nan_2008) { case 0: + if (!(c->fpu_msk31 & FPU_CSR_NAN2008)) + t->thread.fpu.fcr31 &= ~FPU_CSR_NAN2008; + if (!(c->fpu_msk31 & FPU_CSR_ABS2008)) + t->thread.fpu.fcr31 &= ~FPU_CSR_ABS2008; break; case 1: if (!(c->fpu_msk31 & FPU_CSR_NAN2008)) diff --git a/arch/mips/kernel/fpu-probe.c b/arch/mips/kernel/fpu-probe.c index e689d6a832342..6bf3f19b1c335 100644 --- a/arch/mips/kernel/fpu-probe.c +++ b/arch/mips/kernel/fpu-probe.c @@ -144,7 +144,7 @@ static void cpu_set_fpu_2008(struct cpuinfo_mips *c) * IEEE 754 conformance mode to use. Affects the NaN encoding and the * ABS.fmt/NEG.fmt execution mode. */ -static enum { STRICT, LEGACY, STD2008, RELAXED } ieee754 = STRICT; +static enum { STRICT, EMULATED, LEGACY, STD2008, RELAXED } ieee754 = STRICT; /* * Set the IEEE 754 NaN encodings and the ABS.fmt/NEG.fmt execution modes @@ -160,6 +160,7 @@ static void cpu_set_nofpu_2008(struct cpuinfo_mips *c) switch (ieee754) { case STRICT: + case EMULATED: if (c->isa_level & (MIPS_CPU_ISA_M32R1 | MIPS_CPU_ISA_M64R1 | MIPS_CPU_ISA_M32R2 | MIPS_CPU_ISA_M64R2 | MIPS_CPU_ISA_M32R5 | MIPS_CPU_ISA_M64R5 | @@ -204,6 +205,10 @@ static void cpu_set_nan_2008(struct cpuinfo_mips *c) mips_use_nan_legacy = !cpu_has_nan_2008; mips_use_nan_2008 = !!cpu_has_nan_2008; break; + case EMULATED: + /* Pretend ABS2008/NAN2008 options are dynamic */ + c->fpu_msk31 &= ~(FPU_CSR_NAN2008 | FPU_CSR_ABS2008); + fallthrough; case RELAXED: mips_use_nan_legacy = true; mips_use_nan_2008 = true; @@ -226,6 +231,8 @@ static int __init ieee754_setup(char *s) return -1; else if (!strcmp(s, "strict")) ieee754 = STRICT; + else if (!strcmp(s, "emulated")) + ieee754 = EMULATED; else if (!strcmp(s, "legacy")) ieee754 = LEGACY; else if (!strcmp(s, "2008")) diff --git a/arch/mips/kernel/proc.c b/arch/mips/kernel/proc.c index 8eba5a1ed664c..23fe62a3cdde4 100644 --- a/arch/mips/kernel/proc.c +++ b/arch/mips/kernel/proc.c @@ -15,6 +15,7 @@ #include #include #include +#include unsigned int vced_count, vcei_count; @@ -63,6 +64,11 @@ static int show_cpuinfo(struct seq_file *m, void *v) seq_printf(m, fmt, __cpu_name[n], (version >> 4) & 0x0f, version & 0x0f, (fp_vers >> 4) & 0x0f, fp_vers & 0x0f); + if (__cpu_full_name[n]) + seq_printf(m, "model name\t\t: %s\n", __cpu_full_name[n]); + if (mips_cpu_frequency) + seq_printf(m, "CPU MHz\t\t\t: %u.%02u\n", + mips_cpu_frequency / 1000000, (mips_cpu_frequency / 10000) % 100); seq_printf(m, "BogoMIPS\t\t: %u.%02u\n", cpu_data[n].udelay_val / (500000/HZ), (cpu_data[n].udelay_val / (5000/HZ)) % 100); diff --git a/arch/mips/kernel/ptrace.c b/arch/mips/kernel/ptrace.c index 61503a36067e9..69169aa6bf26e 100644 --- a/arch/mips/kernel/ptrace.c +++ b/arch/mips/kernel/ptrace.c @@ -31,6 +31,7 @@ #include #include +#include #include #include #include @@ -49,6 +50,12 @@ #define CREATE_TRACE_POINTS #include +/* + * The standard AUDITSC_RESULT() assumes errno values < 0 indicate error + * but MIPS passes an error flag instead. + */ +#define MIPS_AUDITSC_RESULT(x) ((x) ? AUDITSC_FAILURE : AUDITSC_SUCCESS) + unsigned long exception_ip(struct pt_regs *regs) { return exception_epc(regs); diff --git a/arch/mips/kernel/setup.c b/arch/mips/kernel/setup.c index 3f45b72561db9..778d3909e2e66 100644 --- a/arch/mips/kernel/setup.c +++ b/arch/mips/kernel/setup.c @@ -58,6 +58,10 @@ EXPORT_SYMBOL(cpu_data); struct screen_info screen_info; #endif +#ifdef CONFIG_CRASH_DUMP +static phys_addr_t crashmem_start, crashmem_size; +#endif + /* * Setup information * @@ -361,8 +365,10 @@ static int __init early_parse_mem(char *p) */ if (usermem == 0) { usermem = 1; +#ifndef CONFIG_CRASH_DUMP memblock_remove(memblock_start_of_DRAM(), memblock_end_of_DRAM() - memblock_start_of_DRAM()); +#endif } start = 0; size = memparse(p, &p); @@ -374,6 +380,13 @@ static int __init early_parse_mem(char *p) else memblock_add(start, size); +#ifdef CONFIG_CRASH_DUMP + if (start && size) { + crashmem_start = start; + crashmem_size = size; + } +#endif + return 0; } early_param("mem", early_parse_mem); @@ -612,6 +625,48 @@ static void __init bootcmdline_init(void) bootcmdline_append(builtin_cmdline, COMMAND_LINE_SIZE); } +/* Traditionally, MIPS's contiguous low memory is 256M, so crashkernel=X@Y is + * unable to be large enough in some cases. Thus, if the total memory of a node + * is more than 1GB, we reserve the top 128MB for the crash kernel */ +static void reserve_crashm_region(int node, unsigned long s0, unsigned long e0) +{ +#ifdef CONFIG_KEXEC + if (crashk_res.start == crashk_res.end) + return; + + if ((e0 - s0) <= (SZ_1G >> PAGE_SHIFT)) + return; + + s0 = e0 - (SZ_128M >> PAGE_SHIFT); + + memblock_reserve(PFN_PHYS(s0), (e0 - s0) << PAGE_SHIFT); +#endif +} + +static void reserve_oldmem_region(int node, unsigned long s0, unsigned long e0) +{ +#ifdef CONFIG_CRASH_DUMP + unsigned long s1, e1; + + if (!is_kdump_kernel()) + return; + + if ((e0 - s0) > (SZ_1G >> PAGE_SHIFT)) + e0 = e0 - (SZ_128M >> PAGE_SHIFT); + + /* crashmem_start is crashk_res reserved by primary kernel */ + s1 = PFN_UP(crashmem_start); + e1 = PFN_DOWN(crashmem_start + crashmem_size); + + if (node == 0) { + memblock_reserve(PFN_PHYS(s0), (s1 - s0) << PAGE_SHIFT); + memblock_reserve(PFN_PHYS(e1), (e0 - e1) << PAGE_SHIFT); + } else { + memblock_reserve(PFN_PHYS(s0), (e0 - s0) << PAGE_SHIFT); + } +#endif +} + /* * arch_mem_init - initialize memory management subsystem * @@ -636,6 +691,9 @@ static void __init bootcmdline_init(void) */ static void __init arch_mem_init(char **cmdline_p) { + unsigned int node; + unsigned long start_pfn, end_pfn; + /* call board setup routine */ plat_mem_setup(); memblock_set_bottom_up(true); @@ -671,6 +729,12 @@ static void __init arch_mem_init(char **cmdline_p) mips_reserve_vmcore(); mips_parse_crashkernel(); + for_each_online_node(node) { + get_pfn_range_for_nid(node, &start_pfn, &end_pfn); + reserve_crashm_region(node, start_pfn, end_pfn); + reserve_oldmem_region(node, start_pfn, end_pfn); + } + device_tree_init(); /* diff --git a/arch/mips/kernel/signal.c b/arch/mips/kernel/signal.c index 479999b7f2de7..e3c3907105a13 100644 --- a/arch/mips/kernel/signal.c +++ b/arch/mips/kernel/signal.c @@ -8,6 +8,7 @@ * Copyright (C) 1999, 2000 Silicon Graphics, Inc. * Copyright (C) 2014, Imagination Technologies Ltd. */ +#include #include #include #include @@ -810,6 +811,23 @@ struct mips_abi mips_abi = { .off_sc_used_math = offsetof(struct sigcontext, sc_used_math), .vdso = &vdso_image, +#ifdef CONFIG_64BIT +# ifdef __BIG_ENDIAN + .audit_arch = AUDIT_ARCH_MIPS64, +# elif defined(__LITTLE_ENDIAN) + .audit_arch = AUDIT_ARCH_MIPSEL64, +# else +# error "Neither big nor little endian ???" +# endif +#else +# ifdef __BIG_ENDIAN + .audit_arch = AUDIT_ARCH_MIPS, +# elif defined(__LITTLE_ENDIAN) + .audit_arch = AUDIT_ARCH_MIPSEL, +# else +# error "Neither big nor little endian ???" +# endif +#endif }; static void handle_signal(struct ksignal *ksig, struct pt_regs *regs) diff --git a/arch/mips/kernel/signal_n32.c b/arch/mips/kernel/signal_n32.c index cfc77b69420a1..5bc4918d6fab2 100644 --- a/arch/mips/kernel/signal_n32.c +++ b/arch/mips/kernel/signal_n32.c @@ -2,6 +2,7 @@ /* * Copyright (C) 2003 Broadcom Corporation */ +#include #include #include #include @@ -145,4 +146,11 @@ struct mips_abi mips_abi_n32 = { .off_sc_used_math = offsetof(struct sigcontext, sc_used_math), .vdso = &vdso_image_n32, +#ifdef __BIG_ENDIAN + .audit_arch = AUDIT_ARCH_MIPS64N32, +#elif defined(__LITTLE_ENDIAN) + .audit_arch = AUDIT_ARCH_MIPSEL64N32, +#else +# error "Neither big nor little endian ???" +#endif }; diff --git a/arch/mips/kernel/signal_o32.c b/arch/mips/kernel/signal_o32.c index 299a7a28ca33f..8e86ff609f529 100644 --- a/arch/mips/kernel/signal_o32.c +++ b/arch/mips/kernel/signal_o32.c @@ -8,6 +8,7 @@ * Copyright (C) 1999, 2000 Silicon Graphics, Inc. * Copyright (C) 2016, Imagination Technologies Ltd. */ +#include #include #include #include @@ -250,6 +251,13 @@ struct mips_abi mips_abi_32 = { .off_sc_used_math = offsetof(struct sigcontext32, sc_used_math), .vdso = &vdso_image_o32, +#ifdef __BIG_ENDIAN + .audit_arch = AUDIT_ARCH_MIPS, +#elif defined(__LITTLE_ENDIAN) + .audit_arch = AUDIT_ARCH_MIPSEL, +#else +# error "Neither big nor little endian ???" +#endif }; diff --git a/arch/mips/kernel/smp.c b/arch/mips/kernel/smp.c index 81f6c4f8fbc15..094453f4fc6e0 100644 --- a/arch/mips/kernel/smp.c +++ b/arch/mips/kernel/smp.c @@ -367,7 +367,8 @@ asmlinkage void start_secondary(void) */ calibrate_delay(); - cpu_data[cpu].udelay_val = loops_per_jiffy; + if (!cpu_data[cpu].udelay_val) + cpu_data[cpu].udelay_val = loops_per_jiffy; set_cpu_sibling_map(cpu); set_cpu_core_map(cpu); diff --git a/arch/mips/kernel/time.c b/arch/mips/kernel/time.c index ed339d7979f3f..849c6be53b296 100644 --- a/arch/mips/kernel/time.c +++ b/arch/mips/kernel/time.c @@ -120,6 +120,8 @@ EXPORT_SYMBOL(perf_irq); * 2) calculate a couple of cached variables for later usage */ +unsigned int mips_cpu_frequency; +EXPORT_SYMBOL_GPL(mips_cpu_frequency); unsigned int mips_hpt_frequency; EXPORT_SYMBOL_GPL(mips_hpt_frequency); diff --git a/arch/mips/lib/Makefile b/arch/mips/lib/Makefile index 5d5b993cbc2bf..b5532bab9337b 100644 --- a/arch/mips/lib/Makefile +++ b/arch/mips/lib/Makefile @@ -10,6 +10,7 @@ lib-y += bitops.o csum_partial.o delay.o memcpy.o memset.o \ obj-y += iomap_copy.o obj-$(CONFIG_PCI) += iomap-pci.o lib-$(CONFIG_GENERIC_CSUM) := $(filter-out csum_partial.o, $(lib-y)) +lib-$(CONFIG_CPU_LOONGSON64) := $(filter-out memset.o, $(lib-y)) obj-$(CONFIG_CPU_GENERIC_DUMP_TLB) += dump_tlb.o obj-$(CONFIG_CPU_R3000) += r3k_dump_tlb.o diff --git a/arch/mips/loongson64/Makefile b/arch/mips/loongson64/Makefile index e806280bbb850..402cc006a5bfc 100644 --- a/arch/mips/loongson64/Makefile +++ b/arch/mips/loongson64/Makefile @@ -4,11 +4,14 @@ # obj-$(CONFIG_MACH_LOONGSON64) += cop2-ex.o dma.o \ setup.o init.o env.o time.o reset.o \ + clock.o constant_timer.o ec_wpce775l.o \ + platform.o loongson3-memset.o \ + loongson3-memcpy.o \ obj-$(CONFIG_SMP) += smp.o obj-$(CONFIG_NUMA) += numa.o obj-$(CONFIG_RS780_HPET) += hpet.o -obj-$(CONFIG_SUSPEND) += pm.o +obj-$(CONFIG_SUSPEND) += pm.o sleeper.o obj-$(CONFIG_PCI_QUIRKS) += vbios_quirk.o obj-$(CONFIG_CPU_LOONGSON3_CPUCFG_EMULATION) += cpucfg-emul.o obj-$(CONFIG_SYSFS) += boardinfo.o diff --git a/arch/mips/loongson64/clock.c b/arch/mips/loongson64/clock.c new file mode 100644 index 0000000000000..09d3c28a23abd --- /dev/null +++ b/arch/mips/loongson64/clock.c @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2008 - 2014 Lemote Inc. + * Author: Yan Hua, yanh@lemote.com + * Chen Huacai, chenhc@lemote.com + * + * 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. + */ + +#include +#include +#include +#include +#include + +#include + +#include + +static LIST_HEAD(clock_list); +static DEFINE_SPINLOCK(clock_lock); +static DEFINE_MUTEX(clock_list_sem); + +/* Minimum CLK support */ +enum { + DC_ZERO, DC_12PT, DC_25PT, DC_37PT, DC_50PT, DC_62PT, + DC_75PT, DC_87PT, DC_DISABLE, DC_RESV +}; + +struct cpufreq_frequency_table gs464_clockmod_table[] = { + {0, DC_ZERO, CPUFREQ_ENTRY_INVALID}, + {0, DC_12PT, 0}, + {0, DC_25PT, 0}, + {0, DC_37PT, 0}, + {0, DC_50PT, 0}, + {0, DC_62PT, 0}, + {0, DC_75PT, 0}, + {0, DC_87PT, 0}, + {0, DC_DISABLE, 0}, + {0, DC_RESV, CPUFREQ_TABLE_END}, +}; + +static struct clk cpu_clks[NR_CPUS]; +static char clk_names[NR_CPUS][10]; + +struct clk *gs464_cpu_clk_get(int cpu) +{ + return &cpu_clks[cpu]; +} + +struct clk *gs464_clk_get(struct device *dev, const char *id) +{ + int i; + struct clk *clk; + + if (!id) + return NULL; + + for_each_possible_cpu(i) { + clk = &cpu_clks[i]; + if (strcmp(clk->name, id) == 0) + return clk; + } + + return NULL; +} + +void gs464_propagate_rate(struct clk *clk) +{ + struct clk *clkp; + + list_for_each_entry(clkp, &clock_list, node) { + if (likely(clkp->parent != clk)) + continue; + if (likely(clkp->ops && clkp->ops->recalc)) + clkp->ops->recalc(clkp); + if (unlikely(clkp->flags & CLK_RATE_PROPAGATES)) + gs464_propagate_rate(clkp); + } +} + +unsigned long gs464_clk_get_rate(struct clk *clk) +{ + if (!clk) + return 0; + + return (unsigned long)clk->rate; +} + +int gs464_clk_set_rate(struct clk *clk, unsigned long rate) +{ + int regval, ret = 0; + struct cpufreq_frequency_table *pos; + int cpu = clk - cpu_clks; + uint64_t core_id = cpu_core(&cpu_data[cpu]); + uint64_t package_id = cpu_data[cpu].package; + + if (likely(clk->ops && clk->ops->set_rate)) { + unsigned long flags; + + spin_lock_irqsave(&clock_lock, flags); + ret = clk->ops->set_rate(clk, rate, 0); + spin_unlock_irqrestore(&clock_lock, flags); + } + + if (unlikely(clk->flags & CLK_RATE_PROPAGATES)) + gs464_propagate_rate(clk); + + cpufreq_for_each_valid_entry(pos, gs464_clockmod_table) + if (rate == pos->frequency) + break; + if (rate != pos->frequency) + return -ENOTSUPP; + + clk->rate = rate; + + if ((read_c0_prid() & 0xf) == PRID_REV_LOONGSON3A_R1) { + regval = LOONGSON_CHIPCFG(package_id); + regval = (regval & ~0x7) | (pos->driver_data - 1); + LOONGSON_CHIPCFG(package_id) = regval; + } else { + regval = LOONGSON_FREQCTRL(package_id); + regval = (regval & ~(0x7 << (core_id*4))) | + ((pos->driver_data - 1) << (core_id*4)); + LOONGSON_FREQCTRL(package_id) = regval; + } + + return ret; +} + +long gs464_clk_round_rate(struct clk *clk, unsigned long rate) +{ + if (likely(clk->ops && clk->ops->round_rate)) { + unsigned long flags, rounded; + + spin_lock_irqsave(&clock_lock, flags); + rounded = clk->ops->round_rate(clk, rate); + spin_unlock_irqrestore(&clock_lock, flags); + + return rounded; + } + + return rate; +} + +int gs464_clock_init(void) +{ + int i; + + for_each_possible_cpu(i) { + sprintf(clk_names[i], "cpu%d_clk", i); + cpu_clks[i].name = clk_names[i]; + cpu_clks[i].flags = CLK_ALWAYS_ENABLED | CLK_RATE_PROPAGATES; + cpu_clks[i].rate = cpu_clock_freq / 1000; + } + + /* clock table init */ + for (i = 1; + (gs464_clockmod_table[i].frequency != CPUFREQ_TABLE_END); + i++) + gs464_clockmod_table[i].frequency = ((cpu_clock_freq / 1000) * i) / 8; + + return 0; +} +arch_initcall(gs464_clock_init); + +MODULE_AUTHOR("Huacai Chen "); +MODULE_DESCRIPTION("CPUFreq driver for GS464 (MIPS-based Loongson-3) processors"); +MODULE_LICENSE("GPL"); diff --git a/arch/mips/loongson64/constant_timer.c b/arch/mips/loongson64/constant_timer.c new file mode 100644 index 0000000000000..c1fda4a3ae165 --- /dev/null +++ b/arch/mips/loongson64/constant_timer.c @@ -0,0 +1,258 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +int constant_timer_irq_installed; + +static DEFINE_SPINLOCK(state_lock); +DEFINE_PER_CPU(struct clock_event_device, constant_clockevent_device); + +static void constant_event_handler(struct clock_event_device *dev) +{ +} + +static irqreturn_t constant_timer_interrupt(int irq, void *data) +{ + int cpu = smp_processor_id(); + const int r2 = cpu_has_mips_r2_r6; + struct clock_event_device *cd; + + if ((cp0_perfcount_irq < 0) && perf_irq() == IRQ_HANDLED && !r2) + return IRQ_HANDLED; + + /* + * The same applies to performance counter interrupts. But with the + * above we now know that the reason we got here must be a timer + * interrupt. Being the paranoiacs we are we check anyway. + */ + if (!r2 || (read_c0_cause() & CAUSEF_TI)) { + /* Clear Count/Compare Interrupt */ + write_c0_compare(read_c0_compare()); + cd = &per_cpu(constant_clockevent_device, cpu); + cd->event_handler(cd); + + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static int constant_set_state_oneshot(struct clock_event_device *evt) +{ + unsigned int raw_cpuid; + unsigned long cfg, addr; + + spin_lock(&state_lock); + + raw_cpuid = cpu_logical_map(smp_processor_id()); + addr = CSR_TO_RAW_ADDR(raw_cpuid, LOONGSON_CSR_TIMER_CFG); + + if (!cpu_has_csr()) + cfg = readq((void *)addr); + else + cfg = csr_readq(LOONGSON_CSR_TIMER_CFG); + + /* set timer type + * 1 : periodic interrupt + * 0 : non-periodic(oneshot) interrupt + */ + cfg |= CONSTANT_TIMER_CFG_EN; + cfg &= ~CONSTANT_TIMER_CFG_PERIODIC; + + if (!cpu_has_csr()) + writeq(cfg, (void *)addr); + else + csr_writeq(cfg, LOONGSON_CSR_TIMER_CFG); + + spin_unlock(&state_lock); + + return 0; +} + +static int constant_set_state_oneshot_stopped(struct clock_event_device *evt) +{ + return 0; +} + +static int constant_set_state_periodic(struct clock_event_device *evt) +{ + unsigned int period; + unsigned int raw_cpuid; + unsigned long cfg, addr; + + spin_lock(&state_lock); + + raw_cpuid = cpu_logical_map(smp_processor_id()); + addr = CSR_TO_RAW_ADDR(raw_cpuid, LOONGSON_CSR_TIMER_CFG); + + if (!cpu_has_csr()) + cfg = readq((void *)addr); + else + cfg = csr_readq(LOONGSON_CSR_TIMER_CFG); + + cfg &= CONSTANT_TIMER_INITVAL_RESET; + cfg |= (CONSTANT_TIMER_CFG_PERIODIC | CONSTANT_TIMER_CFG_EN); + + period = calc_const_freq(); + if (!period) + period = cpu_clock_freq; + + period = period / HZ; + + if (!cpu_has_csr()) + writeq(cfg | period, (void *)addr); + else + csr_writeq(cfg | period, LOONGSON_CSR_TIMER_CFG); + + spin_unlock(&state_lock); + + return 0; +} + +static int constant_set_state_shutdown(struct clock_event_device *evt) +{ + return 0; +} + +static int constant_next_event(unsigned long delta, + struct clock_event_device *evt) +{ + unsigned long addr; + unsigned int raw_cpuid; + + raw_cpuid = cpu_logical_map(smp_processor_id()); + addr = CSR_TO_RAW_ADDR(raw_cpuid, LOONGSON_CSR_TIMER_CFG); + + writeq(delta | CONSTANT_TIMER_CFG_EN, (void *)addr); + + return 0; +} + +static int csr_constant_next_event(unsigned long delta, + struct clock_event_device *evt) +{ + csr_writeq(delta | CONSTANT_TIMER_CFG_EN, LOONGSON_CSR_TIMER_CFG); + return 0; +} + +int constant_clockevent_init(void) +{ + unsigned int irq; + unsigned int config; + unsigned int const_freq; + unsigned long min_delta = 0x600; + unsigned long max_delta = (1UL << 48) - 1; + unsigned int cpu = smp_processor_id(); + struct clock_event_device *cd; + int err; + + config = read_c0_config6(); + config |= LOONGSON_CONF6_EXTIMER; + write_c0_config6(config); + + const_freq = calc_const_freq(); + if (!const_freq) + const_freq = cpu_clock_freq; + + irq = get_c0_compare_int(); + + cd = &per_cpu(constant_clockevent_device, cpu); + + cd->name = "Constant"; + cd->features = CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_PERIODIC | + CLOCK_EVT_FEAT_PERCPU; + + cd->rating = 320; + cd->irq = irq; + cd->cpumask = cpumask_of(cpu); + cd->set_state_oneshot = constant_set_state_oneshot; + cd->set_state_oneshot_stopped = constant_set_state_oneshot_stopped; + cd->set_state_periodic = constant_set_state_periodic; + cd->set_state_shutdown = constant_set_state_shutdown; + if (!cpu_has_csr()) + cd->set_next_event = constant_next_event; + else + cd->set_next_event = csr_constant_next_event; + + cd->event_handler = constant_event_handler; + + clockevents_config_and_register(cd, const_freq, min_delta, max_delta); + + if (constant_timer_irq_installed) + return 0; + + constant_timer_irq_installed = 1; + + err = request_irq(irq, constant_timer_interrupt, + IRQF_PERCPU | IRQF_TIMER | IRQF_SHARED, + "timer", cd); + if (err) { + pr_err("loongson64: setup irq for constant_clock_event failed: %d\n", err); + return err; + } + + return 0; +} + +static u64 read_const_counter(struct clocksource *clk) +{ + u64 count; + + __asm__ __volatile__( + " .set push\n" + " .set mips32r2\n" + " rdhwr %0, $30\n" + " .set pop\n" + : "=r" (count)); + + return count; +} + +static struct clocksource clocksource_const = { + .name = "Constant", + .rating = 400, + .read = read_const_counter, + .mask = CLOCKSOURCE_MASK(64), + .flags = CLOCK_SOURCE_IS_CONTINUOUS, + .mult = 0, + .shift = 10, +}; + +static u64 read_sched_clock(void) +{ + u64 count; + + __asm__ __volatile__( + " .set push\n" + " .set mips32r2\n" + " rdhwr %0, $30\n" + " .set pop\n" + : "=r" (count)); + + return count; +} + +int __init init_constant_clocksource(void) +{ + int res; + unsigned long freq; + + freq = calc_const_freq(); + if (freq) + freq = cpu_clock_freq; + + clocksource_const.mult = + clocksource_hz2mult(freq, clocksource_const.shift); + + res = clocksource_register_hz(&clocksource_const, freq); + + sched_clock_register(read_sched_clock, 64, freq); + clocksource_const.vdso_clock_mode = VDSO_CLOCKMODE_CONST; + + return res; +} diff --git a/arch/mips/loongson64/ec_wpce775l.c b/arch/mips/loongson64/ec_wpce775l.c new file mode 100644 index 0000000000000..f407ddbe933a1 --- /dev/null +++ b/arch/mips/loongson64/ec_wpce775l.c @@ -0,0 +1,249 @@ +/* + * EC (Embedded Controller) WPCE775L device function for Linux. + * Author : Wang Rui + * Author : Huang Wei + * Date : 2011-02-21 + * + * 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 + +/* This spinlock is dedicated for 62&66 ports and super io port access. */ +extern spinlock_t i8042_lock; +#define index_access_lock i8042_lock +DEFINE_SPINLOCK(port_access_lock); + +static int send_ec_command(unsigned char command) +{ + int timeout, ret = 0; + + timeout = EC_SEND_TIMEOUT; + while ((inb(EC_STS_PORT) & EC_IBF) && --timeout) + ; + if (!timeout) { + printk(KERN_ERR "Timeout while sending command 0x%02x to EC!\n", command); + ret = -1; + goto out; + } + + outb(command, EC_CMD_PORT); + +out: + return ret; +} + +static int send_ec_data(unsigned char data) +{ + int timeout, ret = 0; + + timeout = EC_SEND_TIMEOUT; + while ((inb(EC_STS_PORT) & EC_IBF) && --timeout) + ; + if (!timeout) { + printk(KERN_ERR "Timeout while sending data 0x%02x to EC!\n", data); + ret = -1; + goto out; + } + + outb(data, EC_DAT_PORT); + +out: + return ret; +} + +static unsigned char recv_ec_data(void) +{ + int timeout; + unsigned char data; + + timeout = EC_RECV_TIMEOUT; + while (!(inb(EC_STS_PORT) & EC_OBF) && --timeout) + ; + if (!timeout) { + printk(KERN_ERR "Timeout while receiving data from EC! status 0x%x.\n", inb(EC_STS_PORT)); + data = 0; + goto skip_data; + } + + data = inb(EC_DAT_PORT); + +skip_data: + return data; +} + +void clean_ec_event_status(void) +{ + unsigned long flags; + + spin_lock_irqsave(&port_access_lock, flags); + outl(0x404000, 0x810); + spin_unlock_irqrestore(&port_access_lock, flags); +} +EXPORT_SYMBOL(clean_ec_event_status); + +unsigned char ec_read(unsigned char index) +{ + unsigned char value = 0; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&index_access_lock, flags); + ret = send_ec_command(CMD_READ_EC); + if (ret < 0) { + printk(KERN_ERR "Send command fail!\n"); + value = 0; + goto out; + } + ret = send_ec_data(index); + if (ret < 0) { + printk(KERN_ERR "Send data fail!\n"); + value = 0; + goto out; + } + value = recv_ec_data(); +out: + spin_unlock_irqrestore(&index_access_lock, flags); + + return value; +} +EXPORT_SYMBOL(ec_read); + +unsigned char ec_read_all(unsigned char command, unsigned char index) +{ + unsigned char value = 0; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&index_access_lock, flags); + ret = send_ec_command(command); + if (ret < 0) { + printk(KERN_ERR "Send command fail!\n"); + goto out; + } + ret = send_ec_data(index); + if (ret < 0) { + printk(KERN_ERR "Send data fail!\n"); + goto out; + } + value = recv_ec_data(); +out: + spin_unlock_irqrestore(&index_access_lock, flags); + + return value; +} +EXPORT_SYMBOL(ec_read_all); + +unsigned char ec_read_noindex(unsigned char command) +{ + unsigned char value = 0; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&index_access_lock, flags); + ret = send_ec_command(command); + if (ret < 0) { + printk(KERN_ERR "Send command fail!\n"); + goto out; + } + value = recv_ec_data(); +out: + spin_unlock_irqrestore(&index_access_lock, flags); + + return value; +} +EXPORT_SYMBOL(ec_read_noindex); + +int ec_write(unsigned char index, unsigned char data) +{ + int ret = 0; + unsigned long flags; + + spin_lock_irqsave(&index_access_lock, flags); + ret = send_ec_command(CMD_WRITE_EC); + if (ret < 0) { + printk(KERN_ERR "Send command 0x81 fail!\n"); + goto out; + } + ret = send_ec_data(index); + if (ret < 0) { + printk(KERN_ERR "Send index 0x%x fail!\n", index); + goto out; + } + + ret = send_ec_data(data); + if (ret < 0) { + printk(KERN_ERR "Send data 0x%x fail!\n", data); + } +out: + spin_unlock_irqrestore(&index_access_lock, flags); + + return ret; +} +EXPORT_SYMBOL(ec_write); + +int ec_write_all(unsigned char command, unsigned char index, unsigned char data) +{ + unsigned long flags; + + spin_lock_irqsave(&index_access_lock, flags); + send_ec_command(command); + send_ec_data(index); + send_ec_data(data); + spin_unlock_irqrestore(&index_access_lock, flags); + + return 0; +} +EXPORT_SYMBOL(ec_write_all); + +int ec_write_noindex(unsigned char command, unsigned char data) +{ + unsigned long flags; + + spin_lock_irqsave(&index_access_lock, flags); + send_ec_command(command); + send_ec_data(data); + spin_unlock_irqrestore(&index_access_lock, flags); + + return 0; +} +EXPORT_SYMBOL(ec_write_noindex); + +int ec_query_get_event_num(void) +{ + unsigned char value = 0; + unsigned long flags; + int ret = 0; + unsigned int timeout; + + spin_lock_irqsave(&index_access_lock, flags); + ret = send_ec_command(CMD_GET_EVENT_NUM); + if (ret < 0) { + printk(KERN_ERR "Send command fail!\n"); + goto out; + } + + /* check if the command is received by ec */ + timeout = EC_CMD_TIMEOUT; + while ((inb(EC_STS_PORT) & EC_IBF) && timeout--) + ; + if (timeout <= 0) { + printk(KERN_ERR "EC QUERY SEQ: deadable error : timeout...\n"); + ret = -EINVAL; + goto out; + } + + value = recv_ec_data(); +out: + spin_unlock_irqrestore(&index_access_lock, flags); + + return value; +} +EXPORT_SYMBOL(ec_query_get_event_num); diff --git a/arch/mips/loongson64/env.c b/arch/mips/loongson64/env.c index 09ff052698614..5e2e8571305b0 100644 --- a/arch/mips/loongson64/env.c +++ b/arch/mips/loongson64/env.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -27,6 +28,7 @@ u32 cpu_clock_freq; EXPORT_SYMBOL(cpu_clock_freq); +char cpu_full_name[64]; struct efi_memory_map_loongson *loongson_memmap; struct loongson_system_configuration loongson_sysconf; @@ -58,6 +60,7 @@ void __init prom_dtb_init_env(void) void __init prom_lefi_init_env(void) { + char freq[12]; struct boot_params *boot_p; struct loongson_params *loongson_p; struct system_loongson *esys; @@ -152,6 +155,10 @@ void __init prom_lefi_init_env(void) loongson_sysconf.nr_nodes = (loongson_sysconf.nr_cpus + loongson_sysconf.cores_per_node - 1) / loongson_sysconf.cores_per_node; + if (!strncmp(ecpu->cpuname, "Loongson", 8)) + strncpy(cpu_full_name, ecpu->cpuname, sizeof(cpu_full_name)); + if (cpu_full_name[0] == 0) + strncpy(cpu_full_name, __cpu_full_name[0], sizeof(cpu_full_name)); loongson_sysconf.dma_mask_bits = eirq_source->dma_mask_bits; if (loongson_sysconf.dma_mask_bits < 32 || @@ -175,8 +182,14 @@ void __init prom_lefi_init_env(void) loongson_sysconf.workarounds |= esys->workarounds; + mips_cpu_frequency = cpu_clock_freq; pr_info("CpuClock = %u\n", cpu_clock_freq); + /* Append default cpu frequency with round-off */ + sprintf(freq, " @ %uMHz", (cpu_clock_freq + 500000) / 1000000); + strncat(cpu_full_name, freq, strlen(freq)); + __cpu_full_name[0] = cpu_full_name; + /* Read the ID of PCI host bridge to detect bridge type */ id = readl(HOST_BRIDGE_CONFIG_ADDR); vendor = id & 0xffff; diff --git a/arch/mips/loongson64/init.c b/arch/mips/loongson64/init.c index f25caa6aa9d30..36d3cf0f8b3ba 100644 --- a/arch/mips/loongson64/init.c +++ b/arch/mips/loongson64/init.c @@ -22,6 +22,16 @@ u32 node_id_offset; +static void __init mips_ebase_setup(void) +{ + ebase = CKSEG0; + + if (cpu_has_ebase_wg) + write_c0_ebase(ebase | MIPS_EBASE_WG); + + write_c0_ebase(ebase); +} + static void __init mips_nmi_setup(void) { void *base; @@ -143,6 +153,7 @@ void __init prom_init(void) setup_8250_early_printk_port(TO_UNCAC(LOONGSON_REG_BASE + 0x1e0), 0, 1024); register_smp_ops(&loongson3_smp_ops); + board_ebase_setup = mips_ebase_setup; board_nmi_handler_setup = mips_nmi_setup; } diff --git a/arch/mips/loongson64/loongson3-memcpy.S b/arch/mips/loongson64/loongson3-memcpy.S new file mode 100644 index 0000000000000..c5dd2e2866c3c --- /dev/null +++ b/arch/mips/loongson64/loongson3-memcpy.S @@ -0,0 +1,508 @@ +/* + ============================================================================ + Name : memcpy.S + Author : Heiher + Chen Jie + Version : 20140307 + Copyright : GPLv2 + Description : The memcpy for Loongson 3. + ============================================================================ + */ + +#define LINUX_KERNEL +#ifndef LINUX_KERNEL +#include +#include + +#define EXC(inst_reg,addr,handler) \ + inst_reg, addr; +#define EXCQ(inst,reg1,reg2,addr,handler) \ + inst reg1, reg2, addr; + +#if _MIPS_SIM == _ABI64 +#define CONFIG_64BIT +#else +#define CONFIG_32BIT +#endif + +#define FEXPORT(symbol) + +#else /* LINUX_KERNEL */ + +#include +#include +#include +#include + +#define EXC(inst_reg,addr,handler) \ +9: inst_reg, addr; \ + .section __ex_table,"a"; \ + PTR_WD 9b, handler; \ + .previous + +#define EXCQ(inst,reg1,reg2,addr,handler) \ +9: inst reg1, reg2, addr; \ + .section __ex_table,"a"; \ + PTR_WD 9b, handler; \ + .previous + +#endif + +#define dst a0 +#define src a1 +#define len a2 +#define rem t8 + +/* + * 64bit ABI vs 32bit ABI + */ +#ifdef CONFIG_64BIT +#define ADDU daddu +#define ADDI daddi +#define SUBU dsubu +#define SLL dsll +#define SRL dsrl +#define PTR_LA dla + +#define LOAD ld + +/* + * As we are sharing code base with the mips32 tree (which use the o32 ABI + * register definitions). We need to redefine the register definitions from + * the n64 ABI register naming to the o32 ABI register naming. + */ +#undef t0 +#undef t1 +#undef t2 +#undef t3 +#define t0 $8 +#define t1 $9 +#define t2 $10 +#define t3 $11 +#define t4 $12 +#define t5 $13 +#define t6 $14 +#define t7 $15 + +#else + +#define ADDU addu +#define ADDI addi +#define SUBU subu +#define SLL sll +#define SRL srl +#define PTR_LA la + +#define LOAD lw +#endif /* CONFIG_64BIT */ + +#define LDFIRST ldr +#define LDREST ldl + +#define SDFIRST sdr +#define SDREST sdl + +#define LWFIRST lwr +#define LWREST lwl + +#define SWFIRST swr +#define SWREST swl + +/* void * memcpy (void *s1, const void *s2, size_t n); */ + .text + .align 5 + .set noreorder + .set noat + .set arch=loongson3a + +LEAF(memcpy) /* a0=dst a1=src a2=len */ +EXPORT_SYMBOL(memcpy) + move v0, dst +.L__memcpy: +FEXPORT(__raw_copy_from_user) +EXPORT_SYMBOL(__raw_copy_from_user) +FEXPORT(__raw_copy_to_user) +EXPORT_SYMBOL(__raw_copy_to_user) + /* if less then 0x28 bytes */ + sltu t2, a2, 0x28 + andi t0, dst, 0xf + bnez t2, .L_memcpy_less + andi t1, src, 0xf + + beqz t0, 1f + ADDI rem, t0, -0x10 + + /* upgrade */ +EXC( LDFIRST t3, 0(src), .Ll_exc) + sltu t4, t0, 0x8 +EXC( LDREST t3, 7(src), .Ll_exc_copy) + SUBU src, rem +EXC( SDFIRST t3, 0(dst), .Ls_exc) + SUBU dst, rem + beqz t4, 1f + ADDU len, rem +EXC( LDFIRST t3, -8(src), .Ll_exc_a8) +EXC( LDREST t3, -1(src), .Ll_exc_copy_a8) +EXC( sd t3, -8(dst), .Ls_exc_p8) + +1: andi t7, src, 0x7 + beq t0, t1, .L_memcpy_16_16 + nop + bnez t7, .L_memcpy_16_4_2_1 + +.L_memcpy_16_8: + SRL t0, len, 6 # 64B per iteration + beqz t0, 2f + and rem, len, 0x3f + .align 4 +1: +EXC( ld t4, (16 * 0)(src), .Ll_exc) +EXC( ld t7, (8 + 16 * 0)(src), .Ll_exc_copy) +EXC( ld t2, (16 * 1)(src), .Ll_exc_copy) +EXC( ld t3, (8 + 16 * 1)(src), .Ll_exc_copy) +EXC( ld t0, (16 * 2)(src), .Ll_exc_copy) +EXC( ld t1, (8 + 16 * 2)(src), .Ll_exc_copy) + ADDI len, -16 * 4 +EXCQ( gssq, t7, t4, (16 * 0)(dst), .Ls_exc_p64) +EXC( ld t4, (16 * 3)(src), .Ll_exc_copy) +EXC( ld t7, (8 + 16 * 3)(src), .Ll_exc_copy) + ADDU src, 16 * 4 + ADDU dst, 16 * 4 +EXCQ( gssq, t3, t2, (-16 * 3)(dst), .Ls_exc_p48) +EXCQ( gssq, t1, t0, (-16 * 2)(dst), .Ls_exc_p32) +EXCQ( gssq, t7, t4, (-16 * 1)(dst), .Ls_exc_p16) + bne len, rem, 1b + nop + beqz len, .Ldone +2: sltu t0, len, 32 + bnez t0, 3f + and rem, len, 0xf +EXC( ld t2, (16 * 0)(src), .Ll_exc) +EXC( ld t3, (8 + 16 * 0)(src), .Ll_exc_copy) +EXC( ld t0, (16 * 1)(src), .Ll_exc_copy) +EXC( ld t1, (8 + 16 * 1)(src), .Ll_exc_copy) + ADDI len, -16 * 2 + ADDU src, 16 * 2 +EXCQ( gssq, t3, t2, (16 * 0)(dst), .Ls_exc_p32) +EXCQ( gssq, t1, t0, (16 * 1)(dst), .Ls_exc_p16) + beqz len, .Ldone + ADDU dst, 32 +3: /* copy less than 32B */ + beq rem, len, .L_memcpy_1_15B_8B_aligned + nop +EXC( ld t2, 0(src), .Ll_exc) +EXC( ld t3, 8(src), .Ll_exc_copy) + ADDI len, -16 + ADDU src, 16 +EXCQ( gssq, t3, t2, 0(dst), .Ls_exc_p16) + bnez len, .L_memcpy_1_15B_8B_aligned + ADDU dst, 16 + + jr ra + nop + +.L_memcpy_16_16: + SRL t0, len, 6 # 64B per iteration + beqz t0, 2f + and rem, len, 0x3f + .align 4 +1: +EXCQ( gslq, t7, t4, (16 * 0)(src), .Ll_exc) +EXCQ( gslq, t3, t2, (16 * 1)(src), .Ll_exc_copy) +EXCQ( gslq, t1, t0, (16 * 2)(src), .Ll_exc_copy) + ADDI len, -16 * 4 +EXCQ( gssq, t7, t4, (16 * 0)(dst), .Ls_exc_p64) +EXCQ( gslq, t7, t4, (16 * 3)(src), .Ll_exc_copy) + ADDU src, 16 * 4 + ADDU dst, 16 * 4 +EXCQ( gssq, t3, t2, (-16 * 3)(dst), .Ls_exc_p48) +EXCQ( gssq, t1, t0, (-16 * 2)(dst), .Ls_exc_p32) +EXCQ( gssq, t7, t4, (-16 * 1)(dst), .Ls_exc_p16) + bne len, rem, 1b + nop + beqz len, .Ldone +2: sltu t0, len, 32 + bnez t0, 3f + and rem, len, 0xf +EXCQ( gslq, t3, t2, (16 * 0)(src), .Ll_exc) +EXCQ( gslq, t1, t0, (16 * 1)(src), .Ll_exc_copy) + ADDI len, -16 * 2 + ADDU src, 32 +EXCQ( gssq, t3, t2, (16 * 0)(dst), .Ls_exc_p32) +EXCQ( gssq, t1, t0, (16 * 1)(dst), .Ls_exc_p16) + beqz len, .Ldone + ADDU dst, 32 +3: /* copy less than 32B */ + beq rem, len, .L_memcpy_1_15B_8B_aligned + nop +EXCQ( gslq, t3, t2, 0(src), .Ll_exc) + ADDI len, -16 + ADDU src, 16 +EXCQ( gssq, t3, t2, 0(dst), .Ls_exc_p16) + beqz len, .Ldone + ADDU dst, 16 +/* + * copy 1 - 15B, src & dst are 8B aligned + */ +.L_memcpy_1_15B_8B_aligned: + sltu t0, len, 0x9 + bnez t0, 1f + nop +EXC( ld t1, (src), .Ll_exc) +EXC( sd t1, (dst), .Ls_exc) +1: ADDU src, len + ADDU dst, len +EXC( LDREST t1, -1(src), .Ll_exc_copy_len) +EXC( SDREST t1, -1(dst), .Ls_exc) +.Ldone: + jr ra + move len, zero + +.L_memcpy_16_4_2_1: + SRL t0, len, 5 # 32B per iteration + beqz t0, 2f + and rem, len, 0x1f +1: +EXC( LDFIRST t4, 0(src), .Ll_exc) +EXC( LDFIRST t7, 8(src), .Ll_exc_copy) + ADDI len, -16 * 2 +EXC( LDREST t4, 7(src), .Ll_exc_copy) +EXC( LDREST t7, 15(src), .Ll_exc_copy) +EXC( LDFIRST t2, 16(src), .Ll_exc_copy) +EXC( LDFIRST t3, 24(src), .Ll_exc_copy) +EXC( LDREST t2, 23(src), .Ll_exc_copy) +EXC( LDREST t3, 31(src), .Ll_exc_copy) + ADDU src, 16 * 2 +EXCQ( gssq, t7, t4, (16 * 0)(dst), .Ls_exc_p32) +EXCQ( gssq, t3, t2, (16 * 1)(dst), .Ls_exc_p16) + bne len, rem, 1b + ADDU dst, 16 * 2 + beqz len, .Ldone +2: and rem, len, 0xf + beq rem, len, .L_memcpy_less + nop +EXC( LDFIRST t0, 0(src), .Ll_exc) +EXC( LDFIRST t1, 8(src), .Ll_exc_copy) + ADDI len, -16 +EXC( LDREST t0, 7(src), .Ll_exc_copy) +EXC( LDREST t1, 15(src), .Ll_exc_copy) + ADDU src, 16 +EXCQ( gssq, t1, t0, 0(dst), .Ls_exc_p16) + beqz len, .Ldone + ADDU dst, 16 + +.L_memcpy_less: + andi t0, len, 0x7 + beq t0, len, 2f + andi t4, len, 0x3 + + .set reorder + SUBU t1, len, t0 + ADDU dst, t1 + ADDU src, t1 + .set at=t2 + PTR_LA t3, 1f + .set noat + SLL t2, t1, 0x1 /* 4 * 4B instructions move 8B data*/ + SUBU t3, t2 + jr t3 + .set noreorder + +EXC( LDFIRST t2, (-8 * 6)(src), .Ll_exc_a48) +EXC( LDREST t2, (-8 * 6 + 7)(src), .Ll_exc_copy_a48) +EXC( SDFIRST t2, (-8 * 6)(dst), .Ls_exc) +EXC( SDREST t2, (-8 * 6 + 7)(dst), .Ls_exc_a8) + +EXC( LDFIRST t3, (-8 * 5)(src), .Ll_exc_a40) +EXC( LDREST t3, (-8 * 5 + 7)(src), .Ll_exc_copy_a40) +EXC( SDFIRST t3, (-8 * 5)(dst), .Ls_exc_a8) +EXC( SDREST t3, (-8 * 5 + 7)(dst), .Ls_exc_a16) + +EXC( LDFIRST t1, (-8 * 4)(src), .Ll_exc_a32) +EXC( LDREST t1, (-8 * 4 + 7)(src), .Ll_exc_copy_a32) +EXC( SDFIRST t1, (-8 * 4)(dst), .Ls_exc_a16) +EXC( SDREST t1, (-8 * 4 + 7)(dst), .Ls_exc_a24) + +EXC( LDFIRST t2, (-8 * 3)(src), .Ll_exc_a24) +EXC( LDREST t2, (-8 * 3 + 7)(src), .Ll_exc_copy_a24) +EXC( SDFIRST t2, (-8 * 3)(dst), .Ls_exc_a24) +EXC( SDREST t2, (-8 * 3 + 7)(dst), .Ls_exc_a32) + +EXC( LDFIRST t3, (-8 * 2)(src), .Ll_exc_a16) +EXC( LDREST t3, (-8 * 2 + 7)(src), .Ll_exc_copy_a16) +EXC( SDFIRST t3, (-8 * 2)(dst), .Ls_exc_a32) +EXC( SDREST t3, (-8 * 2 + 7)(dst), .Ls_exc_a40) + +EXC( LDFIRST t1, (-8 * 1)(src), .Ll_exc_a8) +EXC( LDREST t1, (-8 * 1 + 7)(src), .Ll_exc_copy_a8) +EXC( SDFIRST t1, (-8 * 1)(dst), .Ls_exc_a40) +EXC( SDREST t1, (-8 * 1 + 7)(dst), .Ls_exc_a48) +1: beqz t0, .Ldone + ADDU src, t0 + ADDU dst, t0 +EXC( LDFIRST t2, -8(src), .Ll_exc_a8) +EXC( LDREST t2, -1(src), .Ll_exc_copy_a8) +EXC( SDFIRST t2, -8(dst), .Ls_exc) +EXC( SDREST t2, -1(dst), .Ls_exc) + jr ra + move len, zero + +2: + beq t4, len, 3f + nop +EXC( LWFIRST t2, (src), .Ll_exc) +EXC( LWREST t2, 3(src), .Ll_exc_copy) + ADDU src, len +EXC( SWFIRST t2, (dst), .Ls_exc) +EXC( SWREST t2, 3(dst), .Ls_exc) + beqz t4, .Ldone + ADDU dst, len + +EXC( LWFIRST t1, -4(src), .Ll_exc_a4) +EXC( LWREST t1, -1(src), .Ll_exc_copy_a4) +EXC( SWFIRST t1, -4(dst), .Ls_exc) +EXC( SWREST t1, -1(dst), .Ls_exc) + jr ra + move len, zero + +3: + beqz len, .Ldone + ADDU t0, src, len +1: +EXC( lb t2, (src), .Ll_exc) + ADDU src, 1 +EXC( sb t2, (dst), .Ls_exc_p1) + bne t0, src, 1b + ADDU dst, 1 + + jr ra + move len, zero + + END(memcpy) + +#ifdef LINUX_KERNEL + +#define LEXC_a(n) \ +.Ll_exc_copy_a ## n: \ + ADDI src, -n; \ + b .Ll_exc_copy; \ + ADDI dst, -n; \ +.Ll_exc_a ## n: \ + ADDI src, -n; \ + b .Ll_exc; \ + ADDI dst, -n; + +.Ll_exc_copy_len: + SUBU src, len + b .Ll_exc_copy + SUBU dst, len + +LEXC_a(4) +LEXC_a(8) +LEXC_a(16) +LEXC_a(24) +LEXC_a(32) +LEXC_a(40) +LEXC_a(48) + +.Ll_exc_copy: + /* + * Copy bytes from src until faulting load address (or until a + * lb faults) + * + * When reached by a faulting LDFIRST/LDREST, THREAD_BUADDR($28) + * may be more than a byte beyond the last address. + * Hence, the lb below may get an exception. + * + * Assumes src < THREAD_BUADDR($28) + */ + LOAD t0, TI_TASK($28) + nop + LOAD t0, THREAD_BUADDR(t0) +1: +EXC( lb t1, 0(src), .Ll_exc) + ADDU src, 1 + sb t1, 0(dst) # can't fault -- we're copy_from_user + bne src, t0, 1b + ADDU dst, 1 +.Ll_exc: + LOAD t0, TI_TASK($28) + nop + LOAD t0, THREAD_BUADDR(t0) # t0 is just past last good address + nop + SUBU len, AT, t0 # len number of uncopied bytes + jr ra + nop + +#define SEXC_p(n) \ +.Ls_exc_p ## n: \ + jr ra; \ + ADDU len, n; + +#define SEXC_a(n) \ +.Ls_exc_a ## n: \ + jr ra; \ + ADDI len, -n; + +SEXC_p(1) +SEXC_p(8) +SEXC_p(16) +SEXC_p(32) +SEXC_p(48) +SEXC_p(64) +SEXC_a(48) +SEXC_a(40) +SEXC_a(32) +SEXC_a(24) +SEXC_a(16) +SEXC_a(8) + +.Ls_exc: + jr ra + nop + + .align 5 +LEAF(memmove) +EXPORT_SYMBOL(memmove) + ADDU t0, a0, a2 + ADDU t1, a1, a2 + sltu t0, a1, t0 # dst + len <= src -> memcpy + sltu t1, a0, t1 # dst >= src + len -> memcpy + and t0, t1 + beqz t0, .L__memcpy + move v0, a0 /* return value */ + beqz a2, .Lr_out + END(memmove) + + /* fall through to __rmemcpy */ +LEAF(__rmemcpy) /* a0=dst a1=src a2=len */ + sltu t0, a1, a0 + beqz t0, .Lr_end_bytes_up # src >= dst + nop + ADDU a0, a2 # dst = dst + len + ADDU a1, a2 # src = src + len + +.Lr_end_bytes: + lb t0, -1(a1) + ADDI a2, -0x1 + sb t0, -1(a0) + ADDI a1, -0x1 + bnez a2, .Lr_end_bytes + ADDI a0, -0x1 + +.Lr_out: + jr ra + move a2, zero + +.Lr_end_bytes_up: + lb t0, (a1) + ADDI a2, -0x1 + sb t0, (a0) + ADDU a1, 0x1 + bnez a2, .Lr_end_bytes_up + ADDU a0, 0x1 + + jr ra + move a2, zero + END(__rmemcpy) +#endif diff --git a/arch/mips/loongson64/loongson3-memset.S b/arch/mips/loongson64/loongson3-memset.S new file mode 100644 index 0000000000000..55c3b8d7bc458 --- /dev/null +++ b/arch/mips/loongson64/loongson3-memset.S @@ -0,0 +1,206 @@ +/* + * 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. + * + * Copyright (C) 1998, 1999, 2000 by Ralf Baechle + * Copyright (C) 1999, 2000 Silicon Graphics, Inc. + * Copyright (C) 2007 by Maciej W. Rozycki + * Copyright (C) 2011, 2012 MIPS Technologies, Inc. + */ +#include +#include +#include +#include + +#define LONG_S_L sdl +#define LONG_S_R sdr + +#define STORSIZE 16 +#define STORMASK 15 + +#define EX(insn,reg,addr,handler) \ +9: insn reg, addr; \ + .section __ex_table,"a"; \ + PTR_WD 9b, handler; \ + .previous + +#define EX_GSSQ(reg, addr, handler) \ + .set push; \ + .set arch=loongson3a; \ +9: gssq reg, reg, addr; \ + .set pop; \ + .section __ex_table,"a"; \ + PTR_WD 9b, handler; \ + .previous + + .macro f_fill128 dst, offset, val, fixup + EX_GSSQ(\val, (\offset + 0 * STORSIZE)(\dst), \fixup) + EX_GSSQ(\val, (\offset + 1 * STORSIZE)(\dst), \fixup) + EX_GSSQ(\val, (\offset + 2 * STORSIZE)(\dst), \fixup) + EX_GSSQ(\val, (\offset + 3 * STORSIZE)(\dst), \fixup) + EX_GSSQ(\val, (\offset + 4 * STORSIZE)(\dst), \fixup) + EX_GSSQ(\val, (\offset + 5 * STORSIZE)(\dst), \fixup) + EX_GSSQ(\val, (\offset + 6 * STORSIZE)(\dst), \fixup) + EX_GSSQ(\val, (\offset + 7 * STORSIZE)(\dst), \fixup) + .endm + +/* + * memset(void *s, int c, size_t n) + * + * a0: start of area to clear + * a1: char to fill with + * a2: size of area to clear + */ + .set noreorder + .align 5 +LEAF(memset) +EXPORT_SYMBOL(memset) + beqz a1, 1f + move v0, a0 /* result */ + + andi a1, 0xff /* spread fillword */ + LONG_SLL t1, a1, 8 + or a1, t1 + LONG_SLL t1, a1, 16 + or a1, t1 + LONG_SLL t1, a1, 32 + or a1, t1 +1: + +FEXPORT(__bzero) +EXPORT_SYMBOL(__bzero) + sltiu t0, a2, STORSIZE /* very small region? */ + bnez t0, .Lsmall_memset + andi t0, a0, STORMASK /* aligned? */ + + .set noat + li AT, STORSIZE + beqz t0, 1f + PTR_SUBU t0, AT /* alignment in bytes */ + .set at + + EX(LONG_S_R, a1, (a0), .Lfirst_fixup) /* make word/dword 8B aligned */ + .set push + .set arch=mips64r2 + PTR_ADDIU t1, a0, 8 + dins t1, zero, 0, 3 + .set pop + EX(LONG_S, a1, (t1), .Lsecond_fixup) /* May double copy 8B */ + + PTR_SUBU a0, t0 /* long align ptr */ + PTR_ADDU a2, t0 /* correct size */ + +1: ori t1, a2, 0x7f /* # of full blocks */ + xori t1, 0x7f + beqz t1, .Lmemset_partial /* no block to fill */ + andi t0, a2, 0x80-STORSIZE + + PTR_ADDU t1, a0 /* end address */ + .set reorder +1: PTR_ADDIU a0, 128 + f_fill128 a0, -128, a1, .Lfwd_fixup + bne t1, a0, 1b + .set noreorder + +.Lmemset_partial: + PTR_LA t1, 2f /* where to start */ + .set noat + LONG_SRL AT, t0, 2 + PTR_SUBU t1, AT + .set at + jr t1 + PTR_ADDU a0, t0 /* dest ptr */ + + .set push + .set noreorder + .set nomacro + f_fill128 a0, -128, a1, .Lpartial_fixup /* ... but first do 16Bs ... */ +2: .set pop + andi a2, STORMASK /* At most 15B to go */ + + beqz a2, 1f + PTR_ADDU a0, a2 /* What's left */ + .set push + .set arch=mips64r2 + PTR_ADDI t1, a0, -8 + dins t1, zero, 0, 3 + .set pop + EX(LONG_S, a1, (t1), .Lnotlast_fixup) /* May double copy 8B */ + EX(LONG_S_L, a1, -1(a0), .Llast_fixup) +1: jr ra + move a2, zero + +.Lsmall_memset: + andi t1, a2, 7 + beq t1, a2, 1f + LONG_SLL t1, 2 + + EX(LONG_S_R, a1, (a0), .Lfirst_fixup) + EX(LONG_S_L, a1, 7(a0), .Lsmall_memset_fixup) + +1: PTR_LA t0, 2f + PTR_SUBU t1, t0, t1 + jr t1 + PTR_ADDU a0, a2 + + EX(sb, a1, -7(a0), .Lsmall_memset_partial_fixup) + EX(sb, a1, -6(a0), .Lsmall_memset_partial_fixup) + EX(sb, a1, -5(a0), .Lsmall_memset_partial_fixup) + EX(sb, a1, -4(a0), .Lsmall_memset_partial_fixup) + EX(sb, a1, -3(a0), .Lsmall_memset_partial_fixup) + EX(sb, a1, -2(a0), .Lsmall_memset_partial_fixup) + EX(sb, a1, -1(a0), .Lsmall_memset_partial_fixup) + +2: jr ra /* done */ + move a2, zero + END(memset) + +.Lsmall_memset_fixup: + PTR_ADDIU t0, a0, 8 + .set push + .set arch=mips64r2 + dins t0, zero, 0, 3 + .set pop + LONG_ADDU a2, a0 + jr ra + LONG_SUBU a2, t0 + +.Lsmall_memset_partial_fixup: + PTR_L t0, TI_TASK($28) + LONG_L t0, THREAD_BUADDR(t0) + jr ra + LONG_SUBU a2, a0, t0 + +.Lfirst_fixup: + jr ra + nop + +.Lsecond_fixup: + LONG_ADDU a2, a0 + jr ra + LONG_SUBU a2, t1 + +.Lfwd_fixup: + PTR_L t0, TI_TASK($28) + andi a2, 0x7f + LONG_L t0, THREAD_BUADDR(t0) + LONG_ADDU a2, t1 + jr ra + LONG_SUBU a2, t0 + +.Lpartial_fixup: + PTR_L t0, TI_TASK($28) + andi a2, STORMASK + LONG_L t0, THREAD_BUADDR(t0) + LONG_ADDU a2, a0 + jr ra + LONG_SUBU a2, t0 + +.Llast_fixup: + jr ra + andi a2, 0x7 + +.Lnotlast_fixup: + jr ra + PTR_SUBU a2, a0, t1 diff --git a/arch/mips/loongson64/numa.c b/arch/mips/loongson64/numa.c index 8f61e93c0c5bc..d2c264317fe4d 100644 --- a/arch/mips/loongson64/numa.c +++ b/arch/mips/loongson64/numa.c @@ -62,7 +62,7 @@ static int __init compute_node_distance(int row, int col) else if (package_row == package_col) return 40; else - return 100; + return 200; } static void __init init_topology_matrix(void) diff --git a/arch/mips/loongson64/platform.c b/arch/mips/loongson64/platform.c new file mode 100644 index 0000000000000..56a0299b297db --- /dev/null +++ b/arch/mips/loongson64/platform.c @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2009 Lemote Inc. + * Author: Wu Zhangjin, wuzhangjin@gmail.com + * Xiang Yu, xiangy@lemote.com + * Chen Huacai, chenhc@lemote.com + */ + +#include +#include +#include +#include + +/* + * Kernel helper policy + * + * Fan is controlled by EC in laptop pruducts, but EC can not get the current + * cpu temperature which used for adjusting the current fan speed. + * + * So, kernel read the CPU temperature and notify it to EC per second, + * that's all! + */ +struct loongson_fan_policy kernel_helper_policy = { + .type = KERNEL_HELPER_POLICY, + .adjust_period = 1, + .depend_temp = loongson3_cpu_temp, +}; + +/* + * Policy at step mode + * + * up_step array | down_step array + * | + * [min, 50), 50% | (min, 45), 50% + * [50, 60), 60% | [45, 55), 60% + * [60, 70), 70% | [55, 65), 70% + * [70, 80), 80% | [65, 75), 80% + * [80, max), 100% | [75, max), 100% + * + */ +struct loongson_fan_policy step_speed_policy = { + .type = STEP_SPEED_POLICY, + .adjust_period = 1, + .depend_temp = loongson3_cpu_temp, + .up_step_num = 5, + .down_step_num = 5, + .up_step = { + {MIN_TEMP, 50, 50}, + { 50, 60, 60}, + { 60, 70, 70}, + { 70, 80, 80}, + { 80, MAX_TEMP, 100}, + }, + .down_step = { + {MIN_TEMP, 45, 50}, + { 45, 55, 60}, + { 55, 65, 70}, + { 65, 75, 80}, + { 75, MAX_TEMP, 100}, + }, +}; + +/* + * Constant speed policy + * + */ +struct loongson_fan_policy constant_speed_policy = { + .type = CONSTANT_SPEED_POLICY, +}; diff --git a/arch/mips/loongson64/pm.c b/arch/mips/loongson64/pm.c index 7c8556f097812..5f0604af8f136 100644 --- a/arch/mips/loongson64/pm.c +++ b/arch/mips/loongson64/pm.c @@ -6,98 +6,46 @@ * Author: Wu Zhangjin */ #include -#include #include -#include #include #include -static unsigned int __maybe_unused cached_master_mask; /* i8259A */ -static unsigned int __maybe_unused cached_slave_mask; -static unsigned int __maybe_unused cached_bonito_irq_mask; /* bonito */ +asmlinkage void loongson_lefi_sleep(unsigned long sleep_addr); -void arch_suspend_disable_irqs(void) +static int lefi_pm_enter(suspend_state_t state) { - /* disable all mips events */ - local_irq_disable(); - -#ifdef CONFIG_I8259 - /* disable all events of i8259A */ - cached_slave_mask = inb(PIC_SLAVE_IMR); - cached_master_mask = inb(PIC_MASTER_IMR); - - outb(0xff, PIC_SLAVE_IMR); - inb(PIC_SLAVE_IMR); - outb(0xff, PIC_MASTER_IMR); - inb(PIC_MASTER_IMR); -#endif - /* disable all events of bonito */ - cached_bonito_irq_mask = LOONGSON_INTEN; - LOONGSON_INTENCLR = 0xffff; - (void)LOONGSON_INTENCLR; -} - -void arch_suspend_enable_irqs(void) -{ - /* enable all mips events */ - local_irq_enable(); -#ifdef CONFIG_I8259 - /* only enable the cached events of i8259A */ - outb(cached_slave_mask, PIC_SLAVE_IMR); - outb(cached_master_mask, PIC_MASTER_IMR); -#endif - /* enable all cached events of bonito */ - LOONGSON_INTENSET = cached_bonito_irq_mask; - (void)LOONGSON_INTENSET; -} - -/* - * Setup the board-specific events for waking up loongson from wait mode - */ -void __weak setup_wakeup_events(void) -{ -} - -void __weak mach_suspend(void) -{ -} - -void __weak mach_resume(void) -{ -} - -static int loongson_pm_enter(suspend_state_t state) -{ - mach_suspend(); - - mach_resume(); - - return 0; + switch (state) { + case PM_SUSPEND_MEM: + pm_set_suspend_via_firmware(); + loongson_lefi_sleep(loongson_sysconf.suspend_addr); + pm_set_resume_via_firmware(); + return 0; + default: + return -EINVAL; + } } -static int loongson_pm_valid_state(suspend_state_t state) +static int lefi_pm_valid_state(suspend_state_t state) { switch (state) { - case PM_SUSPEND_ON: - case PM_SUSPEND_STANDBY: case PM_SUSPEND_MEM: - return 1; - + return !!loongson_sysconf.suspend_addr; default: return 0; } } -static const struct platform_suspend_ops loongson_pm_ops = { - .valid = loongson_pm_valid_state, - .enter = loongson_pm_enter, +static const struct platform_suspend_ops lefi_pm_ops = { + .valid = lefi_pm_valid_state, + .enter = lefi_pm_enter, }; static int __init loongson_pm_init(void) { - suspend_set_ops(&loongson_pm_ops); + if (loongson_sysconf.fw_interface == LOONGSON_LEFI) + suspend_set_ops(&lefi_pm_ops); return 0; } diff --git a/arch/mips/loongson64/sleeper.S b/arch/mips/loongson64/sleeper.S new file mode 100644 index 0000000000000..cf16877409e2f --- /dev/null +++ b/arch/mips/loongson64/sleeper.S @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2024, Jiaxun Yang + * Loongson EFI firmware sleeper routine + */ + +#include +#include + +#include + +LEAF(loongson_lefi_sleep) + SUSPEND_SAVE + move t9, a0 + PTR_LA a0, wake + move a1, sp + jalr t9 +wake: + smp_slave_setup + RESUME_RESTORE_REGS_RETURN +END(loongson_lefi_sleep) diff --git a/arch/mips/loongson64/smp.c b/arch/mips/loongson64/smp.c index 979993679d913..0a83e8aa925be 100644 --- a/arch/mips/loongson64/smp.c +++ b/arch/mips/loongson64/smp.c @@ -449,13 +449,16 @@ static void loongson3_init_secondary(void) initcount = core0_c0count[cpu] + i/2; write_c0_count(initcount); + __cpu_full_name[cpu] = cpu_full_name; } static void loongson3_smp_finish(void) { int cpu = smp_processor_id(); - write_c0_compare(read_c0_count() + mips_hpt_frequency/HZ); + if (!cpu_has_constant_timer) + write_c0_compare(read_c0_count() + mips_hpt_frequency/HZ); + local_irq_enable(); ipi_clear_buf(cpu); diff --git a/arch/mips/loongson64/smp.h b/arch/mips/loongson64/smp.h index 957bde81e0e4e..6112d9d7dd226 100644 --- a/arch/mips/loongson64/smp.h +++ b/arch/mips/loongson64/smp.h @@ -3,6 +3,7 @@ #define __LOONGSON_SMP_H_ /* for Loongson-3 smp support */ +extern char cpu_full_name[64]; extern unsigned long long smp_group[4]; /* 4 groups(nodes) in maximum in numa case */ diff --git a/arch/mips/loongson64/workarounds.c b/arch/mips/loongson64/workarounds.c new file mode 100644 index 0000000000000..7eea4553b1fa1 --- /dev/null +++ b/arch/mips/loongson64/workarounds.c @@ -0,0 +1,30 @@ +#include +#include + +static int __init usb_fix_for_tmcs(void) +{ + if (loongson_sysconf.workarounds & WORKAROUND_USB_TMCS) { + gpio_request(13, "gpio13"); + gpio_request(12, "gpio12"); + gpio_request(11, "gpio11"); + gpio_request(9, "gpio9"); + gpio_request(8, "gpio8"); + + gpio_direction_output(11, 1); + msleep(1000); + gpio_direction_output(13, 0); + gpio_direction_output(12, 0); + gpio_direction_output(9, 0); + gpio_direction_output(8, 0); + + gpio_set_value(8, 1); + gpio_set_value(13, 1); + msleep(1000); + gpio_set_value(9, 1); + gpio_set_value(12, 1); + } + + return 0; +} + +late_initcall(usb_fix_for_tmcs); diff --git a/arch/mips/math-emu/cp1emu.c b/arch/mips/math-emu/cp1emu.c index 265bc57819dfb..0c618683e57cf 100644 --- a/arch/mips/math-emu/cp1emu.c +++ b/arch/mips/math-emu/cp1emu.c @@ -1451,6 +1451,7 @@ static union ieee754sp fpemu_sp_rsqrt(union ieee754sp s) return ieee754sp_div(ieee754sp_one(0), ieee754sp_sqrt(s)); } +#ifndef CONFIG_CPU_LOONGSON64 DEF3OP(madd, sp, ieee754sp_mul, ieee754sp_add, ); DEF3OP(msub, sp, ieee754sp_mul, ieee754sp_sub, ); DEF3OP(nmadd, sp, ieee754sp_mul, ieee754sp_add, ieee754sp_neg); @@ -1459,6 +1460,7 @@ DEF3OP(madd, dp, ieee754dp_mul, ieee754dp_add, ); DEF3OP(msub, dp, ieee754dp_mul, ieee754dp_sub, ); DEF3OP(nmadd, dp, ieee754dp_mul, ieee754dp_add, ieee754dp_neg); DEF3OP(nmsub, dp, ieee754dp_mul, ieee754dp_sub, ieee754dp_neg); +#endif static int fpux_emu(struct pt_regs *xcp, struct mips_fpu_struct *ctx, mips_instruction ir, void __user **fault_addr) @@ -1513,6 +1515,20 @@ static int fpux_emu(struct pt_regs *xcp, struct mips_fpu_struct *ctx, } break; +#ifdef CONFIG_CPU_LOONGSON64 + case madd_s_op: + handler = ieee754sp_madd; + goto scoptop; + case msub_s_op: + handler = ieee754sp_msub; + goto scoptop; + case nmadd_s_op: + handler = ieee754sp_nmadd; + goto scoptop; + case nmsub_s_op: + handler = ieee754sp_nmsub; + goto scoptop; +#else case madd_s_op: if (cpu_has_mac2008_only) handler = ieee754sp_madd; @@ -1537,6 +1553,7 @@ static int fpux_emu(struct pt_regs *xcp, struct mips_fpu_struct *ctx, else handler = fpemu_sp_nmsub; goto scoptop; +#endif scoptop: SPFROMREG(fr, MIPSInst_FR(ir)); @@ -1621,6 +1638,20 @@ static int fpux_emu(struct pt_regs *xcp, struct mips_fpu_struct *ctx, } break; +#ifdef CONFIG_CPU_LOONGSON64 + case madd_d_op: + handler = ieee754dp_madd; + goto dcoptop; + case msub_d_op: + handler = ieee754dp_msub; + goto dcoptop; + case nmadd_d_op: + handler = ieee754dp_nmadd; + goto dcoptop; + case nmsub_d_op: + handler = ieee754dp_nmsub; + goto dcoptop; +#else case madd_d_op: if (cpu_has_mac2008_only) handler = ieee754dp_madd; @@ -1645,6 +1676,7 @@ static int fpux_emu(struct pt_regs *xcp, struct mips_fpu_struct *ctx, else handler = fpemu_dp_nmsub; goto dcoptop; +#endif dcoptop: DPFROMREG(fr, MIPSInst_FR(ir)); diff --git a/arch/mips/mm/page.c b/arch/mips/mm/page.c index d3b4459d0fe85..4dbecc10e7a01 100644 --- a/arch/mips/mm/page.c +++ b/arch/mips/mm/page.c @@ -218,6 +218,11 @@ static void set_prefetch_parameters(void) else if (cpu_has_cache_cdex_p) cache_line_size = cpu_dcache_line_size(); } + +#ifdef CONFIG_CPU_LOONGSON64 + clear_word_size = 16; +#endif + /* * Too much unrolling will overflow the available space in * clear_space_array / copy_page_array. @@ -232,11 +237,15 @@ static void set_prefetch_parameters(void) static void build_clear_store(u32 **buf, int off) { +#ifdef CONFIG_CPU_LOONGSON64 + uasm_i_gssq(buf, ZERO, ZERO, off, A0); +#else if (cpu_has_64bit_gp_regs || cpu_has_64bit_zero_reg) { uasm_i_sd(buf, ZERO, off, A0); } else { uasm_i_sw(buf, ZERO, off, A0); } +#endif } static inline void build_clear_pref(u32 **buf, int off) diff --git a/arch/mips/mm/tlbex.c b/arch/mips/mm/tlbex.c index b4e1c783e6177..c595f16e1ef56 100644 --- a/arch/mips/mm/tlbex.c +++ b/arch/mips/mm/tlbex.c @@ -695,13 +695,12 @@ static void build_huge_tlb_write_entry(u32 **p, struct uasm_label **l, */ static void build_is_huge_pte(u32 **p, struct uasm_reloc **r, unsigned int tmp, - unsigned int pmd, int lid) + unsigned int pmde, int lid) { - UASM_i_LW(p, tmp, 0, pmd); if (use_bbit_insns()) { - uasm_il_bbit1(p, r, tmp, ilog2(_PAGE_HUGE), lid); + uasm_il_bbit1(p, r, pmde, ilog2(_PAGE_HUGE), lid); } else { - uasm_i_andi(p, tmp, tmp, _PAGE_HUGE); + uasm_i_andi(p, tmp, pmde, _PAGE_HUGE); uasm_il_bnez(p, r, tmp, lid); } } @@ -1061,7 +1060,6 @@ EXPORT_SYMBOL_GPL(build_update_entries); struct mips_huge_tlb_info { int huge_pte; int restore_scratch; - bool need_reload_pte; }; static struct mips_huge_tlb_info @@ -1076,7 +1074,6 @@ build_fast_tlb_refill_handler (u32 **p, struct uasm_label **l, rv.huge_pte = scratch; rv.restore_scratch = 0; - rv.need_reload_pte = false; if (check_for_high_segbits) { UASM_i_MFC0(p, tmp, C0_BADVADDR); @@ -1282,7 +1279,6 @@ static void build_r4000_tlb_refill_handler(void) } else { htlb_info.huge_pte = K0; htlb_info.restore_scratch = 0; - htlb_info.need_reload_pte = true; vmalloc_mode = refill_noscratch; /* * create the plain linear handler @@ -1307,11 +1303,14 @@ static void build_r4000_tlb_refill_handler(void) build_get_pgde32(&p, K0, K1); /* get pgd in K1 */ #endif + UASM_i_LW(&p, K0, 0, K1); /* get pmd entry in K0 */ #ifdef CONFIG_MIPS_HUGE_TLB_SUPPORT - build_is_huge_pte(&p, &r, K0, K1, label_tlb_huge_update); + build_is_huge_pte(&p, &r, K1, K0, label_tlb_huge_update); #endif - build_get_ptep(&p, K0, K1); + GET_CONTEXT(&p, K1); /* get context reg */ + build_adjust_context(&p, K1); + UASM_i_ADDU(&p, K1, K0, K1); /* add in offset */ build_update_entries(&p, K0, K1); build_tlb_write_entry(&p, &l, &r, tlb_random); uasm_l_leave(&l, p); @@ -1319,8 +1318,6 @@ static void build_r4000_tlb_refill_handler(void) } #ifdef CONFIG_MIPS_HUGE_TLB_SUPPORT uasm_l_tlb_huge_update(&l, p); - if (htlb_info.need_reload_pte) - UASM_i_LW(&p, htlb_info.huge_pte, 0, K1); build_huge_update_entries(&p, htlb_info.huge_pte, K1); build_huge_tlb_write_entry(&p, &l, &r, K0, tlb_random, htlb_info.restore_scratch); @@ -2019,20 +2016,20 @@ build_r4000_tlbchange_handler_head(u32 **p, struct uasm_label **l, build_get_pgde32(p, wr.r1, wr.r2); /* get pgd in ptr */ #endif + UASM_i_LW(p, wr.r3, 0, wr.r2); /* get pmd entry in wr.r3 */ #ifdef CONFIG_MIPS_HUGE_TLB_SUPPORT /* - * For huge tlb entries, pmd doesn't contain an address but + * For huge tlb entries, pmde doesn't contain an address but * instead contains the tlb pte. Check the PAGE_HUGE bit and * see if we need to jump to huge tlb processing. */ - build_is_huge_pte(p, r, wr.r1, wr.r2, label_tlb_huge_update); + build_is_huge_pte(p, r, wr.r1, wr.r3, label_tlb_huge_update); #endif UASM_i_MFC0(p, wr.r1, C0_BADVADDR); - UASM_i_LW(p, wr.r2, 0, wr.r2); UASM_i_SRL(p, wr.r1, wr.r1, PAGE_SHIFT - PTE_T_LOG2); uasm_i_andi(p, wr.r1, wr.r1, (PTRS_PER_PTE - 1) << PTE_T_LOG2); - UASM_i_ADDU(p, wr.r2, wr.r2, wr.r1); + UASM_i_ADDU(p, wr.r2, wr.r3, wr.r1); #ifdef CONFIG_SMP uasm_l_smp_pgtable_change(l, *p); diff --git a/arch/mips/mm/uasm-mips.c b/arch/mips/mm/uasm-mips.c index e15c6700cd088..7e44ea365795b 100644 --- a/arch/mips/mm/uasm-mips.c +++ b/arch/mips/mm/uasm-mips.c @@ -27,6 +27,10 @@ #define RT_SH 16 #define SCIMM_MASK 0xfffff #define SCIMM_SH 6 +#define RZ_MASK 0x1f +#define RZ_SH 0 +#define RC_MASK 0x1ff +#define RC_SH 6 /* This macro sets the non-variable bits of an instruction. */ #define M(a, b, c, d, e, f) \ @@ -203,6 +207,7 @@ static const struct insn insn_table[insn_invalid] = { [insn_xor] = {M(spec_op, 0, 0, 0, 0, xor_op), RS | RT | RD}, [insn_xori] = {M(xori_op, 0, 0, 0, 0, 0), RS | RT | UIMM}, [insn_yield] = {M(spec3_op, 0, 0, 0, 0, yield_op), RS | RD}, + [insn_gssq] = {M(swc2_op, 0, 0, 0, 0, gssq_op), RT | RS | RZ | RC}, }; #undef M @@ -225,6 +230,21 @@ static inline u32 build_jimm(u32 arg) return (arg >> 2) & JIMM_MASK; } +static inline u32 build_rz(u32 arg) +{ + WARN(arg & ~RZ_MASK, KERN_WARNING "Micro-assembler field overflow\n"); + + return (arg & RZ_MASK) << RZ_SH; +} + +static inline u32 build_rc(s32 arg) +{ + WARN((arg >> 4) > 0xff || + (arg >> 4) < -0x100, KERN_WARNING "Micro-assembler field overflow\n"); + + return ((arg >> 4) & RC_MASK) << RC_SH; +} + /* * The order of opcode arguments is implicitly left to right, * starting with RS and ending with FUNC or IMM. @@ -252,6 +272,10 @@ static void build_insn(u32 **buf, enum opcode opc, ...) op |= build_rd(va_arg(ap, u32)); if (ip->fields & RE) op |= build_re(va_arg(ap, u32)); + if (ip->fields & RZ) + op |= build_rz(va_arg(ap, u32)); + if (ip->fields & RC) + op |= build_rc(va_arg(ap, s32)); if (ip->fields & SIMM) op |= build_simm(va_arg(ap, s32)); if (ip->fields & UIMM) diff --git a/arch/mips/mm/uasm.c b/arch/mips/mm/uasm.c index 125140979d62c..2a9ca7de43760 100644 --- a/arch/mips/mm/uasm.c +++ b/arch/mips/mm/uasm.c @@ -26,6 +26,8 @@ enum fields { SET = 0x200, SCIMM = 0x400, SIMM9 = 0x800, + RZ = 0x1000, + RC = 0x2000 }; #define OP_MASK 0x3f @@ -65,7 +67,7 @@ enum opcode { insn_sllv, insn_slt, insn_slti, insn_sltiu, insn_sltu, insn_sra, insn_srav, insn_srl, insn_srlv, insn_subu, insn_sw, insn_sync, insn_syscall, insn_tlbp, insn_tlbr, insn_tlbwi, insn_tlbwr, insn_wait, - insn_wsbh, insn_xor, insn_xori, insn_yield, + insn_wsbh, insn_xor, insn_xori, insn_yield, insn_gssq, insn_invalid /* insn_invalid must be last */ }; @@ -198,6 +200,13 @@ Ip_u2s3u1(op) \ } \ UASM_EXPORT_SYMBOL(uasm_i##op); +#define I_u4u2u1s3(op) \ +Ip_u4u2u1s3(op) \ +{ \ + build_insn(buf, insn##op, d, b, a, c); \ +} \ +UASM_EXPORT_SYMBOL(uasm_i##op); + #define I_u2u1s3(op) \ Ip_u2u1s3(op) \ { \ @@ -356,6 +365,7 @@ I_u2s3u1(_sd) I_u3u1u2(_seleqz) I_u3u1u2(_selnez) I_u2s3u1(_sh) +I_u4u2u1s3(_gssq) I_u2u1u3(_sll) I_u3u2u1(_sllv) I_s3s1s2(_slt) diff --git a/drivers/cpufreq/Kconfig b/drivers/cpufreq/Kconfig index b14584bfdf3f5..393cb75809741 100644 --- a/drivers/cpufreq/Kconfig +++ b/drivers/cpufreq/Kconfig @@ -271,6 +271,18 @@ config LOONGSON2_CPUFREQ Loongson2F and its successors support this feature. If in doubt, say N. + +config GS464_CPUFREQ + bool "GS464 (MIPS-based Loongson-3) CPUFreq Driver" + depends on CPU_LOONGSON64 && MIPS + help + This option adds a CPUFreq driver for GS464 (MIPS-based + Loongson-3) processors which support software configurable CPU + frequency. + + For details, take a look at . + + If in doubt, say N. endif if LOONGARCH diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index 076ea3ac1b56d..b6f271b5b40e9 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -105,6 +105,8 @@ obj-$(CONFIG_BMIPS_CPUFREQ) += bmips-cpufreq.o obj-$(CONFIG_IA64_ACPI_CPUFREQ) += ia64-acpi-cpufreq.o obj-$(CONFIG_LOONGSON2_CPUFREQ) += loongson2_cpufreq.o obj-$(CONFIG_LOONGSON3_ACPI_CPUFREQ) += loongson3-acpi-cpufreq.o +obj-$(CONFIG_GS464_CPUFREQ) += gs464_cpufreq.o +obj-$(CONFIG_LOONGSON1_CPUFREQ) += loongson1-cpufreq.o obj-$(CONFIG_SH_CPU_FREQ) += sh-cpufreq.o obj-$(CONFIG_SPARC_US2E_CPUFREQ) += sparc-us2e-cpufreq.o obj-$(CONFIG_SPARC_US3_CPUFREQ) += sparc-us3-cpufreq.o diff --git a/drivers/cpufreq/gs464_cpufreq.c b/drivers/cpufreq/gs464_cpufreq.c new file mode 100644 index 0000000000000..940a578b88456 --- /dev/null +++ b/drivers/cpufreq/gs464_cpufreq.c @@ -0,0 +1,317 @@ +/* + * CPUFreq driver for the loongson-3 processors + * + * All revisions of Loongson-3 processor support this feature. + * + * Copyright (C) 2008 - 2014 Lemote Inc. + * Author: Yan Hua, yanh@lemote.com + * Chen Huacai, chenhc@lemote.com + * + * 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +static int new_method = 0; +static int boost_supported = 0; +static struct mutex cpufreq_mutex[MAX_PACKAGES]; +static spinlock_t cpufreq_reg_lock[MAX_PACKAGES]; + +enum freq { + FREQ_LEV0, /* Reserved */ + FREQ_LEV1, FREQ_LEV2, FREQ_LEV3, FREQ_LEV4, + FREQ_LEV5, FREQ_LEV6, FREQ_LEV7, FREQ_LEV8, + FREQ_LEV9, FREQ_LEV10, FREQ_LEV11, FREQ_LEV12, + FREQ_LEV13, FREQ_LEV14, FREQ_LEV15, FREQ_LEV16, + FREQ_RESV +}; + +/* For Loongson-3A4000+, support boost */ +static struct cpufreq_frequency_table gs464_cpufreq_table[] = { + {0, FREQ_LEV0, CPUFREQ_ENTRY_INVALID}, + {0, FREQ_LEV1, CPUFREQ_ENTRY_INVALID}, + {0, FREQ_LEV2, CPUFREQ_ENTRY_INVALID}, + {0, FREQ_LEV3, CPUFREQ_ENTRY_INVALID}, + {0, FREQ_LEV4, CPUFREQ_ENTRY_INVALID}, + {0, FREQ_LEV5, CPUFREQ_ENTRY_INVALID}, + {0, FREQ_LEV6, CPUFREQ_ENTRY_INVALID}, + {0, FREQ_LEV7, CPUFREQ_ENTRY_INVALID}, + {0, FREQ_LEV8, CPUFREQ_ENTRY_INVALID}, + {0, FREQ_LEV9, CPUFREQ_ENTRY_INVALID}, + {0, FREQ_LEV10, CPUFREQ_ENTRY_INVALID}, + {0, FREQ_LEV11, CPUFREQ_ENTRY_INVALID}, + {0, FREQ_LEV12, CPUFREQ_ENTRY_INVALID}, + {0, FREQ_LEV13, CPUFREQ_ENTRY_INVALID}, + {0, FREQ_LEV14, CPUFREQ_ENTRY_INVALID}, + {0, FREQ_LEV15, CPUFREQ_ENTRY_INVALID}, + {0, FREQ_LEV16, CPUFREQ_ENTRY_INVALID}, + {0, FREQ_RESV, CPUFREQ_TABLE_END}, +}; +extern struct clk *gs464_cpu_clk_get(int cpu); + +static int gs464_cpu_freq_notifier(struct notifier_block *nb, + unsigned long val, void *data); + +static struct notifier_block gs464_cpufreq_notifier_block = { + .notifier_call = gs464_cpu_freq_notifier +}; + +#ifdef CONFIG_SMP +static int gs464_cpu_freq_notifier(struct notifier_block *nb, + unsigned long val, void *data) +{ + unsigned long cpu; + struct clock_event_device *cd; + struct cpufreq_freqs *freqs = (struct cpufreq_freqs *)data; + + if (freqs->flags & CPUFREQ_CONST_LOOPS) + return 0; + + if (val != CPUFREQ_POSTCHANGE) + return 0; + + for_each_cpu(cpu, freqs->policy->cpus) { + cd = &per_cpu(mips_clockevent_device, cpu); + + if (cpu == smp_processor_id()) + clockevents_update_freq(cd, freqs->new * 1000 / 2); + else { + clockevents_calc_mult_shift(cd, freqs->new * 1000 / 2, 4); + cd->min_delta_ns = clockevent_delta2ns(cd->min_delta_ticks, cd); + cd->max_delta_ns = clockevent_delta2ns(cd->max_delta_ticks, cd); + } + } + + return 0; +} +#else +static int gs464_cpu_freq_notifier(struct notifier_block *nb, + unsigned long val, void *data) +{ + struct cpufreq_freqs *freqs = (struct cpufreq_freqs *)data; + struct clock_event_device *cd = &per_cpu(mips_clockevent_device, 0); + + if (freqs->flags & CPUFREQ_CONST_LOOPS) + return 0; + + if (val == CPUFREQ_POSTCHANGE) + clockevents_update_freq(cd, freqs->new * 1000 / 2); + + return 0; +} +#endif + +static unsigned int gs464_cpufreq_get(unsigned int cpu) +{ + return gs464_clk_get_rate(gs464_cpu_clk_get(cpu)); +} + +static int gs464_cpufreq_set(struct cpufreq_policy *policy, int freq_level) +{ + uint32_t core_id = cpu_core(&cpu_data[policy->cpu]); + struct freq_level_setting_args args; + + args.level = freq_level; + args.cpumask = 1 << core_id; + do_service_request(CMD_SET_CPU_LEVEL, &args); + + return 0; +} + +/* + * Here we notify other drivers of the proposed change and the final change. + */ +static int gs464_cpufreq_target(struct cpufreq_policy *policy, + unsigned int index) +{ + unsigned int freq; + unsigned int cpu = policy->cpu; + unsigned int package = cpu_data[cpu].package; + + if (!cpu_online(cpu)) + return -ENODEV; + + freq = + ((cpu_clock_freq / 1000) * + gs464_clockmod_table[index].driver_data) / 8; + + /* setting the cpu frequency */ + if (new_method) { + mutex_lock(&cpufreq_mutex[package]); + gs464_cpufreq_set(policy, index); + mutex_unlock(&cpufreq_mutex[package]); + } else { + spin_lock(&cpufreq_reg_lock[package]); + gs464_clk_set_rate(policy->clk, freq); + spin_unlock(&cpufreq_reg_lock[package]); + } + + return 0; +} + +static int gs464_cpufreq_cpu_init(struct cpufreq_policy *policy) +{ + if (!cpu_online(policy->cpu)) + return -ENODEV; + + policy->clk = gs464_cpu_clk_get(policy->cpu); + policy->cur = gs464_cpufreq_get(policy->cpu); + + if (new_method) { + /* Loongson-3A R4: new method */ + policy->cpuinfo.transition_latency = 5000; + policy->freq_table = gs464_cpufreq_table; + } else { + policy->cpuinfo.transition_latency = 1000; + policy->freq_table = gs464_clockmod_table; + /* Loongson-3A R1: all cores in a package share one clock */ + if ((read_c0_prid() & PRID_REV_MASK) == PRID_REV_LOONGSON3A_R1) + cpumask_copy(policy->cpus, topology_core_cpumask(policy->cpu)); + } + + return 0; +} + +static int gs464_cpufreq_exit(struct cpufreq_policy *policy) +{ + return 0; +} + +static struct cpufreq_driver gs464_cpufreq_driver = { + .name = "loongson3", + .init = gs464_cpufreq_cpu_init, + .verify = cpufreq_generic_frequency_table_verify, + .target_index = gs464_cpufreq_target, + .get = gs464_cpufreq_get, + .exit = gs464_cpufreq_exit, + .attr = cpufreq_generic_attr, +}; + +static struct platform_device_id platform_device_ids[] = { + { + .name = "gs464_cpufreq", + }, + {} +}; + +MODULE_DEVICE_TABLE(platform, platform_device_ids); + +static struct platform_driver platform_driver = { + .driver = { + .name = "gs464_cpufreq", + .owner = THIS_MODULE, + }, + .id_table = platform_device_ids, +}; + +static int configure_cpufreq_info(void) +{ + int i, r, max_level; + struct feature_args args1; + struct freq_level_args args2; + struct freq_info_args args3; + + if (!cpu_has_csr()) + return -EPERM; + + if (cpu_has_constant_timer) + gs464_cpufreq_driver.flags |= CPUFREQ_CONST_LOOPS; + + args1.index = FEATURE_INDEX_GENERAL; + r = do_service_request(CMD_GET_FEATURES, &args1); + if (r < 0) + return -EPERM; + + if (args1.flags & FEATURE_FREQ_SCALE) + new_method = 1; + + if (args1.flags & FEATURE_BOOST) + boost_supported = 1; + + r = do_service_request(CMD_SET_ENABLED_FEATURES, &args1); + if (r < 0) { + new_method = 0; + boost_supported = 0; + return -EPERM; + } + + r = do_service_request(CMD_GET_FREQ_LEVELS, &args2); + if (r < 0) { + new_method = 0; + boost_supported = 0; + return -EPERM; + } + + if (boost_supported) + max_level = args2.max_boost_level; + else + max_level = args2.max_normal_level; + + for (i = args2.min_level; i <= max_level; i++) { + args3.info = i; + args3.index = FREQ_INFO_INDEX_FREQ; + do_service_request(CMD_GET_FREQ_INFO, &args3); + gs464_cpufreq_table[i].frequency = args3.info * 1000; + if (i > args2.max_normal_level) + gs464_cpufreq_table[i].flags = CPUFREQ_BOOST_FREQ; + } + + return 0; +} + +static int __init cpufreq_init(void) +{ + int i, ret; + + /* Register platform stuff */ + ret = platform_driver_register(&platform_driver); + if (ret) + return ret; + + pr_info("cpufreq: Loongson-3 CPU frequency driver.\n"); + + configure_cpufreq_info(); + + for (i = 0; i < MAX_PACKAGES; i++) { + mutex_init(&cpufreq_mutex[i]); + spin_lock_init(&cpufreq_reg_lock[i]); + } + + cpufreq_register_notifier(&gs464_cpufreq_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + + ret = cpufreq_register_driver(&gs464_cpufreq_driver); + + if (boost_supported) + cpufreq_enable_boost_support(); + + return ret; +} + +static void __exit cpufreq_exit(void) +{ + cpufreq_unregister_driver(&gs464_cpufreq_driver); + cpufreq_unregister_notifier(&gs464_cpufreq_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + + platform_driver_unregister(&platform_driver); +} + +module_init(cpufreq_init); +module_exit(cpufreq_exit); + +MODULE_AUTHOR("Huacai Chen "); +MODULE_DESCRIPTION("CPUFreq driver for GS464 (MIPS-based Loongson-3) processors"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index f068798efa63b..e48ade3456fd4 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -482,6 +482,13 @@ config GPIO_MXS select GPIO_GENERIC select GENERIC_IRQ_CHIP +config GPIO_NCT6102 + bool "NCT6102 GPIO driver" + def_bool y + depends on CPU_LOONGSON64 + help + Say yes here to support the NCT6102 GPIO device + config GPIO_OCTEON tristate "Cavium OCTEON GPIO" depends on CAVIUM_OCTEON_SOC diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 8fcc69932b04a..3bf93c81f2b96 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -116,6 +116,7 @@ obj-$(CONFIG_GPIO_MT7621) += gpio-mt7621.o obj-$(CONFIG_GPIO_MVEBU) += gpio-mvebu.o obj-$(CONFIG_GPIO_MXC) += gpio-mxc.o obj-$(CONFIG_GPIO_MXS) += gpio-mxs.o +obj-$(CONFIG_GPIO_NCT6102) += gpio-nct6102.o obj-$(CONFIG_GPIO_OCTEON) += gpio-octeon.o obj-$(CONFIG_GPIO_OMAP) += gpio-omap.o obj-$(CONFIG_GPIO_PALMAS) += gpio-palmas.o diff --git a/drivers/gpio/gpio-nct6102.c b/drivers/gpio/gpio-nct6102.c new file mode 100644 index 0000000000000..143f0a139e212 --- /dev/null +++ b/drivers/gpio/gpio-nct6102.c @@ -0,0 +1,182 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NCT6102_GPIO_BASE 80 + +#define EFIR_ADDR 0x2E +#define EFDR_ADDR 0x2F + +#define SIO_NCT6102_ID 0xc450 +#define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */ +#define SIO_REG_LDSEL 0x7 /* Logical device select */ +#define SIO_REG_MFS 0x1C /* Multi Function Selection */ +#define SIO_GP2X_DISABLE 0x02 /* Disable GP2x */ +#define SIO_DEV_GPIO 0x7 /* Logical device 7(GPIO) */ +#define SIO_GPIO_DESC_BASE 0xE0 /* GPIO description base address */ +#define SIO_GPIO_DATA_BASE 0xE1 /* GPIO data base address*/ +#define SIO_GPIO_ENABLE 0x30 /* GPIO enable register */ + +#define REG_ADDR_OFFSET(x) ((x / 8) * 4) +#define REG_VALUE(x) (x % 8) + +static DEFINE_SPINLOCK(gpio_lock); + +/* + read/write_gbl() is used to read/write Global register. + */ +static unsigned char read_gbl(u8 gbl_reg) +{ + u8 ret = 0; + + /* Enter the Extended Function Mode */ + outb(0x87, EFIR_ADDR); + outb(0x87, EFIR_ADDR); + /* Read Global Reg */ + outb(gbl_reg, EFIR_ADDR); + ret = inb(EFDR_ADDR); + /* Exit the Extended Function Mode */ + outb(0xAA, EFIR_ADDR); + + return ret; +} + +/* + read/write_dev() is used to read/write Logical Device register. + */ +static unsigned char read_dev(u8 dev_num, u8 dev_reg) +{ + u8 ret = 0; + + /* Enter the Extended Function Mode */ + outb(0x87, EFIR_ADDR); + outb(0x87, EFIR_ADDR); + /* Point to Logical Device Number Reg */ + outb(SIO_REG_LDSEL, EFIR_ADDR); + /* Select Logical Device x */ + outb(dev_num, EFDR_ADDR); + /* Select Logical Device Reg */ + outb(dev_reg, EFIR_ADDR); + /* Read Logica Device Reg */ + ret = inb(EFDR_ADDR); + /* Exit the Extended Function Mode */ + outb(0xAA, EFIR_ADDR); + + return ret; +} + +static void write_dev(u8 val, u8 dev_num, u8 dev_reg) +{ + outb(0x87, EFIR_ADDR); + outb(0x87, EFIR_ADDR); + outb(SIO_REG_LDSEL, EFIR_ADDR); + outb(dev_num, EFDR_ADDR); + outb(dev_reg, EFIR_ADDR); + outb(val, EFDR_ADDR); + outb(0xAA, EFIR_ADDR); +} + +/* Detect chip */ +static u16 nct6102_chip_detect(void) +{ + u16 tmp; + + tmp = (read_gbl(SIO_REG_DEVID) << 8) | read_gbl(SIO_REG_DEVID + 1); + + return tmp & 0xFFF0; +} + +static int nct6102_gpio_get_value(struct gpio_chip *chip, unsigned offset) +{ + u8 tmp; + + spin_lock(&gpio_lock); + tmp = read_dev(SIO_DEV_GPIO, SIO_GPIO_DATA_BASE + REG_ADDR_OFFSET(offset)); + tmp &= 1 << REG_VALUE(offset); + spin_unlock(&gpio_lock); + + return tmp; +} + +static void nct6102_gpio_set_value(struct gpio_chip *chip, unsigned offset, int value) +{ + u8 tmp; + + spin_lock(&gpio_lock); + tmp = read_dev(SIO_DEV_GPIO, SIO_GPIO_DATA_BASE + REG_ADDR_OFFSET(offset)); + if(value) + tmp |= 1 << REG_VALUE(offset); + else + tmp &= ~(1 << REG_VALUE(offset)); + write_dev(tmp, SIO_DEV_GPIO, SIO_GPIO_DATA_BASE + REG_ADDR_OFFSET(offset)); + spin_unlock(&gpio_lock); +} + +static int nct6102_gpio_direction_input(struct gpio_chip *chip, unsigned offset) +{ + u8 tmp; + + spin_lock(&gpio_lock); + tmp = read_dev(SIO_DEV_GPIO, SIO_GPIO_DESC_BASE + REG_ADDR_OFFSET(offset)); + tmp |= 1 << REG_VALUE(offset); + write_dev(tmp, SIO_DEV_GPIO, SIO_GPIO_DESC_BASE + REG_ADDR_OFFSET(offset)); + spin_unlock(&gpio_lock); + + return 0; +} + +static int nct6102_gpio_direction_output(struct gpio_chip *chip, unsigned offset, int level) +{ + u8 tmp; + + nct6102_gpio_set_value(chip, offset, level); + spin_lock(&gpio_lock); + tmp = read_dev(SIO_DEV_GPIO, SIO_GPIO_DESC_BASE + REG_ADDR_OFFSET(offset)); + tmp &= ~(1 << REG_VALUE(offset)); + write_dev(tmp, SIO_DEV_GPIO, SIO_GPIO_DESC_BASE + REG_ADDR_OFFSET(offset)); + spin_unlock(&gpio_lock); + + return 0; +} + +static int nct6102_gpio_request(struct gpio_chip *chip, unsigned offset) +{ + u8 val; + + val = read_dev(SIO_DEV_GPIO, SIO_GPIO_ENABLE); + + if (val & 1 << (offset / 8)) + return 0; + + return -EINVAL; +} + +static struct gpio_chip nct6102_chip = { + .label ="nct6102-gpio-chip", + .request = nct6102_gpio_request, + .direction_input = nct6102_gpio_direction_input, + .direction_output = nct6102_gpio_direction_output, + .get = nct6102_gpio_get_value, + .set = nct6102_gpio_set_value, + .base = NCT6102_GPIO_BASE, + .ngpio = 64, +}; + +static int __init nct6102_gpio_setup(void) +{ + u16 dev_id; + + /* Detect device */ + dev_id = nct6102_chip_detect(); + if (dev_id != SIO_NCT6102_ID) + return 0; + + return gpiochip_add_data(&nct6102_chip, NULL); +} +subsys_initcall(nct6102_gpio_setup); diff --git a/drivers/gpu/drm/radeon/cik.c b/drivers/gpu/drm/radeon/cik.c index 581060fd632c9..811d9f1591f8d 100644 --- a/drivers/gpu/drm/radeon/cik.c +++ b/drivers/gpu/drm/radeon/cik.c @@ -5435,6 +5435,7 @@ static int cik_pcie_gart_enable(struct radeon_device *rdev) r = radeon_gart_table_vram_pin(rdev); if (r) return r; + radeon_gart_restore(rdev); /* Setup TLB control */ WREG32(MC_VM_MX_L1_TLB_CNTL, (0xA << 7) | diff --git a/drivers/gpu/drm/radeon/evergreen.c b/drivers/gpu/drm/radeon/evergreen.c index ca2cc4c6a5ba5..34382024c887c 100644 --- a/drivers/gpu/drm/radeon/evergreen.c +++ b/drivers/gpu/drm/radeon/evergreen.c @@ -1009,6 +1009,11 @@ static void evergreen_init_golden_registers(struct radeon_device *rdev) (const u32)ARRAY_SIZE(cypress_mgcg_init)); break; case CHIP_JUNIPER: +#ifdef CONFIG_MACH_LOONGSON64 + if (rdev->pdev->subsystem_vendor == PCI_VENDOR_ID_ATI) + break; /* Fix hibernation for Loongson */ +#endif + radeon_program_register_sequence(rdev, evergreen_golden_registers, (const u32)ARRAY_SIZE(evergreen_golden_registers)); @@ -2408,6 +2413,7 @@ static int evergreen_pcie_gart_enable(struct radeon_device *rdev) r = radeon_gart_table_vram_pin(rdev); if (r) return r; + radeon_gart_restore(rdev); /* Setup L2 cache */ WREG32(VM_L2_CNTL, ENABLE_L2_CACHE | ENABLE_L2_FRAGMENT_PROCESSING | ENABLE_L2_PTE_CACHE_LRU_UPDATE_BY_WRITE | diff --git a/drivers/gpu/drm/radeon/ni.c b/drivers/gpu/drm/radeon/ni.c index 4cd89fd6e9a22..23d9983e7a388 100644 --- a/drivers/gpu/drm/radeon/ni.c +++ b/drivers/gpu/drm/radeon/ni.c @@ -1269,6 +1269,7 @@ static int cayman_pcie_gart_enable(struct radeon_device *rdev) r = radeon_gart_table_vram_pin(rdev); if (r) return r; + radeon_gart_restore(rdev); /* Setup TLB control */ WREG32(MC_VM_MX_L1_TLB_CNTL, (0xA << 7) | diff --git a/drivers/gpu/drm/radeon/r100.c b/drivers/gpu/drm/radeon/r100.c index 54cbfac3605fb..1f5d7e31f9112 100644 --- a/drivers/gpu/drm/radeon/r100.c +++ b/drivers/gpu/drm/radeon/r100.c @@ -671,6 +671,7 @@ int r100_pci_gart_enable(struct radeon_device *rdev) { uint32_t tmp; + radeon_gart_restore(rdev); /* discard memory request outside of configured range */ tmp = RREG32(RADEON_AIC_CNTL) | RADEON_DIS_OUT_OF_PCI_GART_ACCESS; WREG32(RADEON_AIC_CNTL, tmp); diff --git a/drivers/gpu/drm/radeon/r300.c b/drivers/gpu/drm/radeon/r300.c index 430a4263ccf7a..dc63ea470d529 100644 --- a/drivers/gpu/drm/radeon/r300.c +++ b/drivers/gpu/drm/radeon/r300.c @@ -160,6 +160,7 @@ int rv370_pcie_gart_enable(struct radeon_device *rdev) r = radeon_gart_table_vram_pin(rdev); if (r) return r; + radeon_gart_restore(rdev); /* discard memory request outside of configured range */ tmp = RADEON_PCIE_TX_GART_UNMAPPED_ACCESS_DISCARD; WREG32_PCIE(RADEON_PCIE_TX_GART_CNTL, tmp); diff --git a/drivers/gpu/drm/radeon/r600.c b/drivers/gpu/drm/radeon/r600.c index c7a9956a410d9..6ef6bd73a2862 100644 --- a/drivers/gpu/drm/radeon/r600.c +++ b/drivers/gpu/drm/radeon/r600.c @@ -1137,6 +1137,7 @@ static int r600_pcie_gart_enable(struct radeon_device *rdev) r = radeon_gart_table_vram_pin(rdev); if (r) return r; + radeon_gart_restore(rdev); /* Setup L2 cache */ WREG32(VM_L2_CNTL, ENABLE_L2_CACHE | ENABLE_L2_FRAGMENT_PROCESSING | diff --git a/drivers/gpu/drm/radeon/radeon.h b/drivers/gpu/drm/radeon/radeon.h index e0a02b357ce72..29dedac455e08 100644 --- a/drivers/gpu/drm/radeon/radeon.h +++ b/drivers/gpu/drm/radeon/radeon.h @@ -627,6 +627,7 @@ void radeon_gart_unbind(struct radeon_device *rdev, unsigned offset, int radeon_gart_bind(struct radeon_device *rdev, unsigned offset, int pages, struct page **pagelist, dma_addr_t *dma_addr, uint32_t flags); +void radeon_gart_restore(struct radeon_device *rdev); /* @@ -2407,6 +2408,8 @@ struct radeon_device { struct delayed_work hotplug_work; struct work_struct dp_work; struct work_struct audio_work; + int need_recover; + struct delayed_work recover_work; int num_crtc; /* number of crtcs */ struct mutex dc_hw_i2c_mutex; /* display controller hw i2c mutex */ bool has_uvd; diff --git a/drivers/gpu/drm/radeon/radeon_atombios.c b/drivers/gpu/drm/radeon/radeon_atombios.c index c025ce6eb3167..1850a7c53cdbc 100644 --- a/drivers/gpu/drm/radeon/radeon_atombios.c +++ b/drivers/gpu/drm/radeon/radeon_atombios.c @@ -1317,7 +1317,7 @@ bool radeon_atombios_sideport_present(struct radeon_device *rdev) return true; break; default: - DRM_ERROR("Unsupported IGP table: %d %d\n", frev, crev); + DRM_INFO("Unsupported IGP table: %d %d\n", frev, crev); break; } } diff --git a/drivers/gpu/drm/radeon/radeon_device.c b/drivers/gpu/drm/radeon/radeon_device.c index 32851632643db..578c70be27a09 100644 --- a/drivers/gpu/drm/radeon/radeon_device.c +++ b/drivers/gpu/drm/radeon/radeon_device.c @@ -1645,7 +1645,28 @@ int radeon_suspend_kms(struct drm_device *dev, bool suspend, return 0; } -/* +void radeon_recover_callback(struct work_struct *work) +{ + int resched; + struct radeon_device *rdev = container_of(to_delayed_work(work), + struct radeon_device, recover_work); + + printk("Radeon GPU Recover...\n"); + down_write(&rdev->exclusive_lock); + radeon_save_bios_scratch_regs(rdev); + resched = ttm_bo_lock_delayed_workqueue(&rdev->mman.bdev); + radeon_pm_suspend(rdev); + radeon_suspend(rdev); + radeon_resume(rdev); + radeon_restore_bios_scratch_regs(rdev); + radeon_ib_ring_tests(rdev); + radeon_pm_resume(rdev); + drm_helper_resume_force_mode(rdev->ddev); + ttm_bo_unlock_delayed_workqueue(&rdev->mman.bdev, resched); + up_write(&rdev->exclusive_lock); +} + +/** * radeon_resume_kms - initiate device resume * * Bring the hw back to operating state (all asics). @@ -1680,8 +1701,10 @@ int radeon_resume_kms(struct drm_device *dev, bool resume, bool fbcon) radeon_resume(rdev); r = radeon_ib_ring_tests(rdev); - if (r) + if (r) { + rdev->need_recover = 1; DRM_ERROR("ib ring test failed (%d).\n", r); + } if ((rdev->pm.pm_method == PM_METHOD_DPM) && rdev->pm.dpm_enabled) { /* do dpm late init */ @@ -1754,6 +1777,11 @@ int radeon_resume_kms(struct drm_device *dev, bool resume, bool fbcon) console_unlock(); } + + if (rdev->need_recover) { + rdev->need_recover = 0; + schedule_delayed_work_on(0, &rdev->recover_work, msecs_to_jiffies(2000)); + } return 0; } @@ -1773,6 +1801,7 @@ int radeon_gpu_reset(struct radeon_device *rdev) bool saved = false; int i, r; + int resched; down_write(&rdev->exclusive_lock); @@ -1784,6 +1813,8 @@ int radeon_gpu_reset(struct radeon_device *rdev) atomic_inc(&rdev->gpu_reset_counter); radeon_save_bios_scratch_regs(rdev); + /* block TTM */ + resched = ttm_bo_lock_delayed_workqueue(&rdev->mman.bdev); radeon_suspend(rdev); radeon_hpd_fini(rdev); @@ -1842,6 +1873,8 @@ int radeon_gpu_reset(struct radeon_device *rdev) /* reset hpd state */ radeon_hpd_init(rdev); + ttm_bo_unlock_delayed_workqueue(&rdev->mman.bdev, resched); + rdev->in_reset = true; rdev->needs_reset = false; diff --git a/drivers/gpu/drm/radeon/radeon_device.h b/drivers/gpu/drm/radeon/radeon_device.h index 31a0ae295cc8f..e7f6d468b5674 100644 --- a/drivers/gpu/drm/radeon/radeon_device.h +++ b/drivers/gpu/drm/radeon/radeon_device.h @@ -29,4 +29,6 @@ bool radeon_device_is_virtual(void); +void radeon_recover_callback(struct work_struct *work); + #endif /* __RADEON_DEVICE_H__ */ diff --git a/drivers/gpu/drm/radeon/radeon_gart.c b/drivers/gpu/drm/radeon/radeon_gart.c index 4bb242437ff60..b34b8b9066947 100644 --- a/drivers/gpu/drm/radeon/radeon_gart.c +++ b/drivers/gpu/drm/radeon/radeon_gart.c @@ -317,6 +317,30 @@ int radeon_gart_bind(struct radeon_device *rdev, unsigned int offset, return 0; } +/** + * radeon_gart_restore - bind all pages in the gart page table + * + * @rdev: radeon_device pointer + * + * Binds all pages in the gart page table (all asics). + * Used to rebuild the gart table on device startup or resume. + */ +void radeon_gart_restore(struct radeon_device *rdev) +{ + int i; + uint64_t page_entry; + + if (!rdev->gart.ptr) + return; + + for (i = 0; i < rdev->gart.num_gpu_pages; i++) { + page_entry = rdev->gart.pages_entry[i]; + radeon_gart_set_page(rdev, i, page_entry); + } + mb(); + radeon_gart_tlb_flush(rdev); +} + /** * radeon_gart_init - init the driver info for managing the gart * diff --git a/drivers/gpu/drm/radeon/radeon_irq_kms.c b/drivers/gpu/drm/radeon/radeon_irq_kms.c index 9961251b44ba0..d4ad1fa826454 100644 --- a/drivers/gpu/drm/radeon/radeon_irq_kms.c +++ b/drivers/gpu/drm/radeon/radeon_irq_kms.c @@ -40,6 +40,7 @@ #include "radeon_kms.h" #include "radeon_reg.h" +#include "radeon_device.h" #define RADEON_WAIT_IDLE_TIMEOUT 200 @@ -344,6 +345,9 @@ int radeon_irq_kms_init(struct radeon_device *rdev) INIT_WORK(&rdev->dp_work, radeon_dp_work_func); INIT_WORK(&rdev->audio_work, r600_audio_update_hdmi); + rdev->need_recover = 0; + INIT_DEFERRABLE_WORK(&rdev->recover_work, radeon_recover_callback); + rdev->irq.installed = true; r = radeon_irq_install(rdev, rdev->pdev->irq); if (r) { diff --git a/drivers/gpu/drm/radeon/radeon_pm.c b/drivers/gpu/drm/radeon/radeon_pm.c index 66fe9fb920452..e6a30b252327f 100644 --- a/drivers/gpu/drm/radeon/radeon_pm.c +++ b/drivers/gpu/drm/radeon/radeon_pm.c @@ -1353,7 +1353,7 @@ static int radeon_pm_init_old(struct radeon_device *rdev) { int ret; - rdev->pm.profile = PM_PROFILE_DEFAULT; + rdev->pm.profile = PM_PROFILE_AUTO; rdev->pm.dynpm_state = DYNPM_STATE_DISABLED; rdev->pm.dynpm_planned_action = DYNPM_ACTION_NONE; rdev->pm.dynpm_can_upclock = true; @@ -1519,6 +1519,9 @@ int radeon_pm_init(struct radeon_device *rdev) case CHIP_RS780: case CHIP_RS880: case CHIP_RV770: +#ifdef CONFIG_MACH_LOONGSON64 + case CHIP_JUNIPER: +#endif /* DPM requires the RLC, RV770+ dGPU requires SMC */ if (!rdev->rlc_fw) rdev->pm.pm_method = PM_METHOD_PROFILE; @@ -1536,7 +1539,9 @@ int radeon_pm_init(struct radeon_device *rdev) case CHIP_RV740: case CHIP_CEDAR: case CHIP_REDWOOD: +#ifndef CONFIG_MACH_LOONGSON64 case CHIP_JUNIPER: +#endif case CHIP_CYPRESS: case CHIP_HEMLOCK: case CHIP_PALM: @@ -1853,10 +1858,11 @@ static bool radeon_pm_debug_check_in_vbl(struct radeon_device *rdev, bool finish static void radeon_dynpm_idle_work_handler(struct work_struct *work) { struct radeon_device *rdev; - + int resched; rdev = container_of(work, struct radeon_device, pm.dynpm_idle_work.work); + resched = ttm_bo_lock_delayed_workqueue(&rdev->mman.bdev); mutex_lock(&rdev->pm.mutex); if (rdev->pm.dynpm_state == DYNPM_STATE_ACTIVE) { int not_processed = 0; @@ -1907,6 +1913,7 @@ static void radeon_dynpm_idle_work_handler(struct work_struct *work) msecs_to_jiffies(RADEON_IDLE_LOOP_MS)); } mutex_unlock(&rdev->pm.mutex); + ttm_bo_unlock_delayed_workqueue(&rdev->mman.bdev, resched); } /* diff --git a/drivers/gpu/drm/radeon/rs400.c b/drivers/gpu/drm/radeon/rs400.c index 4f93fe468ec7f..f9360d9b06bcc 100644 --- a/drivers/gpu/drm/radeon/rs400.c +++ b/drivers/gpu/drm/radeon/rs400.c @@ -112,6 +112,7 @@ int rs400_gart_enable(struct radeon_device *rdev) uint32_t size_reg; uint32_t tmp; + radeon_gart_restore(rdev); tmp = RREG32_MC(RS690_AIC_CTRL_SCRATCH); tmp |= RS690_DIS_OUT_OF_PCI_GART_ACCESS; WREG32_MC(RS690_AIC_CTRL_SCRATCH, tmp); diff --git a/drivers/gpu/drm/radeon/rs600.c b/drivers/gpu/drm/radeon/rs600.c index fa4cc2a185dd0..fedc636bda104 100644 --- a/drivers/gpu/drm/radeon/rs600.c +++ b/drivers/gpu/drm/radeon/rs600.c @@ -572,6 +572,7 @@ static int rs600_gart_enable(struct radeon_device *rdev) r = radeon_gart_table_vram_pin(rdev); if (r) return r; + radeon_gart_restore(rdev); /* Enable bus master */ tmp = RREG32(RADEON_BUS_CNTL) & ~RS600_BUS_MASTER_DIS; WREG32(RADEON_BUS_CNTL, tmp); @@ -654,6 +655,9 @@ uint64_t rs600_gart_get_page_entry(uint64_t addr, uint32_t flags) addr |= R600_PTE_WRITEABLE; if (flags & RADEON_GART_PAGE_SNOOP) addr |= R600_PTE_SNOOPED; +#ifdef CONFIG_CRASH_DUMP + addr |= R600_PTE_VALID | R600_PTE_READABLE | R600_PTE_WRITEABLE; +#endif return addr; } diff --git a/drivers/gpu/drm/radeon/rs690.c b/drivers/gpu/drm/radeon/rs690.c index 016eb4992803d..3189a6ddb6c9f 100644 --- a/drivers/gpu/drm/radeon/rs690.c +++ b/drivers/gpu/drm/radeon/rs690.c @@ -112,7 +112,7 @@ void rs690_pm_info(struct radeon_device *rdev) rdev->pm.igp_system_mclk.full = dfixed_const(200); rdev->pm.igp_ht_link_clk.full = dfixed_const(1000); rdev->pm.igp_ht_link_width.full = dfixed_const(8); - DRM_ERROR("No integrated system info for your GPU, using safe default\n"); + DRM_INFO("No integrated system info for your GPU, using safe default\n"); break; } } else { diff --git a/drivers/gpu/drm/radeon/rv770.c b/drivers/gpu/drm/radeon/rv770.c index 7d4b0bf591090..c7317aff4cd64 100644 --- a/drivers/gpu/drm/radeon/rv770.c +++ b/drivers/gpu/drm/radeon/rv770.c @@ -903,6 +903,7 @@ static int rv770_pcie_gart_enable(struct radeon_device *rdev) r = radeon_gart_table_vram_pin(rdev); if (r) return r; + radeon_gart_restore(rdev); /* Setup L2 cache */ WREG32(VM_L2_CNTL, ENABLE_L2_CACHE | ENABLE_L2_FRAGMENT_PROCESSING | ENABLE_L2_PTE_CACHE_LRU_UPDATE_BY_WRITE | diff --git a/drivers/gpu/drm/radeon/si.c b/drivers/gpu/drm/radeon/si.c index 5bf7e40bf3549..0298963265fc5 100644 --- a/drivers/gpu/drm/radeon/si.c +++ b/drivers/gpu/drm/radeon/si.c @@ -4292,6 +4292,7 @@ static int si_pcie_gart_enable(struct radeon_device *rdev) r = radeon_gart_table_vram_pin(rdev); if (r) return r; + radeon_gart_restore(rdev); /* Setup TLB control */ WREG32(MC_VM_MX_L1_TLB_CNTL, (0xA << 7) | diff --git a/drivers/gpu/drm/ttm/ttm_bo.c b/drivers/gpu/drm/ttm/ttm_bo.c index b3e5185835c37..d07fe37f7dda0 100644 --- a/drivers/gpu/drm/ttm/ttm_bo.c +++ b/drivers/gpu/drm/ttm/ttm_bo.c @@ -395,6 +395,20 @@ void ttm_bo_put(struct ttm_buffer_object *bo) } EXPORT_SYMBOL(ttm_bo_put); +int ttm_bo_lock_delayed_workqueue(struct ttm_device *bdev) +{ + return cancel_delayed_work_sync(&bdev->dwork); +} +EXPORT_SYMBOL(ttm_bo_lock_delayed_workqueue); + +void ttm_bo_unlock_delayed_workqueue(struct ttm_device *bdev, int resched) +{ + if (resched) + schedule_delayed_work(&bdev->dwork, + ((HZ / 100) < 1) ? 1 : HZ / 100); +} +EXPORT_SYMBOL(ttm_bo_unlock_delayed_workqueue); + static int ttm_bo_bounce_temp_buffer(struct ttm_buffer_object *bo, struct ttm_resource **mem, struct ttm_operation_ctx *ctx, diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index f16940f3d93d4..ff515e454589c 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -643,7 +643,17 @@ #define USB_DEVICE_ID_IDEACOM_IDC6680 0x6680 #define USB_VENDOR_ID_ILITEK 0x222a -#define USB_DEVICE_ID_ILITEK_MULTITOUCH 0x0001 +#define USB_DEVICE_ID_ILITEK_MULTITOUCH01 0x0001 +#define USB_DEVICE_ID_ILITEK_MULTITOUCH02 0x0002 +#define USB_DEVICE_ID_ILITEK_MULTITOUCH06 0x0006 +#define USB_DEVICE_ID_ILITEK_MULTITOUCH10 0x0010 +#define USB_DEVICE_ID_ILITEK_MULTITOUCH15 0x0015 +#define USB_DEVICE_ID_ILITEK_MULTITOUCH1C 0x001C +#define USB_DEVICE_ID_ILITEK_MULTITOUCH1F 0x001F +#define USB_DEVICE_ID_ILITEK_MULTITOUCH24 0x0024 +#define USB_DEVICE_ID_ILITEK_MULTITOUCH41 0x0041 +#define USB_DEVICE_ID_ILITEK_MULTITOUCH88 0x0088 +#define USB_DEVICE_ID_ILITEK_MULTITOUCH94 0x0094 #define USB_VENDOR_ID_INTEL_0 0x8086 #define USB_VENDOR_ID_INTEL_1 0x8087 diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index bf9cad7112592..d3c1b174d6544 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -2101,7 +2101,47 @@ static const struct hid_device_id mt_devices[] = { /* Ilitek dual touch panel */ { .driver_data = MT_CLS_NSMU, MT_USB_DEVICE(USB_VENDOR_ID_ILITEK, - USB_DEVICE_ID_ILITEK_MULTITOUCH) }, + USB_DEVICE_ID_ILITEK_MULTITOUCH01) }, + + { .driver_data = MT_CLS_NSMU, + MT_USB_DEVICE(USB_VENDOR_ID_ILITEK, + USB_DEVICE_ID_ILITEK_MULTITOUCH02) }, + + { .driver_data = MT_CLS_NSMU, + MT_USB_DEVICE(USB_VENDOR_ID_ILITEK, + USB_DEVICE_ID_ILITEK_MULTITOUCH06) }, + + { .driver_data = MT_CLS_NSMU, + MT_USB_DEVICE(USB_VENDOR_ID_ILITEK, + USB_DEVICE_ID_ILITEK_MULTITOUCH10) }, + + { .driver_data = MT_CLS_NSMU, + MT_USB_DEVICE(USB_VENDOR_ID_ILITEK, + USB_DEVICE_ID_ILITEK_MULTITOUCH15) }, + + { .driver_data = MT_CLS_NSMU, + MT_USB_DEVICE(USB_VENDOR_ID_ILITEK, + USB_DEVICE_ID_ILITEK_MULTITOUCH1C) }, + + { .driver_data = MT_CLS_NSMU, + MT_USB_DEVICE(USB_VENDOR_ID_ILITEK, + USB_DEVICE_ID_ILITEK_MULTITOUCH1F) }, + + { .driver_data = MT_CLS_NSMU, + MT_USB_DEVICE(USB_VENDOR_ID_ILITEK, + USB_DEVICE_ID_ILITEK_MULTITOUCH24) }, + + { .driver_data = MT_CLS_NSMU, + MT_USB_DEVICE(USB_VENDOR_ID_ILITEK, + USB_DEVICE_ID_ILITEK_MULTITOUCH41) }, + + { .driver_data = MT_CLS_NSMU, + MT_USB_DEVICE(USB_VENDOR_ID_ILITEK, + USB_DEVICE_ID_ILITEK_MULTITOUCH88) }, + + { .driver_data = MT_CLS_NSMU, + MT_USB_DEVICE(USB_VENDOR_ID_ILITEK, + USB_DEVICE_ID_ILITEK_MULTITOUCH94) }, /* LG Melfas panel */ { .driver_data = MT_CLS_LG, diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 515bf6a00c4b2..d78b187bb3d83 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1596,6 +1596,17 @@ config SENSORS_NCT6775_I2C This driver can also be built as a module. If so, the module will be called nct6775-i2c. +config SENSORS_NCT7511 + tristate "Nuvoton NCT7511Y" + depends on I2C + select REGMAP_I2C + help + If you say yes here you get support for the Nuvoton NCT7511Y + hardware monitoring chip. + + This driver can also be built as a module. If so, the module + will be called nct7511. + config SENSORS_NCT7802 tristate "Nuvoton NCT7802Y" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 065767eec3cde..5f0e08bdc1631 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -166,6 +166,7 @@ obj-$(CONFIG_SENSORS_NCT6775_CORE) += nct6775-core.o nct6775-objs := nct6775-platform.o obj-$(CONFIG_SENSORS_NCT6775) += nct6775.o obj-$(CONFIG_SENSORS_NCT6775_I2C) += nct6775-i2c.o +obj-$(CONFIG_SENSORS_NCT7511) += nct7511.o obj-$(CONFIG_SENSORS_NCT7802) += nct7802.o obj-$(CONFIG_SENSORS_NCT7904) += nct7904.o obj-$(CONFIG_SENSORS_NPCM7XX) += npcm750-pwm-fan.o diff --git a/drivers/hwmon/lm93.c b/drivers/hwmon/lm93.c index 75bca805720e7..f4df8620ee145 100644 --- a/drivers/hwmon/lm93.c +++ b/drivers/hwmon/lm93.c @@ -2497,8 +2497,9 @@ ATTRIBUTE_GROUPS(lm93); static void lm93_init_client(struct i2c_client *client) { - int i; - u8 reg; + int i, nr; + u8 reg, ctl2, ctl4; + struct lm93_data *data = i2c_get_clientdata(client); /* configure VID pin input thresholds */ reg = lm93_read_byte(client, LM93_REG_GPI_VID_CTL); @@ -2529,6 +2530,25 @@ static void lm93_init_client(struct i2c_client *client) reg = lm93_read_byte(client, LM93_REG_CONFIG); lm93_write_byte(client, LM93_REG_CONFIG, reg | 0x01); + mutex_lock(&data->update_lock); + for (nr = 0; nr < 2; nr++) { + /* set manual mode */ + ctl2 = lm93_read_byte(client, LM93_REG_PWM_CTL(nr, LM93_PWM_CTL2)); + ctl2 |= 0x01; + lm93_write_byte(client, LM93_REG_PWM_CTL(nr, LM93_PWM_CTL2), ctl2); + /* set fanspeed 50% */ + ctl2 = lm93_read_byte(client, LM93_REG_PWM_CTL(nr, LM93_PWM_CTL2)); + ctl4 = lm93_read_byte(client, LM93_REG_PWM_CTL(nr, LM93_PWM_CTL4)); + ctl2 = (ctl2 & 0x0f) | LM93_PWM_TO_REG(0x80, (ctl4 & 0x07) ? + LM93_PWM_MAP_LO_FREQ : LM93_PWM_MAP_HI_FREQ) << 4; + /* save user commanded value */ + data->pwm_override[nr] = LM93_PWM_FROM_REG(ctl2 >> 4, + (ctl4 & 0x07) ? LM93_PWM_MAP_LO_FREQ : + LM93_PWM_MAP_HI_FREQ); + lm93_write_byte(client, LM93_REG_PWM_CTL(nr, LM93_PWM_CTL2), ctl2); + } + mutex_unlock(&data->update_lock); + /* spin until ready */ for (i = 0; i < 20; i++) { msleep(10); diff --git a/drivers/hwmon/nct7511.c b/drivers/hwmon/nct7511.c new file mode 100644 index 0000000000000..808755e2c37d5 --- /dev/null +++ b/drivers/hwmon/nct7511.c @@ -0,0 +1,800 @@ +/* + * nct7511 - Driver for Nuvoton NCT7511Y + * + * Copyright (C) 2014 Guenter Roeck + * Copyright (C) 2019 Ainux + * + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "nct7511" + +static const u8 REG_VOLTAGE[5] = { 0x09, 0x0a, 0x0c, 0x0d, 0x0e }; + +static const u8 REG_VOLTAGE_LIMIT_LSB[2][5] = { + { 0x40, 0x00, 0x42, 0x44, 0x46 }, + { 0x3f, 0x00, 0x41, 0x43, 0x45 }, +}; + +static const u8 REG_VOLTAGE_LIMIT_MSB[5] = { 0x48, 0x00, 0x47, 0x47, 0x48 }; + +static const u8 REG_VOLTAGE_LIMIT_MSB_SHIFT[2][5] = { + { 0, 0, 4, 0, 4 }, + { 2, 0, 6, 2, 6 }, +}; + +#define REG_RTD1_MSB 0x01 +#define REG_RTD2_MSB 0x02 +#define REG_LTD_MSB 0x04 +#define REG_TEMP_LSB 0x05 +#define REG_FANCOUNT_MSB 0x10 +#define REG_FANCOUNT_LSB 0x13 +#define REG_DFAS 0x17 +#define REG_LAS 0x18 +#define REG_HAS 0x19 +#define REG_FAS 0x1a +#define REG_CAS 0x1b +#define REG_START 0x21 +#define REG_MODE 0x22 /* 7.2.32 Mode Selection Register */ +#define REG_FAN_ENABLE 0x24 +#define REG_VMON_ENABLE 0x25 +#define REG_RTD1_THL 0x30 +#define REG_RTD1_TLL 0x31 +#define REG_RTD2_THL 0x32 +#define REG_RTD2_TLL 0x33 +#define REG_LTD_THL 0x36 +#define REG_LTD_TLL 0x37 +#define REG_RTD1_CRIT 0x3a +#define REG_RTD2_CRIT 0x3b +#define REG_LTD_CRIT 0x3d +#define REG_FANLIMIT_LSB 0x49 +#define REG_FANLIMIT_MSB 0x4c +#define REG_FANOUTPUT_CRTTYPE 0x5e +#define REG_PWM(x) (0x60 + (x)) +#define REG_SMARTFAN_EN(x) (0x64 + (x) / 2) +#define SMARTFAN_EN_SHIFT(x) ((x) % 2 * 4) +#define REG_VENDOR_ID 0xfd +#define REG_CHIP_ID 0xfe +#define REG_DEVICE_ID 0xff + +/* + * Data structures and manipulation thereof + */ + +struct nct7511_data { + struct regmap *regmap; + struct mutex access_lock; /* for multi-byte read and write operations */ +}; + +static ssize_t show_temp_label(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + + switch (sattr->index) { + case 0: + return sprintf(buf, "%s\n", "RTD1 Temperature"); + break; + case 1: + return sprintf(buf, "%s\n", "RTD2 Temperature"); + break; + case 2: + return sprintf(buf, "%s\n", "LTD Temperature"); + break; + default: + return -EOPNOTSUPP; + } +} + +static ssize_t show_pwm_mode(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + struct nct7511_data *data = dev_get_drvdata(dev); + unsigned int regval; + int ret; + + if (sattr->index > 1) + return sprintf(buf, "1\n"); + + ret = regmap_read(data->regmap, REG_FANOUTPUT_CRTTYPE, ®val); + if (ret < 0) + return ret; + return sprintf(buf, "%u\n", regval & (1 << sattr->index)); +} + +static ssize_t show_pwm(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct nct7511_data *data = dev_get_drvdata(dev); + unsigned int val; + int ret; + + if (!attr->index) + return sprintf(buf, "255\n"); + + ret = regmap_read(data->regmap, attr->index, &val); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", val); +} + +static ssize_t store_pwm(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct nct7511_data *data = dev_get_drvdata(dev); + int err; + u8 val; + + err = kstrtou8(buf, 0, &val); + if (err < 0) + return err; + + err = regmap_write(data->regmap, attr->index, val); + return err ? : count; +} + +static ssize_t show_pwm_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct nct7511_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + unsigned int reg, enabled; + int ret; + + ret = regmap_read(data->regmap, REG_SMARTFAN_EN(sattr->index), ®); + if (ret < 0) + return ret; + enabled = reg >> SMARTFAN_EN_SHIFT(sattr->index) & 1; + return sprintf(buf, "%u\n", enabled); +} + +static ssize_t store_pwm_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct7511_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + u8 val; + int ret; + + ret = kstrtou8(buf, 0, &val); + if (ret < 0) + return ret; + if (val > 1) + return -EINVAL; + ret = regmap_update_bits(data->regmap, REG_SMARTFAN_EN(sattr->index), + 1 << SMARTFAN_EN_SHIFT(sattr->index), + val << SMARTFAN_EN_SHIFT(sattr->index)); + return ret ? : count; +} + +static int nct7511_read_temp(struct nct7511_data *data, + u8 reg_temp, u8 reg_temp_low, int *temp) +{ + unsigned int t1, t2 = 0; + int err; + + *temp = 0; + + mutex_lock(&data->access_lock); + err = regmap_read(data->regmap, reg_temp, &t1); + if (err < 0) + goto abort; + t1 <<= 8; + if (reg_temp_low) { /* 11 bit data */ + err = regmap_read(data->regmap, reg_temp_low, &t2); + if (err < 0) + goto abort; + } + t1 |= t2 & 0xe0; + *temp = (s16)t1 / 32 * 125; +abort: + mutex_unlock(&data->access_lock); + return err; +} + +static int nct7511_read_fan(struct nct7511_data *data, u8 reg_fan) +{ + unsigned int f1, f2; + int ret; + + mutex_lock(&data->access_lock); + ret = regmap_read(data->regmap, reg_fan, &f1); + if (ret < 0) + goto abort; + ret = regmap_read(data->regmap, REG_FANCOUNT_LSB, &f2); + if (ret < 0) + goto abort; + ret = (f1 << 5) | (f2 >> 3); + /* convert fan count to rpm */ + if (ret == 0x1fff) /* maximum value, assume fan is stopped */ + ret = 0; + else if (ret) + ret = DIV_ROUND_CLOSEST(1350000U, ret); +abort: + mutex_unlock(&data->access_lock); + return ret; +} + +static int nct7511_read_fan_min(struct nct7511_data *data, u8 reg_fan_low, + u8 reg_fan_high) +{ + unsigned int f1, f2; + int ret; + + mutex_lock(&data->access_lock); + ret = regmap_read(data->regmap, reg_fan_low, &f1); + if (ret < 0) + goto abort; + ret = regmap_read(data->regmap, reg_fan_high, &f2); + if (ret < 0) + goto abort; + ret = f1 | ((f2 & 0xf8) << 5); + /* convert fan count to rpm */ + if (ret == 0x1fff) /* maximum value, assume no limit */ + ret = 0; + else if (ret) + ret = DIV_ROUND_CLOSEST(1350000U, ret); + else + ret = 1350000U; +abort: + mutex_unlock(&data->access_lock); + return ret; +} + +static int nct7511_write_fan_min(struct nct7511_data *data, u8 reg_fan_low, + u8 reg_fan_high, unsigned long limit) +{ + int err; + + if (limit) + limit = DIV_ROUND_CLOSEST(1350000U, limit); + else + limit = 0x1fff; + limit = clamp_val(limit, 0, 0x1fff); + + mutex_lock(&data->access_lock); + err = regmap_write(data->regmap, reg_fan_low, limit & 0xff); + if (err < 0) + goto abort; + + err = regmap_write(data->regmap, reg_fan_high, (limit & 0x1f00) >> 5); +abort: + mutex_unlock(&data->access_lock); + return err; +} + +static ssize_t show_temp(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct nct7511_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int err, temp; + + err = nct7511_read_temp(data, sattr->nr, sattr->index, &temp); + if (err < 0) + return err; + + return sprintf(buf, "%d\n", temp); +} + +static ssize_t store_temp(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct nct7511_data *data = dev_get_drvdata(dev); + int nr = sattr->nr; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err < 0) + return err; + + val = DIV_ROUND_CLOSEST(clamp_val(val, -128000, 127000), 1000); + + err = regmap_write(data->regmap, nr, val & 0xff); + return err ? : count; +} + +static ssize_t show_fan(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + struct nct7511_data *data = dev_get_drvdata(dev); + int speed; + + speed = nct7511_read_fan(data, sattr->index); + if (speed < 0) + return speed; + + return sprintf(buf, "%d\n", speed); +} + +static ssize_t show_fan_min(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct nct7511_data *data = dev_get_drvdata(dev); + int speed; + + speed = nct7511_read_fan_min(data, sattr->nr, sattr->index); + if (speed < 0) + return speed; + + return sprintf(buf, "%d\n", speed); +} + +static ssize_t store_fan_min(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct nct7511_data *data = dev_get_drvdata(dev); + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + err = nct7511_write_fan_min(data, sattr->nr, sattr->index, val); + return err ? : count; +} + +static ssize_t show_alarm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct nct7511_data *data = dev_get_drvdata(dev); + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + int bit = sattr->index; + unsigned int val; + int ret; + + ret = regmap_read(data->regmap, sattr->nr, &val); + if (ret < 0) + return ret; + + return sprintf(buf, "%u\n", !!(val & (1 << bit))); +} + +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, + show_temp_label, NULL, 0); +static SENSOR_DEVICE_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, REG_RTD1_MSB, + REG_TEMP_LSB); +static SENSOR_DEVICE_ATTR_2(temp1_min, S_IRUGO | S_IWUSR, show_temp, + store_temp, REG_RTD1_TLL, 0); +static SENSOR_DEVICE_ATTR_2(temp1_max, S_IRUGO | S_IWUSR, show_temp, + store_temp, REG_RTD1_THL, 0); +static SENSOR_DEVICE_ATTR_2(temp1_crit, S_IRUGO | S_IWUSR, show_temp, + store_temp, REG_RTD1_CRIT, 0); + +static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, + show_temp_label, NULL, 1); +static SENSOR_DEVICE_ATTR_2(temp2_input, S_IRUGO, show_temp, NULL, REG_RTD2_MSB, + REG_TEMP_LSB); +static SENSOR_DEVICE_ATTR_2(temp2_min, S_IRUGO | S_IWUSR, show_temp, + store_temp, REG_RTD2_TLL, 0); +static SENSOR_DEVICE_ATTR_2(temp2_max, S_IRUGO | S_IWUSR, show_temp, + store_temp, REG_RTD2_THL, 0); +static SENSOR_DEVICE_ATTR_2(temp2_crit, S_IRUGO | S_IWUSR, show_temp, + store_temp, REG_RTD2_CRIT, 0); + +static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, + show_temp_label, NULL, 2); +static SENSOR_DEVICE_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, REG_LTD_MSB, + REG_TEMP_LSB); +static SENSOR_DEVICE_ATTR_2(temp3_min, S_IRUGO | S_IWUSR, show_temp, + store_temp, REG_LTD_TLL, 0); +static SENSOR_DEVICE_ATTR_2(temp3_max, S_IRUGO | S_IWUSR, show_temp, + store_temp, REG_LTD_THL, 0); +static SENSOR_DEVICE_ATTR_2(temp3_crit, S_IRUGO | S_IWUSR, show_temp, + store_temp, REG_LTD_CRIT, 0); + +static SENSOR_DEVICE_ATTR_2(temp1_min_alarm, S_IRUGO, show_alarm, NULL, + REG_LAS, 0); +static SENSOR_DEVICE_ATTR_2(temp2_min_alarm, S_IRUGO, show_alarm, NULL, + REG_LAS, 1); +static SENSOR_DEVICE_ATTR_2(temp3_min_alarm, S_IRUGO, show_alarm, NULL, + REG_LAS, 2); + +static SENSOR_DEVICE_ATTR_2(temp1_max_alarm, S_IRUGO, show_alarm, NULL, + REG_HAS, 0); +static SENSOR_DEVICE_ATTR_2(temp2_max_alarm, S_IRUGO, show_alarm, NULL, + REG_HAS, 1); +static SENSOR_DEVICE_ATTR_2(temp3_max_alarm, S_IRUGO, show_alarm, NULL, + REG_HAS, 2); + +static SENSOR_DEVICE_ATTR_2(temp1_crit_alarm, S_IRUGO, show_alarm, NULL, + REG_CAS, 0); +static SENSOR_DEVICE_ATTR_2(temp2_crit_alarm, S_IRUGO, show_alarm, NULL, + REG_CAS, 1); +static SENSOR_DEVICE_ATTR_2(temp3_crit_alarm, S_IRUGO, show_alarm, NULL, + REG_CAS, 2); + +static SENSOR_DEVICE_ATTR_2(temp1_fault, S_IRUGO, show_alarm, NULL, REG_DFAS, 0); +static SENSOR_DEVICE_ATTR_2(temp2_fault, S_IRUGO, show_alarm, NULL, REG_DFAS, 1); +static SENSOR_DEVICE_ATTR_2(temp3_fault, S_IRUGO, show_alarm, NULL, REG_DFAS, 2); + +static struct attribute *nct7511_temp_attrs[] = { + &sensor_dev_attr_temp1_label.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_fault.dev_attr.attr, + + &sensor_dev_attr_temp2_label.dev_attr.attr, /* 9 */ + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_crit.dev_attr.attr, + &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + + &sensor_dev_attr_temp3_label.dev_attr.attr, /* 18 */ + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_crit.dev_attr.attr, + &sensor_dev_attr_temp3_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_fault.dev_attr.attr, + + NULL +}; + +static umode_t nct7511_temp_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct nct7511_data *data = dev_get_drvdata(dev); + unsigned int reg; + int err; + + err = regmap_read(data->regmap, REG_MODE, ®); + if (err < 0) + return 0; + + if (index < 10 && (reg & 0x03) != 0x01) /* RD1 */ + return 0; + + if (index >= 10 && index < 20 && (reg & 0x0c) != 0x04) /* RD2 */ + return 0; + + if (index >= 30 && index < 38 && (reg & 0x40) != 0x40) /* LTD */ + return 0; + + return attr->mode; +} + +static const struct attribute_group nct7511_temp_group = { + .attrs = nct7511_temp_attrs, + .is_visible = nct7511_temp_is_visible, +}; + +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL, REG_FANCOUNT_MSB); +static SENSOR_DEVICE_ATTR_2(fan1_min, S_IRUGO | S_IWUSR, show_fan_min, + store_fan_min, REG_FANLIMIT_LSB, REG_FANLIMIT_MSB); +static SENSOR_DEVICE_ATTR_2(fan1_alarm, S_IRUGO, show_alarm, NULL, REG_FAS, 0); + +/* 7.2.42 Fan Control Output Type */ +static SENSOR_DEVICE_ATTR(pwm1_mode, S_IRUGO, show_pwm_mode, NULL, 0); + +/* 7.2.44 Fan Control Output Value */ +static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, show_pwm, store_pwm, + REG_PWM(0)); + +/* 7.2.46... Temperature to Fan mapping Relationships Register */ +static SENSOR_DEVICE_ATTR(pwm1_enable, S_IRUGO | S_IWUSR, show_pwm_enable, + store_pwm_enable, 0); +static SENSOR_DEVICE_ATTR(pwm2_enable, S_IRUGO | S_IWUSR, show_pwm_enable, + store_pwm_enable, 1); +static SENSOR_DEVICE_ATTR(pwm3_enable, S_IRUGO | S_IWUSR, show_pwm_enable, + store_pwm_enable, 2); + +static struct attribute *nct7511_fan_attrs[] = { + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan1_min.dev_attr.attr, + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + + NULL +}; + +static umode_t nct7511_fan_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct nct7511_data *data = dev_get_drvdata(dev); + int fan = index / 4; /* 4 attributes per fan */ + unsigned int reg; + int err; + + err = regmap_read(data->regmap, REG_FAN_ENABLE, ®); + if (err < 0 || !(reg & (1 << fan))) + return 0; + + return attr->mode; +} + +static const struct attribute_group nct7511_fan_group = { + .attrs = nct7511_fan_attrs, + .is_visible = nct7511_fan_is_visible, +}; + +static struct attribute *nct7511_pwm_attrs[] = { + &sensor_dev_attr_pwm1_enable.dev_attr.attr, + &sensor_dev_attr_pwm1_mode.dev_attr.attr, + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm2_enable.dev_attr.attr, + &sensor_dev_attr_pwm3_enable.dev_attr.attr, + NULL +}; + +static const struct attribute_group nct7511_pwm_group = { + .attrs = nct7511_pwm_attrs, +}; + +/* 7.2.63... 0x80-0x83, 0x84 Temperature (X-axis) transition */ +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point1_temp, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x80, 0); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point2_temp, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x81, 0); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point3_temp, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x82, 0); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point4_temp, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x83, 0); +static SENSOR_DEVICE_ATTR_2(pwm1_auto_point5_temp, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x84, 0); + +/* 7.2.63... 0x85-0x88 PWM (Y-axis) transition */ +static SENSOR_DEVICE_ATTR(pwm1_auto_point1_pwm, S_IRUGO | S_IWUSR, + show_pwm, store_pwm, 0x85); +static SENSOR_DEVICE_ATTR(pwm1_auto_point2_pwm, S_IRUGO | S_IWUSR, + show_pwm, store_pwm, 0x86); +static SENSOR_DEVICE_ATTR(pwm1_auto_point3_pwm, S_IRUGO | S_IWUSR, + show_pwm, store_pwm, 0x87); +static SENSOR_DEVICE_ATTR(pwm1_auto_point4_pwm, S_IRUGO | S_IWUSR, + show_pwm, store_pwm, 0x88); +static SENSOR_DEVICE_ATTR(pwm1_auto_point5_pwm, S_IRUGO, show_pwm, NULL, 0); + +/* 7.2.63 Table 2 X-axis Transition Point 1 Register */ +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point1_temp, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x90, 0); +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point2_temp, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x91, 0); +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point3_temp, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x92, 0); +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point4_temp, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x93, 0); +static SENSOR_DEVICE_ATTR_2(pwm2_auto_point5_temp, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0x94, 0); + +/* 7.2.63 Table 2 Y-axis Transition Point 1 Register */ +static SENSOR_DEVICE_ATTR(pwm2_auto_point1_pwm, S_IRUGO | S_IWUSR, + show_pwm, store_pwm, 0x95); +static SENSOR_DEVICE_ATTR(pwm2_auto_point2_pwm, S_IRUGO | S_IWUSR, + show_pwm, store_pwm, 0x96); +static SENSOR_DEVICE_ATTR(pwm2_auto_point3_pwm, S_IRUGO | S_IWUSR, + show_pwm, store_pwm, 0x97); +static SENSOR_DEVICE_ATTR(pwm2_auto_point4_pwm, S_IRUGO | S_IWUSR, + show_pwm, store_pwm, 0x98); +static SENSOR_DEVICE_ATTR(pwm2_auto_point5_pwm, S_IRUGO, show_pwm, NULL, 0); + +/* 7.2.63 Table 3 X-axis Transition Point 1 Register */ +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point1_temp, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0xA0, 0); +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point2_temp, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0xA1, 0); +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point3_temp, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0xA2, 0); +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point4_temp, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0xA3, 0); +static SENSOR_DEVICE_ATTR_2(pwm3_auto_point5_temp, S_IRUGO | S_IWUSR, + show_temp, store_temp, 0xA4, 0); + +/* 7.2.63 Table 3 Y-axis Transition Point 1 Register */ +static SENSOR_DEVICE_ATTR(pwm3_auto_point1_pwm, S_IRUGO | S_IWUSR, + show_pwm, store_pwm, 0xA5); +static SENSOR_DEVICE_ATTR(pwm3_auto_point2_pwm, S_IRUGO | S_IWUSR, + show_pwm, store_pwm, 0xA6); +static SENSOR_DEVICE_ATTR(pwm3_auto_point3_pwm, S_IRUGO | S_IWUSR, + show_pwm, store_pwm, 0xA7); +static SENSOR_DEVICE_ATTR(pwm3_auto_point4_pwm, S_IRUGO | S_IWUSR, + show_pwm, store_pwm, 0xA8); +static SENSOR_DEVICE_ATTR(pwm3_auto_point5_pwm, S_IRUGO, show_pwm, NULL, 0); + +static struct attribute *nct7511_auto_point_attrs[] = { + &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point5_temp.dev_attr.attr, + + &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point4_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point5_pwm.dev_attr.attr, + + &sensor_dev_attr_pwm2_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point4_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point5_temp.dev_attr.attr, + + &sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point3_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point4_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point5_pwm.dev_attr.attr, + + &sensor_dev_attr_pwm3_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point4_temp.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point5_temp.dev_attr.attr, + + &sensor_dev_attr_pwm3_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point3_pwm.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point4_pwm.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_point5_pwm.dev_attr.attr, + + NULL +}; + +static const struct attribute_group nct7511_auto_point_group = { + .attrs = nct7511_auto_point_attrs, +}; + +static const struct attribute_group *nct7511_groups[] = { + &nct7511_temp_group, + &nct7511_fan_group, + &nct7511_pwm_group, + &nct7511_auto_point_group, + NULL +}; + +static int nct7511_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + int reg; + + reg = i2c_smbus_read_byte_data(client, REG_VENDOR_ID); + if (reg != 0x50) + return -ENODEV; + + reg = i2c_smbus_read_byte_data(client, REG_CHIP_ID); + if (reg != 0xc3) + return -ENODEV; + + reg = i2c_smbus_read_byte_data(client, REG_DEVICE_ID); + /* Device id range is 2xh(x = 0,1,2...) */ + if (reg < 0x20 || reg > 0x2f) + return -ENODEV; + + /* Also validate lower bits of temperature registers */ + reg = i2c_smbus_read_byte_data(client, REG_TEMP_LSB); + if (reg < 0 || (reg & 0x1f)) + return -ENODEV; + + strscpy(info->type, "nct7511", I2C_NAME_SIZE); + return 0; +} + +static bool nct7511_regmap_is_volatile(struct device *dev, unsigned int reg) +{ + return (reg <= 0x20) || + (reg >= REG_PWM(0) && reg <= REG_PWM(2)); +} + +static const struct regmap_config nct7511_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .cache_type = REGCACHE_RBTREE, + .volatile_reg = nct7511_regmap_is_volatile, +}; + +static int nct7511_init_chip(struct nct7511_data *data) +{ + int err; + + /* Enable ADC */ + err = regmap_update_bits(data->regmap, REG_START, 0x01, 0x01); + if (err) + return err; + + /* Enable local temperature sensor */ + err = regmap_update_bits(data->regmap, REG_MODE, 0x40, 0x40); + if (err) + return err; + + /* Enable Vcore and VCC voltage monitoring */ + return regmap_update_bits(data->regmap, REG_VMON_ENABLE, 0x03, 0x03); +} + +static int nct7511_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct nct7511_data *data; + struct device *hwmon_dev; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + + data->regmap = devm_regmap_init_i2c(client, &nct7511_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); + + mutex_init(&data->access_lock); + + ret = nct7511_init_chip(data); + if (ret < 0) + return ret; + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, + data, + nct7511_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const unsigned short nct7511_address_list[] = { + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, I2C_CLIENT_END +}; + +static const struct i2c_device_id nct7511_idtable[] = { + { "nct7511", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, nct7511_idtable); + +static struct i2c_driver nct7511_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = DRVNAME, + }, + .detect = nct7511_detect, + .probe = nct7511_probe, + .id_table = nct7511_idtable, + .address_list = nct7511_address_list, +}; + +module_i2c_driver(nct7511_driver); + +MODULE_AUTHOR("Guenter Roeck "); +MODULE_AUTHOR("Ainux "); +MODULE_DESCRIPTION("NCT7511Y Hardware Monitoring Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index bd943d9676854..15a7d7e7c62c9 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -156,6 +156,17 @@ config KEYBOARD_ATKBD_RDI_KEYCODES right-hand column will be interpreted as the key shown in the left-hand column. +config KEYBOARD_ATKBD_LEMOTE_KEYCODES + bool "Use Lemote laptop (A1201) keyboard scancodes" + depends on MACH_LOONGSON64 && KEYBOARD_ATKBD + default n + help + Say Y here if you want to use an AT or PS/2 keyboard, and your + keyboard uses keycodes that are specific to Lemote laptop (A1201) + keyboards. + + Say N if you use a standard keyboard. + config KEYBOARD_QT1050 tristate "Microchip AT42QT1050 Touch Sensor Chip" depends on I2C diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c index c229bd6b3f7f2..c9f387e61d850 100644 --- a/drivers/input/keyboard/atkbd.c +++ b/drivers/input/keyboard/atkbd.c @@ -77,7 +77,13 @@ MODULE_PARM_DESC(terminal, "Enable break codes on an IBM Terminal keyboard conne static const unsigned short atkbd_set2_keycode[ATKBD_KEYMAP_SIZE] = { -#ifdef CONFIG_KEYBOARD_ATKBD_HP_KEYCODES +#if defined(CONFIG_KEYBOARD_ATKBD_LEMOTE_KEYCODES) + +/* XXX: need a more general approach */ + +#include "lmps2atkbd.h" /* include the keyboard scancodes */ + +#elif defined(CONFIG_KEYBOARD_ATKBD_HP_KEYCODES) /* XXX: need a more general approach */ @@ -389,7 +395,7 @@ static unsigned int atkbd_compat_scancode(struct atkbd *atkbd, unsigned int code if (atkbd->set == 3) { if (atkbd->emul == 1) code |= 0x100; - } else { + } else { code = (code & 0x7f) | ((code & 0x80) << 1); if (atkbd->emul == 1) code |= 0x80; @@ -1017,6 +1023,14 @@ static void atkbd_apply_forced_release_keylist(struct atkbd* atkbd, __set_bit(keys[i], atkbd->force_release_mask); } +/* + * Most special keys (Fn+F?) on LS2GQ laptops do not generate release + * events so we have to do it ourselves. + */ +static unsigned int atkbd_lemote_laptop_forced_release_keys[] = { + 0x61, 0x6F, 0x76, -1U +}; + /* * Most special keys (Fn+F?) on Dell laptops do not generate release * events so we have to do it ourselves. @@ -1950,6 +1964,9 @@ static int __init atkbd_init(void) { dmi_check_system(atkbd_dmi_quirk_table); + atkbd_platform_fixup = atkbd_apply_forced_release_keylist; + atkbd_platform_fixup_data = atkbd_lemote_laptop_forced_release_keys; + return serio_register_driver(&atkbd_drv); } diff --git a/drivers/input/keyboard/lmps2atkbd.h b/drivers/input/keyboard/lmps2atkbd.h new file mode 100644 index 0000000000000..95d4ac5365c0d --- /dev/null +++ b/drivers/input/keyboard/lmps2atkbd.h @@ -0,0 +1,32 @@ +/* + * drivers/input/keyboard/lmps2atkbd.h + * + * Copyright (c) 2012 Huang Wei + * + * Lemote PS/2 AT-compatible Keyboard, found in Lemote Laptops (A1201) + * + * 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. + */ + +/* These are offset for escaped keycodes: */ + 0, 67, 65, 63, 61, 59, 60, 88, 0, 68, 66, 64, 62, 15, 41,117, + 0, 56, 42, 93, 29, 16, 2, 0, 0, 0, 44, 31, 30, 17, 3, 0, + 0, 46, 45, 32, 18, 5, 4, 95, 0, 57, 47, 33, 20, 19, 6,183, + 0, 49, 48, 35, 34, 21, 7,184, 0, 0, 50, 36, 22, 8, 9,185, + 0, 51, 37, 23, 24, 11, 10, 0, 0, 52, 53, 38, 39, 25, 12,225, + 0, 89, 40, 0, 26, 13, 0, 0, 58, 54, 28, 27, 0, 43, 85,224, + 0, 86, 91, 90, 92, 0, 14, 94, 0, 79,124, 75, 71,121, 0,227, + 82, 83, 80, 76, 77, 72, 1, 69, 87, 78, 81, 74, 55, 73, 70, 99, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 217,100,255, 0, 97,165, 0, 0,156, 0, 0, 0, 0, 0, 0,125, + 173,114, 0,113, 0, 0, 0,126,128, 0, 0,140, 0, 0, 0,127, + 159, 0,115, 0,164, 0, 0,116,158, 0,172,166, 0, 0, 0,142, + 157, 0, 0, 0, 0, 0, 0, 0,155, 0, 98, 0, 0,163, 0, 0, + 226, 0, 0, 0, 0, 0, 0, 0, 0,255, 96, 0, 0, 0,143, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0,107, 0,105,102, 0, 0,112, + 110,111,108,112,106,103, 0,119, 0,118,109, 0, 99,104,119, 0, + + 0, 0, 0, 65, 99, diff --git a/drivers/input/mouse/sentelic.c b/drivers/input/mouse/sentelic.c index 2716d2ba386a1..ed3ad58fd393b 100644 --- a/drivers/input/mouse/sentelic.c +++ b/drivers/input/mouse/sentelic.c @@ -899,8 +899,8 @@ static int fsp_activate_protocol(struct psmouse *psmouse) pad->flags |= FSPDRV_FLAG_EN_OPC; /* Enable on-pad vertical and horizontal scrolling */ - fsp_onpad_vscr(psmouse, true); - fsp_onpad_hscr(psmouse, true); + fsp_onpad_vscr(psmouse, pad->vscroll); + fsp_onpad_hscr(psmouse, pad->hscroll); } else { /* Enable absolute coordinates output for Cx/Dx hardware */ if (fsp_reg_write(psmouse, FSP_REG_SWC1, diff --git a/drivers/input/serio/i8042.c b/drivers/input/serio/i8042.c index 29340f8095bb2..76ce5c5a424eb 100644 --- a/drivers/input/serio/i8042.c +++ b/drivers/input/serio/i8042.c @@ -131,7 +131,9 @@ MODULE_PARM_DESC(unmask_kbd_data, "Unconditional enable (may reveal sensitive da #endif static bool i8042_present; -static bool i8042_bypass_aux_irq_test; +static bool i8042_bypass_aux_irq_test = true; +module_param_named(bypass_aux_test, i8042_bypass_aux_irq_test, bool, 0600); +MODULE_PARM_DESC(debug, "Turn i8042 aux irq test off"); static char i8042_kbd_firmware_id[128]; static char i8042_aux_firmware_id[128]; static struct fwnode_handle *i8042_kbd_fwnode; @@ -142,7 +144,7 @@ static struct fwnode_handle *i8042_kbd_fwnode; * i8042_lock protects serialization between i8042_command and * the interrupt handler. */ -static DEFINE_SPINLOCK(i8042_lock); +DEFINE_SPINLOCK(i8042_lock); /* * Writers to AUX and KBD ports as well as users issuing i8042_command @@ -959,7 +961,7 @@ static int i8042_controller_selftest(void) do { if (i8042_command(¶m, I8042_CMD_CTL_TEST)) { - pr_err("i8042 controller selftest timeout\n"); + pr_info("i8042 controller selftest timeout\n"); return -ENODEV; } diff --git a/drivers/net/ethernet/intel/e1000e/e1000.h b/drivers/net/ethernet/intel/e1000e/e1000.h index ba9c19e6994c9..0e56b992dd3c5 100644 --- a/drivers/net/ethernet/intel/e1000e/e1000.h +++ b/drivers/net/ethernet/intel/e1000e/e1000.h @@ -228,6 +228,7 @@ struct e1000_adapter { bool detect_tx_hung; bool tx_hang_recheck; + bool rx_hang_recheck; u8 tx_timeout_factor; u32 tx_int_delay; @@ -270,6 +271,10 @@ struct e1000_adapter { u32 alloc_rx_buff_failed; u32 rx_dma_failed; u32 rx_hwtstamp_cleared; + u32 rdh_old; + u32 rdt_old; + u32 rx_ntu_old; + u32 rx_ntc_old; unsigned int rx_ps_pages; u16 rx_ps_bsize0; @@ -315,6 +320,7 @@ struct e1000_adapter { struct work_struct print_hang_task; int phy_hang_count; + int weird_hang_count; u16 tx_ring_count; u16 rx_ring_count; diff --git a/drivers/net/ethernet/intel/e1000e/netdev.c b/drivers/net/ethernet/intel/e1000e/netdev.c index 721c098f2bb1b..d62a7e4a887e4 100644 --- a/drivers/net/ethernet/intel/e1000e/netdev.c +++ b/drivers/net/ethernet/intel/e1000e/netdev.c @@ -637,6 +637,69 @@ static void e1000e_update_tdt_wa(struct e1000_ring *tx_ring, unsigned int i) } } +static void e1000_check_82574_weird_hang(struct e1000_ring *rx_ring, bool ps) +{ + u32 i, rdh, rdt, staterr, next_staterr; + struct e1000_adapter *adapter = rx_ring->adapter; + + if (adapter->link_speed != SPEED_1000) + return; + + i = rx_ring->next_to_clean; + rdh = readl(rx_ring->head); + rdt = readl(rx_ring->tail); + if (ps) { + union e1000_rx_desc_packet_split *rx_desc = E1000_RX_DESC_PS(*rx_ring, i); + union e1000_rx_desc_packet_split *next_rxd = E1000_RX_DESC_PS(*rx_ring, (i+1)%rx_ring->count); + staterr = le32_to_cpu(rx_desc->wb.middle.status_error); + next_staterr = le32_to_cpu(next_rxd->wb.middle.status_error); + } + else { + union e1000_rx_desc_extended *rx_desc = E1000_RX_DESC_EXT(*rx_ring, i); + union e1000_rx_desc_extended *next_rxd = E1000_RX_DESC_EXT(*rx_ring, (i+1)%rx_ring->count); + staterr = le32_to_cpu(rx_desc->wb.upper.status_error); + next_staterr = le32_to_cpu(next_rxd->wb.upper.status_error); + } + + /* Rx Hung */ + if (e1000_desc_unused(rx_ring)==0 && rdh==rdt && !(staterr & E1000_RXD_STAT_DD)) { + if (!adapter->rx_hang_recheck) { /* Check Twice*/ + adapter->rx_hang_recheck = true; + adapter->rdh_old = rdh; + adapter->rdt_old = rdt; + adapter->rx_ntu_old = rx_ring->next_to_use; + adapter->rx_ntc_old = rx_ring->next_to_clean; + } + else { + adapter->rx_hang_recheck = false; + if (rdh==adapter->rdh_old && rdt==adapter->rdt_old + && rx_ring->next_to_use==adapter->rx_ntu_old + && rx_ring->next_to_clean==adapter->rx_ntc_old) { + adapter->weird_hang_count++; + e_err("Detected the %dth Weird Rx Hang:\n" + " RDH <%x>\n" + " RDT <%x>\n" + " next_to_use <%x>\n" + " next_to_clean <%x>\n" + " rx_desc status <%x>\n", + adapter->weird_hang_count, + rdh, rdt, + rx_ring->next_to_use, + rx_ring->next_to_clean, + staterr); + + if (next_staterr & E1000_RXD_STAT_DD) { /* Really hang, light recover */ + e_err("Recover by skipping rx_desc...\n"); + rx_ring->next_to_clean = (i+1) % rx_ring->count; + } + else { /* Really hang, heavy recover */ + e_err("Recover by resetting device...\n"); + schedule_work(&adapter->reset_task); + } + } + } + } +} /** * e1000_alloc_rx_buffers - Replace used receive buffers * @rx_ring: Rx descriptor ring @@ -926,6 +989,8 @@ static bool e1000_clean_rx_irq(struct e1000_ring *rx_ring, int *work_done, bool cleaned = false; unsigned int total_rx_bytes = 0, total_rx_packets = 0; + e1000_check_82574_weird_hang(rx_ring, false); + i = rx_ring->next_to_clean; rx_desc = E1000_RX_DESC_EXT(*rx_ring, i); staterr = le32_to_cpu(rx_desc->wb.upper.status_error); @@ -1324,6 +1389,8 @@ static bool e1000_clean_rx_irq_ps(struct e1000_ring *rx_ring, int *work_done, bool cleaned = false; unsigned int total_rx_bytes = 0, total_rx_packets = 0; + e1000_check_82574_weird_hang(rx_ring, true); + i = rx_ring->next_to_clean; rx_desc = E1000_RX_DESC_PS(*rx_ring, i); staterr = le32_to_cpu(rx_desc->wb.middle.status_error); @@ -1518,6 +1585,8 @@ static bool e1000_clean_jumbo_rx_irq(struct e1000_ring *rx_ring, int *work_done, unsigned int total_rx_bytes = 0, total_rx_packets = 0; struct skb_shared_info *shinfo; + e1000_check_82574_weird_hang(rx_ring, false); + i = rx_ring->next_to_clean; rx_desc = E1000_RX_DESC_EXT(*rx_ring, i); staterr = le32_to_cpu(rx_desc->wb.upper.status_error); @@ -4681,6 +4750,7 @@ int e1000e_open(struct net_device *netdev) e1000_irq_enable(adapter); adapter->tx_hang_recheck = false; + adapter->rx_hang_recheck = false; hw->mac.get_link_status = true; pm_runtime_put(&pdev->dev); @@ -7689,6 +7759,12 @@ static int e1000_probe(struct pci_dev *pdev, const struct pci_device_id *ent) if (pci_dev_run_wake(pdev)) pm_runtime_put_noidle(&pdev->dev); + adapter->rdh_old = 0; + adapter->rdt_old = 0; + adapter->rx_ntu_old = 0; + adapter->rx_ntc_old = 0; + adapter->weird_hang_count = 0; + return 0; err_register: diff --git a/drivers/net/ethernet/intel/igb/igb.h b/drivers/net/ethernet/intel/igb/igb.h index a2b759531cb7b..ff88e897aad2f 100644 --- a/drivers/net/ethernet/intel/igb/igb.h +++ b/drivers/net/ethernet/intel/igb/igb.h @@ -374,6 +374,13 @@ struct igb_q_vector { struct rcu_head rcu; /* to avoid race with update stats on free */ char name[IFNAMSIZ + 9]; + u32 rdh_old; + u32 rdt_old; + u32 rx_ntu_old; + u32 rx_ntc_old; + int weird_hang_count; + bool weird_hang_recheck; + /* for dynamic allocation of rings associated with this q_vector */ struct igb_ring ring[] ____cacheline_internodealigned_in_smp; }; diff --git a/drivers/net/ethernet/intel/igb/igb_main.c b/drivers/net/ethernet/intel/igb/igb_main.c index c38be2880efcf..6a66cb516163a 100644 --- a/drivers/net/ethernet/intel/igb/igb_main.c +++ b/drivers/net/ethernet/intel/igb/igb_main.c @@ -4221,6 +4221,15 @@ static int __igb_open(struct net_device *netdev, bool resuming) hw->mac.get_link_status = 1; schedule_work(&adapter->watchdog_task); + for (i = 0; i < adapter->num_q_vectors; i++) { + adapter->q_vector[i]->rdh_old = 0; + adapter->q_vector[i]->rdt_old = 0; + adapter->q_vector[i]->rx_ntu_old = 0; + adapter->q_vector[i]->rx_ntc_old = 0; + adapter->q_vector[i]->weird_hang_count = 0; + adapter->q_vector[i]->weird_hang_recheck = false; + } + return 0; err_set_queues: @@ -8895,6 +8904,64 @@ static void igb_put_rx_buffer(struct igb_ring *rx_ring, rx_buffer->page = NULL; } +static void igb_check_weird_hang(struct igb_ring *rx_ring) +{ + u32 i, rdh, rdt, staterr; + union e1000_adv_rx_desc *rx_desc, *next_rxd; + struct igb_q_vector *q_vector = rx_ring->q_vector; + struct e1000_hw *hw = &q_vector->adapter->hw; + + if (q_vector->adapter->link_speed != SPEED_1000) + return; + + i = rx_ring->next_to_clean; + rdh = rd32(E1000_RDH(rx_ring->reg_idx)); + rdt = rd32(E1000_RDT(rx_ring->reg_idx)); + rx_desc = IGB_RX_DESC(rx_ring, i); + staterr = le32_to_cpu(rx_desc->wb.upper.status_error); + next_rxd = IGB_RX_DESC(rx_ring, (i+1)%rx_ring->count); + + /* Weird Hung */ + if (igb_desc_unused(rx_ring)==0 && rdh==rdt && !igb_test_staterr(rx_desc, E1000_RXD_STAT_DD)) { + if (!q_vector->weird_hang_recheck) { /* Check Twice*/ + q_vector->weird_hang_recheck = true; + q_vector->rdh_old = rdh; + q_vector->rdt_old = rdt; + q_vector->rx_ntu_old = rx_ring->next_to_use; + q_vector->rx_ntc_old = rx_ring->next_to_clean; + } + else { + q_vector->weird_hang_recheck = false; + if (rdh==q_vector->rdh_old && rdt==q_vector->rdt_old + && rx_ring->next_to_use==q_vector->rx_ntu_old + && rx_ring->next_to_clean==q_vector->rx_ntc_old) { + q_vector->weird_hang_count++; + dev_err(rx_ring->dev, + "Detected the %dth Weird Rx Hang:\n" + " RDH <%x>\n" + " RDT <%x>\n" + " next_to_use <%x>\n" + " next_to_clean <%x>\n" + " rx_desc status <%x>\n", + q_vector->weird_hang_count, + rdh, rdt, + rx_ring->next_to_use, + rx_ring->next_to_clean, + staterr); + + if (igb_test_staterr(next_rxd, E1000_RXD_STAT_DD)) { /* Really hang, light recover */ + dev_err(rx_ring->dev, "Recover by skipping rx_desc...\n"); + rx_ring->next_to_clean = (i+1) % rx_ring->count; + } + else { /* Really hang, heavy recover */ + dev_err(rx_ring->dev, "Recover by resetting device...\n"); + schedule_work(&q_vector->adapter->reset_task); + } + } + } + } +} + static int igb_clean_rx_irq(struct igb_q_vector *q_vector, const int budget) { unsigned int total_bytes = 0, total_packets = 0; @@ -8915,6 +8982,8 @@ static int igb_clean_rx_irq(struct igb_q_vector *q_vector, const int budget) #endif xdp_init_buff(&xdp, frame_sz, &rx_ring->xdp_rxq); + igb_check_weird_hang(rx_ring); + while (likely(total_packets < budget)) { union e1000_adv_rx_desc *rx_desc; struct igb_rx_buffer *rx_buffer; diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00.h b/drivers/net/wireless/ralink/rt2x00/rt2x00.h index 07a6a5a9ce13a..5ccf6ddf283f9 100644 --- a/drivers/net/wireless/ralink/rt2x00/rt2x00.h +++ b/drivers/net/wireless/ralink/rt2x00/rt2x00.h @@ -1174,23 +1174,27 @@ static inline bool rt2x00_intf(struct rt2x00_dev *rt2x00dev, static inline bool rt2x00_is_pci(struct rt2x00_dev *rt2x00dev) { - return rt2x00_intf(rt2x00dev, RT2X00_CHIP_INTF_PCI) || - rt2x00_intf(rt2x00dev, RT2X00_CHIP_INTF_PCIE); + return IS_ENABLED(CONFIG_RT2X00_LIB_PCI) && + (rt2x00_intf(rt2x00dev, RT2X00_CHIP_INTF_PCI) || + rt2x00_intf(rt2x00dev, RT2X00_CHIP_INTF_PCIE)); } static inline bool rt2x00_is_pcie(struct rt2x00_dev *rt2x00dev) { - return rt2x00_intf(rt2x00dev, RT2X00_CHIP_INTF_PCIE); + return IS_ENABLED(CONFIG_RT2X00_LIB_PCI) && + rt2x00_intf(rt2x00dev, RT2X00_CHIP_INTF_PCIE); } static inline bool rt2x00_is_usb(struct rt2x00_dev *rt2x00dev) { - return rt2x00_intf(rt2x00dev, RT2X00_CHIP_INTF_USB); + return IS_ENABLED(CONFIG_RT2X00_LIB_USB) && + rt2x00_intf(rt2x00dev, RT2X00_CHIP_INTF_USB); } static inline bool rt2x00_is_soc(struct rt2x00_dev *rt2x00dev) { - return rt2x00_intf(rt2x00dev, RT2X00_CHIP_INTF_SOC); + return IS_ENABLED(CONFIG_RT2X00_LIB_SOC) && + rt2x00_intf(rt2x00dev, RT2X00_CHIP_INTF_SOC); } /* Helpers for capability flags */ diff --git a/drivers/parport/parport_serial.c b/drivers/parport/parport_serial.c index 3644997a83425..d09d328196090 100644 --- a/drivers/parport/parport_serial.c +++ b/drivers/parport/parport_serial.c @@ -60,6 +60,7 @@ enum parport_pc_pci_cards { wch_ch353_2s1p, wch_ch382_0s1p, wch_ch382_2s1p, + wch_ch384_4s1p, brainboxes_5s1p, sunix_4008a, sunix_5069a, @@ -157,6 +158,7 @@ static struct parport_pc_pci cards[] = { /* wch_ch353_2s1p*/ { 1, { { 2, -1}, } }, /* wch_ch382_0s1p*/ { 1, { { 2, -1}, } }, /* wch_ch382_2s1p*/ { 1, { { 2, -1}, } }, + /* wch_ch384_4s1p*/ { 1, { { 2, -1}, } }, /* brainboxes_5s1p */ { 1, { { 3, -1 }, } }, /* sunix_4008a */ { 1, { { 1, 2 }, } }, /* sunix_5069a */ { 1, { { 1, 2 }, } }, @@ -270,6 +272,7 @@ static struct pci_device_id parport_serial_pci_tbl[] = { { 0x4348, 0x7053, 0x4348, 0x3253, 0, 0, wch_ch353_2s1p}, { 0x1c00, 0x3050, 0x1c00, 0x3050, 0, 0, wch_ch382_0s1p}, { 0x1c00, 0x3250, 0x1c00, 0x3250, 0, 0, wch_ch382_2s1p}, + { 0x1c00, 0x3450, 0x1c00, 0x3450, 0, 0, wch_ch384_4s1p}, /* BrainBoxes PX272/PX306 MIO card */ { PCI_VENDOR_ID_INTASHIELD, 0x4100, @@ -558,6 +561,13 @@ static struct pciserial_board pci_parport_serial_boards[] = { .uart_offset = 8, .first_offset = 0xC0, }, + [wch_ch384_4s1p] = { + .flags = FL_BASE0, + .num_ports = 4, + .base_baud = 115200, + .uart_offset = 8, + .first_offset = 0xC0, + }, [brainboxes_5s1p] = { .flags = FL_BASE2, .num_ports = 5, diff --git a/drivers/pci/controller/pci-loongson.c b/drivers/pci/controller/pci-loongson.c index d5495c59a6929..af97aee9bd101 100644 --- a/drivers/pci/controller/pci-loongson.c +++ b/drivers/pci/controller/pci-loongson.c @@ -484,7 +484,9 @@ static int loongson_pci_probe(struct platform_device *pdev) } if (priv->data->flags & FLAG_CFG1) { +#ifdef CONFIG_LOONGARCH if (priv->cfg0_base) +#endif num = 1; regs = platform_get_resource(pdev, IORESOURCE_MEM, num); if (!regs) diff --git a/drivers/platform/mips/Kconfig b/drivers/platform/mips/Kconfig index fb4ac4b08e891..a5a80e0a399ff 100644 --- a/drivers/platform/mips/Kconfig +++ b/drivers/platform/mips/Kconfig @@ -37,4 +37,21 @@ config LS2K_RESET help Loongson-2K1000 Reset Controller driver. +config LEMOTE3A_LAPTOP + tristate "Lemote Loongson-3A Laptop Driver" + depends on MACH_LOONGSON64 + select BACKLIGHT_LCD_SUPPORT + select LCD_CLASS_DEVICE + select BACKLIGHT_CLASS_DEVICE + select POWER_SUPPLY + select HWMON + select VIDEO_OUTPUT_CONTROL + select INPUT_SPARSEKMAP + select INPUT_EVDEV + depends on INPUT + select LEDS_CLASS + default y + help + Lemote Loongson-3A/2Gq family laptops driver. + endif # MIPS_PLATFORM_DEVICES diff --git a/drivers/platform/mips/Makefile b/drivers/platform/mips/Makefile index 4c71444e453a6..a3e9e6b2f38fb 100644 --- a/drivers/platform/mips/Makefile +++ b/drivers/platform/mips/Makefile @@ -2,3 +2,11 @@ obj-$(CONFIG_CPU_HWMON) += cpu_hwmon.o obj-$(CONFIG_RS780E_ACPI) += rs780e-acpi.o obj-$(CONFIG_LS2K_RESET) += ls2k-reset.o +obj-$(CONFIG_I2C_PIIX4) += emc1412.o sd5075.o tmp75.o +obj-$(CONFIG_CPU_HWMON) += wpce_fan.o ls7a_fan.o sbx00_fan.o +obj-$(CONFIG_LEMOTE3A_LAPTOP) += lemote3a-laptop.o +obj-m += ec_rom.o at24c04.o + +ifdef CONFIG_MTD +obj-m += pmon_flash.o +endif diff --git a/drivers/platform/mips/at24c04.c b/drivers/platform/mips/at24c04.c new file mode 100644 index 0000000000000..7ba3d1c8692e1 --- /dev/null +++ b/drivers/platform/mips/at24c04.c @@ -0,0 +1,26 @@ +#include + +static int __init at24c04_init(void) +{ + printk("====Please use device-tree for AT24 Driver====\n"); + printk("You can put the at24 node under the i2c node:\n" + "eeprom@51 {\n" + " compatible = \"atmel,24c04\"\n" + " reg = <0x51>\n" + " size = <512>\n" + " pagesize = <16>\n" + "}\n"); + printk("==================Thank you!==================\n"); + return 0; +} + +static void __exit at24c04_exit(void) +{ +} + +module_init(at24c04_init); +module_exit(at24c04_exit); + +MODULE_AUTHOR("Binbin Zhou "); +MODULE_DESCRIPTION("AT24c04 driver, based on the at24 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/mips/ec_rom.c b/drivers/platform/mips/ec_rom.c new file mode 100644 index 0000000000000..b7b1624191f7a --- /dev/null +++ b/drivers/platform/mips/ec_rom.c @@ -0,0 +1,179 @@ +/* + * EC(Embedded Controller) WPCE775L misc device driver on Linux + * Author : Huang Wei + * Date : 2010-06-28 + * + * NOTE : + * 1, The EC resources accessing and programming are supported. + */ + +/*******************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include /* for ioremap(offset, size) */ + +#include "ec_wpce775l.h" + +/*******************************************************************/ + +/* the register operation access struct */ +struct ec_reg { + u32 addr; /* the address is EC flash address and ACPI command */ + u8 index; /* the index is ACPI command's index */ + u16 val; /* the val is EC flash data and EC space value */ + u8 flag; /* Different access methods. */ +}; + +const char *version = EC_VERSION; + +/* Ec misc device name */ +#define EC_MISC_DEV "ec_misc" + +/* Ec misc device minor number */ +#define ECMISC_MINOR_DEV MISC_DYNAMIC_MINOR + +#define EC_IOC_MAGIC 'E' + +/* misc ioctl operations */ +#define IOCTL_RDREG _IOR(EC_IOC_MAGIC, 1, int) +#define IOCTL_WRREG _IOW(EC_IOC_MAGIC, 2, int) + +/* ioctl */ +static int misc_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg) +{ + void __user *ptr = (void __user *)arg; + struct ec_reg *ecreg = (struct ec_reg *)(filp->private_data); + int ret = 0; + + switch (cmd) { + case IOCTL_RDREG: + ret = copy_from_user(ecreg, ptr, sizeof(struct ec_reg)); + if (ret) { + printk(KERN_ERR "ACPI command read : copy from user error.\n"); + return -EFAULT; + } + + if (ecreg->flag == 0) { + /* has index read ACPI command */ + ecreg->val = (unsigned short)ec_read_all((unsigned char)ecreg->addr, ecreg->index); + } else if (ecreg->flag == 1) { + /* no index read ACPI command */ + ecreg->val = (unsigned short)ec_read_noindex((unsigned char)ecreg->addr); + } + ret = copy_to_user(ptr, ecreg, sizeof(struct ec_reg)); + if (ret) { + printk(KERN_ERR "ACPI command read : copy to user error.\n"); + return -EFAULT; + } + break; + case IOCTL_WRREG: + ret = copy_from_user(ecreg, ptr, sizeof(struct ec_reg)); + if (ret) { + printk(KERN_ERR "WCB and ACPI command write : copy from user error.\n"); + return -EFAULT; + } + + if (ecreg->flag == 1) { + /* write no index command */ + ec_write_noindex((unsigned char)ecreg->addr, (unsigned char)ecreg->val); + } else { + /* write has index command */ + ec_write_all((unsigned char)ecreg->addr, (unsigned char)ecreg->index, (unsigned char)ecreg->val); + } + break; + + default : + break; + } + + return 0; +} + +static long misc_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + return misc_ioctl(file->f_path.dentry->d_inode, file, cmd, arg); +} + +static int misc_open(struct inode * inode, struct file * filp) +{ + struct ec_reg *ecreg = NULL; + ecreg = kmalloc(sizeof(struct ec_reg), GFP_KERNEL); + if (ecreg) { + filp->private_data = ecreg; + } + + return ecreg ? 0 : -ENOMEM; +} + +static int misc_release(struct inode * inode, struct file * filp) +{ + struct ec_reg *ecreg = (struct ec_reg *)(filp->private_data); + + filp->private_data = NULL; + kfree(ecreg); + + return 0; +} + +static struct file_operations ecmisc_fops = { + .owner = THIS_MODULE, + .open = misc_open, + .release = misc_release, + .read = NULL, + .write = NULL, +#ifdef CONFIG_64BIT + .compat_ioctl = misc_compat_ioctl, +#else + .ioctl = misc_ioctl, +#endif +}; + +/*********************************************************/ + +static struct miscdevice ecmisc_device = { + .minor = ECMISC_MINOR_DEV, + .name = EC_MISC_DEV, + .fops = &ecmisc_fops +}; + +static int __init ecmisc_init(void) +{ + int ret; + + ret = misc_register(&ecmisc_device); + printk(KERN_INFO "EC misc device init V%s.\n", version); + + return ret; +} + +static void __exit ecmisc_exit(void) +{ + misc_deregister(&ecmisc_device); + printk(KERN_INFO "EC misc device exit.\n"); +} + +module_init(ecmisc_init); +module_exit(ecmisc_exit); + +MODULE_AUTHOR("Huang Wei "); +MODULE_DESCRIPTION("WPCE775L resources misc Management"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/mips/emc1412.c b/drivers/platform/mips/emc1412.c new file mode 100644 index 0000000000000..fc1a26cd34fe0 --- /dev/null +++ b/drivers/platform/mips/emc1412.c @@ -0,0 +1,370 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_EMC1412_CLIENTS 4 +#define EMC1412_THERMAL_THRESHOLD 90000 + +#define EMC1412_TEMP_EXT_HI_REG 0x01 +#define EMC1412_TEMP_EXT_LO_REG 0x10 +#define EMC1412_TEMP_INT_HI_REG 0x00 +#define EMC1412_TEMP_INT_LO_REG 0x29 +#define EMC1412_THERM_LIMIT_EXT_REG 0x19 +#define EMC1412_THERM_LIMIT_INT_REG 0x20 +#define EMC1412_THERM_LIMIT_HYS_REG 0x21 + +struct i2c_client *emc1412_client[MAX_EMC1412_CLIENTS] = {NULL}; + +static struct device *emc1412_hwmon_dev; + +static ssize_t get_hwmon_name(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t get_emc1412_label(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t get_emc1412_temp(struct device *dev, + struct device_attribute *attr, char *buf); + +static ssize_t get_emc1412_crit(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t set_emc1412_crit(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); +static ssize_t get_emc1412_crit_hyst(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t set_emc1412_crit_hyst(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static SENSOR_DEVICE_ATTR(name, S_IRUGO, get_hwmon_name, NULL, 0); + +static struct attribute *emc1412_hwmon_attributes[] = +{ + &sensor_dev_attr_name.dev_attr.attr, + NULL +}; + +/* Hwmon device attribute group */ +static struct attribute_group emc1412_hwmon_attribute_group = +{ + .attrs = emc1412_hwmon_attributes, +}; + +/* Hwmon device get name */ +static ssize_t get_hwmon_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "emc1412\n"); +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, get_emc1412_temp, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, get_emc1412_label, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO | S_IWUSR, get_emc1412_crit, set_emc1412_crit, 1); +static SENSOR_DEVICE_ATTR(temp1_crit_hyst, S_IRUGO | S_IWUSR, get_emc1412_crit_hyst, set_emc1412_crit_hyst, 1); + +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, get_emc1412_temp, NULL, 2); +static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, get_emc1412_label, NULL, 2); +static SENSOR_DEVICE_ATTR(temp2_crit, S_IRUGO | S_IWUSR, get_emc1412_crit, set_emc1412_crit, 2); +static SENSOR_DEVICE_ATTR(temp2_crit_hyst, S_IRUGO | S_IWUSR, get_emc1412_crit_hyst, set_emc1412_crit_hyst, 2); + +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, get_emc1412_temp, NULL, 3); +static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, get_emc1412_label, NULL, 3); +static SENSOR_DEVICE_ATTR(temp3_crit, S_IRUGO | S_IWUSR, get_emc1412_crit, set_emc1412_crit, 3); +static SENSOR_DEVICE_ATTR(temp3_crit_hyst, S_IRUGO | S_IWUSR, get_emc1412_crit_hyst, set_emc1412_crit_hyst, 3); + +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, get_emc1412_temp, NULL, 4); +static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, get_emc1412_label, NULL, 4); +static SENSOR_DEVICE_ATTR(temp4_crit, S_IRUGO | S_IWUSR, get_emc1412_crit, set_emc1412_crit, 4); +static SENSOR_DEVICE_ATTR(temp4_crit_hyst, S_IRUGO | S_IWUSR, get_emc1412_crit_hyst, set_emc1412_crit_hyst, 4); + +static const struct attribute *emc1412_hwmon_temp[4][5] = { + { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_label.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_crit_hyst.dev_attr.attr, + NULL + }, + + { + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_label.dev_attr.attr, + &sensor_dev_attr_temp2_crit.dev_attr.attr, + &sensor_dev_attr_temp2_crit_hyst.dev_attr.attr, + NULL + }, + + { + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_label.dev_attr.attr, + &sensor_dev_attr_temp3_crit.dev_attr.attr, + &sensor_dev_attr_temp3_crit_hyst.dev_attr.attr, + NULL + }, + + { + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp4_label.dev_attr.attr, + &sensor_dev_attr_temp4_crit.dev_attr.attr, + &sensor_dev_attr_temp4_crit_hyst.dev_attr.attr, + NULL + } +}; + +static int emc1412_probe(struct platform_device *dev) +{ + struct i2c_adapter *adapter = NULL; + struct i2c_board_info info; + int i = 0, r = 0, found = 0, id = dev->id - 1; + struct sensor_device *sdev = (struct sensor_device *)dev->dev.platform_data; + + memset(&info, 0, sizeof(struct i2c_board_info)); + + adapter = i2c_get_adapter(i++); + while (adapter) { + if (strncmp(adapter->name, "SMBus PIIX4", 11) == 0) { + found = 1; + break; + } + + adapter = i2c_get_adapter(i++); + } + + if (!found) + goto fail; + + info.addr = sdev->base_addr; + info.platform_data = dev->dev.platform_data; + strncpy(info.type, "emc1412", I2C_NAME_SIZE); + emc1412_client[id] = i2c_new_client_device(adapter, &info); + if (emc1412_client[id] == NULL) { + printk(KERN_ERR "failed to attach EMC1412 sensor\n"); + goto fail; + } + + r = sysfs_create_files(&emc1412_hwmon_dev->kobj, emc1412_hwmon_temp[id]); + if (r) + goto fail; + + i2c_smbus_write_byte_data(emc1412_client[id], EMC1412_THERM_LIMIT_EXT_REG, (EMC1412_THERMAL_THRESHOLD / 1000 + 10)); + i2c_smbus_write_byte_data(emc1412_client[id], EMC1412_THERM_LIMIT_INT_REG, (EMC1412_THERMAL_THRESHOLD / 1000 + 10)); + + printk(KERN_INFO "Success to attach EMC1412 sensor\n"); + + return 0; + +fail: + printk(KERN_ERR "Fail to found smbus controller attach EMC1412 sensor\n"); + + return r; +} + +static void emc1412_shutdown(struct platform_device *dev) +{ + int id = dev->id - 1; + + emc1412_client[id] = NULL; + msleep(15); /* Release I2C/SMBus resources */ +} + +static int emc1412_external_temp(int id) +{ + u8 reg; + int temp; + struct i2c_client *client; + + /* not ready ??? */ + if (id < 0 || !(client = emc1412_client[id])) + return NOT_VALID_TEMP; + + reg = i2c_smbus_read_byte_data(client, EMC1412_TEMP_EXT_LO_REG); + temp = i2c_smbus_read_byte_data(client, EMC1412_TEMP_EXT_HI_REG) * 1000; + temp += (reg >> 5) * 125; + + return temp; +} + +static ssize_t get_emc1412_label(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int id = (to_sensor_dev_attr(attr))->index - 1; + struct sensor_device *sdev = (struct sensor_device *)emc1412_client[id]->dev.platform_data; + + return sprintf(buf, "%s\n", sdev->label); +} + +static ssize_t get_emc1412_temp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int id = (to_sensor_dev_attr(attr))->index - 1; + int value = emc1412_external_temp(id); + + return sprintf(buf, "%d\n", value); +} + +static ssize_t get_emc1412_crit(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client; + int ext_limit, int_limit, ave_limit; + int id = (to_sensor_dev_attr(attr))->index - 1; + + if (id < 0 || !(client = emc1412_client[id])) + return NOT_VALID_TEMP; + + ext_limit = i2c_smbus_read_byte_data(client, EMC1412_THERM_LIMIT_EXT_REG); + int_limit = i2c_smbus_read_byte_data(client, EMC1412_THERM_LIMIT_INT_REG); + ave_limit = (ext_limit + int_limit) / 2; + + return sprintf(buf, "%d\n", (ave_limit * 1000)); +} + +static ssize_t set_emc1412_crit(struct device *dev, + struct device_attribute *attr, const char *buf ,size_t count) +{ + unsigned long set_limit; + struct i2c_client *client; + int id = (to_sensor_dev_attr(attr))->index - 1; + + if (id < 0 || !(client = emc1412_client[id])) + return NOT_VALID_TEMP; + + if (kstrtoul(buf, 10, &set_limit)) + return -EINVAL; + + set_limit = clamp_val(set_limit, 0, 125000); + set_limit /= 1000; + i2c_smbus_write_byte_data(client, EMC1412_THERM_LIMIT_EXT_REG, set_limit); + i2c_smbus_write_byte_data(client, EMC1412_THERM_LIMIT_INT_REG, set_limit); + + return count; +} + +static ssize_t get_emc1412_crit_hyst(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client; + int ext_limit, int_limit, ave_limit, hyst; + int id = (to_sensor_dev_attr(attr))->index - 1; + + if (id < 0 || !(client = emc1412_client[id])) + return NOT_VALID_TEMP; + + ext_limit = i2c_smbus_read_byte_data(client, EMC1412_THERM_LIMIT_EXT_REG); + int_limit = i2c_smbus_read_byte_data(client, EMC1412_THERM_LIMIT_INT_REG); + ave_limit = (ext_limit + int_limit) / 2; + + hyst = i2c_smbus_read_byte_data(client, EMC1412_THERM_LIMIT_HYS_REG); + + return sprintf(buf, "%d\n", (ave_limit - hyst) * 1000); +} + +static ssize_t set_emc1412_crit_hyst(struct device *dev, + struct device_attribute *attr, const char *buf ,size_t count) +{ + unsigned long set_limit; + int ext_limit, int_limit, ave_limit, hyst; + struct i2c_client *client; + int id = (to_sensor_dev_attr(attr))->index - 1; + + if (id < 0 || !(client = emc1412_client[id])) + return NOT_VALID_TEMP; + + if (kstrtoul(buf, 10, &set_limit)) + return -EINVAL; + + ext_limit = i2c_smbus_read_byte_data(client, EMC1412_THERM_LIMIT_EXT_REG); + int_limit = i2c_smbus_read_byte_data(client, EMC1412_THERM_LIMIT_INT_REG); + ave_limit = (ext_limit + int_limit) / 2; + set_limit = clamp_val(set_limit, 0, 125000); + set_limit /= 1000; + hyst = ave_limit - set_limit; + + i2c_smbus_write_byte_data(client, EMC1412_THERM_LIMIT_HYS_REG, hyst); + + return count; +} + +static struct delayed_work thermal_work; + +static void do_thermal_timer(struct work_struct *work) +{ + int i, value, temp_max = 0; + + for (i=0; i temp_max) + temp_max = value; + } + + if (temp_max <= EMC1412_THERMAL_THRESHOLD) + schedule_delayed_work(&thermal_work, msecs_to_jiffies(5000)); + else { + pr_emerg("Power off due to high EMC1412 temp: %d\n", temp_max); + orderly_poweroff(true); + } +} + +static struct platform_driver emc1412_driver = { + .probe = emc1412_probe, + .shutdown = emc1412_shutdown, + .driver = { + .name = "emc1412", + .owner = THIS_MODULE, + }, +}; + +static int __init emc1412_init(void) +{ + int ret; + + emc1412_hwmon_dev = hwmon_device_register(NULL); + if (IS_ERR(emc1412_hwmon_dev)) { + ret = -ENOMEM; + printk(KERN_ERR "hwmon_device_register fail!\n"); + goto fail_hwmon_device_register; + } + + ret = sysfs_create_group(&emc1412_hwmon_dev->kobj, + &emc1412_hwmon_attribute_group); + if (ret) { + printk(KERN_ERR "fail to create loongson hwmon!\n"); + goto fail_sysfs_create_group_hwmon; + } + + platform_driver_register(&emc1412_driver); + INIT_DEFERRABLE_WORK(&thermal_work, do_thermal_timer); + schedule_delayed_work(&thermal_work, msecs_to_jiffies(20000)); + + return 0; + +fail_sysfs_create_group_hwmon: + hwmon_device_unregister(emc1412_hwmon_dev); + +fail_hwmon_device_register: + return ret; +} + +static void __exit emc1412_exit(void) +{ + cancel_delayed_work_sync(&thermal_work); + platform_driver_unregister(&emc1412_driver); + sysfs_remove_group(&emc1412_hwmon_dev->kobj, + &emc1412_hwmon_attribute_group); + hwmon_device_unregister(emc1412_hwmon_dev); +} + +late_initcall(emc1412_init); +module_exit(emc1412_exit); + +MODULE_AUTHOR("Yu Xiang "); +MODULE_AUTHOR("Huacai Chen "); +MODULE_DESCRIPTION("EMC1412 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/mips/lemote3a-laptop.c b/drivers/platform/mips/lemote3a-laptop.c new file mode 100644 index 0000000000000..dbb37e376ee80 --- /dev/null +++ b/drivers/platform/mips/lemote3a-laptop.c @@ -0,0 +1,1278 @@ +/* + * Driver for Lemote Loongson-3A/2Gq Laptops with WPCE775l Embeded Controller + * + * Copyright (C) 2011 Lemote Inc. + * Author : Huang Wei + * : Wang Rui + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define KEY_TOUCHPAD_SW KEY_F21 +#define KEY_MODEM KEY_F24 + +/* Backlight */ +#define MAX_BRIGHTNESS 9 + +/* Power supply */ +#define BIT_BAT_POWER_ACIN (1 << 0) +enum +{ + APM_AC_OFFLINE = 0, + APM_AC_ONLINE, + APM_AC_BACKUP, + APM_AC_UNKNOWN = 0xff +}; +enum +{ + APM_BAT_STATUS_HIGH = 0, + APM_BAT_STATUS_LOW, + APM_BAT_STATUS_CRITICAL, + APM_BAT_STATUS_CHARGING, + APM_BAT_STATUS_NOT_PRESENT, + APM_BAT_STATUS_UNKNOWN = 0xff +}; + +enum /* bat_reg_flag */ +{ + BAT_REG_TEMP_FLAG = 1, + BAT_REG_VOLTAGE_FLAG, + BAT_REG_CURRENT_FLAG, + BAT_REG_AC_FLAG, + BAT_REG_RC_FLAG, + BAT_REG_FCC_FLAG, + BAT_REG_ATTE_FLAG, + BAT_REG_ATTF_FLAG, + BAT_REG_RSOC_FLAG, + BAT_REG_CYCLCNT_FLAG +}; + +/* Power info cached timeout */ +#define POWER_INFO_CACHED_TIMEOUT 100 /* jiffies */ + +/* SCI device */ +#define EC_SCI_DEV "sci" /* < 10 bytes. */ +#define SCI_IRQ_NUM 0x07 +#define GPIO_SIZE 256 + +const char *version = EC_VERSION; + +/* Power information structure */ +struct lemote3a_power_info +{ + /* AC insert or not */ + unsigned int ac_in; + /* Battery insert or not */ + unsigned int bat_in; + unsigned int health; + + /* Battery designed capacity */ + unsigned int design_capacity; + /* Battery designed voltage */ + unsigned int design_voltage; + /* Battery capacity after full charged */ + unsigned int full_charged_capacity; + /* Battery Manufacture Date */ + unsigned char manufacture_date[11]; + /* Battery Serial number */ + unsigned char serial_number[8]; + /* Battery Manufacturer Name, max 11 + 1(length) bytes */ + unsigned char manufacturer_name[12]; + /* Battery Device Name, max 7 + 1(length) bytes */ + unsigned char device_name[8]; + /* Battery Technology */ + unsigned int technology; + /* Battery cell count */ + unsigned char cell_count; + + /* Battery dynamic charge/discharge voltage */ + unsigned int voltage_now; + /* Battery dynamic charge/discharge average current */ + int current_now; + int current_sign; + int current_average; + /* Battery current remaining capacity */ + unsigned int remain_capacity; + /* Battery current remaining capacity percent */ + unsigned int remain_capacity_percent; + /* Battery current temperature */ + unsigned int temperature; + /* Battery current remaining time (AverageTimeToEmpty) */ + unsigned int remain_time; + /* Battery current full charging time (averageTimeToFull) */ + unsigned int fullchg_time; + /* Battery Status */ + unsigned int charge_status; + /* Battery current cycle count (CycleCount) */ + unsigned int cycle_count; +}; + +/* SCI device structure */ +struct sci_device +{ + /* The sci number get from ec */ + unsigned char number; + /* Sci count */ + unsigned char parameter; + /* Irq relative */ + unsigned char irq; + unsigned char irq_data; + /* Device name */ + unsigned char name[10]; +}; +/* SCI device event structure */ +struct sci_event +{ + int index; + sci_handler handler; +}; + +/* Platform driver init handler */ +static int __init lemote3a_laptop_init(void); +/* Platform driver exit handler */ +static void __exit lemote3a_laptop_exit(void); +/* Platform device suspend handler */ +static int lemote3a_laptop_suspend(struct platform_device * pdev, pm_message_t state); +/* Platform device resume handler */ +static int lemote3a_laptop_resume(struct platform_device * pdev); + +static ssize_t version_show(struct device_driver * driver, char * buf); +static ssize_t data_destroy_store(struct device_driver *driver, const char *buf, size_t count); + +/* Camera control misc device open handler */ +static int lemote3a_cam_misc_open(struct inode * inode, struct file * filp); +/* Camera control misc device release handler */ +static int lemote3a_cam_misc_release(struct inode * inode, struct file * filp); +/* Camera control misc device read handler */ +ssize_t lemote3a_cam_misc_read(struct file * filp, + char __user * buffer, size_t size, loff_t * offset); +/* Camera control misc device write handler */ +static ssize_t lemote3a_cam_misc_write(struct file * filp, + const char __user * buffer, size_t size, loff_t * offset); + +/* Backlight device set brightness handler */ +static int lemote3a_set_brightness(struct backlight_device * pdev); +/* Backlight device get brightness handler */ +static int lemote3a_get_brightness(struct backlight_device * pdev); + +/* >>>Power management operation */ +/* Update battery information handle function. */ +static void lemote3a_power_battery_info_update(unsigned char bat_reg_flag); +/* Clear battery static information. */ +static void lemote3a_power_info_battery_static_clear(void); +/* Get battery static information. */ +static void lemote3a_power_info_battery_static_update(void); +/* Update power_status value */ +static void lemote3a_power_info_power_status_update(void); +static void lemote3a_bat_get_string(unsigned char index, unsigned char *bat_string); +/* Power supply Battery get property handler */ +static int lemote3a_bat_get_property(struct power_supply * pws, + enum power_supply_property psp, union power_supply_propval * val); +/* Power supply AC get property handler */ +static int lemote3a_ac_get_property(struct power_supply * pws, + enum power_supply_property psp, union power_supply_propval * val); +/* <<props.max_brightness = ec_read(INDEX_DISPLAY_MAXBRIGHTNESS_LEVEL); + lemote3a_backlight_dev->props.brightness = ec_read(INDEX_DISPLAY_BRIGHTNESS); + backlight_update_status(lemote3a_backlight_dev); + /* Register backlight END */ + + /* Register power supply START */ + power_info = kzalloc(sizeof(struct lemote3a_power_info), GFP_KERNEL); + if (!power_info) { + printk(KERN_ERR "Lemote Laptop Platform Driver: Alloc memory for power_info failed!\n"); + ret = -ENOMEM; + goto fail_power_info_alloc; + } + + lemote3a_power_info_power_status_update(); + if (power_info->bat_in) { + /* Get battery static information. */ + lemote3a_power_info_battery_static_update(); + } + else { + printk(KERN_ERR "Lemote Laptop Platform Driver: The battery does not exist!!\n"); + } + lemote3a_bat = power_supply_register(NULL, &lemote3a_bat_desc, NULL); + if (IS_ERR(lemote3a_bat)) { + ret = -ENOMEM; + goto fail_bat_power_supply_register; + } + + lemote3a_ac = power_supply_register(NULL, &lemote3a_ac_desc, NULL); + if (IS_ERR(lemote3a_ac)) { + ret = -ENOMEM; + goto fail_ac_power_supply_register; + } + /* Register power supply END */ + + /* Touchpad enable/disable LED */ + ret = led_classdev_register(NULL, &lemote3a_tp_led); + if (ret == 0) + register_reboot_notifier(&tp_led_nb); + else + goto fail_tp_led_register; + + /* Hotkey device START */ + ret = lemote3a_hotkey_init(); + if (ret) { + printk(KERN_ERR "Lemote Laptop Platform Driver : Fail to register hotkey device.\n"); + goto fail_hotkey_init; + } + /* Hotkey device END */ + + /* SCI PCI Driver Init START */ + ret = sci_pci_driver_init(); + if (ret) { + printk(KERN_ERR "LS3ANB Driver : Fail to register sci pci driver.\n"); + goto fail_sci_pci_driver_init; + } + /* SCI PCI Driver Init END */ + + /* Camera control misc Device START */ + ret = misc_register(&lemote3a_cam_misc_dev); + if (ret) { + printk(KERN_ERR "Lemote Laptop Platform Driver : Fail to register camera control misc device.\n"); + goto fail_misc_register; + } + /* Camera control misc Device END */ + + ret = lemote3a_healthy_led_init(); + + /* Request control for backlight device START */ + ec_write(INDEX_BACKLIGHT_CTRLMODE, BACKLIGHT_CTRL_BYHOST); + /* Request control for backlight device END */ + + return 0; + +fail_misc_register: + sci_pci_driver_exit(); +fail_sci_pci_driver_init: + lemote3a_hotkey_exit(); +fail_hotkey_init: + led_classdev_unregister(&lemote3a_tp_led); +fail_tp_led_register: + power_supply_unregister(lemote3a_ac); +fail_ac_power_supply_register: + power_supply_unregister(lemote3a_bat); +fail_bat_power_supply_register: + kfree(power_info); +fail_power_info_alloc: + backlight_device_unregister(lemote3a_backlight_dev); +fail_backlight_device_register: + platform_driver_unregister(&platform_driver); + + return ret; +} + +static void wpce775l_shutdown(struct platform_device *dev) +{ + cancel_delayed_work_sync(&healthy_led_control); +} + +/* Platform driver init handler */ +static int __init lemote3a_laptop_init(void) +{ + int ret; + + printk(KERN_INFO "Lemote Laptop Platform Driver version V%s\n", version); + + ret = platform_driver_register(&platform_driver); + if(ret) { + printk(KERN_ERR "Lemote Laptop Platform Driver: Fail to register lemote laptop platform driver.\n"); + return ret; + } + + ret = driver_create_file(&platform_driver.driver, &driver_attr_version); + ret = driver_create_file(&platform_driver.driver, &driver_attr_data_destroy); + + return ret; +} + +/* Platform driver exit handler */ +static void __exit lemote3a_laptop_exit(void) +{ + free_irq(lemote3a_sci_device->irq, lemote3a_sci_device); + + /* Return control for backlight device START */ + ec_write(INDEX_BACKLIGHT_CTRLMODE, BACKLIGHT_CTRL_BYEC); + /* Return control for backlight device END */ + + /* Camera control misc device */ + misc_deregister(&lemote3a_cam_misc_dev); + + /* Hotkey & SCI device */ + sci_pci_driver_exit(); + lemote3a_hotkey_exit(); + + /* Touchpad enable/disable LED */ + unregister_reboot_notifier(&tp_led_nb); + led_classdev_unregister(&lemote3a_tp_led); + + /* Power supply */ + power_supply_unregister(lemote3a_ac); + power_supply_unregister(lemote3a_bat); + kfree(power_info); + + /* Backlight */ + backlight_device_unregister(lemote3a_backlight_dev); + + /* Platform device & driver */ + platform_driver_unregister(&platform_driver); + + printk(KERN_INFO "Lemote Laptop Platform Driver : Unload Platform Specific Driver.\n"); +} + +#ifdef CONFIG_PM +/* Platform device suspend handler */ +static int lemote3a_laptop_suspend(struct platform_device * pdev, pm_message_t state) +{ + struct pci_dev *dev; + + dev = pci_get_device(PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_SBX00_SMBUS, NULL); + pci_disable_device(dev); + + return 0; +} + +/* Platform device resume handler */ +static int lemote3a_laptop_resume(struct platform_device * pdev) +{ + int err; + struct pci_dev *dev; + + dev = pci_get_device(PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_SBX00_SMBUS, NULL); + err = pci_enable_device(dev); + if (err) { + dev_err(&pdev->dev, "lemote3a-laptop: cannot enable ATI device: %d\n", + err); + return err; + } + + /* Process LID event */ + lemote3a_sci_event_handler(SCI_EVENT_NUM_LID); + + /* + * Clear sci status: GPM9Status field in bit14 of + * EVENT_STATUS register for SB710, write 1 to clear + * + * Clear all SCI events when suspend + */ + clean_ec_event_status(); + + return 0; +} +#else +static int lemote3a_laptop_suspend(struct platform_device * pdev, pm_message_t state) +{ + return 0; +} + +static int lemote3a_laptop_resume(struct platform_device * pdev) +{ + return 0; +} +#endif /* CONFIG_PM */ + +static ssize_t version_show(struct device_driver * driver, char * buf) +{ + return sprintf(buf, "%s\n", version); +} + +/* the sysctl for the led of data destroy */ +static ssize_t data_destroy_store(struct device_driver *driver, const char *buf, size_t count) +{ + int value; + + if (!sscanf(buf, "%d", &value)) + return -EINVAL; + + if ('0' == buf[0]) + ec_write(INDEX_DATA_DESTROY, DATA_DESTROY_OFF); + else + ec_write(INDEX_DATA_DESTROY, DATA_DESTROY_ON); + + return count; +} + +/* Camera control misc device open handler */ +static int lemote3a_cam_misc_open(struct inode * inode, struct file * filp) +{ + return 0; +} + +/* Camera control misc device release handler */ +static int lemote3a_cam_misc_release(struct inode * inode, struct file * filp) +{ + return 0; +} + +/* Camera control misc device read handler */ +ssize_t lemote3a_cam_misc_read(struct file * filp, + char __user * buffer, size_t size, loff_t * offset) +{ + int ret = 0; + + if (0 != *offset) + return 0; + + ret = ec_read(INDEX_CAM_STSCTRL); + ret = sprintf(buffer, "%d\n", ret); + *offset = ret; + + return ret; +} + +/* Camera control misc device write handler */ +static ssize_t lemote3a_cam_misc_write(struct file * filp, + const char __user * buffer, size_t size, loff_t * offset) +{ + if (0 >= size) + return -EINVAL; + + if ('0' == buffer[0]) + ec_write(INDEX_CAM_STSCTRL, CAM_STSCTRL_OFF); + else + ec_write(INDEX_CAM_STSCTRL, CAM_STSCTRL_ON); + + return size; +} + +/* Backlight device set brightness handler */ +static int lemote3a_set_brightness(struct backlight_device * pdev) +{ + unsigned int level = 0; + + level = (FB_BLANK_UNBLANK==pdev->props.power) ? + pdev->props.brightness : 0; + + if (MAX_BRIGHTNESS < level) { + level = MAX_BRIGHTNESS; + } + else if (level < 0) { + level = 0; + } + + ec_write(INDEX_DISPLAY_BRIGHTNESS, level); + + return 0; +} + +/* Backlight device get brightness handler */ +static int lemote3a_get_brightness(struct backlight_device * pdev) +{ + /* Read level from ec */ + return ec_read(INDEX_DISPLAY_BRIGHTNESS); +} + +/* Update battery information handle function. */ +static void lemote3a_power_battery_info_update(unsigned char bat_reg_flag) +{ + short bat_info_value = 0; + + switch (bat_reg_flag) { + /* Update power_info->temperature value */ + case BAT_REG_TEMP_FLAG: + lemote3a_power_info_power_status_update(); + bat_info_value = (ec_read(INDEX_BATTERY_TEMP_HIGH) << 8) | ec_read(INDEX_BATTERY_TEMP_LOW); + power_info->temperature = (power_info->bat_in) ? (bat_info_value / 10 - 273) : 0; + break; + /* Update power_info->voltage value */ + case BAT_REG_VOLTAGE_FLAG: + lemote3a_power_info_power_status_update(); + bat_info_value = (ec_read(INDEX_BATTERY_VOL_HIGH) << 8) | ec_read(INDEX_BATTERY_VOL_LOW); + power_info->voltage_now = (power_info->bat_in) ? bat_info_value : 0; + break; + /* Update power_info->current_now value */ + case BAT_REG_CURRENT_FLAG: + lemote3a_power_info_power_status_update(); + bat_info_value = (ec_read(INDEX_BATTERY_CURRENT_HIGH) << 8) | ec_read(INDEX_BATTERY_CURRENT_LOW); + power_info->current_now = (power_info->bat_in) ? bat_info_value : 0; + break; + /* Update power_info->current_avg value */ + case BAT_REG_AC_FLAG: + lemote3a_power_info_power_status_update(); + bat_info_value = (ec_read(INDEX_BATTERY_AC_HIGH) << 8) | ec_read(INDEX_BATTERY_AC_LOW); + power_info->current_average = (power_info->bat_in) ? bat_info_value : 0; + break; + /* Update power_info->remain_capacity value */ + case BAT_REG_RC_FLAG: + power_info->remain_capacity = (ec_read(INDEX_BATTERY_RC_HIGH) << 8) | ec_read(INDEX_BATTERY_RC_LOW); + break; + /* Update power_info->full_charged_capacity value */ + case BAT_REG_FCC_FLAG: + power_info->full_charged_capacity = (ec_read(INDEX_BATTERY_FCC_HIGH) << 8) | ec_read(INDEX_BATTERY_FCC_LOW); + break; + /* Update power_info->remain_time value */ + case BAT_REG_ATTE_FLAG: + power_info->remain_time = (ec_read(INDEX_BATTERY_ATTE_HIGH) << 8) | ec_read(INDEX_BATTERY_ATTE_LOW); + break; + /* Update power_info->fullchg_time value */ + case BAT_REG_ATTF_FLAG: + power_info->fullchg_time = (ec_read(INDEX_BATTERY_ATTF_HIGH) << 8) | ec_read(INDEX_BATTERY_ATTF_LOW); + break; + /* Update power_info->curr_cap value */ + case BAT_REG_RSOC_FLAG: + power_info->remain_capacity_percent = ec_read(INDEX_BATTERY_CAPACITY); + break; + /* Update power_info->cycle_count value */ + case BAT_REG_CYCLCNT_FLAG: + power_info->cycle_count = (ec_read(INDEX_BATTERY_CYCLECNT_HIGH) << 8) | ec_read(INDEX_BATTERY_CYCLECNT_LOW); + break; + + default: + break; + } +} + +/* Clear battery static information. */ +static void lemote3a_power_info_battery_static_clear(void) +{ + strcpy(power_info->manufacturer_name, "Unknown"); + strcpy(power_info->device_name, "Unknown"); + power_info->technology = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; + strcpy(power_info->serial_number, "Unknown"); + strcpy(power_info->manufacture_date, "Unknown"); + power_info->cell_count = 0; + power_info->design_capacity = 0; + power_info->design_voltage = 0; +} + +/* Get battery static information. */ +static void lemote3a_power_info_battery_static_update(void) +{ + unsigned int manufacture_date, bat_serial_number; + char device_chemistry[5]; + + manufacture_date = (ec_read(INDEX_BATTERY_MFD_HIGH) << 8) | ec_read(INDEX_BATTERY_MFD_LOW); + sprintf(power_info->manufacture_date, "%d-%d-%d", (manufacture_date >> 9) + 1980, + (manufacture_date & 0x01E0) >> 5, manufacture_date & 0x001F); + lemote3a_bat_get_string(INDEX_BATTERY_MFN_LENG, power_info->manufacturer_name); + lemote3a_bat_get_string(INDEX_BATTERY_DEVNAME_LENG, power_info->device_name); + lemote3a_bat_get_string(INDEX_BATTERY_DEVCHEM_LENG, device_chemistry); + if ((device_chemistry[2] == 'o') || (device_chemistry[2] == 'O')) { + power_info->technology = POWER_SUPPLY_TECHNOLOGY_LION; + } + else if (((device_chemistry[1] = 'h') && (device_chemistry[2] == 'm')) || + ((device_chemistry[1] = 'H') && (device_chemistry[2] == 'M'))) { + power_info->technology = POWER_SUPPLY_TECHNOLOGY_NiMH; + } + else if ((device_chemistry[2] == 'p') || (device_chemistry[2] == 'P')) { + power_info->technology = POWER_SUPPLY_TECHNOLOGY_LIPO; + } + else if ((device_chemistry[2] == 'f') || (device_chemistry[2] == 'F')) { + power_info->technology = POWER_SUPPLY_TECHNOLOGY_LiFe; + } + else if ((device_chemistry[2] == 'c') || (device_chemistry[2] == 'C')) { + power_info->technology = POWER_SUPPLY_TECHNOLOGY_NiCd; + } + else if (((device_chemistry[1] = 'n') && (device_chemistry[2] == 'm')) || + ((device_chemistry[1] = 'N') && (device_chemistry[2] == 'M'))) { + power_info->technology = POWER_SUPPLY_TECHNOLOGY_LiMn; + } + else { + power_info->technology = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; + } + + bat_serial_number = (ec_read(INDEX_BATTERY_SN_HIGH) << 8) | ec_read(INDEX_BATTERY_SN_LOW); + snprintf(power_info->serial_number, 8, "%x", bat_serial_number); + + power_info->cell_count = ((ec_read(INDEX_BATTERY_CV_HIGH) << 8) | ec_read(INDEX_BATTERY_CV_LOW)) / 4200; + + power_info->design_capacity = (ec_read(INDEX_BATTERY_DC_HIGH) << 8) | ec_read(INDEX_BATTERY_DC_LOW); + power_info->design_voltage = (ec_read(INDEX_BATTERY_DV_HIGH) << 8) | ec_read(INDEX_BATTERY_DV_LOW); + power_info->full_charged_capacity = (ec_read(INDEX_BATTERY_FCC_HIGH) << 8) | ec_read(INDEX_BATTERY_FCC_LOW); + printk(KERN_INFO "LS3ANB Battery Information:\nManufacturerName: %s, DeviceName: %s, DeviceChemistry: %s\n", + power_info->manufacturer_name, power_info->device_name, device_chemistry); + printk(KERN_INFO "SerialNumber: %s, ManufactureDate: %s, CellNumber: %d\n", + power_info->serial_number, power_info->manufacture_date, power_info->cell_count); + printk(KERN_INFO "DesignCapacity: %dmAh, DesignVoltage: %dmV, FullChargeCapacity: %dmAh\n", + power_info->design_capacity, power_info->design_voltage, power_info->full_charged_capacity); +} + +/* Update power_status value */ +static void lemote3a_power_info_power_status_update(void) +{ + unsigned int power_status = 0; + + power_status = ec_read(INDEX_POWER_STATUS); + + power_info->ac_in = (power_status & MASK(BIT_POWER_ACPRES)) ? + APM_AC_ONLINE : APM_AC_OFFLINE; + + power_info->bat_in = (power_status & MASK(BIT_POWER_BATPRES)) ? 1 : 0; + if( power_info->bat_in && ((ec_read(INDEX_BATTERY_DC_LOW) | (ec_read(INDEX_BATTERY_DC_HIGH) << 8)) == 0) ) + power_info->bat_in = 0; + + power_info->health = (power_info->bat_in) ? POWER_SUPPLY_HEALTH_GOOD : + POWER_SUPPLY_HEALTH_UNKNOWN; + if (power_status & (MASK(BIT_POWER_BATL) | MASK(BIT_POWER_BATVL))) { + power_info->health = POWER_SUPPLY_HEALTH_DEAD; + } + + if (!power_info->bat_in) { + power_info->charge_status = POWER_SUPPLY_STATUS_UNKNOWN; + } + else { + if (power_status & MASK(BIT_POWER_BATFCHG)) { + power_info->charge_status = POWER_SUPPLY_STATUS_FULL; + } + else if (power_status & MASK(BIT_POWER_BATCHG)) { + power_info->charge_status = POWER_SUPPLY_STATUS_CHARGING; + } + else if (power_status & MASK(BIT_POWER_TERMINATE)) { + power_info->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + } + else { + power_info->charge_status = POWER_SUPPLY_STATUS_DISCHARGING; + } + } +} + +/* Get battery static information string */ +static void lemote3a_bat_get_string(unsigned char index, unsigned char *bat_string) +{ + unsigned char length, i; + + length = ec_read(index); + for (i = 0; i < length; i++) { + *bat_string++ = ec_read(++index); + } + *bat_string = '\0'; +} + +/* Power supply Battery get property handler */ +static int lemote3a_bat_get_property(struct power_supply * pws, + enum power_supply_property psp, union power_supply_propval * val) +{ + switch (psp) { + /* Get battery static information. */ + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = power_info->design_voltage * 1000; /* mV -> uV */ + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = power_info->design_capacity * 1000; /* mAh -> uAh */ + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = power_info->device_name; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = power_info->manufacturer_name; + break; + case POWER_SUPPLY_PROP_SERIAL_NUMBER: + val->strval = power_info->serial_number; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = power_info->technology; + break; + + /* Get battery dynamic information. */ + case POWER_SUPPLY_PROP_STATUS: + lemote3a_power_info_power_status_update(); + val->intval = power_info->charge_status; + break; + case POWER_SUPPLY_PROP_PRESENT: + lemote3a_power_info_power_status_update(); + val->intval = power_info->bat_in; + break; + case POWER_SUPPLY_PROP_HEALTH: + lemote3a_power_info_power_status_update(); + val->intval = power_info->health; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + lemote3a_power_battery_info_update(BAT_REG_CURRENT_FLAG); + val->intval = power_info->current_now * 1000; /* mA -> uA */ + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + lemote3a_power_battery_info_update(BAT_REG_AC_FLAG); + val->intval = power_info->current_average * 1000; /* mA -> uA */ + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + lemote3a_power_battery_info_update(BAT_REG_VOLTAGE_FLAG); + val->intval = power_info->voltage_now * 1000; /* mV -> uV */ + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + lemote3a_power_battery_info_update(BAT_REG_RC_FLAG); + val->intval = power_info->remain_capacity * 1000; /* mAh -> uAh */ + break; + case POWER_SUPPLY_PROP_CAPACITY: + lemote3a_power_battery_info_update(BAT_REG_RSOC_FLAG); + val->intval = power_info->remain_capacity_percent; /* Percentage */ + break; + case POWER_SUPPLY_PROP_TEMP: + lemote3a_power_battery_info_update(BAT_REG_TEMP_FLAG); + val->intval = power_info->temperature; /* Celcius */ + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: + lemote3a_power_battery_info_update(BAT_REG_ATTE_FLAG); + if (power_info->remain_time == 0xFFFF) { + power_info->remain_time = 0; + } + val->intval = power_info->remain_time * 60; /* seconds */ + break; + case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: + lemote3a_power_battery_info_update(BAT_REG_ATTF_FLAG); + if (power_info->fullchg_time == 0xFFFF) { + power_info->fullchg_time = 0; + } + val->intval = power_info->fullchg_time * 60; /* seconds */ + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + lemote3a_power_battery_info_update(BAT_REG_FCC_FLAG); + val->intval = power_info->full_charged_capacity * 1000;/* mAh -> uAh */ + break; + case POWER_SUPPLY_PROP_CYCLE_COUNT: + lemote3a_power_battery_info_update(BAT_REG_CYCLCNT_FLAG); + val->intval = power_info->cycle_count; + break; + default: + return -EINVAL; + } + + return 0; +} + +/* Power supply AC get property handler */ +static int lemote3a_ac_get_property(struct power_supply * pws, + enum power_supply_property psp, union power_supply_propval * val) +{ + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + lemote3a_power_info_power_status_update(); + val->intval = power_info->ac_in; + break; + default: + return -EINVAL; + } + + return 0; +} + +/* SCI device pci driver init handler */ +static int sci_pci_driver_init(void) +{ + int ret; + + ret = sci_pci_init(); + if (ret) { + printk(KERN_ERR "Lemote Laptop Platform Driver : Register pci driver error.\n"); + return ret; + } + + printk(KERN_INFO "Lemote Laptop Platform Driver : SCI event handler on WPCE775L Embedded Controller init.\n"); + + return ret; +} + +/* SCI device pci driver exit handler */ +static void sci_pci_driver_exit(void) +{ + printk(KERN_INFO "Lemote Laptop Platform Driver : SCI event handler on WPCE775L Embedded Controll exit.\n"); +} + +/* SCI device pci driver init */ +static int sci_pci_init(void) +{ + int ret = -EIO; + struct pci_dev *pdev; + + pdev = pci_get_device(PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_SBX00_SMBUS, NULL); + + /* Create the sci device */ + lemote3a_sci_device = kmalloc(sizeof(struct sci_device), GFP_KERNEL); + if (NULL == lemote3a_sci_device) { + printk(KERN_ERR "LS3ANB Drvier : Malloc memory for sci_device failed!\n"); + return -ENOMEM; + } + + /* Fill sci device */ + lemote3a_sci_device->irq = SCI_IRQ_NUM; + lemote3a_sci_device->irq_data = 0x00; + lemote3a_sci_device->number = 0x00; + lemote3a_sci_device->parameter = 0x00; + strcpy(lemote3a_sci_device->name, EC_SCI_DEV); + + /* Enable pci device and get the GPIO resources. */ + ret = pci_enable_device(pdev); + if (ret) { + printk(KERN_ERR "Lemote Laptop Platform Driver : Enable pci device failed!\n"); + ret = -ENODEV; + goto out_pdev; + } + + /* Clear sci status: GPM9Status field in bit14 of + * EVENT_STATUS register for SB710, write 1 to clear */ + clean_ec_event_status(); + + /* Alloc the interrupt for sci not pci */ + ret = request_irq(lemote3a_sci_device->irq, lemote3a_sci_int_routine, + IRQF_SHARED, lemote3a_sci_device->name, lemote3a_sci_device); + if (ret) { + printk(KERN_ERR "Lemote Laptop Platform Driver : Request irq %d failed!\n", lemote3a_sci_device->irq); + ret = -EFAULT; + goto out_irq; + } + + ret = 0; + printk(KERN_DEBUG "Lemote Laptop Platform Driver : PCI Init successful!\n"); + goto out; + +out_irq: + pci_disable_device(pdev); +out_pdev: + kfree(lemote3a_sci_device); +out: + return ret; +} + +/* SCI event routine handler */ +static irqreturn_t lemote3a_sci_int_routine(int irq, void * dev_id) +{ + int event; + + if (lemote3a_sci_device->irq != irq) { + return IRQ_NONE; + } + + event = ec_query_get_event_num(); + if ((SCI_EVENT_NUM_START > event) || (SCI_EVENT_NUM_END < event)) { + goto exit_event_action; + } + + /* Do event action */ + lemote3a_sci_event_handler(event); + + /* Clear sci status: GPM9Status field in bit14 of + * EVENT_STATUS register for SB710, write 1 to clear */ + clean_ec_event_status(); + + return IRQ_HANDLED; + +exit_event_action: + clean_ec_event_status(); + return IRQ_NONE; +} + +/* SCI device event handler */ +void lemote3a_sci_event_handler(int event) +{ + int status = 0; + struct key_entry * ke = NULL; + struct sci_event * sep = NULL; + + sep = (struct sci_event*)&(se[event]); + if (0 != sep->index) { + status = ec_read(sep->index); + } + if (NULL != sep->handler) { + status = sep->handler(status); + } + + ke = sparse_keymap_entry_from_scancode(lemote3a_hotkey_dev, event); + if (ke) { + if (SW_LID == ke->keycode) { + /* report LID event. */ + input_report_switch(lemote3a_hotkey_dev, SW_LID, status); + input_sync(lemote3a_hotkey_dev); + } + else { + sparse_keymap_report_entry(lemote3a_hotkey_dev, ke, 1, true); + } + } +} + +/* SCI device over temperature event handler */ +static int lemote3a_over_temp_handler(int status) +{ + return 0; +} + +/* SCI device Throttling the CPU event handler */ +static int lemote3a_throttling_CPU_handler(int status) +{ + return 0; +} + +/* SCI device AC event handler */ +static int lemote3a_ac_handler(int status) +{ + /* Report status changed */ + power_supply_changed(lemote3a_ac); + + return 0; +} + +/* SCI device Battery event handler */ +static int lemote3a_bat_handler(int status) +{ + /* Battery insert/pull-out to handle battery static information. */ + if (status & MASK(BIT_POWER_BATPRES)) { + /* If battery is insert, get battery static information. */ + lemote3a_power_info_battery_static_update(); + } + else { + /* Else if battery is pull-out, clear battery static information. */ + lemote3a_power_info_battery_static_clear(); + } + /* Report status changed */ + power_supply_changed(lemote3a_bat); + + return 0; +} + +/* SCI device Battery low event handler */ +static int lemote3a_bat_low_handler(int status) +{ + /* Report status changed */ + power_supply_changed(lemote3a_bat); + + return 0; +} + +/* SCI device Battery very low event handler */ +static int lemote3a_bat_very_low_handler(int status) +{ + /* Report status changed */ + power_supply_changed(lemote3a_bat); + + return 0; +} + +/* SCI device LID event handler */ +static int lemote3a_lid_handler(int status) +{ + if (status & BIT(BIT_DEVICE_LID)) { + return 1; + } + + return 0; +} + +/* Set touchpad en/dis led */ +static void lemote3a_tp_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + int val = brightness ? TP_EN_LED_ON : TP_EN_LED_OFF; + + ec_write(INDEX_TOUCHPAD_ENABLE_LED, val); +} + +static void do_healthy_led_timer(struct work_struct *work) +{ + static unsigned char times_count = 0; + + ec_write(INDEX_BOARD_HEALTHY, times_count); + schedule_delayed_work(&healthy_led_control, msecs_to_jiffies(1000)); + times_count++; +} + +static int lemote3a_healthy_led_init(void) +{ + INIT_DELAYED_WORK(&healthy_led_control, do_healthy_led_timer); + schedule_delayed_work(&healthy_led_control, msecs_to_jiffies(1000)); + + return 0; +} + +/* Hotkey device init handler */ +static int lemote3a_hotkey_init(void) +{ + int ret; + + lemote3a_hotkey_dev = input_allocate_device(); + if (!lemote3a_hotkey_dev) { + return -ENOMEM; + } + + lemote3a_hotkey_dev->name = "Lemote Laptop Hotkeys"; + lemote3a_hotkey_dev->phys = "button/input0"; + lemote3a_hotkey_dev->id.bustype = BUS_HOST; + lemote3a_hotkey_dev->dev.parent = NULL; + + ret = sparse_keymap_setup(lemote3a_hotkey_dev, lemote3a_keymap, NULL); + if (ret) { + printk(KERN_ERR "Lemote Laptop Platform Driver : Fail to setup input device keymap\n"); + input_free_device(lemote3a_hotkey_dev); + return ret; + } + + ret = input_register_device(lemote3a_hotkey_dev); + if (ret) { + input_free_device(lemote3a_hotkey_dev); + return ret; + } + return 0; +} + +/* Hotkey device exit handler */ +static void lemote3a_hotkey_exit(void) +{ + if (lemote3a_hotkey_dev) { + input_unregister_device(lemote3a_hotkey_dev); + lemote3a_hotkey_dev = NULL; + } +} + +module_init(lemote3a_laptop_init); +module_exit(lemote3a_laptop_exit); + +MODULE_ALIAS("platform:wpce775l"); +MODULE_AUTHOR("Huang Wei ; Wang Rui "); +MODULE_DESCRIPTION("Lemote Loongson-3A/2Gq Laptop Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/mips/ls7a_fan.c b/drivers/platform/mips/ls7a_fan.c new file mode 100644 index 0000000000000..e9cf2dded39e4 --- /dev/null +++ b/drivers/platform/mips/ls7a_fan.c @@ -0,0 +1,604 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/** + * Miscellaneous register base (LS7A_MISC_REG_BASE) starts at + * LOONGSON_PCILO0_BASE + 0x80000, according to legacy loongson-pch.h + * from Lemote. + */ +#define LS7A_PWM_REG_BASE (void *)TO_UNCAC(LOONGSON_PCILO0_BASE + \ + 0x80000 + 0x20000) + +#define LS7A_PWM0_LOW 0x004 +#define LS7A_PWM0_FULL 0x008 +#define LS7A_PWM0_CTRL 0x00c + +#define LS7A_PWM1_LOW 0x104 +#define LS7A_PWM1_FULL 0x108 +#define LS7A_PWM1_CTRL 0x10c + +#define LS7A_PWM2_LOW 0x204 +#define LS7A_PWM2_FULL 0x208 +#define LS7A_PWM2_CTRL 0x20c + +#define LS7A_PWM3_LOW 0x304 +#define LS7A_PWM3_FULL 0x308 +#define LS7A_PWM3_CTRL 0x30c + +#define MAX_LS7A_FANS 4 + +static struct device *pwm_hwmon_dev; +static enum fan_control_mode ls7a_fan_mode[MAX_LS7A_FANS]; +static struct loongson_fan_policy fan_policy[MAX_LS7A_FANS]; + +/* up_temp & down_temp used in fan auto adjust */ +static u8 fan_up_temp[MAX_LS7A_FANS]; +static u8 fan_down_temp[MAX_LS7A_FANS]; +static u8 fan_up_temp_level[MAX_LS7A_FANS]; +static u8 fan_down_temp_level[MAX_LS7A_FANS]; + +static void fan1_adjust(struct work_struct *work); +static void fan2_adjust(struct work_struct *work); +static void fan3_adjust(struct work_struct *work); +static void fan4_adjust(struct work_struct *work); + +#define pwm_read(addr) readl(LS7A_PWM_REG_BASE + (addr)) + +#define pwm_write(val, addr) \ + do { \ + writel(val, LS7A_PWM_REG_BASE + (addr));\ + readl(LS7A_PWM_REG_BASE + (addr)); \ + } while (0) + +static ssize_t get_hwmon_name(struct device *dev, + struct device_attribute *attr, char *buf); +static SENSOR_DEVICE_ATTR(name, S_IRUGO, get_hwmon_name, NULL, 0); + +static struct attribute *pwm_hwmon_attributes[] = +{ + &sensor_dev_attr_name.dev_attr.attr, + NULL +}; + +/* Hwmon device attribute group */ +static struct attribute_group pwm_hwmon_attribute_group = +{ + .attrs = pwm_hwmon_attributes, +}; + +/* Hwmon device get name */ +static ssize_t get_hwmon_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "ls7a-fan\n"); +} + +static ssize_t get_fan_level(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t set_fan_level(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); +static ssize_t get_fan_mode(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t set_fan_mode(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, + get_fan_level, set_fan_level, 1); +static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, + get_fan_mode, set_fan_mode, 1); + +static SENSOR_DEVICE_ATTR(pwm2, S_IWUSR | S_IRUGO, + get_fan_level, set_fan_level, 2); +static SENSOR_DEVICE_ATTR(pwm2_enable, S_IWUSR | S_IRUGO, + get_fan_mode, set_fan_mode, 2); + +static SENSOR_DEVICE_ATTR(pwm3, S_IWUSR | S_IRUGO, + get_fan_level, set_fan_level, 3); +static SENSOR_DEVICE_ATTR(pwm3_enable, S_IWUSR | S_IRUGO, + get_fan_mode, set_fan_mode, 3); + +static SENSOR_DEVICE_ATTR(pwm4, S_IWUSR | S_IRUGO, + get_fan_level, set_fan_level, 4); +static SENSOR_DEVICE_ATTR(pwm4_enable, S_IWUSR | S_IRUGO, + get_fan_mode, set_fan_mode, 4); + +static const struct attribute *ls7a_hwmon_fan[4][3] = { + { + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm1_enable.dev_attr.attr, + NULL + }, + { + &sensor_dev_attr_pwm2.dev_attr.attr, + &sensor_dev_attr_pwm2_enable.dev_attr.attr, + NULL + }, + { + &sensor_dev_attr_pwm3.dev_attr.attr, + &sensor_dev_attr_pwm3_enable.dev_attr.attr, + NULL + }, + { + &sensor_dev_attr_pwm4.dev_attr.attr, + &sensor_dev_attr_pwm4_enable.dev_attr.attr, + NULL + } +}; + +static u32 ls7a_get_fan_level(int id) +{ + unsigned long low = 0, full = 0; + + if (id == 0) { + low = pwm_read(LS7A_PWM0_LOW); + full = pwm_read(LS7A_PWM0_FULL); + } + if (id == 1) { + low = pwm_read(LS7A_PWM1_LOW); + full = pwm_read(LS7A_PWM1_FULL); + } + if (id == 2) { + low = pwm_read(LS7A_PWM2_LOW); + full = pwm_read(LS7A_PWM2_FULL); + } + if (id == 3) { + low = pwm_read(LS7A_PWM3_LOW); + full = pwm_read(LS7A_PWM3_FULL); + } + + return 255 * (full - low) / full; +} + +static void ls7a_set_fan_level(u8 level, int id) +{ + if (id == 0){ + pwm_write(255, LS7A_PWM0_FULL); + pwm_write(255 - level, LS7A_PWM0_LOW); + } + if (id == 1){ + pwm_write(255, LS7A_PWM1_FULL); + pwm_write(255 - level, LS7A_PWM1_LOW); + } + if (id == 2){ + pwm_write(255, LS7A_PWM2_FULL); + pwm_write(255 - level, LS7A_PWM2_LOW); + } + if (id == 3){ + pwm_write(255, LS7A_PWM3_FULL); + pwm_write(255 - level, LS7A_PWM3_LOW); + } + +} + +static void get_up_temp(struct loongson_fan_policy *policy, + u8 current_temp, u8* up_temp, u8* up_temp_level) +{ + int i; + + for (i = 0; i < policy->up_step_num; i++) { + if (current_temp <= policy->up_step[i].low) { + *up_temp = policy->up_step[i].low; + *up_temp_level = i; + return; + } + } + + *up_temp = MAX_TEMP; + *up_temp_level = policy->up_step_num - 1; +} + +static void get_down_temp(struct loongson_fan_policy *policy, u8 current_temp, + u8* down_temp, u8* down_temp_level) +{ + int i; + + for (i = policy->down_step_num-1; i >= 0; i--) { + if (current_temp >= policy->down_step[i].high) { + *down_temp = policy->down_step[i].high; + *down_temp_level = i; + return; + } + } + + *down_temp = MIN_TEMP; + *down_temp_level = 0; +} + + +static ssize_t get_fan_level(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int id = (to_sensor_dev_attr(attr))->index - 1; + u32 val = ls7a_get_fan_level(id); + + return sprintf(buf, "%d\n", val); +} + +static ssize_t set_fan_level(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + u8 new_speed; + int id = (to_sensor_dev_attr(attr))->index - 1; + + if(!ls7a_fan_mode[id]) + return count; + + new_speed = clamp_val(simple_strtoul(buf, NULL, 10), 0, 255); + + ls7a_set_fan_level(new_speed, id); + + return count; +} + +static ssize_t get_fan_mode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int id = (to_sensor_dev_attr(attr))->index - 1; + return sprintf(buf, "%d\n", ls7a_fan_mode[id]); +} + +static void ls7a_fan_step_mode(get_temp_fun fun, + struct loongson_fan_policy *policy, int id) +{ + u8 current_temp; + int init_temp_level, target_fan_level; + + current_temp = fun(0) / 1000; + get_up_temp(policy, current_temp, &fan_up_temp[id], + &fan_up_temp_level[id]); + get_down_temp(policy, current_temp, &fan_down_temp[id], + &fan_down_temp_level[id]); + + /* current speed is not sure, setting now */ + init_temp_level = (fan_up_temp_level[id] + fan_down_temp_level[id]) / 2; + target_fan_level = policy->up_step[init_temp_level].level * 255 / 100; + ls7a_set_fan_level(target_fan_level * policy->percent / 100, id); + + schedule_delayed_work_on(0, &policy->work, policy->adjust_period * HZ); +} + +static void ls7a_fan_start_auto(int id) +{ + u8 level; + + switch (fan_policy[id].type) { + case CONSTANT_SPEED_POLICY: + level = fan_policy[id].percent * 255 / 100; + if (level > MAX_FAN_LEVEL) + level = MAX_FAN_LEVEL; + ls7a_set_fan_level(level, id); + break; + case STEP_SPEED_POLICY: + ls7a_fan_step_mode(fan_policy[id].depend_temp, &fan_policy[id], id); + break; + default: + printk(KERN_ERR "ls7a fan not support fan policy id %d!\n", fan_policy[id].type); + ls7a_set_fan_level(MAX_FAN_LEVEL, id); + } + + return; +} + +static void ls7a_fan_stop_auto(int id) +{ + if ((fan_policy[id].type == STEP_SPEED_POLICY) && + (ls7a_fan_mode[id] == FAN_AUTO_MODE)) { + cancel_delayed_work(&fan_policy[id].work); + } +} + + +static ssize_t set_fan_mode(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int id = (to_sensor_dev_attr(attr))->index - 1; + u8 new_mode; + + new_mode = clamp_val(simple_strtoul(buf, NULL, 10), + FAN_FULL_MODE, FAN_AUTO_MODE); + if (new_mode == ls7a_fan_mode[id]) + return count; + + switch (new_mode) { + case FAN_FULL_MODE: + ls7a_fan_stop_auto(id); + ls7a_set_fan_level(MAX_FAN_LEVEL, id); + break; + case FAN_MANUAL_MODE: + ls7a_fan_stop_auto(id); + break; + case FAN_AUTO_MODE: + ls7a_fan_start_auto(id); + break; + default: + break; + } + + ls7a_fan_mode[id] = new_mode; + + return count; +} + +static void fan1_adjust(struct work_struct *work) +{ + u8 current_temp; + int target_fan_level; + + current_temp = fan_policy[0].depend_temp(0) / 1000; + + if ((current_temp <= fan_up_temp[0]) && + (current_temp >= fan_down_temp[0])) + goto exit; + + if (current_temp > fan_up_temp[0]) + target_fan_level = fan_policy[0].up_step[fan_up_temp_level[0]].level * 255 / 100; + + if (current_temp < fan_down_temp[0]) + target_fan_level = fan_policy[0].down_step[fan_down_temp_level[0]].level * 255 / 100; + + ls7a_set_fan_level(target_fan_level * fan_policy[0].percent / 100, 0); + + get_up_temp(&fan_policy[0], current_temp, &fan_up_temp[0], &fan_up_temp_level[0]); + get_down_temp(&fan_policy[0], current_temp, &fan_down_temp[0], &fan_down_temp_level[0]); + +exit: + schedule_delayed_work_on(0, &fan_policy[0].work, fan_policy[0].adjust_period * HZ); +} + +static void ls7a_fan1_init(void) +{ + int ret; + + pwm_write(1, LS7A_PWM0_CTRL); + pwm_write(255, LS7A_PWM0_FULL); /* Full = 255, so Low + Pwm = 255 */ + + INIT_DEFERRABLE_WORK(&fan_policy[0].work, fan1_adjust); + + /* force fans in auto mode first */ + ls7a_fan_mode[0] = FAN_AUTO_MODE; + ls7a_fan_start_auto(0); + + ret = sysfs_create_files(&pwm_hwmon_dev->kobj, ls7a_hwmon_fan[0]); + if (ret) + printk(KERN_ERR "fail to create sysfs files\n"); +} + +static void fan2_adjust(struct work_struct *work) +{ + u8 current_temp; + int target_fan_level; + + current_temp = fan_policy[1].depend_temp(0) / 1000; + + if ((current_temp <= fan_up_temp[1]) && + (current_temp >= fan_down_temp[1])) + goto exit; + + if (current_temp > fan_up_temp[1]) + target_fan_level = fan_policy[1].up_step[fan_up_temp_level[1]].level * 255 / 100; + + if (current_temp < fan_down_temp[1]) + target_fan_level = fan_policy[1].down_step[fan_down_temp_level[1]].level * 255 / 100; + + ls7a_set_fan_level(target_fan_level * fan_policy[1].percent / 100, 1); + + get_up_temp(&fan_policy[1], current_temp, &fan_up_temp[1], &fan_up_temp_level[1]); + get_down_temp(&fan_policy[1], current_temp, &fan_down_temp[1], &fan_down_temp_level[1]); + +exit: + schedule_delayed_work_on(0, &fan_policy[1].work, fan_policy[1].adjust_period * HZ); +} + +static void ls7a_fan2_init(void) +{ + int ret; + + pwm_write(1, LS7A_PWM1_CTRL); + pwm_write(255, LS7A_PWM1_FULL); /* Full = 255, so Low + Pwm = 255 */ + + INIT_DEFERRABLE_WORK(&fan_policy[1].work, fan2_adjust); + + /* force fans in auto mode first */ + ls7a_fan_mode[1] = FAN_AUTO_MODE; + ls7a_fan_start_auto(1); + + ret = sysfs_create_files(&pwm_hwmon_dev->kobj, ls7a_hwmon_fan[1]); + if (ret) + printk(KERN_ERR "fail to create sysfs files\n"); +} + +static void fan3_adjust(struct work_struct *work) +{ + u8 current_temp; + int target_fan_level; + + current_temp = fan_policy[2].depend_temp(0) / 1000; + + if ((current_temp <= fan_up_temp[2]) && + (current_temp >= fan_down_temp[2])) + goto exit; + + if (current_temp > fan_up_temp[2]) + target_fan_level = fan_policy[2].up_step[fan_up_temp_level[2]].level * 255 / 100; + + if (current_temp < fan_down_temp[2]) + target_fan_level = fan_policy[2].down_step[fan_down_temp_level[2]].level * 255 / 100; + + ls7a_set_fan_level(target_fan_level * fan_policy[2].percent / 100, 2); + + get_up_temp(&fan_policy[2], current_temp, &fan_up_temp[2], &fan_up_temp_level[2]); + get_down_temp(&fan_policy[2], current_temp, &fan_down_temp[2], &fan_down_temp_level[2]); + +exit: + schedule_delayed_work_on(0, &fan_policy[2].work, fan_policy[2].adjust_period * HZ); +} + +static void ls7a_fan3_init(void) +{ + int ret; + + pwm_write(1, LS7A_PWM2_CTRL); + pwm_write(255, LS7A_PWM2_FULL); /* Full = 255, so Low + Pwm = 255 */ + + INIT_DEFERRABLE_WORK(&fan_policy[2].work, fan3_adjust); + + /* force fans in auto mode first */ + ls7a_fan_mode[2] = FAN_AUTO_MODE; + ls7a_fan_start_auto(2); + + ret = sysfs_create_files(&pwm_hwmon_dev->kobj, ls7a_hwmon_fan[2]); + if (ret) + printk(KERN_ERR "fail to create sysfs files\n"); +} + +static void fan4_adjust(struct work_struct *work) +{ + u8 current_temp; + int target_fan_level; + + current_temp = fan_policy[3].depend_temp(0) / 1000; + + if ((current_temp <= fan_up_temp[3]) && + (current_temp >= fan_down_temp[3])) + goto exit; + + if (current_temp > fan_up_temp[3]) + target_fan_level = fan_policy[3].up_step[fan_up_temp_level[3]].level * 255 / 100; + + if (current_temp < fan_down_temp[3]) + target_fan_level = fan_policy[3].down_step[fan_down_temp_level[3]].level * 255 / 100; + + ls7a_set_fan_level(target_fan_level * fan_policy[3].percent / 100, 3); + + get_up_temp(&fan_policy[3], current_temp, &fan_up_temp[3], &fan_up_temp_level[3]); + get_down_temp(&fan_policy[3], current_temp, &fan_down_temp[3], &fan_down_temp_level[3]); + +exit: + schedule_delayed_work_on(0, &fan_policy[3].work, fan_policy[3].adjust_period * HZ); +} + +static void ls7a_fan4_init(void) +{ + int ret; + + pwm_write(1, LS7A_PWM3_CTRL); + pwm_write(255, LS7A_PWM3_FULL); /* Full = 255, so Low + Pwm = 255 */ + + INIT_DEFERRABLE_WORK(&fan_policy[3].work, fan4_adjust); + + /* force fans in auto mode first */ + ls7a_fan_mode[3] = FAN_AUTO_MODE; + ls7a_fan_start_auto(3); + + ret = sysfs_create_files(&pwm_hwmon_dev->kobj, ls7a_hwmon_fan[3]); + if (ret) + printk(KERN_ERR "fail to create sysfs files\n"); +} + +static int ls7a_fan_probe(struct platform_device *dev) +{ + int id = dev->id - 1; + struct sensor_device *sdev = (struct sensor_device *)dev->dev.platform_data; + + /* get fan policy */ + switch (sdev->fan_policy) { + case STEP_SPEED_POLICY: + memcpy(&fan_policy[id], &step_speed_policy, sizeof(struct loongson_fan_policy)); + fan_policy[id].percent = sdev->fan_percent; + if (fan_policy[id].percent == 0 || fan_policy[id].percent > 100) + fan_policy[id].percent = 100; + break; + case CONSTANT_SPEED_POLICY: + default: + memcpy(&fan_policy[id], &constant_speed_policy, sizeof(struct loongson_fan_policy)); + fan_policy[id].percent = sdev->fan_percent; + if (fan_policy[id].percent == 0 || fan_policy[id].percent > 100) + fan_policy[id].percent = 100; + break; + } + + if (id == 0) + ls7a_fan1_init(); + if (id == 1) + ls7a_fan2_init(); + if (id == 2) + ls7a_fan3_init(); + if (id == 3) + ls7a_fan4_init(); + + return 0; +} + + +static struct platform_driver pwm_fan_driver = { + .probe = ls7a_fan_probe, + .driver = { + .name = "ls7a-fan", + .owner = THIS_MODULE, + }, +}; + + +static int ls7a_fan_init(void) +{ + int ret; + + pwm_hwmon_dev = hwmon_device_register(NULL); + if (IS_ERR(pwm_hwmon_dev)) { + ret = -ENOMEM; + printk(KERN_ERR "hwmon_device_register fail!\n"); + goto fail_hwmon_device_register; + } + + ret = sysfs_create_group(&pwm_hwmon_dev->kobj, + &pwm_hwmon_attribute_group); + if (ret) { + printk(KERN_ERR "fail to create loongson hwmon!\n"); + goto fail_sysfs_create_group_hwmon; + } + + ret = platform_driver_register(&pwm_fan_driver); + if (ret) { + printk(KERN_ERR "fail to register fan driver!\n"); + goto fail_register_fan; + } + + return 0; + +fail_register_fan: + sysfs_remove_group(&pwm_hwmon_dev->kobj, + &pwm_hwmon_attribute_group); + +fail_sysfs_create_group_hwmon: + hwmon_device_unregister(pwm_hwmon_dev); + +fail_hwmon_device_register: + return ret; +} + +static void ls7a_fan_exit(void) +{ + platform_driver_unregister(&pwm_fan_driver); + sysfs_remove_group(&pwm_hwmon_dev->kobj, + &pwm_hwmon_attribute_group); + hwmon_device_unregister(pwm_hwmon_dev); +} + +late_initcall(ls7a_fan_init); +module_exit(ls7a_fan_exit); + +MODULE_AUTHOR("Sun Ce "); +MODULE_AUTHOR("Huacai Chen "); +MODULE_DESCRIPTION("LS7A fan control driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/mips/pmon_flash.c b/drivers/platform/mips/pmon_flash.c new file mode 100644 index 0000000000000..6c5a3c4342b29 --- /dev/null +++ b/drivers/platform/mips/pmon_flash.c @@ -0,0 +1,100 @@ +/* + * Copyright www.lemote.com + * + * 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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FLASH_PHYS_ADDR 0x1fc00000 +#define FLASH_SIZE 0x100000 + +#define FLASH_PARTITION0_ADDR 0x00000000 +#define FLASH_PARTITION0_SIZE 0x00100000 + +struct map_info flash_map = { + .name = "flash device", + .size = FLASH_SIZE, + .bankwidth = 1, +}; + +struct mtd_partition flash_parts[] = { + { + .name = "Bootloader", + .offset = FLASH_PARTITION0_ADDR, + .size = FLASH_PARTITION0_SIZE + }, +}; + +#define PARTITION_COUNT ARRAY_SIZE(flash_parts) + +static struct mtd_info *mymtd; + +static int __init init_flash(void) +{ + printk(KERN_NOTICE "Flash flash device: %x at %x\n", + FLASH_SIZE, FLASH_PHYS_ADDR); + + flash_map.phys = FLASH_PHYS_ADDR; + flash_map.virt = ioremap(FLASH_PHYS_ADDR, + FLASH_SIZE); + + if (!flash_map.virt) { + printk("Failed to ioremap\n"); + return -EIO; + } + + simple_map_init(&flash_map); + + mymtd = do_map_probe("jedec_probe", &flash_map); + if (mymtd) { + mtd_device_register(mymtd, flash_parts, PARTITION_COUNT); + printk(KERN_NOTICE "pmon flash device initialized\n"); + return 0; + } + + iounmap((void *)flash_map.virt); + return -ENXIO; +} + +static void __exit cleanup_flash(void) +{ + if (mymtd) { + mtd_device_unregister(mymtd); + map_destroy(mymtd); + } + if (flash_map.virt) { + iounmap((void *)flash_map.virt); + flash_map.virt = 0; + } +} + +module_init(init_flash); +module_exit(cleanup_flash); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Yanhua"); +MODULE_DESCRIPTION("MTD map driver for pmon programming module"); diff --git a/drivers/platform/mips/rs780e-acpi.c b/drivers/platform/mips/rs780e-acpi.c index bb0e8ae0eefd8..7d3d23459a071 100644 --- a/drivers/platform/mips/rs780e-acpi.c +++ b/drivers/platform/mips/rs780e-acpi.c @@ -1,10 +1,13 @@ // SPDX-License-Identifier: GPL-2.0 #include #include +#include +#include #include #include #include #include +#include static unsigned long acpi_iobase; @@ -20,6 +23,9 @@ static unsigned long acpi_iobase; #define PM2_INDEX 0xCD0 #define PM2_DATA 0xCD1 +static int acpi_irq; +static struct input_dev *button; + static void pmio_write_index(u16 index, u8 reg, u8 value) { outb(reg, index); @@ -69,6 +75,68 @@ static void acpi_hw_clear_status(void) outl(inl(ACPI_GPE0_BLK), ACPI_GPE0_BLK); } +static irqreturn_t acpi_int_routine(int irq, void *dev_id) +{ + u16 value; + + /* PMStatus: Check PwrBtnStatus */ + value = inw(ACPI_PM_EVT_BLK); + if (value & (1 << 8)) { + outw(1 << 8, ACPI_PM_EVT_BLK); + pr_info("Power Button pressed...\n"); + input_report_key(button, KEY_POWER, 1); + input_sync(button); + input_report_key(button, KEY_POWER, 0); + input_sync(button); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static int __init power_button_init(void) +{ + int ret; + struct pci_dev *dev; + + dev = pci_get_domain_bus_and_slot(0, 0, 0); + switch (dev->vendor) { + case PCI_VENDOR_ID_AMD: + case PCI_VENDOR_ID_ATI: + acpi_irq = 7; + break; + default: + return -ENODEV; + } + + button = input_allocate_device(); + if (!button) + return -ENOMEM; + + button->name = "ACPI Power Button"; + button->phys = "acpi/button/input0"; + button->id.bustype = BUS_HOST; + button->dev.parent = NULL; + input_set_capability(button, EV_KEY, KEY_POWER); + + ret = request_irq(acpi_irq, acpi_int_routine, IRQF_SHARED, "acpi", acpi_int_routine); + if (ret) { + pr_err("ACPI Power Button Driver: Request irq %d failed!\n", acpi_irq); + return -EFAULT; + } + + ret = input_register_device(button); + if (ret) { + input_free_device(button); + return ret; + } + + pr_info("ACPI Power Button Driver: Init successful!\n"); + + return 0; +} +device_initcall(power_button_init); + static void acpi_registers_setup(void) { u32 value; @@ -130,6 +198,11 @@ static void acpi_registers_setup(void) value = pm2_ioread(0xf8); value |= ((1 << 5) | (1 << 1)); pm2_iowrite(0xf8, value); + + /* PMEnable: Enable PwrBtn */ + value = inw(ACPI_PM_EVT_BLK + 2); + value |= 1 << 8; + outw(value, ACPI_PM_EVT_BLK + 2); } static int rs780e_acpi_probe(struct platform_device *pdev) diff --git a/drivers/platform/mips/sbx00_fan.c b/drivers/platform/mips/sbx00_fan.c new file mode 100644 index 0000000000000..51c2017ebe1d6 --- /dev/null +++ b/drivers/platform/mips/sbx00_fan.c @@ -0,0 +1,801 @@ +/* + * SB700/SB710/SB800 series South Bridge provide 4 Fan controllers, + * Loongson products almost use 1/2 of them. + * + * Fan1 & Fan2 usage: + * 1. configure gpio3 as gpio48 to Fanout mode + * 2. configure fan controll mode(depmod on HW design) + * 3. set pwm freqency + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* PM regs */ +#define PM_OPTION_0 0x60 +#define FAN0_EN (1 << 6) +#define FAN1_EN (1 << 2) +#define FAN2_EN (1 << 3) + +/* PM2 regs*/ +#define FAN_CNTR_REG 0 + +#define FAN_MODE0 0 +#define FAN_MODE1 1 +#define FAN_MODE2 2 +#define FAN_MODE3 3 +#define FAN_MODE_MASK 3 +#define FAN0_CNTR_SHIFT 0 +#define FAN1_CNTR_SHIFT 2 +#define FAN2_CNTR_SHIFT 4 +#define FAN3_CNTR_SHIFT 6 + +#define FAN_MISC0_REG 0x01 +#define FAN_MISC1_REG 0x0e +#define FAN_MISC2_REG 0x1b +#define AUTPMODE0 0 +#define LINEARMODE0 1 +#define FAN0POLARITY 2 +#define AUTPMODE1 0 +#define LINEARMODE1 1 +#define FAN1POLARITY 2 + +#define FREQDIV0_REG 0x02 +#define FREQDIV1_REG 0x0f +#define FREQDIV2_REG 0x1c +#define PWM_28KHZ 0 /* 28.64KHz */ +#define PWM_25KHZ 1 /* 25.78KHz */ +#define PWM_23KHZ 2 /* 23.44KHz */ +#define PWM_21KHZ 3 /* 21.48KHz */ +#define PWM_19KHZ 4 /* 19.83KHz */ +#define PWM_18KHZ 5 /* 18.41KHz */ + +#define LOWDUTY0 0x03 +#define LOWDUTY1 0x10 +#define LOWDUTY2 0x1d + +#define FAN0DETCONTROL 0x31 +#define FAN0SPEEDLIMLO 0x32 +#define FAN0SPEEDLIMHI 0x33 +#define FAN0SPEEDLO 0x34 +#define FAN0SPEEDHI 0x35 + +#define FAN1DETCONTROL 0x36 +#define FAN1SPEEDLIMLO 0x37 +#define FAN1SPEEDLIMHI 0x38 +#define FAN1SPEEDLO 0x39 +#define FAN1SPEEDHI 0x3A + +#define FAN2DETCONTROL 0x3B +#define FAN2SPEEDLIMLO 0x3C +#define FAN2SPEEDLIMHI 0x3D +#define FAN2SPEEDLO 0x3E +#define FAN2SPEEDHI 0x3F + +#define FANSTATUS 1 /* bit 0 */ +#define FANTOOSLOW 1 + +#define FANDET_EN 1 /* bit 0 */ +#define USEAVERAGE 2 /* bit 1 */ +#define ShutDown_EN 0x10 /* bit 4 */ + +#define MAX_SBX00_FANS 3 +int sbx00_fan_enable[MAX_SBX00_FANS]; +static enum fan_control_mode sbx00_fan_mode[MAX_SBX00_FANS]; +static struct loongson_fan_policy fan_policy[MAX_SBX00_FANS]; + +/* threshold = SpeedOfPWM(0)/SpeedOfPWM(255) */ +static int speed_percent_threshold[MAX_SBX00_FANS]; + +/* from rs780-acpi */ +#define PM_INDEX 0xCD6 +#define PM_DATA 0xCD7 +#define PM2_INDEX 0xCD0 +#define PM2_DATA 0xCD1 + +static void pmio_write_index(u16 index, u8 reg, u8 value) +{ + outb(reg, index); + outb(value, index + 1); +} + +static u8 pmio_read_index(u16 index, u8 reg) +{ + outb(reg, index); + return inb(index + 1); +} + +static void pm_iowrite(u8 reg, u8 value) +{ + pmio_write_index(PM_INDEX, reg, value); +} + +static u8 pm_ioread(u8 reg) +{ + return pmio_read_index(PM_INDEX, reg); +} + +static void pm2_iowrite(u8 reg, u8 value) +{ + pmio_write_index(PM2_INDEX, reg, value); +} + +static u8 pm2_ioread(u8 reg) +{ + return pmio_read_index(PM2_INDEX, reg); +} + + +/* up_temp & down_temp used in fan auto adjust */ +static u8 fan_up_temp[MAX_SBX00_FANS]; +static u8 fan_down_temp[MAX_SBX00_FANS]; +static u8 fan_up_temp_level[MAX_SBX00_FANS]; +static u8 fan_down_temp_level[MAX_SBX00_FANS]; + +static void fan1_adjust(struct work_struct *work); +static void fan2_adjust(struct work_struct *work); +static void fan3_adjust(struct work_struct *work); + +static struct device *sbx00_hwmon_dev; + +static ssize_t get_hwmon_name(struct device *dev, + struct device_attribute *attr, char *buf); +static SENSOR_DEVICE_ATTR(name, S_IRUGO, get_hwmon_name, NULL, 0); + +static struct attribute *sbx00_hwmon_attributes[] = +{ + &sensor_dev_attr_name.dev_attr.attr, + NULL +}; + +/* Hwmon device attribute group */ +static struct attribute_group sbx00_hwmon_attribute_group = +{ + .attrs = sbx00_hwmon_attributes, +}; + +/* Hwmon device get name */ +static ssize_t get_hwmon_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "sbx00-fan\n"); +} + +/* low-level fucntions */ +static void sbx00_set_fan_level(u8 level, int id) +{ + if (id == 0) + pm2_iowrite(LOWDUTY0, level); + if (id == 1) + pm2_iowrite(LOWDUTY1, level); + if (id == 2) + pm2_iowrite(LOWDUTY2, level); +} + +static void get_up_temp(struct loongson_fan_policy *policy, + u8 current_temp, u8* up_temp, u8* up_temp_level) +{ + int i; + + for (i = 0; i < policy->up_step_num; i++) { + if (current_temp <= policy->up_step[i].low) { + *up_temp = policy->up_step[i].low; + *up_temp_level = i; + return; + } + } + + *up_temp = MAX_TEMP; + *up_temp_level = policy->up_step_num - 1; +} + +static void get_down_temp(struct loongson_fan_policy *policy, u8 current_temp, + u8* down_temp, u8* down_temp_level) +{ + int i; + + for (i = policy->down_step_num-1; i >= 0; i--) { + if (current_temp >= policy->down_step[i].high) { + *down_temp = policy->down_step[i].high; + *down_temp_level = i; + return; + } + } + + *down_temp = MIN_TEMP; + *down_temp_level = 0; +} + +static ssize_t get_fan_level(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t set_fan_level(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); +static ssize_t get_fan_mode(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t set_fan_mode(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); +static ssize_t get_fan_speed(struct device *dev, + struct device_attribute *attr, char *buf); + +static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, + get_fan_level, set_fan_level, 1); +static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, + get_fan_mode, set_fan_mode, 1); +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, get_fan_speed, NULL, 1); + +static SENSOR_DEVICE_ATTR(pwm2, S_IWUSR | S_IRUGO, + get_fan_level, set_fan_level, 2); +static SENSOR_DEVICE_ATTR(pwm2_enable, S_IWUSR | S_IRUGO, + get_fan_mode, set_fan_mode, 2); +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, get_fan_speed, NULL, 2); + +static SENSOR_DEVICE_ATTR(pwm3, S_IWUSR | S_IRUGO, + get_fan_level, set_fan_level, 3); +static SENSOR_DEVICE_ATTR(pwm3_enable, S_IWUSR | S_IRUGO, + get_fan_mode, set_fan_mode, 3); +static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, get_fan_speed, NULL, 3); + +static const struct attribute *sbx00_hwmon_fan[3][4] = { + { + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm1_enable.dev_attr.attr, + &sensor_dev_attr_fan1_input.dev_attr.attr, + NULL + }, + { + &sensor_dev_attr_pwm2.dev_attr.attr, + &sensor_dev_attr_pwm2_enable.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + NULL + }, + { + &sensor_dev_attr_pwm3.dev_attr.attr, + &sensor_dev_attr_pwm3_enable.dev_attr.attr, + &sensor_dev_attr_fan3_input.dev_attr.attr, + NULL + } +}; + +static ssize_t get_fan_level(struct device *dev, + struct device_attribute *attr, char *buf) +{ + u8 val = 0; + int id = (to_sensor_dev_attr(attr))->index - 1; + + if (id == 0) + val = pm2_ioread(LOWDUTY0); + if (id == 1) + val = pm2_ioread(LOWDUTY1); + if (id == 2) + val = pm2_ioread(LOWDUTY2); + + return sprintf(buf, "%d\n", val); +} + +static ssize_t set_fan_level(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + u8 new_level; + int id = (to_sensor_dev_attr(attr))->index - 1; + + new_level = clamp_val(simple_strtoul(buf, NULL, 10), 0, 255); + if (sbx00_fan_mode[id] == FAN_MANUAL_MODE) { + if (id == 0) + pm2_iowrite(LOWDUTY0, new_level); + if (id == 1) + pm2_iowrite(LOWDUTY1, new_level); + if (id == 2) + pm2_iowrite(LOWDUTY2, new_level); + } + + return count; +} + +static ssize_t get_fan_mode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int id = (to_sensor_dev_attr(attr))->index - 1; + return sprintf(buf, "%d\n", sbx00_fan_mode[id]); +} + +static void sbx00_fan_step_mode(get_temp_fun fun, + struct loongson_fan_policy *policy, int id) +{ + u8 current_temp; + int init_temp_level, target_fan_level, bias; + + current_temp = fun(0) / 1000; + get_up_temp(policy, current_temp, &fan_up_temp[id], + &fan_up_temp_level[id]); + get_down_temp(policy, current_temp, &fan_down_temp[id], + &fan_down_temp_level[id]); + + /* current speed is not sure, setting now */ + init_temp_level = (fan_up_temp_level[id] + fan_down_temp_level[id]) / 2; + target_fan_level = policy->up_step[init_temp_level].level * 255 / 100; + bias = (speed_percent_threshold[id] * 255) / 100; /* bias of pwm(0) */ + bias = bias * (255 - target_fan_level) / 255; /* bias of pwm(target) */ + target_fan_level = (target_fan_level > bias) ? target_fan_level - bias : 0; + target_fan_level = target_fan_level * fan_policy[id].percent / 100; + sbx00_set_fan_level(target_fan_level, id); + + schedule_delayed_work_on(0, &policy->work, policy->adjust_period * HZ); +} + +static void sbx00_fan_start_auto(int id) +{ + u8 level; + + switch (fan_policy[id].type) { + case CONSTANT_SPEED_POLICY: + level = fan_policy[id].percent * 255 / 100; + if (level > MAX_FAN_LEVEL) + level = MAX_FAN_LEVEL; + sbx00_set_fan_level(level, id); + break; + case STEP_SPEED_POLICY: + sbx00_fan_step_mode(fan_policy[id].depend_temp, &fan_policy[id], id); + break; + default: + printk(KERN_ERR "sbx00 fan not support fan policy id %d!\n", fan_policy[id].type); + sbx00_set_fan_level(MAX_FAN_LEVEL, id); + } + + return; +} + +static void sbx00_fan_stop_auto(int id) +{ + if ((fan_policy[id].type == STEP_SPEED_POLICY) && + (sbx00_fan_mode[id] == FAN_AUTO_MODE)) { + cancel_delayed_work(&fan_policy[id].work); + } +} + +static ssize_t set_fan_mode(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + u8 new_mode; + int id = (to_sensor_dev_attr(attr))->index - 1; + + new_mode = clamp_val(simple_strtoul(buf, NULL, 10), + FAN_FULL_MODE, FAN_AUTO_MODE); + if (new_mode == sbx00_fan_mode[id]) + return count; + + switch (new_mode) { + case FAN_FULL_MODE: + sbx00_fan_stop_auto(id); + sbx00_set_fan_level(MAX_FAN_LEVEL, id); + break; + case FAN_MANUAL_MODE: + sbx00_fan_stop_auto(id); + break; + case FAN_AUTO_MODE: + sbx00_fan_start_auto(id); + break; + default: + break; + } + + sbx00_fan_mode[id] = new_mode; + + return count; +} + +static u32 sbx00_get_fan_speed(int id, bool warn) +{ + int speed_lo = 0, speed_hi = 0, count = 1; + + if (id == 0) { + speed_lo = pm2_ioread(FAN0SPEEDLO); + speed_hi = pm2_ioread(FAN0SPEEDHI); + } + if (id == 1) { + speed_lo = pm2_ioread(FAN1SPEEDLO); + speed_hi = pm2_ioread(FAN1SPEEDHI); + } + if (id == 2) { + speed_lo = pm2_ioread(FAN2SPEEDLO); + speed_hi = pm2_ioread(FAN2SPEEDHI); + } + + if (speed_lo & FANTOOSLOW) { + if (warn) + pr_warn("FanSpeed too slow!\n"); + return 0; + } + count = speed_hi << 8 | (speed_lo & ~FANSTATUS); + return 360000 / count; +} + +static ssize_t get_fan_speed(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int id = (to_sensor_dev_attr(attr))->index - 1; + u32 val = sbx00_get_fan_speed(id, true); + + return sprintf(buf, "%d\n", val); +} + +static void sbx00_fan1_init(struct work_struct *work) +{ + u8 reg; + int ret; + int speed_low, speed_high; + + /* Configure gpio3 as FANOUT0 */ + reg = pm_ioread(PM_OPTION_0); + pm_iowrite(PM_OPTION_0, reg | FAN0_EN); + + /* setting FANOUT0 mode: + * without temperuature input, controlled by software + */ + reg = pm2_ioread(FAN_CNTR_REG); + reg &= ~(FAN_MODE_MASK << FAN0_CNTR_SHIFT); + reg |= FAN_MODE1 << FAN0_CNTR_SHIFT; + pm2_iowrite(FAN_CNTR_REG, reg); + + reg = pm2_ioread(FAN_MISC0_REG); + reg &= ~(1 << AUTPMODE0); /* Disable auto mode*/ + reg &= ~(1 << LINEARMODE0); /* Use step mode*/ + reg |= (1 << FAN0POLARITY); /* Active High */ + pm2_iowrite(FAN_MISC0_REG, reg); + + /* Set PWM freqency*/ + pm2_iowrite(FREQDIV0_REG, PWM_19KHZ); + + /* Enable Speed Detect */ + pm2_iowrite(FAN0DETCONTROL, FANDET_EN | USEAVERAGE); + pm2_iowrite(FAN0SPEEDLIMLO, 0xFF); /* Set limits register */ + pm2_iowrite(FAN0SPEEDLIMHI, 0xFF); + + sbx00_set_fan_level(255, 0); + msleep(250); + speed_high = sbx00_get_fan_speed(0, false); + sbx00_set_fan_level(0, 0); + msleep(5000); + speed_low = sbx00_get_fan_speed(0, false); + sbx00_set_fan_level(255, 0); + if (speed_high >= speed_low) + speed_percent_threshold[0] = speed_low * 100 / speed_high; + pr_info("SpeedPercentThreshold of FAN1 is %d%%\n", speed_percent_threshold[0]); + + /* force fans in auto mode first */ + work->func = fan1_adjust; + sbx00_fan_mode[0] = FAN_AUTO_MODE; + sbx00_fan_start_auto(0); + + ret = sysfs_create_files(&sbx00_hwmon_dev->kobj, sbx00_hwmon_fan[0]); + if (ret) + printk(KERN_ERR "fail to create sysfs files\n"); +} + +static void fan1_adjust(struct work_struct *work) +{ + u8 current_temp; + int target_fan_level, bias; + + current_temp = fan_policy[0].depend_temp(0) / 1000; + + if ((current_temp <= fan_up_temp[0]) && + (current_temp >= fan_down_temp[0])) + goto exit; + + if (current_temp > fan_up_temp[0]) + target_fan_level = fan_policy[0].up_step[fan_up_temp_level[0]].level * 255 / 100; + + if (current_temp < fan_down_temp[0]) + target_fan_level = fan_policy[0].down_step[fan_down_temp_level[0]].level * 255 / 100; + + bias = (speed_percent_threshold[0] * 255) / 100; + bias = bias * (255 - target_fan_level) / 255; + target_fan_level = (target_fan_level > bias) ? target_fan_level - bias : 0; + target_fan_level = target_fan_level * fan_policy[0].percent / 100; + sbx00_set_fan_level(target_fan_level, 0); + + get_up_temp(&fan_policy[0], current_temp, &fan_up_temp[0], &fan_up_temp_level[0]); + get_down_temp(&fan_policy[0], current_temp, &fan_down_temp[0], &fan_down_temp_level[0]); + +exit: + schedule_delayed_work_on(0, &fan_policy[0].work, fan_policy[0].adjust_period * HZ); +} + +static void sbx00_fan2_init(struct work_struct *work) +{ + u8 reg; + int ret; + int speed_low, speed_high; + + /* Configure gpio48 as FANOUT1 */ + reg = pm_ioread(PM_OPTION_0); + pm_iowrite(PM_OPTION_0, reg | FAN1_EN); + + /* setting FANOUT1 mode: + * without temperuature input, controlled by software + */ + reg = pm2_ioread(FAN_CNTR_REG); + reg &= ~(FAN_MODE_MASK << FAN1_CNTR_SHIFT); + reg |= FAN_MODE1 << FAN1_CNTR_SHIFT; + pm2_iowrite(FAN_CNTR_REG, reg); + + reg = pm2_ioread(FAN_MISC1_REG); + reg &= ~(1 << AUTPMODE1); /* Disable auto mode*/ + reg &= ~(1 << LINEARMODE1); /* Use step mode*/ + reg |= (1 << FAN1POLARITY); /* Active High */ + pm2_iowrite(FAN_MISC1_REG, reg); + + /* Set PWM freqency at 19.83KHz*/ + pm2_iowrite(FREQDIV1_REG, PWM_19KHZ); + + /* Enable Speed Detect */ + pm2_iowrite(FAN1DETCONTROL, FANDET_EN | USEAVERAGE); + pm2_iowrite(FAN1SPEEDLIMLO, 0xFF); /* Set limits register */ + pm2_iowrite(FAN1SPEEDLIMHI, 0xFF); + + sbx00_set_fan_level(255, 1); + msleep(250); + speed_high = sbx00_get_fan_speed(1, false); + sbx00_set_fan_level(0, 1); + msleep(5000); + speed_low = sbx00_get_fan_speed(1, false); + sbx00_set_fan_level(255, 1); + if (speed_high >= speed_low) + speed_percent_threshold[1] = speed_low * 100 / speed_high; + pr_info("SpeedPercentThreshold of FAN2 is %d%%\n", speed_percent_threshold[1]); + + /* force fans in auto mode first */ + work->func = fan2_adjust; + sbx00_fan_mode[1] = FAN_AUTO_MODE; + sbx00_fan_start_auto(1); + + ret = sysfs_create_files(&sbx00_hwmon_dev->kobj, sbx00_hwmon_fan[1]); + if (ret) + printk(KERN_ERR "fail to create sysfs files\n"); +} + +static void fan2_adjust(struct work_struct *work) +{ + u8 current_temp; + int target_fan_level, bias; + + current_temp = fan_policy[1].depend_temp(0) / 1000; + + if ((current_temp <= fan_up_temp[1]) && + (current_temp >= fan_down_temp[1])) + goto exit; + + if (current_temp > fan_up_temp[1]) + target_fan_level = fan_policy[1].up_step[fan_up_temp_level[1]].level * 255 / 100; + + if (current_temp < fan_down_temp[1]) + target_fan_level = fan_policy[1].down_step[fan_down_temp_level[1]].level * 255 / 100; + + bias = (speed_percent_threshold[1] * 255) / 100; + bias = bias * (255 - target_fan_level) / 255; + target_fan_level = (target_fan_level > bias) ? target_fan_level - bias : 0; + target_fan_level = target_fan_level * fan_policy[1].percent / 100; + sbx00_set_fan_level(target_fan_level, 1); + + get_up_temp(&fan_policy[1], current_temp, &fan_up_temp[1], &fan_up_temp_level[1]); + get_down_temp(&fan_policy[1], current_temp, &fan_down_temp[1], &fan_down_temp_level[1]); + +exit: + schedule_delayed_work_on(0, &fan_policy[1].work, fan_policy[1].adjust_period * HZ); +} + +static void sbx00_fan3_init(struct work_struct *work) +{ + u8 reg; + int ret; + int speed_low, speed_high; + + /* Configure gpio49 as FANOUT2 */ + reg = pm_ioread(PM_OPTION_0); + pm_iowrite(PM_OPTION_0, reg | FAN2_EN); + + /* setting FANOUT2 mode: + * without temperuature input, controlled by software + */ + reg = pm2_ioread(FAN_CNTR_REG); + reg &= ~(FAN_MODE_MASK << FAN2_CNTR_SHIFT); + reg |= FAN_MODE1 << FAN2_CNTR_SHIFT; + pm2_iowrite(FAN_CNTR_REG, reg); + + reg = pm2_ioread(FAN_MISC2_REG); + reg &= ~(1 << AUTPMODE1); /* Disable auto mode*/ + reg &= ~(1 << LINEARMODE1); /* Use step mode*/ + reg |= (1 << FAN1POLARITY); /* Active High */ + pm2_iowrite(FAN_MISC2_REG, reg); + + /* Set PWM freqency at 19.83KHz*/ + pm2_iowrite(FREQDIV2_REG, PWM_19KHZ); + + /* Enable Speed Detect */ + pm2_iowrite(FAN2DETCONTROL, FANDET_EN | USEAVERAGE); + pm2_iowrite(FAN2SPEEDLIMLO, 0xFF); /* Set limits register */ + pm2_iowrite(FAN2SPEEDLIMHI, 0xFF); + + sbx00_set_fan_level(255, 2); + msleep(250); + speed_high = sbx00_get_fan_speed(2, false); + sbx00_set_fan_level(0, 2); + msleep(5000); + speed_low = sbx00_get_fan_speed(2, false); + sbx00_set_fan_level(255, 2); + if (speed_high >= speed_low) + speed_percent_threshold[2] = speed_low * 100 / speed_high; + pr_info("SpeedPercentThreshold of FAN3 is %d%%\n", speed_percent_threshold[2]); + + /* force fans in auto mode first */ + work->func = fan3_adjust; + sbx00_fan_mode[2] = FAN_AUTO_MODE; + sbx00_fan_start_auto(2); + + ret = sysfs_create_files(&sbx00_hwmon_dev->kobj, sbx00_hwmon_fan[2]); + if (ret) + printk(KERN_ERR "fail to create sysfs files\n"); +} + +static void fan3_adjust(struct work_struct *work) +{ + u8 current_temp; + int target_fan_level, bias; + + current_temp = fan_policy[2].depend_temp(0) / 1000; + + if ((current_temp <= fan_up_temp[2]) && + (current_temp >= fan_down_temp[2])) + goto exit; + + if (current_temp > fan_up_temp[2]) + target_fan_level = fan_policy[2].up_step[fan_up_temp_level[2]].level * 255 / 100; + + if (current_temp < fan_down_temp[2]) + target_fan_level = fan_policy[2].down_step[fan_down_temp_level[2]].level * 255 / 100; + + bias = (speed_percent_threshold[2] * 255) / 100; + bias = bias * (255 - target_fan_level) / 255; + target_fan_level = (target_fan_level > bias) ? target_fan_level - bias : 0; + target_fan_level = target_fan_level * fan_policy[2].percent / 100; + sbx00_set_fan_level(target_fan_level, 2); + + get_up_temp(&fan_policy[2], current_temp, &fan_up_temp[2], &fan_up_temp_level[2]); + get_down_temp(&fan_policy[2], current_temp, &fan_down_temp[2], &fan_down_temp_level[2]); + +exit: + schedule_delayed_work_on(0, &fan_policy[2].work, fan_policy[2].adjust_period * HZ); +} + +static int sbx00_fan_probe(struct platform_device *dev) +{ + int id = dev->id - 1; + struct sensor_device *sdev = (struct sensor_device *)dev->dev.platform_data; + + /* get fan policy */ + switch (sdev->fan_policy) { + case KERNEL_HELPER_POLICY: + memcpy(&fan_policy[id], &kernel_helper_policy, sizeof(struct loongson_fan_policy)); + break; + case STEP_SPEED_POLICY: + memcpy(&fan_policy[id], &step_speed_policy, sizeof(struct loongson_fan_policy)); + fan_policy[id].percent = sdev->fan_percent; + if (fan_policy[id].percent == 0 || fan_policy[id].percent > 100) + fan_policy[id].percent = 100; + break; + case CONSTANT_SPEED_POLICY: + default: + memcpy(&fan_policy[id], &constant_speed_policy, sizeof(struct loongson_fan_policy)); + fan_policy[id].percent = sdev->fan_percent; + if (fan_policy[id].percent == 0 || fan_policy[id].percent > 100) + fan_policy[id].percent = 100; + break; + } + + if (id == 0) { + INIT_DEFERRABLE_WORK(&fan_policy[0].work, sbx00_fan1_init); + schedule_delayed_work_on(0, &fan_policy[0].work, 0); + } + if (id == 1) { + INIT_DEFERRABLE_WORK(&fan_policy[1].work, sbx00_fan2_init); + schedule_delayed_work_on(0, &fan_policy[1].work, 0); + } + if (id == 2) { + INIT_DEFERRABLE_WORK(&fan_policy[2].work, sbx00_fan3_init); + schedule_delayed_work_on(0, &fan_policy[2].work, 0); + } + + sbx00_fan_enable[id] = 1; + + return 0; +} + +static struct platform_driver sbx00_fan_driver = { + .probe = sbx00_fan_probe, + .driver = { + .name = "sbx00-fan", + .owner = THIS_MODULE, + }, +}; + +static int __init sbx00_fan_init(void) +{ + int ret; + + sbx00_hwmon_dev = hwmon_device_register(NULL); + if (IS_ERR(sbx00_hwmon_dev)) { + ret = -ENOMEM; + printk(KERN_ERR "hwmon_device_register fail!\n"); + goto fail_hwmon_device_register; + } + + ret = sysfs_create_group(&sbx00_hwmon_dev->kobj, + &sbx00_hwmon_attribute_group); + if (ret) { + printk(KERN_ERR "fail to create loongson hwmon!\n"); + goto fail_sysfs_create_group_hwmon; + } + + ret = platform_driver_register(&sbx00_fan_driver); + if (ret) { + printk(KERN_ERR "fail to register fan driver!\n"); + goto fail_register_fan; + } + + return 0; + +fail_register_fan: + sysfs_remove_group(&sbx00_hwmon_dev->kobj, + &sbx00_hwmon_attribute_group); + +fail_sysfs_create_group_hwmon: + hwmon_device_unregister(sbx00_hwmon_dev); + +fail_hwmon_device_register: + return ret; +} + +static void __exit sbx00_fan_exit(void) +{ + /* set fan at full speed before module exit */ + if (sbx00_fan_enable[0]) { + sbx00_fan_mode[0] = FAN_FULL_MODE; + sbx00_fan_stop_auto(0); + sbx00_set_fan_level(MAX_FAN_LEVEL, 0); + } + + if (sbx00_fan_enable[1]) { + sbx00_fan_mode[1] = FAN_FULL_MODE; + sbx00_fan_stop_auto(1); + sbx00_set_fan_level(MAX_FAN_LEVEL, 1); + } + + if (sbx00_fan_enable[2]) { + sbx00_fan_mode[2] = FAN_FULL_MODE; + sbx00_fan_stop_auto(2); + sbx00_set_fan_level(MAX_FAN_LEVEL, 2); + } + + platform_driver_unregister(&sbx00_fan_driver); + sysfs_remove_group(&sbx00_hwmon_dev->kobj, + &sbx00_hwmon_attribute_group); + hwmon_device_unregister(sbx00_hwmon_dev); +} + +late_initcall(sbx00_fan_init); +module_exit(sbx00_fan_exit); + +MODULE_AUTHOR("Yu Xiang "); +MODULE_AUTHOR("Huacai Chen "); +MODULE_DESCRIPTION("SB700/SB710/SB800 fan control driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/mips/sd5075.c b/drivers/platform/mips/sd5075.c new file mode 100644 index 0000000000000..27e9fb359b9f6 --- /dev/null +++ b/drivers/platform/mips/sd5075.c @@ -0,0 +1,211 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +static struct i2c_client *sd5075_client = NULL; + +static struct device *sd5075_hwmon_dev; + +static ssize_t get_hwmon_name(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t get_sd5075_label(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t get_sd5075_temp(struct device *dev, + struct device_attribute *attr, char *buf); + +static SENSOR_DEVICE_ATTR(name, S_IRUGO, get_hwmon_name, NULL, 0); + +static struct attribute *sd5075_hwmon_attributes[] = +{ + &sensor_dev_attr_name.dev_attr.attr, + NULL +}; + +/* Hwmon device attribute group */ +static struct attribute_group sd5075_hwmon_attribute_group = +{ + .attrs = sd5075_hwmon_attributes, +}; + +/* Hwmon device get name */ +static ssize_t get_hwmon_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "sd5075\n"); +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, get_sd5075_temp, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, get_sd5075_label, NULL, 1); + +static const struct attribute *sd5075_hwmon_temp[1][3] = { + { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_label.dev_attr.attr, + NULL + } +}; + +#define BUS_MASK 0xffffffff00000000ul +#define ADDR_MASK 0x00000000fffffffful + +static int sd5075_probe(struct platform_device *dev) +{ + char i2c_name[16]; + struct i2c_board_info info; + struct i2c_adapter *adapter = NULL; + int found = 0, i = 0, r = 0, i2c_bus; + struct sensor_device *sdev = (struct sensor_device *)dev->dev.platform_data; + + memset(&info, 0, sizeof(struct i2c_board_info)); + + i2c_bus = ((sdev->base_addr & BUS_MASK) >> 32) - 1; + if (i2c_bus < 0) + sprintf(i2c_name, "SMBus PIIX4"); + else + sprintf(i2c_name, "LS2X I2C%d", i2c_bus); + + /* get i2c_adapter */ + adapter = i2c_get_adapter(i++); + while (adapter) { + if (strncmp(adapter->name, i2c_name, strlen(i2c_name)) == 0) { + found = 1; + break; + } + + adapter = i2c_get_adapter(i++); + } + + if (!found) + goto fail; + + info.addr = sdev->base_addr & ADDR_MASK; + info.platform_data = dev->dev.platform_data; + strncpy(info.type, "sd5075", I2C_NAME_SIZE); + sd5075_client = i2c_new_client_device(adapter, &info); + if (sd5075_client == NULL) { + pr_err("failed to attach sd5075 sensor\n"); + goto fail; + } + + r = sysfs_create_files(&sd5075_hwmon_dev->kobj, sd5075_hwmon_temp[0]); + if (r) + goto fail; + + /* set alert mode */ + i2c_smbus_write_byte_data(sd5075_client, 0x1, 0x80); + + printk(KERN_INFO "Success to attach sd5075 sensor\n"); + + return 0; +fail: + pr_err("Fail to found smbus controller attach sd5075 sensor\n"); + + return r; +} + +static void sd5075_shutdown(struct platform_device *dev) +{ + sd5075_client = NULL; + msleep(15); /* Release I2C/SMBus resources */ +} + +static ssize_t get_sd5075_label(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sensor_device *sdev = (struct sensor_device *)sd5075_client->dev.platform_data; + return sprintf(buf, "%s\n", sdev->label); +} + +static int sd5075_internal_temp(int id) +{ + int reg_value = 0, temp; + + reg_value = i2c_smbus_read_word_swapped(sd5075_client, 0x0); + + if (reg_value & 0x8000) + { + reg_value |= 0xffff0000; + temp = (reg_value >> 4) * 1000 / 16; + + } + else + temp = (reg_value >> 4) * 1000 / 16; + + return temp; +} + +static ssize_t get_sd5075_temp(struct device *dev, struct device_attribute *attr, char *buf) { + + int id = (to_sensor_dev_attr(attr))->index -1; + int value = sd5075_internal_temp(id); + + return sprintf(buf, "%d\n", value); + +} + +static struct platform_driver sd5075_driver = { + .driver = { + .name = "sd5075", + .owner = THIS_MODULE, + }, + .probe = sd5075_probe, + .shutdown = sd5075_shutdown, +}; + +static int __init sd5075_init(void) +{ + int ret; + + sd5075_hwmon_dev = hwmon_device_register(NULL); + + if (IS_ERR(sd5075_hwmon_dev)) { + ret = -ENOMEM; + printk(KERN_ERR "hwmon_device_register fail!\n"); + goto fail_hwmon_device_register; + } + + ret = sysfs_create_group(&sd5075_hwmon_dev->kobj, + &sd5075_hwmon_attribute_group); + if (ret) { + printk(KERN_ERR "fail to create loongson hwmon!\n"); + goto fail_sysfs_create_group_hwmon; + } + + platform_driver_register(&sd5075_driver); + + return 0; + +fail_sysfs_create_group_hwmon: + hwmon_device_unregister(sd5075_hwmon_dev); + +fail_hwmon_device_register: + return ret; +} + +static void __exit sd5075_exit(void) +{ + platform_driver_unregister(&sd5075_driver); + sysfs_remove_group(&sd5075_hwmon_dev->kobj, + &sd5075_hwmon_attribute_group); + hwmon_device_unregister(sd5075_hwmon_dev); +} + +late_initcall(sd5075_init); +module_exit(sd5075_exit); + +MODULE_AUTHOR("Huacai Chen "); +MODULE_AUTHOR("Liangliang Huang "); +MODULE_DESCRIPTION("SD5075 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/mips/tmp75.c b/drivers/platform/mips/tmp75.c new file mode 100644 index 0000000000000..0582c8fa2e2b2 --- /dev/null +++ b/drivers/platform/mips/tmp75.c @@ -0,0 +1,24 @@ +#include + +static int __init tmp75_init(void) +{ + printk("====Please use device-tree for TMP75 Driver====\n"); + printk("You can put the tmp75 node under the i2c node:\n" + "tmp75@4e {\n" + " compatible = \"ti,tmp75\"\n" + " reg = <0x4e>\n" + "}\n"); + printk("==================Thank you!===================\n"); + return 0; +} + +static void __exit tmp75_exit(void) +{ +} + +late_initcall(tmp75_init); +module_exit(tmp75_exit); + +MODULE_AUTHOR("Hongbing Hu "); +MODULE_DESCRIPTION("TMP75 driver, based on the EMC1412 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/mips/wpce_fan.c b/drivers/platform/mips/wpce_fan.c new file mode 100644 index 0000000000000..ecef3158c59bc --- /dev/null +++ b/drivers/platform/mips/wpce_fan.c @@ -0,0 +1,311 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifdef MAX_FAN_LEVEL +#undef MAX_FAN_LEVEL +#endif +#define MAX_FAN_LEVEL 5 + +int fan_enable; +enum fan_control_mode fan_mode; +static struct loongson_fan_policy fan_policy; + +static struct device *wpce775l_hwmon_dev; + +static ssize_t get_hwmon_name(struct device *dev, + struct device_attribute *attr, char *buf); +static SENSOR_DEVICE_ATTR(name, S_IRUGO, get_hwmon_name, NULL, 0); + +static struct attribute *wpce775l_hwmon_attributes[] = +{ + &sensor_dev_attr_name.dev_attr.attr, + NULL +}; + +/* Hwmon device attribute group */ +static struct attribute_group wpce775l_hwmon_attribute_group = +{ + .attrs = wpce775l_hwmon_attributes, +}; + +/* Hwmon device get name */ +static ssize_t get_hwmon_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "wpce775l-fan\n"); +} + +static ssize_t get_fan_level(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t set_fan_level(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); +static ssize_t get_fan_mode(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t set_fan_mode(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); +static ssize_t get_fan_speed(struct device *dev, + struct device_attribute *attr, char *buf); + +static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, + get_fan_level, set_fan_level, 1); +static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, + get_fan_mode, set_fan_mode, 1); +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, get_fan_speed, NULL, 1); + +static const struct attribute *hwmon_fan1[] = { + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm1_enable.dev_attr.attr, + &sensor_dev_attr_fan1_input.dev_attr.attr, + NULL +}; + +static struct workqueue_struct *notify_workqueue; +static void notify_temp(struct work_struct *work); +static DECLARE_DELAYED_WORK(notify_work, notify_temp); + +static int wpce_set_fan_level(u8 level) +{ + if (level > MAX_FAN_LEVEL) + level = MAX_FAN_LEVEL; + + ec_write(INDEX_FAN_SPEED_LEVEL, level); + return 0; +} + +static ssize_t get_fan_level(struct device *dev, + struct device_attribute *attr, char *buf) +{ + u8 val; + + val = ec_read(INDEX_FAN_SPEED_LEVEL); + return sprintf(buf, "%d\n", val); +} + +static ssize_t set_fan_level(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + u8 new_level; + + new_level = clamp_val(simple_strtoul(buf, NULL, 10), 0, 255); + if (fan_mode == FAN_MANUAL_MODE) + wpce_set_fan_level(new_level); + + return count; +} + +static ssize_t get_fan_speed(struct device *dev, + struct device_attribute *attr, char *buf) +{ + u32 val; + + val = (ec_read(INDEX_FAN_SPEED_HIGH) << 8) + + ec_read(INDEX_FAN_SPEED_LOW); + return sprintf(buf, "%d\n", val); +} + +static void notify_temp(struct work_struct *work) +{ + u8 temp; + + temp = fan_policy.depend_temp(0) / 1000; + + ec_write_noindex(0x4d, temp); + + queue_delayed_work(notify_workqueue, ¬ify_work, + fan_policy.adjust_period * HZ); +} + +static int notify_temp_to_EC(void) +{ + notify_workqueue = create_singlethread_workqueue("Temprature Notify"); + queue_delayed_work(notify_workqueue, ¬ify_work, HZ); + + return 0; +} + +static int kernel_control_fan(void) +{ + ec_write(INDEX_FAN_CTRLMOD,FAN_CTRL_BYHOST); + return 0; +} + +static int ec_control_fan(void) +{ + ec_write(INDEX_FAN_CTRLMOD,FAN_CTRL_BYEC); + return 0; +} + +static void fan_start_auto(void) +{ + ec_control_fan(); + + switch (fan_policy.type) { + case KERNEL_HELPER_POLICY: + notify_temp_to_EC(); + break; + default: + printk(KERN_ERR "wpce fan not support fan policy id %d!\n", fan_policy.type); + wpce_set_fan_level(MAX_FAN_LEVEL); + } + + return; +} + +static void fan_stop_auto(void) +{ + if ((fan_policy.type == KERNEL_HELPER_POLICY) && + (fan_mode == FAN_AUTO_MODE)) { + cancel_delayed_work(¬ify_work); + destroy_workqueue(notify_workqueue); + } + + kernel_control_fan(); +} + +static ssize_t set_fan_mode(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + u8 new_mode; + + new_mode = clamp_val(simple_strtoul(buf, NULL, 10), + FAN_FULL_MODE, FAN_AUTO_MODE); + + if (new_mode == fan_mode) + return count; + + switch (new_mode) { + case FAN_FULL_MODE: + fan_stop_auto(); + wpce_set_fan_level(MAX_FAN_LEVEL); + break; + case FAN_MANUAL_MODE: + fan_stop_auto(); + break; + case FAN_AUTO_MODE: + fan_start_auto(); + break; + default: + break; + } + + fan_mode = new_mode; + + return count; +} + +static ssize_t get_fan_mode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", fan_mode); +} + +static int fan_probe(struct platform_device *dev) +{ + int ret; + struct sensor_device *sdev = (struct sensor_device *)dev->dev.platform_data; + + /* get fan policy */ + switch (sdev->fan_policy) { + case KERNEL_HELPER_POLICY: + memcpy(&fan_policy, &kernel_helper_policy, sizeof(fan_policy)); + break; + case STEP_SPEED_POLICY: + memcpy(&fan_policy, &step_speed_policy, sizeof(fan_policy)); + break; + case CONSTANT_SPEED_POLICY: + default: + memcpy(&fan_policy, &constant_speed_policy, sizeof(fan_policy)); + fan_policy.percent = sdev->fan_percent; + if (fan_policy.percent == 0 || fan_policy.percent > 100) + fan_policy.percent = 100; + break; + } + + /* force fan in auto mode first */ + fan_mode = FAN_AUTO_MODE; + fan_start_auto(); + + ret = sysfs_create_files(&wpce775l_hwmon_dev->kobj, hwmon_fan1); + if (ret) { + printk(KERN_ERR "fail to create sysfs files\n"); + return ret; + } + + return 0; +} + +static struct platform_driver fan_driver = { + .probe = fan_probe, + .driver = { + .name = "wpce-fan", + .owner = THIS_MODULE, + }, +}; + +static int __init wpce_fan_init(void) +{ + int ret; + + wpce775l_hwmon_dev = hwmon_device_register(NULL); + if (IS_ERR(wpce775l_hwmon_dev)) { + ret = -ENOMEM; + printk(KERN_ERR "hwmon_device_register fail!\n"); + goto fail_hwmon_device_register; + } + + ret = sysfs_create_group(&wpce775l_hwmon_dev->kobj, + &wpce775l_hwmon_attribute_group); + if (ret) { + printk(KERN_ERR "fail to create loongson hwmon!\n"); + goto fail_sysfs_create_group_hwmon; + } + + ret = platform_driver_register(&fan_driver); + if (ret) { + printk(KERN_ERR "fail to register fan driver!\n"); + goto fail_register_fan; + } + + return 0; + +fail_register_fan: + sysfs_remove_group(&wpce775l_hwmon_dev->kobj, + &wpce775l_hwmon_attribute_group); + +fail_sysfs_create_group_hwmon: + hwmon_device_unregister(wpce775l_hwmon_dev); + +fail_hwmon_device_register: + return ret; +} + +static void __exit wpce_fan_exit(void) +{ + /* set fan at full speed mode before module exit */ + if (fan_enable) + fan_mode = FAN_FULL_MODE; + fan_stop_auto(); + wpce_set_fan_level(MAX_FAN_LEVEL); + + platform_driver_unregister(&fan_driver); + sysfs_remove_group(&wpce775l_hwmon_dev->kobj, + &wpce775l_hwmon_attribute_group); + hwmon_device_unregister(wpce775l_hwmon_dev); +} + +late_initcall(wpce_fan_init); +module_exit(wpce_fan_exit); + +MODULE_AUTHOR("Yu Xiang "); +MODULE_AUTHOR("Huacai Chen "); +MODULE_DESCRIPTION("WPCE775L fan control driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/scsi/lpfc/lpfc_compat.h b/drivers/scsi/lpfc/lpfc_compat.h index 43cf46a3a71fe..0cd1e3c82d875 100644 --- a/drivers/scsi/lpfc/lpfc_compat.h +++ b/drivers/scsi/lpfc/lpfc_compat.h @@ -91,8 +91,8 @@ lpfc_memcpy_to_slim( void __iomem *dest, void *src, unsigned int bytes) static inline void lpfc_memcpy_from_slim( void *dest, void __iomem *src, unsigned int bytes) { - /* actually returns 1 byte past dest */ - memcpy_fromio( dest, src, bytes); + /* convert bytes in argument list to word count for copy function */ + __ioread32_copy(dest, src, bytes / sizeof(uint32_t)); } #endif /* __BIG_ENDIAN */ diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 170c4c3cc7cb7..85250eb86b24b 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -525,12 +525,12 @@ config SPI_LM70_LLP config SPI_LOONGSON_CORE tristate - depends on LOONGARCH || COMPILE_TEST + depends on LOONGARCH || MACH_LOONGSON32 || MACH_LOONGSON64 || COMPILE_TEST config SPI_LOONGSON_PCI tristate "Loongson SPI Controller PCI Driver Support" select SPI_LOONGSON_CORE - depends on PCI && (LOONGARCH || COMPILE_TEST) + depends on PCI && (LOONGARCH || MACH_LOONGSON32 || MACH_LOONGSON64 || COMPILE_TEST) help This bus driver supports the Loongson SPI hardware controller in the Loongson platforms and supports to use PCI framework to @@ -541,7 +541,7 @@ config SPI_LOONGSON_PCI config SPI_LOONGSON_PLATFORM tristate "Loongson SPI Controller Platform Driver Support" select SPI_LOONGSON_CORE - depends on OF && (LOONGARCH || COMPILE_TEST) + depends on OF && (LOONGARCH || MACH_LOONGSON32 || MACH_LOONGSON64 || COMPILE_TEST) help This bus driver supports the Loongson SPI hardware controller in the Loongson platforms and supports to use DTS framework to diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index f9aef39cac2e9..4398fb36b23c6 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -58,6 +58,8 @@ source "drivers/staging/board/Kconfig" source "drivers/staging/gdm724x/Kconfig" +source "drivers/staging/sb105x/Kconfig" + source "drivers/staging/fbtft/Kconfig" source "drivers/staging/most/Kconfig" diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index ffa70dda481d3..8f89455d72216 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_USB_EMXX) += emxx_udc/ obj-$(CONFIG_MFD_NVEC) += nvec/ obj-$(CONFIG_STAGING_BOARD) += board/ obj-$(CONFIG_LTE_GDM724X) += gdm724x/ +obj-$(CONFIG_SB105X) += sb105x/ obj-$(CONFIG_FB_TFT) += fbtft/ obj-$(CONFIG_MOST) += most/ obj-$(CONFIG_KS7010) += ks7010/ diff --git a/drivers/staging/sb105x/Kconfig b/drivers/staging/sb105x/Kconfig new file mode 100644 index 0000000000000..c5f741e71c14b --- /dev/null +++ b/drivers/staging/sb105x/Kconfig @@ -0,0 +1,9 @@ +config SB105X + tristate "SystemBase PCI Multiport UART" + select SERIAL_CORE + depends on PCI && (X86 || MIPS) && TTY + help + A driver for the SystemBase Multi-2/PCI serial card + + To compile this driver a module, choose M here: the module + will be called "sb105x". diff --git a/drivers/staging/sb105x/Makefile b/drivers/staging/sb105x/Makefile new file mode 100644 index 0000000000000..b1bf3779acaed --- /dev/null +++ b/drivers/staging/sb105x/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_SB105X) += sb105x.o + +sb105x-y := sb_pci_mp.o diff --git a/drivers/staging/sb105x/sb_mp_register.h b/drivers/staging/sb105x/sb_mp_register.h new file mode 100644 index 0000000000000..276c1bbcc18dd --- /dev/null +++ b/drivers/staging/sb105x/sb_mp_register.h @@ -0,0 +1,295 @@ + +/* + * SB105X_UART.h + * + * Copyright (C) 2008 systembase + * + * UART registers. + * + * 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. + */ + +#ifndef UART_SB105X_H +#define UART_SB105X_H + +/* + * option register + */ + +/* Device Information Register */ +#define MP_OPTR_DIR0 0x04 /* port0 ~ port8 */ +#define MP_OPTR_DIR1 0x05 /* port8 ~ port15 */ +#define MP_OPTR_DIR2 0x06 /* port16 ~ port23 */ +#define MP_OPTR_DIR3 0x07 /* port24 ~ port31 */ + +#define DIR_UART_16C550 0 +#define DIR_UART_16C1050 1 +#define DIR_UART_16C1050A 2 + +#define DIR_CLK_1843200 0x0 /* input clock 1843200 Hz */ +#define DIR_CLK_3686400 0x1 /* input clock 3686400 Hz */ +#define DIR_CLK_7372800 0x2 /* input clock 7372800 Hz */ +#define DIR_CLK_14745600 0x3 /* input clock 14745600 Hz */ +#define DIR_CLK_29491200 0x4 /* input clock 29491200 Hz */ +#define DIR_CLK_58985400 0x5 /* input clock 58985400 Hz */ + +/* Interface Information Register */ +#define MP_OPTR_IIR0 0x08 /* port0 ~ port8 */ +#define MP_OPTR_IIR1 0x09 /* port8 ~ port15 */ +#define MP_OPTR_IIR2 0x0A /* port16 ~ port23 */ +#define MP_OPTR_IIR3 0x0B /* port24 ~ port31 */ + +#define IIR_RS232 0x00 /* RS232 type */ +#define IIR_RS422 0x10 /* RS422 type */ +#define IIR_RS485 0x20 /* RS485 type */ +#define IIR_TYPE_MASK 0x30 + +/* Interrupt Mask Register */ +#define MP_OPTR_IMR0 0x0C /* port0 ~ port8 */ +#define MP_OPTR_IMR1 0x0D /* port8 ~ port15 */ +#define MP_OPTR_IMR2 0x0E /* port16 ~ port23 */ +#define MP_OPTR_IMR3 0x0F /* port24 ~ port31 */ + +/* Interrupt Poll Register */ +#define MP_OPTR_IPR0 0x10 /* port0 ~ port8 */ +#define MP_OPTR_IPR1 0x11 /* port8 ~ port15 */ +#define MP_OPTR_IPR2 0x12 /* port16 ~ port23 */ +#define MP_OPTR_IPR3 0x13 /* port24 ~ port31 */ + +/* General Purpose Output Control Register */ +#define MP_OPTR_GPOCR 0x20 + +/* General Purpose Output Data Register */ +#define MP_OPTR_GPODR 0x21 + +/* Parallel Additional Function Register */ +#define MP_OPTR_PAFR 0x23 + +/* + * systembase 16c105x UART register + */ + +#define PAGE_0 0 +#define PAGE_1 1 +#define PAGE_2 2 +#define PAGE_3 3 +#define PAGE_4 4 + +/* + * ****************************************************************** + * * DLAB=0 =============== Page 0 Registers * + * ****************************************************************** + */ + +#define SB105X_RX 0 /* In: Receive buffer */ +#define SB105X_TX 0 /* Out: Transmit buffer */ + +#define SB105X_IER 1 /* Out: Interrupt Enable Register */ + +#define SB105X_IER_CTSI 0x80 /* CTS# Interrupt Enable (Requires EFR[4] = 1) */ +#define SB105X_IER_RTSI 0x40 /* RTS# Interrupt Enable (Requires EFR[4] = 1) */ +#define SB105X_IER_XOI 0x20 /* Xoff Interrupt Enable (Requires EFR[4] = 1) */ +#define SB105X_IER_SME 0x10 /* Sleep Mode Enable (Requires EFR[4] = 1) */ +#define SB105X_IER_MSI 0x08 /* Enable Modem status interrupt */ +#define SB105X_IER_RLSI 0x04 /* Enable receiver line status interrupt */ +#define SB105X_IER_THRI 0x02 /* Enable Transmitter holding register int. */ +#define SB105X_IER_RDI 0x01 /* Enable receiver data interrupt */ + +#define SB105X_ISR 2 /* In: Interrupt ID Register */ + +#define SB105X_ISR_NOINT 0x01 /* No interrupts pending */ +#define SB105X_ISR_RLSI 0x06 /* Receiver line status interrupt (Priority = 1)*/ +#define SB105X_ISR_RDAI 0x0c /* Receive Data Available interrupt */ +#define SB105X_ISR_CTII 0x04 /* Character Timeout Indication interrupt */ +#define SB105X_ISR_THRI 0x02 /* Transmitter holding register empty */ +#define SB105X_ISR_MSI 0x00 /* Modem status interrupt */ +#define SB105X_ISR_RXCI 0x10 /* Receive Xoff or Special Character interrupt */ +#define SB105X_ISR_RCSI 0x20 /* RTS#, CTS# status interrupt during Auto RTS/CTS flow control */ + +#define SB105X_FCR 2 /* Out: FIFO Control Register */ + +#define SB105X_FCR_FEN 0x01 /* FIFO Enable */ +#define SB105X_FCR_RXFR 0x02 /* RX FIFO Reset */ +#define SB105X_FCR_TXFR 0x04 /* TX FIFO Reset */ +#define SB105X_FCR_DMS 0x08 /* DMA Mode Select */ + +#define SB105X_FCR_RTR08 0x00 /* Receive Trigger Level set at 8 */ +#define SB105X_FCR_RTR16 0x40 /* Receive Trigger Level set at 16 */ +#define SB105X_FCR_RTR56 0x80 /* Receive Trigger Level set at 56 */ +#define SB105X_FCR_RTR60 0xc0 /* Receive Trigger Level set at 60 */ +#define SB105X_FCR_TTR08 0x00 /* Transmit Trigger Level set at 8 */ +#define SB105X_FCR_TTR16 0x10 /* Transmit Trigger Level set at 16 */ +#define SB105X_FCR_TTR32 0x20 /* Transmit Trigger Level set at 32 */ +#define SB105X_FCR_TTR56 0x30 /* Transmit Trigger Level set at 56 */ + +#define SB105X_LCR 3 /* Out: Line Control Register */ +/* + * * Note: if the word length is 5 bits (SB105X_LCR_WLEN5), then setting + * * SB105X_LCR_STOP will select 1.5 stop bits, not 2 stop bits. + */ +#define SB105X_LCR_DLAB 0x80 /* Divisor Latch Enable */ +#define SB105X_LCR_SBC 0x40 /* Break Enable*/ +#define SB105X_LCR_SPAR 0x20 /* Set Stick parity */ +#define SB105X_LCR_EPAR 0x10 /* Even parity select */ +#define SB105X_LCR_PAREN 0x08 /* Parity Enable */ +#define SB105X_LCR_STOP 0x04 /* Stop bits: 0->1 bit, 1->2 bits, 1 and SB105X_LCR_WLEN5 -> 1.5 bit */ +#define SB105X_LCR_WLEN5 0x00 /* Wordlength: 5 bits */ +#define SB105X_LCR_WLEN6 0x01 /* Wordlength: 6 bits */ +#define SB105X_LCR_WLEN7 0x02 /* Wordlength: 7 bits */ +#define SB105X_LCR_WLEN8 0x03 /* Wordlength: 8 bits */ + +#define SB105X_LCR_BF 0xBF + +#define SB105X_MCR 4 /* Out: Modem Control Register */ +#define SB105X_MCR_CPS 0x80 /* Clock Prescaler Select */ +#define SB105X_MCR_P2S 0x40 /* Page 2 Select /Xoff Re-Transmit Access Enable */ +#define SB105X_MCR_XOA 0x20 /* Xon Any Enable */ +#define SB105X_MCR_ILB 0x10 /* Internal Loopback Enable */ +#define SB105X_MCR_OUT2 0x08 /* Out2/Interrupt Output Enable*/ +#define SB105X_MCR_OUT1 0x04 /* Out1/Interrupt Output Enable */ +#define SB105X_MCR_RTS 0x02 /* RTS# Output */ +#define SB105X_MCR_DTR 0x01 /* DTR# Output */ + +#define SB105X_LSR 5 /* In: Line Status Register */ +#define SB105X_LSR_RFEI 0x80 /* Receive FIFO data error Indicator */ +#define SB105X_LSR_TEMI 0x40 /* THR and TSR Empty Indicator */ +#define SB105X_LSR_THRE 0x20 /* THR Empty Indicator */ +#define SB105X_LSR_BII 0x10 /* Break interrupt indicator */ +#define SB105X_LSR_FEI 0x08 /* Frame error indicator */ +#define SB105X_LSR_PEI 0x04 /* Parity error indicator */ +#define SB105X_LSR_OEI 0x02 /* Overrun error indicator */ +#define SB105X_LSR_RDRI 0x01 /* Receive data ready Indicator*/ + +#define SB105X_MSR 6 /* In: Modem Status Register */ +#define SB105X_MSR_DCD 0x80 /* Data Carrier Detect */ +#define SB105X_MSR_RI 0x40 /* Ring Indicator */ +#define SB105X_MSR_DSR 0x20 /* Data Set Ready */ +#define SB105X_MSR_CTS 0x10 /* Clear to Send */ +#define SB105X_MSR_DDCD 0x08 /* Delta DCD */ +#define SB105X_MSR_DRI 0x04 /* Delta ring indicator */ +#define SB105X_MSR_DDSR 0x02 /* Delta DSR */ +#define SB105X_MSR_DCTS 0x01 /* Delta CTS */ + +#define SB105XA_MDR 6 /* Out: Multi Drop mode Register */ +#define SB105XA_MDR_NPS 0x08 /* 9th Bit Polarity Select */ +#define SB105XA_MDR_AME 0x02 /* Auto Multi-drop Enable */ +#define SB105XA_MDR_MDE 0x01 /* Multi Drop Enable */ + +#define SB105X_SPR 7 /* I/O: Scratch Register */ + +/* + * DLAB=1 + */ +#define SB105X_DLL 0 /* Out: Divisor Latch Low */ +#define SB105X_DLM 1 /* Out: Divisor Latch High */ + +/* + * ****************************************************************** + * * DLAB(LCR[7]) = 0 , MCR[6] = 1 ============= Page 2 Registers * + * ****************************************************************** + */ +#define SB105X_GICR 1 /* Global Interrupt Control Register */ +#define SB105X_GICR_GIM 0x01 /* Global Interrupt Mask */ + +#define SB105X_GISR 2 /* Global Interrupt Status Register */ +#define SB105X_GISR_MGICR0 0x80 /* Mirror the content of GICR[0] */ +#define SB105X_GISR_CS3IS 0x08 /* SB105X of CS3# Interrupt Status */ +#define SB105X_GISR_CS2IS 0x04 /* SB105X of CS2# Interrupt Status */ +#define SB105X_GISR_CS1IS 0x02 /* SB105X of CS1# Interrupt Status */ +#define SB105X_GISR_CS0IS 0x01 /* SB105X of CS0# Interrupt Status */ + +#define SB105X_TFCR 5 /* Transmit FIFO Count Register */ + +#define SB105X_RFCR 6 /* Receive FIFO Count Register */ + +#define SB105X_FSR 7 /* Flow Control Status Register */ +#define SB105X_FSR_THFS 0x20 /* Transmit Hardware Flow Control Status */ +#define SB105X_FSR_TSFS 0x10 /* Transmit Software Flow Control Status */ +#define SB105X_FSR_RHFS 0x02 /* Receive Hardware Flow Control Status */ +#define SB105X_FSR_RSFS 0x01 /* Receive Software Flow Control Status */ + +/* + * ****************************************************************** + * * LCR = 0xBF, PSR[0] = 0 ============= Page 3 Registers * + * ****************************************************************** + */ + +#define SB105X_PSR 0 /* Page Select Register */ +#define SB105X_PSR_P3KEY 0xA4 /* Page 3 Select Key */ +#define SB105X_PSR_P4KEY 0xA5 /* Page 5 Select Key */ + +#define SB105X_ATR 1 /* Auto Toggle Control Register */ +#define SB105X_ATR_RPS 0x80 /* RXEN Polarity Select */ +#define SB105X_ATR_RCMS 0x40 /* RXEN Control Mode Select */ +#define SB105X_ATR_TPS 0x20 /* TXEN Polarity Select */ +#define SB105X_ATR_TCMS 0x10 /* TXEN Control Mode Select */ +#define SB105X_ATR_ATDIS 0x00 /* Auto Toggle is disabled */ +#define SB105X_ATR_ART 0x01 /* RTS#/TXEN pin operates as TXEN */ +#define SB105X_ATR_ADT 0x02 /* DTR#/TXEN pin operates as TXEN */ +#define SB105X_ATR_A80 0x03 /* only in 80 pin use */ + +#define SB105X_EFR 2 /* (Auto) Enhanced Feature Register */ +#define SB105X_EFR_ACTS 0x80 /* Auto-CTS Flow Control Enable */ +#define SB105X_EFR_ARTS 0x40 /* Auto-RTS Flow Control Enable */ +#define SB105X_EFR_SCD 0x20 /* Special Character Detect */ +#define SB105X_EFR_EFBEN 0x10 /* Enhanced Function Bits Enable */ + +#define SB105X_XON1 4 /* Xon1 Character Register */ +#define SB105X_XON2 5 /* Xon2 Character Register */ +#define SB105X_XOFF1 6 /* Xoff1 Character Register */ +#define SB105X_XOFF2 7 /* Xoff2 Character Register */ + +/* + * ****************************************************************** + * * LCR = 0xBF, PSR[0] = 1 ============ Page 4 Registers * + * ****************************************************************** + */ + +#define SB105X_AFR 1 /* Additional Feature Register */ +#define SB105X_AFR_GIPS 0x20 /* Global Interrupt Polarity Select */ +#define SB105X_AFR_GIEN 0x10 /* Global Interrupt Enable */ +#define SB105X_AFR_AFEN 0x01 /* 256-byte FIFO Enable */ + +#define SB105X_XRCR 2 /* Xoff Re-transmit Count Register */ +#define SB105X_XRCR_NRC1 0x00 /* Transmits Xoff Character whenever the number of received data is 1 during XOFF status */ +#define SB105X_XRCR_NRC4 0x01 /* Transmits Xoff Character whenever the number of received data is 4 during XOFF status */ +#define SB105X_XRCR_NRC8 0x02 /* Transmits Xoff Character whenever the number of received data is 8 during XOFF status */ +#define SB105X_XRCR_NRC16 0x03 /* Transmits Xoff Character whenever the number of received data is 16 during XOFF status */ + +#define SB105X_TTR 4 /* Transmit FIFO Trigger Level Register */ +#define SB105X_RTR 5 /* Receive FIFO Trigger Level Register */ +#define SB105X_FUR 6 /* Flow Control Upper Threshold Register */ +#define SB105X_FLR 7 /* Flow Control Lower Threshold Register */ + + +/* page 0 */ + +#define SB105X_GET_CHAR(port) inb((port)->iobase + SB105X_RX) +#define SB105X_GET_IER(port) inb((port)->iobase + SB105X_IER) +#define SB105X_GET_ISR(port) inb((port)->iobase + SB105X_ISR) +#define SB105X_GET_LCR(port) inb((port)->iobase + SB105X_LCR) +#define SB105X_GET_MCR(port) inb((port)->iobase + SB105X_MCR) +#define SB105X_GET_LSR(port) inb((port)->iobase + SB105X_LSR) +#define SB105X_GET_MSR(port) inb((port)->iobase + SB105X_MSR) +#define SB105X_GET_SPR(port) inb((port)->iobase + SB105X_SPR) + +#define SB105X_PUT_CHAR(port,v) outb((v),(port)->iobase + SB105X_TX ) +#define SB105X_PUT_IER(port,v) outb((v),(port)->iobase + SB105X_IER ) +#define SB105X_PUT_FCR(port,v) outb((v),(port)->iobase + SB105X_FCR ) +#define SB105X_PUT_LCR(port,v) outb((v),(port)->iobase + SB105X_LCR ) +#define SB105X_PUT_MCR(port,v) outb((v),(port)->iobase + SB105X_MCR ) +#define SB105X_PUT_SPR(port,v) outb((v),(port)->iobase + SB105X_SPR ) + + +/* page 1 */ +#define SB105X_GET_REG(port,reg) inb((port)->iobase + (reg)) +#define SB105X_PUT_REG(port,reg,v) outb((v),(port)->iobase + (reg)) + +/* page 2 */ + +#define SB105X_PUT_PSR(port,v) outb((v),(port)->iobase + SB105X_PSR ) + +#endif diff --git a/drivers/staging/sb105x/sb_pci_mp.c b/drivers/staging/sb105x/sb_pci_mp.c new file mode 100644 index 0000000000000..19098bbfa113e --- /dev/null +++ b/drivers/staging/sb105x/sb_pci_mp.c @@ -0,0 +1,3165 @@ +#include "sb_pci_mp.h" +#include +#include +#include + +extern struct parport *parport_pc_probe_port(unsigned long base_lo, + unsigned long base_hi, + int irq, int dma, + struct device *dev, + int irqflags); + +static struct mp_device_t mp_devs[MAX_MP_DEV]; +static int mp_nrpcibrds = sizeof(mp_pciboards)/sizeof(mppcibrd_t); +static int NR_BOARD=0; +static int NR_PORTS=0; +static struct mp_port multi_ports[MAX_MP_PORT]; +static struct irq_info irq_lists[NR_IRQS]; + +static _INLINE_ unsigned int serial_in(struct mp_port *mtpt, int offset); +static _INLINE_ void serial_out(struct mp_port *mtpt, int offset, int value); +static _INLINE_ unsigned int read_option_register(struct mp_port *mtpt, int offset); +static int sb1054_get_register(struct sb_uart_port *port, int page, int reg); +static int sb1054_set_register(struct sb_uart_port *port, int page, int reg, int value); +static void SendATCommand(struct mp_port *mtpt); +static int set_deep_fifo(struct sb_uart_port *port, int status); +static int get_deep_fifo(struct sb_uart_port *port); +static int get_device_type(int arg); +static int set_auto_rts(struct sb_uart_port *port, int status); +static void mp_stop(struct tty_struct *tty); +static void __mp_start(struct tty_struct *tty); +static void mp_start(struct tty_struct *tty); +static void mp_tasklet_action(unsigned long data); +static inline void mp_update_mctrl(struct sb_uart_port *port, unsigned int set, unsigned int clear); +static int mp_startup(struct sb_uart_state *state, int init_hw); +static void mp_shutdown(struct sb_uart_state *state); +static void mp_change_speed(struct sb_uart_state *state, const struct MP_TERMIOS *old_termios); + +static inline int __mp_put_char(struct sb_uart_port *port, struct circ_buf *circ, unsigned char c); +static int mp_put_char(struct tty_struct *tty, unsigned char ch); + +static void mp_put_chars(struct tty_struct *tty); +static ssize_t mp_write(struct tty_struct *tty, const unsigned char *buf, long unsigned int count); +static unsigned int mp_write_room(struct tty_struct *tty); +static unsigned int mp_chars_in_buffer(struct tty_struct *tty); +static void mp_flush_buffer(struct tty_struct *tty); +static void mp_send_xchar(struct tty_struct *tty, u8 ch); +static void mp_throttle(struct tty_struct *tty); +static void mp_unthrottle(struct tty_struct *tty); +static int mp_get_info(struct sb_uart_state *state, struct serial_struct *retinfo); +static int mp_set_info(struct sb_uart_state *state, struct serial_struct *newinfo); +static int mp_get_lsr_info(struct sb_uart_state *state, unsigned int *value); + +static int mp_tiocmget(struct tty_struct *tty); +static int mp_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear); +static int mp_break_ctl(struct tty_struct *tty, int break_state); +static int mp_do_autoconfig(struct sb_uart_state *state); +static int mp_wait_modem_status(struct sb_uart_state *state, unsigned long arg); +static int mp_get_count(struct sb_uart_state *state, struct serial_icounter_struct *icnt); +static int mp_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg); +static void mp_set_termios(struct tty_struct *tty, const struct MP_TERMIOS *old_termios); +static void mp_close(struct tty_struct *tty, struct file *filp); +static void mp_wait_until_sent(struct tty_struct *tty, int timeout); +static void mp_hangup(struct tty_struct *tty); +static void mp_update_termios(struct sb_uart_state *state); +static int mp_block_til_ready(struct file *filp, struct sb_uart_state *state); +static struct sb_uart_state *uart_get(struct uart_driver *drv, int line); +static int mp_open(struct tty_struct *tty, struct file *filp); +static const char *mp_type(struct sb_uart_port *port); +static void mp_change_pm(struct sb_uart_state *state, int pm_state); +static inline void mp_report_port(struct uart_driver *drv, struct sb_uart_port *port); +static void mp_configure_port(struct uart_driver *drv, struct sb_uart_state *state, struct sb_uart_port *port); +static void mp_unconfigure_port(struct uart_driver *drv, struct sb_uart_state *state); +static int mp_register_driver(struct uart_driver *drv); +static void mp_unregister_driver(struct uart_driver *drv); +static int mp_add_one_port(struct uart_driver *drv, struct sb_uart_port *port); +static int mp_remove_one_port(struct uart_driver *drv, struct sb_uart_port *port); +static void autoconfig(struct mp_port *mtpt, unsigned int probeflags); +static void autoconfig_irq(struct mp_port *mtpt); +static void multi_stop_tx(struct sb_uart_port *port); +static void multi_start_tx(struct sb_uart_port *port); +static void multi_stop_rx(struct sb_uart_port *port); +static void multi_enable_ms(struct sb_uart_port *port); +static _INLINE_ void receive_chars(struct mp_port *mtpt, int *status ); +static _INLINE_ void transmit_chars(struct mp_port *mtpt); +static _INLINE_ void check_modem_status(struct mp_port *mtpt); +static inline void multi_handle_port(struct mp_port *mtpt); +static irqreturn_t multi_interrupt(int irq, void *dev_id); +static void serial_do_unlink(struct irq_info *i, struct mp_port *mtpt); +static int serial_link_irq_chain(struct mp_port *mtpt); +static void serial_unlink_irq_chain(struct mp_port *mtpt); +static void multi_timeout(struct timer_list *t); +static unsigned int multi_tx_empty(struct sb_uart_port *port); +static unsigned int multi_get_mctrl(struct sb_uart_port *port); +static void multi_set_mctrl(struct sb_uart_port *port, unsigned int mctrl); +static void multi_break_ctl(struct sb_uart_port *port, int break_state); +static int multi_startup(struct sb_uart_port *port); +static void multi_shutdown(struct sb_uart_port *port); +static unsigned int multi_get_divisor(struct sb_uart_port *port, unsigned int baud); +static void multi_set_termios(struct sb_uart_port *port, struct MP_TERMIOS *termios, const struct MP_TERMIOS *old); +static void multi_pm(struct sb_uart_port *port, unsigned int state, unsigned int oldstate); +static void multi_release_std_resource(struct mp_port *mtpt); +static void multi_release_port(struct sb_uart_port *port); +static int multi_request_port(struct sb_uart_port *port); +static void multi_config_port(struct sb_uart_port *port, int flags); +static int multi_verify_port(struct sb_uart_port *port, struct serial_struct *ser); +static const char *multi_type(struct sb_uart_port *port); +static void __init multi_init_ports(void); +static void __init multi_register_ports(struct uart_driver *drv); +static int init_mp_dev(struct pci_dev *pcidev, mppcibrd_t brd); + +static int deep[256]; +static int deep_count; +static int fcr_arr[256]; +static int fcr_count; +static int ttr[256]; +static int ttr_count; +static int rtr[256]; +static int rtr_count; + +module_param_array(deep,int,&deep_count,0); +module_param_array(fcr_arr,int,&fcr_count,0); +module_param_array(ttr,int,&ttr_count,0); +module_param_array(rtr,int,&rtr_count,0); + +static _INLINE_ unsigned int serial_in(struct mp_port *mtpt, int offset) +{ + return inb(mtpt->port.iobase + offset); +} + +static _INLINE_ void serial_out(struct mp_port *mtpt, int offset, int value) +{ + outb(value, mtpt->port.iobase + offset); +} + +static _INLINE_ unsigned int read_option_register(struct mp_port *mtpt, int offset) +{ + return inb(mtpt->option_base_addr + offset); +} + +static int sb1053a_get_interface(struct mp_port *mtpt, int port_num) +{ + unsigned long option_base_addr = mtpt->option_base_addr; + unsigned int interface = 0; + + switch (port_num) + { + case 0: + case 1: + /* set GPO[1:0] = 00 */ + outb(0x00, option_base_addr + MP_OPTR_GPODR); + break; + case 2: + case 3: + /* set GPO[1:0] = 01 */ + outb(0x01, option_base_addr + MP_OPTR_GPODR); + break; + case 4: + case 5: + /* set GPO[1:0] = 10 */ + outb(0x02, option_base_addr + MP_OPTR_GPODR); + break; + default: + break; + } + + port_num &= 0x1; + + /* get interface */ + interface = inb(option_base_addr + MP_OPTR_IIR0 + port_num); + + /* set GPO[1:0] = 11 */ + outb(0x03, option_base_addr + MP_OPTR_GPODR); + + return (interface); +} + +static int sb1054_get_register(struct sb_uart_port *port, int page, int reg) +{ + int ret = 0; + unsigned int lcr = 0; + unsigned int mcr = 0; + unsigned int tmp = 0; + + if( page <= 0) + { + printk(" page 0 can not use this function\n"); + return -1; + } + + switch(page) + { + case 1: + lcr = SB105X_GET_LCR(port); + tmp = lcr | SB105X_LCR_DLAB; + SB105X_PUT_LCR(port, tmp); + + tmp = SB105X_GET_LCR(port); + + ret = SB105X_GET_REG(port,reg); + SB105X_PUT_LCR(port,lcr); + break; + case 2: + mcr = SB105X_GET_MCR(port); + tmp = mcr | SB105X_MCR_P2S; + SB105X_PUT_MCR(port,tmp); + + ret = SB105X_GET_REG(port,reg); + + SB105X_PUT_MCR(port,mcr); + break; + case 3: + lcr = SB105X_GET_LCR(port); + tmp = lcr | SB105X_LCR_BF; + SB105X_PUT_LCR(port,tmp); + SB105X_PUT_REG(port,SB105X_PSR,SB105X_PSR_P3KEY); + + ret = SB105X_GET_REG(port,reg); + + SB105X_PUT_LCR(port,lcr); + break; + case 4: + lcr = SB105X_GET_LCR(port); + tmp = lcr | SB105X_LCR_BF; + SB105X_PUT_LCR(port,tmp); + SB105X_PUT_REG(port,SB105X_PSR,SB105X_PSR_P4KEY); + + ret = SB105X_GET_REG(port,reg); + + SB105X_PUT_LCR(port,lcr); + break; + default: + printk(" error invalid page number \n"); + return -1; + } + + return ret; +} + +static int sb1054_set_register(struct sb_uart_port *port, int page, int reg, int value) +{ + int lcr = 0; + int mcr = 0; + int ret = 0; + + if( page <= 0) + { + printk(" page 0 can not use this function\n"); + return -1; + } + switch(page) + { + case 1: + lcr = SB105X_GET_LCR(port); + SB105X_PUT_LCR(port, lcr | SB105X_LCR_DLAB); + + SB105X_PUT_REG(port,reg,value); + + SB105X_PUT_LCR(port, lcr); + ret = 1; + break; + case 2: + mcr = SB105X_GET_MCR(port); + SB105X_PUT_MCR(port, mcr | SB105X_MCR_P2S); + + SB105X_PUT_REG(port,reg,value); + + SB105X_PUT_MCR(port, mcr); + ret = 1; + break; + case 3: + lcr = SB105X_GET_LCR(port); + SB105X_PUT_LCR(port, lcr | SB105X_LCR_BF); + SB105X_PUT_PSR(port, SB105X_PSR_P3KEY); + + SB105X_PUT_REG(port,reg,value); + + SB105X_PUT_LCR(port, lcr); + ret = 1; + break; + case 4: + lcr = SB105X_GET_LCR(port); + SB105X_PUT_LCR(port, lcr | SB105X_LCR_BF); + SB105X_PUT_PSR(port, SB105X_PSR_P4KEY); + + SB105X_PUT_REG(port,reg,value); + + SB105X_PUT_LCR(port, lcr); + ret = 1; + break; + default: + printk(" error invalid page number \n"); + return -1; + } + + return ret; +} + +static int set_multidrop_mode(struct sb_uart_port *port, unsigned int mode) +{ + int mdr = SB105XA_MDR_NPS; + + if (mode & MDMODE_ENABLE) + { + mdr |= SB105XA_MDR_MDE; + } + + if (1) //(mode & MDMODE_AUTO) + { + int efr = 0; + mdr |= SB105XA_MDR_AME; + efr = sb1054_get_register(port, PAGE_3, SB105X_EFR); + efr |= SB105X_EFR_SCD; + sb1054_set_register(port, PAGE_3, SB105X_EFR, efr); + } + + sb1054_set_register(port, PAGE_1, SB105XA_MDR, mdr); + port->mdmode &= ~0x6; + port->mdmode |= mode; + + return 0; +} + +static int get_multidrop_addr(struct sb_uart_port *port) +{ + return sb1054_get_register(port, PAGE_3, SB105X_XOFF2); +} + +static int set_multidrop_addr(struct sb_uart_port *port, unsigned int addr) +{ + sb1054_set_register(port, PAGE_3, SB105X_XOFF2, addr); + + return 0; +} + +static void SendATCommand(struct mp_port *mtpt) +{ + // a t cr lf + unsigned char ch[] = {0x61,0x74,0x0d,0x0a,0x0}; + unsigned char lineControl; + unsigned char i=0; + unsigned char Divisor = 0xc; + + lineControl = serial_inp(mtpt,UART_LCR); + serial_outp(mtpt,UART_LCR,(lineControl | UART_LCR_DLAB)); + serial_outp(mtpt,UART_DLL,(Divisor & 0xff)); + serial_outp(mtpt,UART_DLM,(Divisor & 0xff00)>>8); //baudrate is 4800 + + + serial_outp(mtpt,UART_LCR,lineControl); + serial_outp(mtpt,UART_LCR,0x03); // N-8-1 + serial_outp(mtpt,UART_FCR,7); + serial_outp(mtpt,UART_MCR,0x3); + while(ch[i]){ + while((serial_inp(mtpt,UART_LSR) & 0x60) !=0x60){ + ; + } + serial_outp(mtpt,0,ch[i++]); + } + + +}// end of SendATCommand() + +static int set_deep_fifo(struct sb_uart_port *port, int status) +{ + int afr_status = 0; + afr_status = sb1054_get_register(port, PAGE_4, SB105X_AFR); + + if(status == ENABLE) + { + afr_status |= SB105X_AFR_AFEN; + } + else + { + afr_status &= ~SB105X_AFR_AFEN; + } + + sb1054_set_register(port,PAGE_4,SB105X_AFR,afr_status); + sb1054_set_register(port,PAGE_4,SB105X_TTR,ttr[port->line]); + sb1054_set_register(port,PAGE_4,SB105X_RTR,rtr[port->line]); + afr_status = sb1054_get_register(port, PAGE_4, SB105X_AFR); + + return afr_status; +} + +static int get_device_type(int arg) +{ + int ret; + ret = inb(mp_devs[arg].option_reg_addr+MP_OPTR_DIR0); + ret = (ret & 0xf0) >> 4; + switch (ret) + { + case DIR_UART_16C550: + return PORT_16C55X; + case DIR_UART_16C1050: + return PORT_16C105X; + case DIR_UART_16C1050A: + /* + if (mtpt->port.line < 2) + { + return PORT_16C105XA; + } + else + { + if (mtpt->device->device_id & 0x50) + { + return PORT_16C55X; + } + else + { + return PORT_16C105X; + } + }*/ + return PORT_16C105XA; + default: + return PORT_UNKNOWN; + } + +} +static int get_deep_fifo(struct sb_uart_port *port) +{ + int afr_status = 0; + afr_status = sb1054_get_register(port, PAGE_4, SB105X_AFR); + return afr_status; +} + +static int set_auto_rts(struct sb_uart_port *port, int status) +{ + int atr_status = 0; + +#if 0 + int efr_status = 0; + + efr_status = sb1054_get_register(port, PAGE_3, SB105X_EFR); + if(status == ENABLE) + efr_status |= SB105X_EFR_ARTS; + else + efr_status &= ~SB105X_EFR_ARTS; + sb1054_set_register(port,PAGE_3,SB105X_EFR,efr_status); + efr_status = sb1054_get_register(port, PAGE_3, SB105X_EFR); +#endif + +//ATR + atr_status = sb1054_get_register(port, PAGE_3, SB105X_ATR); + switch(status) + { + case RS422PTP: + atr_status = (SB105X_ATR_TPS) | (SB105X_ATR_A80); + break; + case RS422MD: + atr_status = (SB105X_ATR_TPS) | (SB105X_ATR_TCMS) | (SB105X_ATR_A80); + break; + case RS485NE: + atr_status = (SB105X_ATR_RCMS) | (SB105X_ATR_TPS) | (SB105X_ATR_TCMS) | (SB105X_ATR_A80); + break; + case RS485ECHO: + atr_status = (SB105X_ATR_TPS) | (SB105X_ATR_TCMS) | (SB105X_ATR_A80); + break; + } + + sb1054_set_register(port,PAGE_3,SB105X_ATR,atr_status); + atr_status = sb1054_get_register(port, PAGE_3, SB105X_ATR); + + return atr_status; +} + +static void mp_stop(struct tty_struct *tty) +{ + struct sb_uart_state *state = tty->driver_data; + struct sb_uart_port *port = state->port; + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + port->ops->stop_tx(port); + spin_unlock_irqrestore(&port->lock, flags); +} + +static void __mp_start(struct tty_struct *tty) +{ + struct sb_uart_state *state = tty->driver_data; + struct sb_uart_port *port = state->port; + + if (!uart_circ_empty(&state->info->xmit) && state->info->xmit.buf && + !tty->flow.stopped && !tty->hw_stopped) + port->ops->start_tx(port); +} + +static void mp_start(struct tty_struct *tty) +{ + __mp_start(tty); +} + +static void mp_tasklet_action(unsigned long data) +{ + struct sb_uart_state *state = (struct sb_uart_state *)data; + struct tty_struct *tty; + + tty = state->info->tty; + tty_wakeup(tty); +} + +static inline void mp_update_mctrl(struct sb_uart_port *port, unsigned int set, unsigned int clear) +{ + unsigned int old; + + old = port->mctrl; + port->mctrl = (old & ~clear) | set; + if (old != port->mctrl) + port->ops->set_mctrl(port, port->mctrl); +} + +#define uart_set_mctrl(port,set) mp_update_mctrl(port,set,0) +#define uart_clear_mctrl(port,clear) mp_update_mctrl(port,0,clear) + +static int mp_startup(struct sb_uart_state *state, int init_hw) +{ + struct sb_uart_info *info = state->info; + struct sb_uart_port *port = state->port; + unsigned long page; + int retval = 0; + + if (info->flags & UIF_INITIALIZED) + return 0; + + if (info->tty) + set_bit(TTY_IO_ERROR, &info->tty->flags); + + if (port->type == PORT_UNKNOWN) + return 0; + + if (!info->xmit.buf) { + page = get_zeroed_page(GFP_KERNEL); + if (!page) + return -ENOMEM; + + info->xmit.buf = (unsigned char *) page; + + uart_circ_clear(&info->xmit); + } + + retval = port->ops->startup(port); + if (retval == 0) { + if (init_hw) { + mp_change_speed(state, NULL); + + if (info->tty && (info->tty->termios.c_cflag & CBAUD)) + uart_set_mctrl(port, TIOCM_RTS | TIOCM_DTR); + } + + info->flags |= UIF_INITIALIZED; + + if (info->tty) + clear_bit(TTY_IO_ERROR, &info->tty->flags); + } + + if (retval && capable(CAP_SYS_ADMIN)) + retval = 0; + + return retval; +} + +static void mp_shutdown(struct sb_uart_state *state) +{ + struct sb_uart_info *info = state->info; + struct sb_uart_port *port = state->port; + + if (info->tty) + set_bit(TTY_IO_ERROR, &info->tty->flags); + + if (info->flags & UIF_INITIALIZED) { + info->flags &= ~UIF_INITIALIZED; + + if (!info->tty || (info->tty->termios.c_cflag & HUPCL)) + uart_clear_mctrl(port, TIOCM_DTR | TIOCM_RTS); + + wake_up_interruptible(&info->delta_msr_wait); + + port->ops->shutdown(port); + + synchronize_irq(port->irq); + } + tasklet_kill(&info->tlet); + + if (info->xmit.buf) { + free_page((unsigned long)info->xmit.buf); + info->xmit.buf = NULL; + } +} + +static void mp_change_speed(struct sb_uart_state *state, const struct MP_TERMIOS *old_termios) +{ + struct tty_struct *tty = state->info->tty; + struct sb_uart_port *port = state->port; + + if (!tty || port->type == PORT_UNKNOWN) + return; + + if (tty->termios.c_cflag & CRTSCTS) + state->info->flags |= UIF_CTS_FLOW; + else + state->info->flags &= ~UIF_CTS_FLOW; + + if (tty->termios.c_cflag & CLOCAL) + state->info->flags &= ~UIF_CHECK_CD; + else + state->info->flags |= UIF_CHECK_CD; + + port->ops->set_termios(port, &tty->termios, old_termios); +} + +static inline int __mp_put_char(struct sb_uart_port *port, struct circ_buf *circ, unsigned char c) +{ + unsigned long flags; + int ret = 0; + + if (!circ->buf) + return 0; + + spin_lock_irqsave(&port->lock, flags); + if (uart_circ_chars_free(circ) != 0) { + circ->buf[circ->head] = c; + circ->head = (circ->head + 1) & (UART_XMIT_SIZE - 1); + ret = 1; + } + spin_unlock_irqrestore(&port->lock, flags); + return ret; +} + +static int mp_put_char(struct tty_struct *tty, unsigned char ch) +{ + struct sb_uart_state *state = tty->driver_data; + + return __mp_put_char(state->port, &state->info->xmit, ch); +} + +static void mp_put_chars(struct tty_struct *tty) +{ + mp_start(tty); +} + +static ssize_t mp_write(struct tty_struct *tty, const unsigned char *buf, long unsigned int count) +{ + struct sb_uart_state *state = tty->driver_data; + struct sb_uart_port *port; + struct circ_buf *circ; + int c, ret = 0; + + if (!state || !state->info) { + return -EL3HLT; + } + + port = state->port; + circ = &state->info->xmit; + + if (!circ->buf) + return 0; + + while (1) { + c = CIRC_SPACE_TO_END(circ->head, circ->tail, UART_XMIT_SIZE); + if (count < c) + c = count; + if (c <= 0) + break; + memcpy(circ->buf + circ->head, buf, c); + + circ->head = (circ->head + c) & (UART_XMIT_SIZE - 1); + buf += c; + count -= c; + ret += c; + } + mp_start(tty); + return ret; +} + +static unsigned int mp_write_room(struct tty_struct *tty) +{ + struct sb_uart_state *state = tty->driver_data; + + return uart_circ_chars_free(&state->info->xmit); +} + +static unsigned int mp_chars_in_buffer(struct tty_struct *tty) +{ + struct sb_uart_state *state = tty->driver_data; + + return uart_circ_chars_pending(&state->info->xmit); +} + +static void mp_flush_buffer(struct tty_struct *tty) +{ + struct sb_uart_state *state = tty->driver_data; + struct sb_uart_port *port; + unsigned long flags; + + if (!state || !state->info) { + return; + } + + port = state->port; + spin_lock_irqsave(&port->lock, flags); + uart_circ_clear(&state->info->xmit); + spin_unlock_irqrestore(&port->lock, flags); + wake_up_interruptible(&tty->write_wait); + tty_wakeup(tty); +} + +static void mp_send_xchar(struct tty_struct *tty, u8 ch) +{ + struct sb_uart_state *state = tty->driver_data; + struct sb_uart_port *port = state->port; + unsigned long flags; + + if (port->ops->send_xchar) + port->ops->send_xchar(port, ch); + else { + port->x_char = ch; + if (ch) { + spin_lock_irqsave(&port->lock, flags); + port->ops->start_tx(port); + spin_unlock_irqrestore(&port->lock, flags); + } + } +} + +static void mp_throttle(struct tty_struct *tty) +{ + struct sb_uart_state *state = tty->driver_data; + + if (I_IXOFF(tty)) + mp_send_xchar(tty, STOP_CHAR(tty)); + + if (tty->termios.c_cflag & CRTSCTS) + uart_clear_mctrl(state->port, TIOCM_RTS); +} + +static void mp_unthrottle(struct tty_struct *tty) +{ + struct sb_uart_state *state = tty->driver_data; + struct sb_uart_port *port = state->port; + + if (I_IXOFF(tty)) { + if (port->x_char) + port->x_char = 0; + else + mp_send_xchar(tty, START_CHAR(tty)); + } + + if (tty->termios.c_cflag & CRTSCTS) + uart_set_mctrl(port, TIOCM_RTS); +} + +static int mp_get_info(struct sb_uart_state *state, struct serial_struct *retinfo) +{ + struct sb_uart_port *port = state->port; + struct serial_struct tmp; + + memset(&tmp, 0, sizeof(tmp)); + tmp.type = port->type; + tmp.line = port->line; + tmp.port = port->iobase; + if (HIGH_BITS_OFFSET) + tmp.port_high = (long) port->iobase >> HIGH_BITS_OFFSET; + tmp.irq = port->irq; + tmp.flags = port->flags; + tmp.xmit_fifo_size = port->fifosize; + tmp.baud_base = port->uartclk / 16; + tmp.close_delay = state->close_delay; + tmp.closing_wait = state->closing_wait == USF_CLOSING_WAIT_NONE ? + ASYNC_CLOSING_WAIT_NONE : + state->closing_wait; + tmp.custom_divisor = port->custom_divisor; + tmp.hub6 = port->hub6; + tmp.io_type = port->iotype; + tmp.iomem_reg_shift = port->regshift; + tmp.iomem_base = (void *)port->mapbase; + + if (copy_to_user(retinfo, &tmp, sizeof(*retinfo))) + return -EFAULT; + return 0; +} + +static int mp_set_info(struct sb_uart_state *state, struct serial_struct *newinfo) +{ + struct serial_struct new_serial; + struct sb_uart_port *port = state->port; + unsigned long new_port; + unsigned int change_irq, change_port, closing_wait; + unsigned int old_custom_divisor; + unsigned int old_flags, new_flags; + int retval = 0; + + if (copy_from_user(&new_serial, newinfo, sizeof(new_serial))) + return -EFAULT; + + new_port = new_serial.port; + if (HIGH_BITS_OFFSET) + new_port += (unsigned long) new_serial.port_high << HIGH_BITS_OFFSET; + + new_serial.irq = irq_canonicalize(new_serial.irq); + + closing_wait = new_serial.closing_wait == ASYNC_CLOSING_WAIT_NONE ? + USF_CLOSING_WAIT_NONE : new_serial.closing_wait; + MP_STATE_LOCK(state); + + change_irq = new_serial.irq != port->irq; + change_port = new_port != port->iobase || + (unsigned long)new_serial.iomem_base != port->mapbase || + new_serial.hub6 != port->hub6 || + new_serial.io_type != port->iotype || + new_serial.iomem_reg_shift != port->regshift || + new_serial.type != port->type; + old_flags = port->flags; + new_flags = new_serial.flags; + old_custom_divisor = port->custom_divisor; + + if (!capable(CAP_SYS_ADMIN)) { + retval = -EPERM; + if (change_irq || change_port || + (new_serial.baud_base != port->uartclk / 16) || + (new_serial.close_delay != state->close_delay) || + (closing_wait != state->closing_wait) || + (new_serial.xmit_fifo_size != port->fifosize) || + (((new_flags ^ old_flags) & ~UPF_USR_MASK) != 0)) + goto exit; + port->flags = ((port->flags & ~UPF_USR_MASK) | + (new_flags & UPF_USR_MASK)); + port->custom_divisor = new_serial.custom_divisor; + goto check_and_exit; + } + + if (port->ops->verify_port) + retval = port->ops->verify_port(port, &new_serial); + + if ((new_serial.irq >= NR_IRQS) || (new_serial.irq < 0) || + (new_serial.baud_base < 9600)) + retval = -EINVAL; + + if (retval) + goto exit; + + if (change_port || change_irq) { + retval = -EBUSY; + + if (uart_users(state) > 1) + goto exit; + + mp_shutdown(state); + } + + if (change_port) { + unsigned long old_iobase, old_mapbase; + unsigned int old_type, old_iotype, old_hub6, old_shift; + + old_iobase = port->iobase; + old_mapbase = port->mapbase; + old_type = port->type; + old_hub6 = port->hub6; + old_iotype = port->iotype; + old_shift = port->regshift; + + if (old_type != PORT_UNKNOWN) + port->ops->release_port(port); + + port->iobase = new_port; + port->type = new_serial.type; + port->hub6 = new_serial.hub6; + port->iotype = new_serial.io_type; + port->regshift = new_serial.iomem_reg_shift; + port->mapbase = (unsigned long)new_serial.iomem_base; + + if (port->type != PORT_UNKNOWN) { + retval = port->ops->request_port(port); + } else { + retval = 0; + } + + if (retval && old_type != PORT_UNKNOWN) { + port->iobase = old_iobase; + port->type = old_type; + port->hub6 = old_hub6; + port->iotype = old_iotype; + port->regshift = old_shift; + port->mapbase = old_mapbase; + retval = port->ops->request_port(port); + if (retval) + port->type = PORT_UNKNOWN; + + retval = -EBUSY; + } + } + + port->irq = new_serial.irq; + port->uartclk = new_serial.baud_base * 16; + port->flags = (port->flags & ~UPF_CHANGE_MASK) | + (new_flags & UPF_CHANGE_MASK); + port->custom_divisor = new_serial.custom_divisor; + state->close_delay = new_serial.close_delay; + state->closing_wait = closing_wait; + port->fifosize = new_serial.xmit_fifo_size; + +check_and_exit: + retval = 0; + if (port->type == PORT_UNKNOWN) + goto exit; + if (state->info->flags & UIF_INITIALIZED) { + if (((old_flags ^ port->flags) & UPF_SPD_MASK) || + old_custom_divisor != port->custom_divisor) { + if (port->flags & UPF_SPD_MASK) { + printk(KERN_NOTICE + "%s sets custom speed on ttyMP%d. This " + "is deprecated.\n", current->comm, + port->line); + } + mp_change_speed(state, NULL); + } + } else + retval = mp_startup(state, 1); +exit: + MP_STATE_UNLOCK(state); + return retval; +} + + +static int mp_get_lsr_info(struct sb_uart_state *state, unsigned int *value) +{ + struct sb_uart_port *port = state->port; + unsigned int result; + + result = port->ops->tx_empty(port); + + if (port->x_char || + ((uart_circ_chars_pending(&state->info->xmit) > 0) && + !state->info->tty->flow.stopped && !state->info->tty->hw_stopped)) + result &= ~TIOCSER_TEMT; + + return put_user(result, value); +} + +static int mp_tiocmget(struct tty_struct *tty) +{ + struct sb_uart_state *state = tty->driver_data; + struct sb_uart_port *port = state->port; + int result = -EIO; + + MP_STATE_LOCK(state); + if (!(tty->flags & (1 << TTY_IO_ERROR))) { + result = port->mctrl; + spin_lock_irq(&port->lock); + result |= port->ops->get_mctrl(port); + spin_unlock_irq(&port->lock); + } + MP_STATE_UNLOCK(state); + return result; +} + +static int mp_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear) +{ + struct sb_uart_state *state = tty->driver_data; + struct sb_uart_port *port = state->port; + int ret = -EIO; + + + MP_STATE_LOCK(state); + if (!(tty->flags & (1 << TTY_IO_ERROR))) { + mp_update_mctrl(port, set, clear); + ret = 0; + } + MP_STATE_UNLOCK(state); + + return ret; +} + +static int mp_break_ctl(struct tty_struct *tty, int break_state) +{ + struct sb_uart_state *state = tty->driver_data; + struct sb_uart_port *port = state->port; + + MP_STATE_LOCK(state); + + if (port->type != PORT_UNKNOWN) + port->ops->break_ctl(port, break_state); + + MP_STATE_UNLOCK(state); + return 0; +} + +static int mp_do_autoconfig(struct sb_uart_state *state) +{ + struct sb_uart_port *port = state->port; + int flags, ret; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (mutex_lock_interruptible(&state->mutex)) + return -ERESTARTSYS; + ret = -EBUSY; + if (uart_users(state) == 1) { + mp_shutdown(state); + + if (port->type != PORT_UNKNOWN) + port->ops->release_port(port); + + flags = UART_CONFIG_TYPE; + if (port->flags & UPF_AUTO_IRQ) + flags |= UART_CONFIG_IRQ; + + port->ops->config_port(port, flags); + + ret = mp_startup(state, 1); + } + MP_STATE_UNLOCK(state); + return ret; +} + +static int mp_wait_modem_status(struct sb_uart_state *state, unsigned long arg) +{ + struct sb_uart_port *port = state->port; + DECLARE_WAITQUEUE(wait, current); + struct sb_uart_icount cprev, cnow; + int ret; + + spin_lock_irq(&port->lock); + memcpy(&cprev, &port->icount, sizeof(struct sb_uart_icount)); + + port->ops->enable_ms(port); + spin_unlock_irq(&port->lock); + + add_wait_queue(&state->info->delta_msr_wait, &wait); + for (;;) { + spin_lock_irq(&port->lock); + memcpy(&cnow, &port->icount, sizeof(struct sb_uart_icount)); + spin_unlock_irq(&port->lock); + + set_current_state(TASK_INTERRUPTIBLE); + + if (((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) || + ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) || + ((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd)) || + ((arg & TIOCM_CTS) && (cnow.cts != cprev.cts))) { + ret = 0; + break; + } + + schedule(); + + if (signal_pending(current)) { + ret = -ERESTARTSYS; + break; + } + + cprev = cnow; + } + + current->__state = TASK_RUNNING; + remove_wait_queue(&state->info->delta_msr_wait, &wait); + + return ret; +} + +static int mp_get_count(struct sb_uart_state *state, struct serial_icounter_struct *icnt) +{ + struct serial_icounter_struct icount = {}; + struct sb_uart_icount cnow; + struct sb_uart_port *port = state->port; + + spin_lock_irq(&port->lock); + memcpy(&cnow, &port->icount, sizeof(struct sb_uart_icount)); + spin_unlock_irq(&port->lock); + + icount.cts = cnow.cts; + icount.dsr = cnow.dsr; + icount.rng = cnow.rng; + icount.dcd = cnow.dcd; + icount.rx = cnow.rx; + icount.tx = cnow.tx; + icount.frame = cnow.frame; + icount.overrun = cnow.overrun; + icount.parity = cnow.parity; + icount.brk = cnow.brk; + icount.buf_overrun = cnow.buf_overrun; + + return copy_to_user(icnt, &icount, sizeof(icount)) ? -EFAULT : 0; +} + +static int mp_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) +{ + struct sb_uart_state *state = tty->driver_data; + struct mp_port *info = (struct mp_port *)state->port; + int ret = -ENOIOCTLCMD; + + + switch (cmd) { + case TIOCSMULTIDROP: + /* set multi-drop mode enable or disable, and default operation mode is H/W mode */ + if (info->port.type == PORT_16C105XA) + { + //arg &= ~0x6; + //state->port->mdmode = 0; + return set_multidrop_mode((struct sb_uart_port *)info, (unsigned int)arg); + } + ret = -ENOTSUPP; + break; + case GETDEEPFIFO: + ret = get_deep_fifo(state->port); + return ret; + case SETDEEPFIFO: + ret = set_deep_fifo(state->port,arg); + deep[state->port->line] = arg; + return ret; + case SETTTR: + if (info->port.type == PORT_16C105X || info->port.type == PORT_16C105XA){ + ret = sb1054_set_register(state->port,PAGE_4,SB105X_TTR,arg); + ttr[state->port->line] = arg; + } + return ret; + case SETRTR: + if (info->port.type == PORT_16C105X || info->port.type == PORT_16C105XA){ + ret = sb1054_set_register(state->port,PAGE_4,SB105X_RTR,arg); + rtr[state->port->line] = arg; + } + return ret; + case GETTTR: + if (info->port.type == PORT_16C105X || info->port.type == PORT_16C105XA){ + ret = sb1054_get_register(state->port,PAGE_4,SB105X_TTR); + } + return ret; + case GETRTR: + if (info->port.type == PORT_16C105X || info->port.type == PORT_16C105XA){ + ret = sb1054_get_register(state->port,PAGE_4,SB105X_RTR); + } + return ret; + + case SETFCR: + if (info->port.type == PORT_16C105X || info->port.type == PORT_16C105XA){ + ret = sb1054_set_register(state->port,PAGE_1,SB105X_FCR,arg); + } + else{ + serial_out(info,2,arg); + } + + return ret; + case TIOCSMDADDR: + /* set multi-drop address */ + if (info->port.type == PORT_16C105XA) + { + state->port->mdmode |= MDMODE_ADDR; + return set_multidrop_addr((struct sb_uart_port *)info, (unsigned int)arg); + } + ret = -ENOTSUPP; + break; + + case TIOCGMDADDR: + /* set multi-drop address */ + if ((info->port.type == PORT_16C105XA) && (state->port->mdmode & MDMODE_ADDR)) + { + return get_multidrop_addr((struct sb_uart_port *)info); + } + ret = -ENOTSUPP; + break; + + case TIOCSENDADDR: + /* send address in multi-drop mode */ + if ((info->port.type == PORT_16C105XA) + && (state->port->mdmode & (MDMODE_ENABLE))) + { + if (mp_chars_in_buffer(tty) > 0) + { + tty_wait_until_sent(tty, 0); + } + //while ((serial_in(info, UART_LSR) & 0x60) != 0x60); + //while (sb1054_get_register(state->port, PAGE_2, SB105X_TFCR) != 0); + while ((serial_in(info, UART_LSR) & 0x60) != 0x60); + serial_out(info, UART_SCR, (int)arg); + } + break; + + case TIOCGSERIAL: + ret = mp_get_info(state, (struct serial_struct *)arg); + break; + + case TIOCSSERIAL: + ret = mp_set_info(state, (struct serial_struct *)arg); + break; + + case TIOCSERCONFIG: + ret = mp_do_autoconfig(state); + break; + + case TIOCSERGWILD: /* obsolete */ + case TIOCSERSWILD: /* obsolete */ + ret = 0; + break; + /* for Multiport */ + case TIOCGNUMOFPORT: /* Get number of ports */ + return NR_PORTS; + case TIOCGGETDEVID: + return mp_devs[arg].device_id; + case TIOCGGETREV: + return mp_devs[arg].revision; + case TIOCGGETNRPORTS: + return mp_devs[arg].nr_ports; + case TIOCGGETBDNO: + return NR_BOARD; + case TIOCGGETINTERFACE: + if (mp_devs[arg].revision == 0xc0) + { + /* for SB16C1053APCI */ + return (sb1053a_get_interface(info, info->port.line)); + } + else + { + return (inb(mp_devs[arg].option_reg_addr+MP_OPTR_IIR0+(state->port->line/8))); + } + case TIOCGGETPORTTYPE: + ret = get_device_type(arg); + return ret; + case TIOCSMULTIECHO: /* set to multi-drop mode(RS422) or echo mode(RS485)*/ + outb( ( inb(info->interface_config_addr) & ~0x03 ) | 0x01 , + info->interface_config_addr); + return 0; + case TIOCSPTPNOECHO: /* set to multi-drop mode(RS422) or echo mode(RS485) */ + outb( ( inb(info->interface_config_addr) & ~0x03 ) , + info->interface_config_addr); + return 0; + } + + if (ret != -ENOIOCTLCMD) + goto out; + + if (tty->flags & (1 << TTY_IO_ERROR)) { + ret = -EIO; + goto out; + } + + switch (cmd) { + case TIOCMIWAIT: + ret = mp_wait_modem_status(state, arg); + break; + + case TIOCGICOUNT: + ret = mp_get_count(state, (struct serial_icounter_struct *)arg); + break; + } + + if (ret != -ENOIOCTLCMD) + goto out; + + MP_STATE_LOCK(state); + switch (cmd) { + case TIOCSERGETLSR: /* Get line status register */ + ret = mp_get_lsr_info(state, (unsigned int *)arg); + break; + + default: { + struct sb_uart_port *port = state->port; + if (port->ops->ioctl) + ret = port->ops->ioctl(port, cmd, arg); + break; + } + } + + MP_STATE_UNLOCK(state); +out: + return ret; +} + +static void mp_set_termios(struct tty_struct *tty, const struct MP_TERMIOS *old_termios) +{ + struct sb_uart_state *state = tty->driver_data; + unsigned long flags; + unsigned int cflag = tty->termios.c_cflag; + +#define RELEVANT_IFLAG(iflag) ((iflag) & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK)) + + if ((cflag ^ old_termios->c_cflag) == 0 && + RELEVANT_IFLAG(tty->termios.c_iflag ^ old_termios->c_iflag) == 0) + return; + + mp_change_speed(state, old_termios); + + if ((old_termios->c_cflag & CBAUD) && !(cflag & CBAUD)) + uart_clear_mctrl(state->port, TIOCM_RTS | TIOCM_DTR); + + if (!(old_termios->c_cflag & CBAUD) && (cflag & CBAUD)) { + unsigned int mask = TIOCM_DTR; + if (!(cflag & CRTSCTS) || + !test_bit(TTY_THROTTLED, &tty->flags)) + mask |= TIOCM_RTS; + uart_set_mctrl(state->port, mask); + } + + if ((old_termios->c_cflag & CRTSCTS) && !(cflag & CRTSCTS)) { + spin_lock_irqsave(&state->port->lock, flags); + tty->hw_stopped = 0; + __mp_start(tty); + spin_unlock_irqrestore(&state->port->lock, flags); + } + + if (!(old_termios->c_cflag & CRTSCTS) && (cflag & CRTSCTS)) { + spin_lock_irqsave(&state->port->lock, flags); + if (!(state->port->ops->get_mctrl(state->port) & TIOCM_CTS)) { + tty->hw_stopped = 1; + state->port->ops->stop_tx(state->port); + } + spin_unlock_irqrestore(&state->port->lock, flags); + } +} + +static void mp_close(struct tty_struct *tty, struct file *filp) +{ + struct sb_uart_state *state = tty->driver_data; + struct sb_uart_port *port; + + if (!state || !state->port) + return; + + port = state->port; + + MP_STATE_LOCK(state); + + if (tty_hung_up_p(filp)) + goto done; + + if ((tty->count == 1) && (state->count != 1)) { + printk("mp_close: bad serial port count; tty->count is 1, " + "state->count is %d\n", state->count); + state->count = 1; + } + if (--state->count < 0) { + printk("rs_close: bad serial port count for ttyMP%d: %d\n", + port->line, state->count); + state->count = 0; + } + if (state->count) + goto done; + + tty->closing = 1; + + if (state->closing_wait != USF_CLOSING_WAIT_NONE) + tty_wait_until_sent(tty, state->closing_wait); + + if (state->info->flags & UIF_INITIALIZED) { + unsigned long flags; + spin_lock_irqsave(&port->lock, flags); + port->ops->stop_rx(port); + spin_unlock_irqrestore(&port->lock, flags); + mp_wait_until_sent(tty, port->timeout); + } + + mp_shutdown(state); + mp_flush_buffer(tty); + tty_ldisc_flush(tty); + tty->closing = 0; + state->info->tty = NULL; + if (state->info->blocked_open) + { + if (state->close_delay) + { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(state->close_delay); + } + } + else + { + mp_change_pm(state, 3); + } + + state->info->flags &= ~UIF_NORMAL_ACTIVE; + wake_up_interruptible(&state->info->open_wait); + +done: + MP_STATE_UNLOCK(state); + module_put(THIS_MODULE); +} + +static void mp_wait_until_sent(struct tty_struct *tty, int timeout) +{ + struct sb_uart_state *state = tty->driver_data; + struct sb_uart_port *port = state->port; + unsigned long char_time, expire; + + if (port->type == PORT_UNKNOWN || port->fifosize == 0) + return; + + char_time = (port->timeout - HZ/50) / port->fifosize; + char_time = char_time / 5; + if (char_time == 0) + char_time = 1; + if (timeout && timeout < char_time) + char_time = timeout; + + if (timeout == 0 || timeout > 2 * port->timeout) + timeout = 2 * port->timeout; + + expire = jiffies + timeout; + + while (!port->ops->tx_empty(port)) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(char_time); + if (signal_pending(current)) + break; + if (time_after(jiffies, expire)) + break; + } + set_current_state(TASK_RUNNING); /* might not be needed */ +} + +static void mp_hangup(struct tty_struct *tty) +{ + struct sb_uart_state *state = tty->driver_data; + + MP_STATE_LOCK(state); + if (state->info && state->info->flags & UIF_NORMAL_ACTIVE) { + mp_flush_buffer(tty); + mp_shutdown(state); + state->count = 0; + state->info->flags &= ~UIF_NORMAL_ACTIVE; + state->info->tty = NULL; + wake_up_interruptible(&state->info->open_wait); + wake_up_interruptible(&state->info->delta_msr_wait); + } + MP_STATE_UNLOCK(state); +} + +static void mp_update_termios(struct sb_uart_state *state) +{ + struct tty_struct *tty = state->info->tty; + struct sb_uart_port *port = state->port; + + if (!(tty->flags & (1 << TTY_IO_ERROR))) { + mp_change_speed(state, NULL); + + if (tty->termios.c_cflag & CBAUD) + uart_set_mctrl(port, TIOCM_DTR | TIOCM_RTS); + } +} + +static int mp_block_til_ready(struct file *filp, struct sb_uart_state *state) +{ + DECLARE_WAITQUEUE(wait, current); + struct sb_uart_info *info = state->info; + struct sb_uart_port *port = state->port; + unsigned int mctrl; + + info->blocked_open++; + state->count--; + + add_wait_queue(&info->open_wait, &wait); + while (1) { + set_current_state(TASK_INTERRUPTIBLE); + + if (tty_hung_up_p(filp) || info->tty == NULL) + break; + + if (!(info->flags & UIF_INITIALIZED)) + break; + + if ((filp->f_flags & O_NONBLOCK) || + (info->tty->termios.c_cflag & CLOCAL) || + (info->tty->flags & (1 << TTY_IO_ERROR))) { + break; + } + + if (info->tty->termios.c_cflag & CBAUD) + uart_set_mctrl(port, TIOCM_DTR); + + spin_lock_irq(&port->lock); + port->ops->enable_ms(port); + mctrl = port->ops->get_mctrl(port); + spin_unlock_irq(&port->lock); + if (mctrl & TIOCM_CAR) + break; + + MP_STATE_UNLOCK(state); + schedule(); + MP_STATE_LOCK(state); + + if (signal_pending(current)) + break; + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&info->open_wait, &wait); + + state->count++; + info->blocked_open--; + + if (signal_pending(current)) + return -ERESTARTSYS; + + if (!info->tty || tty_hung_up_p(filp)) + return -EAGAIN; + + return 0; +} + +static struct sb_uart_state *uart_get(struct uart_driver *drv, int line) +{ + struct sb_uart_state *state; + + MP_MUTEX_LOCK(mp_mutex); + state = drv->state + line; + if (mutex_lock_interruptible(&state->mutex)) { + state = ERR_PTR(-ERESTARTSYS); + goto out; + } + state->count++; + if (!state->port) { + state->count--; + MP_STATE_UNLOCK(state); + state = ERR_PTR(-ENXIO); + goto out; + } + + if (!state->info) { + state->info = kmalloc(sizeof(struct sb_uart_info), GFP_KERNEL); + if (state->info) { + memset(state->info, 0, sizeof(struct sb_uart_info)); + init_waitqueue_head(&state->info->open_wait); + init_waitqueue_head(&state->info->delta_msr_wait); + + state->port->info = state->info; + + tasklet_init(&state->info->tlet, mp_tasklet_action, + (unsigned long)state); + } else { + state->count--; + MP_STATE_UNLOCK(state); + state = ERR_PTR(-ENOMEM); + } + } + +out: + MP_MUTEX_UNLOCK(mp_mutex); + return state; +} + +static int mp_open(struct tty_struct *tty, struct file *filp) +{ + struct uart_driver *drv = (struct uart_driver *)tty->driver->driver_state; + struct sb_uart_state *state; + int retval; + int line = tty->index; + struct mp_port *mtpt; + + retval = -ENODEV; + if (line >= tty->driver->num) + goto fail; + + state = uart_get(drv, line); + + if (IS_ERR(state)) { + retval = PTR_ERR(state); + goto fail; + } + + mtpt = (struct mp_port *)state->port; + + tty->driver_data = state; + state->info->tty = tty; + + if (tty_hung_up_p(filp)) { + retval = -EAGAIN; + state->count--; + MP_STATE_UNLOCK(state); + goto fail; + } + + if (state->count == 1) + mp_change_pm(state, 0); + + retval = mp_startup(state, 0); + + if (retval == 0) + retval = mp_block_til_ready(filp, state); + MP_STATE_UNLOCK(state); + + if (retval == 0 && !(state->info->flags & UIF_NORMAL_ACTIVE)) { + state->info->flags |= UIF_NORMAL_ACTIVE; + + mp_update_termios(state); + } + + uart_clear_mctrl(state->port, TIOCM_RTS); + try_module_get(THIS_MODULE); +fail: + return retval; +} + + +static const char *mp_type(struct sb_uart_port *port) +{ + const char *str = NULL; + + if (port->ops->type) + str = port->ops->type(port); + + if (!str) + str = "unknown"; + + return str; +} + +static void mp_change_pm(struct sb_uart_state *state, int pm_state) +{ + struct sb_uart_port *port = state->port; + if (port->ops->pm) + port->ops->pm(port, pm_state, state->pm_state); + state->pm_state = pm_state; +} + +static inline void mp_report_port(struct uart_driver *drv, struct sb_uart_port *port) +{ + char address[64]; + + switch (port->iotype) { + case UPIO_PORT: + snprintf(address, sizeof(address),"I/O 0x%x", port->iobase); + break; + case UPIO_HUB6: + snprintf(address, sizeof(address),"I/O 0x%x offset 0x%x", port->iobase, port->hub6); + break; + case UPIO_MEM: + snprintf(address, sizeof(address),"MMIO 0x%lx", port->mapbase); + break; + default: + snprintf(address, sizeof(address),"*unknown*" ); + strscpy(address, "*unknown*", sizeof(address)); + break; + } + + printk( "%s%d at %s (irq = %d) is a %s\n", + drv->dev_name, port->line, address, port->irq, mp_type(port)); + +} + +static void mp_configure_port(struct uart_driver *drv, struct sb_uart_state *state, struct sb_uart_port *port) +{ + unsigned int flags; + + + if (!port->iobase && !port->mapbase && !port->membase) + { + DPRINTK("%s error \n",__FUNCTION__); + return; + } + flags = UART_CONFIG_TYPE; + if (port->flags & UPF_AUTO_IRQ) + flags |= UART_CONFIG_IRQ; + if (port->flags & UPF_BOOT_AUTOCONF) { + port->type = PORT_UNKNOWN; + port->ops->config_port(port, flags); + } + + if (port->type != PORT_UNKNOWN) { + unsigned long flags; + + mp_report_port(drv, port); + + spin_lock_irqsave(&port->lock, flags); + port->ops->set_mctrl(port, 0); + spin_unlock_irqrestore(&port->lock, flags); + + mp_change_pm(state, 3); + } +} + +static void mp_unconfigure_port(struct uart_driver *drv, struct sb_uart_state *state) +{ + struct sb_uart_port *port = state->port; + struct sb_uart_info *info = state->info; + + if (info && info->tty) + tty_hangup(info->tty); + + MP_STATE_LOCK(state); + + state->info = NULL; + + if (port->type != PORT_UNKNOWN) + port->ops->release_port(port); + + port->type = PORT_UNKNOWN; + + if (info) { + tasklet_kill(&info->tlet); + kfree(info); + } + + MP_STATE_UNLOCK(state); +} +static struct tty_operations sb_mp_ops = { + .open = mp_open, + .close = mp_close, + .write = mp_write, + .put_char = mp_put_char, + .flush_chars = mp_put_chars, + .write_room = mp_write_room, + .chars_in_buffer= mp_chars_in_buffer, + .flush_buffer = mp_flush_buffer, + .ioctl = mp_ioctl, + .throttle = mp_throttle, + .unthrottle = mp_unthrottle, + .send_xchar = mp_send_xchar, + .set_termios = mp_set_termios, + .stop = mp_stop, + .start = mp_start, + .hangup = mp_hangup, + .break_ctl = mp_break_ctl, + .wait_until_sent= mp_wait_until_sent, +#ifdef CONFIG_PROC_FS + .proc_show = NULL, +#endif + .tiocmget = mp_tiocmget, + .tiocmset = mp_tiocmset, +}; + +static int mp_register_driver(struct uart_driver *drv) +{ + struct tty_driver *normal = NULL; + int i, retval; + + drv->state = kmalloc(sizeof(struct sb_uart_state) * drv->nr, GFP_KERNEL); + retval = -ENOMEM; + if (!drv->state) + { + printk("SB PCI Error: Kernel memory allocation error!\n"); + goto out; + } + memset(drv->state, 0, sizeof(struct sb_uart_state) * drv->nr); + + normal = tty_alloc_driver(drv->nr, + TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV); + if (IS_ERR(normal)) + return PTR_ERR(normal); + + drv->tty_driver = normal; + + normal->owner = drv->owner; + normal->driver_name = drv->driver_name; + normal->name = drv->dev_name; + normal->major = drv->major; + normal->minor_start = drv->minor; + + normal->num = MAX_MP_PORT ; + + normal->type = TTY_DRIVER_TYPE_SERIAL; + normal->subtype = SERIAL_TYPE_NORMAL; + normal->init_termios = tty_std_termios; + normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; + normal->driver_state = drv; + + tty_set_operations(normal, &sb_mp_ops); + +for (i = 0; i < drv->nr; i++) { + struct sb_uart_state *state = drv->state + i; + + state->close_delay = 500; + state->closing_wait = 30000; + + mutex_init(&state->mutex); + } + + retval = tty_register_driver(normal); +out: + if (retval < 0) { + printk("Register tty driver Fail!\n"); + tty_driver_kref_put(normal); + kfree(drv->state); + } + + return retval; +} + +void mp_unregister_driver(struct uart_driver *drv) +{ + struct tty_driver *normal = NULL; + + normal = drv->tty_driver; + + if (!normal) + { + return; + } + + tty_unregister_driver(normal); + tty_driver_kref_put(normal); + drv->tty_driver = NULL; + + + kfree(drv->state); + +} + +static int mp_add_one_port(struct uart_driver *drv, struct sb_uart_port *port) +{ + struct sb_uart_state *state; + int ret = 0; + + + if (port->line >= drv->nr) + return -EINVAL; + + state = drv->state + port->line; + + MP_MUTEX_LOCK(mp_mutex); + if (state->port) { + ret = -EINVAL; + goto out; + } + + state->port = port; + + spin_lock_init(&port->lock); + port->cons = drv->cons; + port->info = state->info; + + mp_configure_port(drv, state, port); + + tty_port_link_device(&tt_port[port->line], drv->tty_driver, port->line); + tty_register_device(drv->tty_driver, port->line, port->dev); + +out: + MP_MUTEX_UNLOCK(mp_mutex); + + + return ret; +} + +static int mp_remove_one_port(struct uart_driver *drv, struct sb_uart_port *port) +{ + struct sb_uart_state *state = drv->state + port->line; + + if (state->port != port) + printk(KERN_ALERT "Removing wrong port: %p != %p\n", + state->port, port); + + MP_MUTEX_LOCK(mp_mutex); + + tty_unregister_device(drv->tty_driver, port->line); + + mp_unconfigure_port(drv, state); + state->port = NULL; + MP_MUTEX_UNLOCK(mp_mutex); + + return 0; +} + +static void autoconfig(struct mp_port *mtpt, unsigned int probeflags) +{ + unsigned char status1, scratch, scratch2, scratch3; + unsigned char save_lcr, save_mcr; + unsigned long flags; + + unsigned char u_type; + unsigned char b_ret = 0; + + if (!mtpt->port.iobase && !mtpt->port.mapbase && !mtpt->port.membase) + return; + + DEBUG_AUTOCONF("ttyMP%d: autoconf (0x%04x, 0x%p): ", + mtpt->port.line, mtpt->port.iobase, mtpt->port.membase); + + spin_lock_irqsave(&mtpt->port.lock, flags); + + if (!(mtpt->port.flags & UPF_BUGGY_UART)) { + scratch = serial_inp(mtpt, UART_IER); + serial_outp(mtpt, UART_IER, 0); +#ifdef __i386__ + outb(0xff, 0x080); +#endif + scratch2 = serial_inp(mtpt, UART_IER) & 0x0f; + serial_outp(mtpt, UART_IER, 0x0F); +#ifdef __i386__ + outb(0, 0x080); +#endif + scratch3 = serial_inp(mtpt, UART_IER) & 0x0F; + serial_outp(mtpt, UART_IER, scratch); + if (scratch2 != 0 || scratch3 != 0x0F) { + DEBUG_AUTOCONF("IER test failed (%02x, %02x) ", + scratch2, scratch3); + goto out; + } + } + + save_mcr = serial_in(mtpt, UART_MCR); + save_lcr = serial_in(mtpt, UART_LCR); + + if (!(mtpt->port.flags & UPF_SKIP_TEST)) { + serial_outp(mtpt, UART_MCR, UART_MCR_LOOP | 0x0A); + status1 = serial_inp(mtpt, UART_MSR) & 0xF0; + serial_outp(mtpt, UART_MCR, save_mcr); + if (status1 != 0x90) { + DEBUG_AUTOCONF("LOOP test failed (%02x) ", + status1); + goto out; + } + } + + serial_outp(mtpt, UART_LCR, 0xBF); + serial_outp(mtpt, UART_EFR, 0); + serial_outp(mtpt, UART_LCR, 0); + + serial_outp(mtpt, UART_FCR, UART_FCR_ENABLE_FIFO); + scratch = serial_in(mtpt, UART_IIR) >> 6; + + DEBUG_AUTOCONF("iir=%d ", scratch); + if(mtpt->device->nr_ports >= 8) + b_ret = read_option_register(mtpt,(MP_OPTR_DIR0 + ((mtpt->port.line)/8))); + else + b_ret = read_option_register(mtpt,MP_OPTR_DIR0); + u_type = (b_ret & 0xf0) >> 4; + if(mtpt->port.type == PORT_UNKNOWN ) + { + switch (u_type) + { + case DIR_UART_16C550: + mtpt->port.type = PORT_16C55X; + break; + case DIR_UART_16C1050: + mtpt->port.type = PORT_16C105X; + break; + case DIR_UART_16C1050A: + if (mtpt->port.line < 2) + { + mtpt->port.type = PORT_16C105XA; + } + else + { + if (mtpt->device->device_id & 0x50) + { + mtpt->port.type = PORT_16C55X; + } + else + { + mtpt->port.type = PORT_16C105X; + } + } + break; + default: + mtpt->port.type = PORT_UNKNOWN; + break; + } + } + + if(mtpt->port.type == PORT_UNKNOWN ) + { +printk("unknow2\n"); + switch (scratch) { + case 0: + case 1: + mtpt->port.type = PORT_UNKNOWN; + break; + case 2: + case 3: + mtpt->port.type = PORT_16C55X; + break; + } + } + + serial_outp(mtpt, UART_LCR, save_lcr); + + mtpt->port.fifosize = uart_config[mtpt->port.type].dfl_xmit_fifo_size; + mtpt->capabilities = uart_config[mtpt->port.type].flags; + + if (mtpt->port.type == PORT_UNKNOWN) + goto out; + serial_outp(mtpt, UART_MCR, save_mcr); + serial_outp(mtpt, UART_FCR, (UART_FCR_ENABLE_FIFO | + UART_FCR_CLEAR_RCVR | + UART_FCR_CLEAR_XMIT)); + serial_outp(mtpt, UART_FCR, 0); + (void)serial_in(mtpt, UART_RX); + serial_outp(mtpt, UART_IER, 0); + +out: + spin_unlock_irqrestore(&mtpt->port.lock, flags); + DEBUG_AUTOCONF("type=%s\n", uart_config[mtpt->port.type].name); +} + +static void autoconfig_irq(struct mp_port *mtpt) +{ + unsigned char save_mcr, save_ier; + unsigned long irqs; + int irq; + + /* forget possible initially masked and pending IRQ */ + probe_irq_off(probe_irq_on()); + save_mcr = serial_inp(mtpt, UART_MCR); + save_ier = serial_inp(mtpt, UART_IER); + serial_outp(mtpt, UART_MCR, UART_MCR_OUT1 | UART_MCR_OUT2); + + irqs = probe_irq_on(); + serial_outp(mtpt, UART_MCR, 0); + serial_outp(mtpt, UART_MCR, + UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2); + + serial_outp(mtpt, UART_IER, 0x0f); /* enable all intrs */ + (void)serial_inp(mtpt, UART_LSR); + (void)serial_inp(mtpt, UART_RX); + (void)serial_inp(mtpt, UART_IIR); + (void)serial_inp(mtpt, UART_MSR); + serial_outp(mtpt, UART_TX, 0xFF); + irq = probe_irq_off(irqs); + + serial_outp(mtpt, UART_MCR, save_mcr); + serial_outp(mtpt, UART_IER, save_ier); + + mtpt->port.irq = (irq > 0) ? irq : 0; +} + +static void multi_stop_tx(struct sb_uart_port *port) +{ + struct mp_port *mtpt = (struct mp_port *)port; + + if (mtpt->ier & UART_IER_THRI) { + mtpt->ier &= ~UART_IER_THRI; + serial_out(mtpt, UART_IER, mtpt->ier); + } + + tasklet_schedule(&port->info->tlet); +} + +static void multi_start_tx(struct sb_uart_port *port) +{ + struct mp_port *mtpt = (struct mp_port *)port; + + if (!(mtpt->ier & UART_IER_THRI)) { + mtpt->ier |= UART_IER_THRI; + serial_out(mtpt, UART_IER, mtpt->ier); + } +} + +static void multi_stop_rx(struct sb_uart_port *port) +{ + struct mp_port *mtpt = (struct mp_port *)port; + + mtpt->ier &= ~UART_IER_RLSI; + mtpt->port.read_status_mask &= ~UART_LSR_DR; + serial_out(mtpt, UART_IER, mtpt->ier); +} + +static void multi_enable_ms(struct sb_uart_port *port) +{ + struct mp_port *mtpt = (struct mp_port *)port; + + mtpt->ier |= UART_IER_MSI; + serial_out(mtpt, UART_IER, mtpt->ier); +} + + +static _INLINE_ void receive_chars(struct mp_port *mtpt, int *status ) +{ + unsigned char lsr = *status; + int max_count = 256; + unsigned char ch; + char flag; + + //lsr &= mtpt->port.read_status_mask; + + do { + if ((lsr & UART_LSR_PE) && (mtpt->port.mdmode & MDMODE_ENABLE)) + { + ch = serial_inp(mtpt, UART_RX); + } + else if (lsr & UART_LSR_SPECIAL) + { + flag = 0; + ch = serial_inp(mtpt, UART_RX); + + if (lsr & UART_LSR_BI) + { + + mtpt->port.icount.brk++; + flag = TTY_BREAK; + + if (sb_uart_handle_break(&mtpt->port)) + goto ignore_char; + } + if (lsr & UART_LSR_PE) + { + mtpt->port.icount.parity++; + flag = TTY_PARITY; + } + if (lsr & UART_LSR_FE) + { + mtpt->port.icount.frame++; + flag = TTY_FRAME; + } + if (lsr & UART_LSR_OE) + { + mtpt->port.icount.overrun++; + flag = TTY_OVERRUN; + } + tty_insert_flip_char(&tt_port[mtpt->port.line], ch, flag); + } + else + { + ch = serial_inp(mtpt, UART_RX); + tty_insert_flip_char(&tt_port[mtpt->port.line], ch, 0); + } +ignore_char: + lsr = serial_inp(mtpt, UART_LSR); + } while ((lsr & UART_LSR_DR) && (max_count-- > 0)); + + tty_flip_buffer_push(&tt_port[mtpt->port.line]); +} + + + + +static _INLINE_ void transmit_chars(struct mp_port *mtpt) +{ + struct circ_buf *xmit = &mtpt->port.info->xmit; + int count; + + if (mtpt->port.x_char) { + serial_outp(mtpt, UART_TX, mtpt->port.x_char); + mtpt->port.icount.tx++; + mtpt->port.x_char = 0; + return; + } + if (uart_circ_empty(xmit) || uart_tx_stopped(&mtpt->port)) { + multi_stop_tx(&mtpt->port); + return; + } + + count = uart_circ_chars_pending(xmit); + + if(count > mtpt->port.fifosize) + { + count = mtpt->port.fifosize; + } + + do { +#if 0 + /* check multi-drop mode */ + if ((mtpt->port.mdmode & (MDMODE_ENABLE | MDMODE_ADDR)) == (MDMODE_ENABLE | MDMODE_ADDR)) + { + printk("send address\n"); + /* send multi-drop address */ + serial_out(mtpt, UART_SCR, xmit->buf[xmit->tail]); + } + else +#endif + { + serial_out(mtpt, UART_TX, xmit->buf[xmit->tail]); + } + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + mtpt->port.icount.tx++; + } while (--count > 0); +} + + + +static _INLINE_ void check_modem_status(struct mp_port *mtpt) +{ + int status; + + status = serial_in(mtpt, UART_MSR); + + if ((status & UART_MSR_ANY_DELTA) == 0) + return; + + if (status & UART_MSR_TERI) + mtpt->port.icount.rng++; + if (status & UART_MSR_DDSR) + mtpt->port.icount.dsr++; + if (status & UART_MSR_DDCD) + sb_uart_handle_dcd_change(&mtpt->port, status & UART_MSR_DCD); + if (status & UART_MSR_DCTS) + sb_uart_handle_cts_change(&mtpt->port, status & UART_MSR_CTS); + + wake_up_interruptible(&mtpt->port.info->delta_msr_wait); +} + +static inline void multi_handle_port(struct mp_port *mtpt) +{ + unsigned int status = serial_inp(mtpt, UART_LSR); + + //printk("lsr: %x\n", status); + + if ((status & UART_LSR_DR) || (status & UART_LSR_SPECIAL)) + receive_chars(mtpt, &status); + check_modem_status(mtpt); + if (status & UART_LSR_THRE) + { + if ((mtpt->port.type == PORT_16C105X) + || (mtpt->port.type == PORT_16C105XA)) + transmit_chars(mtpt); + else + { + if (mtpt->interface >= RS485NE) + uart_set_mctrl(&mtpt->port, TIOCM_RTS); + + transmit_chars(mtpt); + + + if (mtpt->interface >= RS485NE) + { + while((status=serial_in(mtpt,UART_LSR) &0x60)!=0x60); + uart_clear_mctrl(&mtpt->port, TIOCM_RTS); + } + } + } +} + + + +static irqreturn_t multi_interrupt(int irq, void *dev_id) +{ + struct irq_info *iinfo = dev_id; + struct list_head *lhead, *end = NULL; + int pass_counter = 0; + + + spin_lock(&iinfo->lock); + + lhead = iinfo->head; + do { + struct mp_port *mtpt; + unsigned int iir; + + mtpt = list_entry(lhead, struct mp_port, list); + + iir = serial_in(mtpt, UART_IIR); + if (!(iir & UART_IIR_NO_INT)) + { + spin_lock(&mtpt->port.lock); + multi_handle_port(mtpt); + spin_unlock(&mtpt->port.lock); + + end = NULL; + } else if (end == NULL) + end = lhead; + + lhead = lhead->next; + if (lhead == iinfo->head && pass_counter++ > PASS_LIMIT) + { + printk(KERN_ERR "multi: too much work for " + "irq%d\n", irq); + printk( "multi: too much work for " + "irq%d\n", irq); + break; + } + } while (lhead != end); + + spin_unlock(&iinfo->lock); + + + return IRQ_HANDLED; +} + +static void serial_do_unlink(struct irq_info *i, struct mp_port *mtpt) +{ + spin_lock_irq(&i->lock); + + if (!list_empty(i->head)) { + if (i->head == &mtpt->list) + i->head = i->head->next; + list_del(&mtpt->list); + } else { + i->head = NULL; + } + + spin_unlock_irq(&i->lock); +} + +static int serial_link_irq_chain(struct mp_port *mtpt) +{ + struct irq_info *i = irq_lists + mtpt->port.irq; + int ret, irq_flags = mtpt->port.flags & UPF_SHARE_IRQ ? IRQF_SHARED : 0; + spin_lock_irq(&i->lock); + + if (i->head) { + list_add(&mtpt->list, i->head); + spin_unlock_irq(&i->lock); + + ret = 0; + } else { + INIT_LIST_HEAD(&mtpt->list); + i->head = &mtpt->list; + spin_unlock_irq(&i->lock); + + ret = request_irq(mtpt->port.irq, multi_interrupt, + irq_flags, "serial", i); + if (ret < 0) + serial_do_unlink(i, mtpt); + } + + return ret; +} + + + + +static void serial_unlink_irq_chain(struct mp_port *mtpt) +{ + struct irq_info *i = irq_lists + mtpt->port.irq; + + if (list_empty(i->head)) + { + free_irq(mtpt->port.irq, i); + } + serial_do_unlink(i, mtpt); +} + +static void multi_timeout(struct timer_list *t) +{ + struct mp_port *mtpt = from_timer(mtpt, t, timer); + + spin_lock(&mtpt->port.lock); + multi_handle_port(mtpt); + spin_unlock(&mtpt->port.lock); + + mod_timer(&mtpt->timer, jiffies+1 ); +} + +static unsigned int multi_tx_empty(struct sb_uart_port *port) +{ + struct mp_port *mtpt = (struct mp_port *)port; + unsigned long flags; + unsigned int ret; + + spin_lock_irqsave(&mtpt->port.lock, flags); + ret = serial_in(mtpt, UART_LSR) & UART_LSR_TEMT ? TIOCSER_TEMT : 0; + spin_unlock_irqrestore(&mtpt->port.lock, flags); + + return ret; +} + + +static unsigned int multi_get_mctrl(struct sb_uart_port *port) +{ + struct mp_port *mtpt = (struct mp_port *)port; + unsigned char status; + unsigned int ret; + + status = serial_in(mtpt, UART_MSR); + + ret = 0; + if (status & UART_MSR_DCD) + ret |= TIOCM_CAR; + if (status & UART_MSR_RI) + ret |= TIOCM_RNG; + if (status & UART_MSR_DSR) + ret |= TIOCM_DSR; + if (status & UART_MSR_CTS) + ret |= TIOCM_CTS; + return ret; +} + +static void multi_set_mctrl(struct sb_uart_port *port, unsigned int mctrl) +{ + struct mp_port *mtpt = (struct mp_port *)port; + unsigned char mcr = 0; + + mctrl &= 0xff; + + if (mctrl & TIOCM_RTS) + mcr |= UART_MCR_RTS; + if (mctrl & TIOCM_DTR) + mcr |= UART_MCR_DTR; + if (mctrl & TIOCM_OUT1) + mcr |= UART_MCR_OUT1; + if (mctrl & TIOCM_OUT2) + mcr |= UART_MCR_OUT2; + if (mctrl & TIOCM_LOOP) + mcr |= UART_MCR_LOOP; + + + serial_out(mtpt, UART_MCR, mcr); +} + + +static void multi_break_ctl(struct sb_uart_port *port, int break_state) +{ + struct mp_port *mtpt = (struct mp_port *)port; + unsigned long flags; + + spin_lock_irqsave(&mtpt->port.lock, flags); + if (break_state == -1) + mtpt->lcr |= UART_LCR_SBC; + else + mtpt->lcr &= ~UART_LCR_SBC; + serial_out(mtpt, UART_LCR, mtpt->lcr); + spin_unlock_irqrestore(&mtpt->port.lock, flags); +} + + + +static int multi_startup(struct sb_uart_port *port) +{ + struct mp_port *mtpt = (struct mp_port *)port; + unsigned long flags; + int retval; + + mtpt->capabilities = uart_config[mtpt->port.type].flags; + mtpt->mcr = 0; + + if (mtpt->capabilities & UART_CLEAR_FIFO) { + serial_outp(mtpt, UART_FCR, UART_FCR_ENABLE_FIFO); + serial_outp(mtpt, UART_FCR, UART_FCR_ENABLE_FIFO | + UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT); + serial_outp(mtpt, UART_FCR, 0); + } + + (void) serial_inp(mtpt, UART_LSR); + (void) serial_inp(mtpt, UART_RX); + (void) serial_inp(mtpt, UART_IIR); + (void) serial_inp(mtpt, UART_MSR); + //test-wlee 9-bit disable + serial_outp(mtpt, UART_MSR, 0); + + + if (!(mtpt->port.flags & UPF_BUGGY_UART) && + (serial_inp(mtpt, UART_LSR) == 0xff)) { + printk("ttyS%d: LSR safety check engaged!\n", mtpt->port.line); + //return -ENODEV; + } + + if ((!is_real_interrupt(mtpt->port.irq)) || (mtpt->poll_type==TYPE_POLL)) { + unsigned int timeout = mtpt->port.timeout; + + timeout = timeout > 6 ? (timeout / 2 - 2) : 1; + + mod_timer(&mtpt->timer, jiffies + timeout); + } + else + { + retval = serial_link_irq_chain(mtpt); + if (retval) + return retval; + } + + serial_outp(mtpt, UART_LCR, UART_LCR_WLEN8); + + spin_lock_irqsave(&mtpt->port.lock, flags); + if ((is_real_interrupt(mtpt->port.irq))||(mtpt->poll_type==TYPE_INTERRUPT)) + mtpt->port.mctrl |= TIOCM_OUT2; + + multi_set_mctrl(&mtpt->port, mtpt->port.mctrl); + spin_unlock_irqrestore(&mtpt->port.lock, flags); + + + mtpt->ier = UART_IER_RLSI | UART_IER_RDI; + serial_outp(mtpt, UART_IER, mtpt->ier); + + (void) serial_inp(mtpt, UART_LSR); + (void) serial_inp(mtpt, UART_RX); + (void) serial_inp(mtpt, UART_IIR); + (void) serial_inp(mtpt, UART_MSR); + + return 0; +} + + + +static void multi_shutdown(struct sb_uart_port *port) +{ + struct mp_port *mtpt = (struct mp_port *)port; + unsigned long flags; + + + mtpt->ier = 0; + serial_outp(mtpt, UART_IER, 0); + + spin_lock_irqsave(&mtpt->port.lock, flags); + mtpt->port.mctrl &= ~TIOCM_OUT2; + + multi_set_mctrl(&mtpt->port, mtpt->port.mctrl); + spin_unlock_irqrestore(&mtpt->port.lock, flags); + + serial_out(mtpt, UART_LCR, serial_inp(mtpt, UART_LCR) & ~UART_LCR_SBC); + serial_outp(mtpt, UART_FCR, UART_FCR_ENABLE_FIFO | + UART_FCR_CLEAR_RCVR | + UART_FCR_CLEAR_XMIT); + serial_outp(mtpt, UART_FCR, 0); + + + (void) serial_in(mtpt, UART_RX); + + if ((!is_real_interrupt(mtpt->port.irq))||(mtpt->poll_type==TYPE_POLL)) + { + del_timer_sync(&mtpt->timer); + } + else + { + serial_unlink_irq_chain(mtpt); + } +} + + + +static unsigned int multi_get_divisor(struct sb_uart_port *port, unsigned int baud) +{ + unsigned int quot; + + if ((port->flags & UPF_MAGIC_MULTIPLIER) && + baud == (port->uartclk/4)) + quot = 0x8001; + else if ((port->flags & UPF_MAGIC_MULTIPLIER) && + baud == (port->uartclk/8)) + quot = 0x8002; + else + quot = sb_uart_get_divisor(port, baud); + + return quot; +} + + + + +static void multi_set_termios(struct sb_uart_port *port, struct MP_TERMIOS *termios, const struct MP_TERMIOS *old) +{ + struct mp_port *mtpt = (struct mp_port *)port; + unsigned char cval, fcr = 0; + unsigned long flags; + unsigned int baud, quot; + + switch (termios->c_cflag & CSIZE) { + case CS5: + cval = 0x00; + break; + case CS6: + cval = 0x01; + break; + case CS7: + cval = 0x02; + break; + default: + case CS8: + cval = 0x03; + break; + } + + if (termios->c_cflag & CSTOPB) + cval |= 0x04; + if (termios->c_cflag & PARENB) + cval |= UART_LCR_PARITY; + if (!(termios->c_cflag & PARODD)) + cval |= UART_LCR_EPAR; + +#ifdef CMSPAR + if (termios->c_cflag & CMSPAR) + cval |= UART_LCR_SPAR; +#endif + + baud = sb_uart_get_baud_rate(port, termios, old, 0, port->uartclk/16); + quot = multi_get_divisor(port, baud); + + if (mtpt->capabilities & UART_USE_FIFO) { + //if (baud < 2400) + // fcr = UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_1; + //else + // fcr = UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_8; + + // fcr = UART_FCR_ENABLE_FIFO | 0x90; + fcr = fcr_arr[mtpt->port.line]; + } + + spin_lock_irqsave(&mtpt->port.lock, flags); + + sb_uart_update_timeout(port, termios->c_cflag, baud); + + mtpt->port.read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR; + if (termios->c_iflag & INPCK) + mtpt->port.read_status_mask |= UART_LSR_FE | UART_LSR_PE; + if (termios->c_iflag & (BRKINT | PARMRK)) + mtpt->port.read_status_mask |= UART_LSR_BI; + + mtpt->port.ignore_status_mask = 0; + if (termios->c_iflag & IGNPAR) + mtpt->port.ignore_status_mask |= UART_LSR_PE | UART_LSR_FE; + if (termios->c_iflag & IGNBRK) { + mtpt->port.ignore_status_mask |= UART_LSR_BI; + if (termios->c_iflag & IGNPAR) + mtpt->port.ignore_status_mask |= UART_LSR_OE; + } + + if ((termios->c_cflag & CREAD) == 0) + mtpt->port.ignore_status_mask |= UART_LSR_DR; + + mtpt->ier &= ~UART_IER_MSI; + if (UART_ENABLE_MS(&mtpt->port, termios->c_cflag)) + mtpt->ier |= UART_IER_MSI; + + serial_out(mtpt, UART_IER, mtpt->ier); + + if (mtpt->capabilities & UART_STARTECH) { + serial_outp(mtpt, UART_LCR, 0xBF); + serial_outp(mtpt, UART_EFR, + termios->c_cflag & CRTSCTS ? UART_EFR_CTS :0); + } + + serial_outp(mtpt, UART_LCR, cval | UART_LCR_DLAB);/* set DLAB */ + + serial_outp(mtpt, UART_DLL, quot & 0xff); /* LS of divisor */ + serial_outp(mtpt, UART_DLM, quot >> 8); /* MS of divisor */ + + serial_outp(mtpt, UART_LCR, cval); /* reset DLAB */ + mtpt->lcr = cval; /* Save LCR */ + + if (fcr & UART_FCR_ENABLE_FIFO) { + /* emulated UARTs (Lucent Venus 167x) need two steps */ + serial_outp(mtpt, UART_FCR, UART_FCR_ENABLE_FIFO); + } + + serial_outp(mtpt, UART_FCR, fcr); /* set fcr */ + + + if ((mtpt->port.type == PORT_16C105X) + || (mtpt->port.type == PORT_16C105XA)) + { + if(deep[mtpt->port.line]!=0) + set_deep_fifo(port, ENABLE); + + if (mtpt->interface != RS232) + set_auto_rts(port,mtpt->interface); + + } + else + { + if (mtpt->interface >= RS485NE) + { + uart_clear_mctrl(&mtpt->port, TIOCM_RTS); + } + } + + if(mtpt->device->device_id == PCI_DEVICE_ID_MP4M) + { + SendATCommand(mtpt); + printk("SendATCommand\n"); + } + multi_set_mctrl(&mtpt->port, mtpt->port.mctrl); + spin_unlock_irqrestore(&mtpt->port.lock, flags); +} + +static void multi_pm(struct sb_uart_port *port, unsigned int state, unsigned int oldstate) +{ + struct mp_port *mtpt = (struct mp_port *)port; + if (state) { + if (mtpt->capabilities & UART_STARTECH) { + serial_outp(mtpt, UART_LCR, 0xBF); + serial_outp(mtpt, UART_EFR, UART_EFR_ECB); + serial_outp(mtpt, UART_LCR, 0); + serial_outp(mtpt, UART_IER, UART_IERX_SLEEP); + serial_outp(mtpt, UART_LCR, 0xBF); + serial_outp(mtpt, UART_EFR, 0); + serial_outp(mtpt, UART_LCR, 0); + } + + if (mtpt->pm) + mtpt->pm(port, state, oldstate); + } + else + { + if (mtpt->capabilities & UART_STARTECH) { + serial_outp(mtpt, UART_LCR, 0xBF); + serial_outp(mtpt, UART_EFR, UART_EFR_ECB); + serial_outp(mtpt, UART_LCR, 0); + serial_outp(mtpt, UART_IER, 0); + serial_outp(mtpt, UART_LCR, 0xBF); + serial_outp(mtpt, UART_EFR, 0); + serial_outp(mtpt, UART_LCR, 0); + } + + if (mtpt->pm) + mtpt->pm(port, state, oldstate); + } +} + +static void multi_release_std_resource(struct mp_port *mtpt) +{ + unsigned int size = 8 << mtpt->port.regshift; + + switch (mtpt->port.iotype) { + case UPIO_MEM: + if (!mtpt->port.mapbase) + break; + + if (mtpt->port.flags & UPF_IOREMAP) { + iounmap(mtpt->port.membase); + mtpt->port.membase = NULL; + } + + release_mem_region(mtpt->port.mapbase, size); + break; + + case UPIO_HUB6: + case UPIO_PORT: + release_region(mtpt->port.iobase,size); + break; + } +} + +static void multi_release_port(struct sb_uart_port *port) +{ +} + +static int multi_request_port(struct sb_uart_port *port) +{ + return 0; +} + +static void multi_config_port(struct sb_uart_port *port, int flags) +{ + struct mp_port *mtpt = (struct mp_port *)port; + int probeflags = PROBE_ANY; + + if (flags & UART_CONFIG_TYPE) + autoconfig(mtpt, probeflags); + if (mtpt->port.type != PORT_UNKNOWN && flags & UART_CONFIG_IRQ) + autoconfig_irq(mtpt); + + if (mtpt->port.type == PORT_UNKNOWN) + multi_release_std_resource(mtpt); +} + +static int multi_verify_port(struct sb_uart_port *port, struct serial_struct *ser) +{ + if (ser->irq >= NR_IRQS || ser->irq < 0 || + ser->baud_base < 9600 || ser->type < PORT_UNKNOWN || + ser->type == PORT_STARTECH) + return -EINVAL; + return 0; +} + +static const char *multi_type(struct sb_uart_port *port) +{ + int type = port->type; + + if (type >= ARRAY_SIZE(uart_config)) + type = 0; + return uart_config[type].name; +} + +static struct sb_uart_ops multi_pops = { + .tx_empty = multi_tx_empty, + .set_mctrl = multi_set_mctrl, + .get_mctrl = multi_get_mctrl, + .stop_tx = multi_stop_tx, + .start_tx = multi_start_tx, + .stop_rx = multi_stop_rx, + .enable_ms = multi_enable_ms, + .break_ctl = multi_break_ctl, + .startup = multi_startup, + .shutdown = multi_shutdown, + .set_termios = multi_set_termios, + .pm = multi_pm, + .type = multi_type, + .release_port = multi_release_port, + .request_port = multi_request_port, + .config_port = multi_config_port, + .verify_port = multi_verify_port, +}; + +static struct uart_driver multi_reg = { + .owner = THIS_MODULE, + .driver_name = "goldel_tulip", + .dev_name = "ttyMP", + .major = SB_TTY_MP_MAJOR, + .minor = 0, + .nr = MAX_MP_PORT, + .cons = NULL, +}; + +static void __init multi_init_ports(void) +{ + struct mp_port *mtpt; + static int first = 1; + int i,j,k; + unsigned char osc; + unsigned char b_ret = 0; + static struct mp_device_t *sbdev; + + if (!first) + return; + first = 0; + + mtpt = multi_ports; + + for (k=0;knr_ports; i++, mtpt++) + { + mtpt->device = sbdev; + mtpt->port.iobase = sbdev->uart_access_addr + 8*i; + mtpt->port.irq = sbdev->irq; + if ( ((sbdev->device_id == PCI_DEVICE_ID_MP4)&&(sbdev->revision==0x91))) + mtpt->interface_config_addr = sbdev->option_reg_addr + 0x08 + i; + else if (sbdev->revision == 0xc0) + mtpt->interface_config_addr = sbdev->option_reg_addr + 0x08 + (i & 0x1); + else + mtpt->interface_config_addr = sbdev->option_reg_addr + 0x08 + i/8; + + mtpt->option_base_addr = sbdev->option_reg_addr; + + mtpt->poll_type = sbdev->poll_type; + + mtpt->port.uartclk = BASE_BAUD * 16; + + /* get input clock information */ + osc = inb(sbdev->option_reg_addr + MP_OPTR_DIR0 + i/8) & 0x0F; + if (osc==0x0f) + osc = 0; + for(j=0;jport.uartclk *= 2; + mtpt->port.flags |= UPF_SHARE_IRQ ; + mtpt->port.iotype = UPIO_PORT; + mtpt->port.ops = &multi_pops; + + if (sbdev->revision == 0xc0) + { + /* for SB16C1053APCI */ + b_ret = sb1053a_get_interface(mtpt, i); + } + else + { + b_ret = read_option_register(mtpt,(MP_OPTR_IIR0 + i/8)); + } + + /* default to RS232 */ + mtpt->interface = RS232; + if (IIR_RS422 == (b_ret & IIR_TYPE_MASK)) + mtpt->interface = RS422PTP; + if (IIR_RS485 == (b_ret & IIR_TYPE_MASK)) + mtpt->interface = RS485NE; + } + } +} + +static void __init multi_register_ports(struct uart_driver *drv) +{ + int i; + + multi_init_ports(); + + for (i = 0; i < NR_PORTS; i++) { + struct mp_port *mtpt = &multi_ports[i]; + + mtpt->port.line = i; + mtpt->port.ops = &multi_pops; + timer_setup(&mtpt->timer, multi_timeout, 0); + tty_port_init(&tt_port[i]); + mp_add_one_port(drv, &mtpt->port); + } +} + +/** + * pci_remap_base - remap BAR value of pci device + * + * PARAMETERS + * pcidev - pci_dev structure address + * offset - BAR offset PCI_BASE_ADDRESS_0 ~ PCI_BASE_ADDRESS_4 + * address - address to be changed BAR value + * size - size of address space + * + * RETURNS + * If this function performs successful, it returns 0. Otherwise, It returns -1. + */ +static int pci_remap_base(struct pci_dev *pcidev, unsigned int offset, + unsigned int address, unsigned int size) +{ +#if 0 + struct resource *root; + unsigned index = (offset - 0x10) >> 2; +#endif + + pci_write_config_dword(pcidev, offset, address); +#if 0 + root = pcidev->resource[index].parent; + release_resource(&pcidev->resource[index]); + address &= ~0x1; + pcidev->resource[index].start = address; + pcidev->resource[index].end = address + size - 1; + + if (request_resource(root, &pcidev->resource[index]) != NULL) + { + printk(KERN_ERR "pci remap conflict!! 0x%x\n", address); + return (-1); + } +#endif + + return (0); +} + +static int init_mp_dev(struct pci_dev *pcidev, mppcibrd_t brd) +{ + static struct mp_device_t *sbdev = mp_devs; + unsigned long addr = 0; + int j; + struct resource *ret = NULL; + + sbdev->device_id = brd.device_id; + pci_read_config_byte(pcidev, PCI_CLASS_REVISION, &(sbdev->revision)); + sbdev->name = brd.name; + sbdev->uart_access_addr = pcidev->resource[0].start & PCI_BASE_ADDRESS_IO_MASK; + + /* check revision. The SB16C1053APCI's option i/o address is BAR4 */ + if (sbdev->revision == 0xc0) + { + /* SB16C1053APCI */ + sbdev->option_reg_addr = pcidev->resource[4].start & PCI_BASE_ADDRESS_IO_MASK; + } + else + { + sbdev->option_reg_addr = pcidev->resource[1].start & PCI_BASE_ADDRESS_IO_MASK; + } +#if 1 + if (sbdev->revision == 0xc0) + { + outb(0x00, sbdev->option_reg_addr + MP_OPTR_GPOCR); + inb(sbdev->option_reg_addr + MP_OPTR_GPOCR); + outb(0x83, sbdev->option_reg_addr + MP_OPTR_GPOCR); + } +#endif + + sbdev->irq = pcidev->irq; + + if ((brd.device_id & 0x0800) || !(brd.device_id &0xff00)) + { + sbdev->poll_type = TYPE_INTERRUPT; + } + else + { + sbdev->poll_type = TYPE_POLL; + } + + /* codes which is specific to each board*/ + switch(brd.device_id){ + case PCI_DEVICE_ID_MP1 : + case PCIE_DEVICE_ID_MP1 : + case PCIE_DEVICE_ID_MP1E : + case PCIE_DEVICE_ID_GT_MP1 : + sbdev->nr_ports = 1; + break; + case PCI_DEVICE_ID_MP2 : + case PCIE_DEVICE_ID_MP2 : + case PCIE_DEVICE_ID_GT_MP2 : + case PCIE_DEVICE_ID_MP2B : + case PCIE_DEVICE_ID_MP2E : + sbdev->nr_ports = 2; + + /* serial base address remap */ + if (sbdev->revision == 0xc0) + { + int prev_port_addr = 0; + + pci_read_config_dword(pcidev, PCI_BASE_ADDRESS_0, &prev_port_addr); + pci_remap_base(pcidev, PCI_BASE_ADDRESS_1, prev_port_addr + 8, 8); + } + break; + case PCI_DEVICE_ID_MP4 : + case PCI_DEVICE_ID_MP4A : + case PCIE_DEVICE_ID_MP4 : + case PCI_DEVICE_ID_GT_MP4 : + case PCI_DEVICE_ID_GT_MP4A : + case PCIE_DEVICE_ID_GT_MP4 : + case PCI_DEVICE_ID_MP4M : + case PCIE_DEVICE_ID_MP4B : + sbdev->nr_ports = 4; + + if(sbdev->revision == 0x91){ + sbdev->reserved_addr[0] = pcidev->resource[0].start & PCI_BASE_ADDRESS_IO_MASK; + outb(0x03 , sbdev->reserved_addr[0] + 0x01); + outb(0x03 , sbdev->reserved_addr[0] + 0x02); + outb(0x01 , sbdev->reserved_addr[0] + 0x20); + outb(0x00 , sbdev->reserved_addr[0] + 0x21); + request_region(sbdev->reserved_addr[0], 32, sbdev->name); + sbdev->uart_access_addr = pcidev->resource[1].start & PCI_BASE_ADDRESS_IO_MASK; + sbdev->option_reg_addr = pcidev->resource[2].start & PCI_BASE_ADDRESS_IO_MASK; + } + + /* SB16C1053APCI */ + if (sbdev->revision == 0xc0) + { + int prev_port_addr = 0; + + pci_read_config_dword(pcidev, PCI_BASE_ADDRESS_0, &prev_port_addr); + pci_remap_base(pcidev, PCI_BASE_ADDRESS_1, prev_port_addr + 8, 8); + pci_remap_base(pcidev, PCI_BASE_ADDRESS_2, prev_port_addr + 16, 8); + pci_remap_base(pcidev, PCI_BASE_ADDRESS_3, prev_port_addr + 24, 8); + } + break; + case PCI_DEVICE_ID_MP6 : + case PCI_DEVICE_ID_MP6A : + case PCI_DEVICE_ID_GT_MP6 : + case PCI_DEVICE_ID_GT_MP6A : + sbdev->nr_ports = 6; + + /* SB16C1053APCI */ + if (sbdev->revision == 0xc0) + { + int prev_port_addr = 0; + + pci_read_config_dword(pcidev, PCI_BASE_ADDRESS_0, &prev_port_addr); + pci_remap_base(pcidev, PCI_BASE_ADDRESS_1, prev_port_addr + 8, 8); + pci_remap_base(pcidev, PCI_BASE_ADDRESS_2, prev_port_addr + 16, 16); + pci_remap_base(pcidev, PCI_BASE_ADDRESS_3, prev_port_addr + 32, 16); + } + break; + case PCI_DEVICE_ID_MP8 : + case PCIE_DEVICE_ID_MP8 : + case PCI_DEVICE_ID_GT_MP8 : + case PCIE_DEVICE_ID_GT_MP8 : + case PCIE_DEVICE_ID_MP8B : + sbdev->nr_ports = 8; + break; + case PCI_DEVICE_ID_MP32 : + case PCIE_DEVICE_ID_MP32 : + case PCI_DEVICE_ID_GT_MP32 : + case PCIE_DEVICE_ID_GT_MP32 : + { + int portnum_hex=0; + portnum_hex = inb(sbdev->option_reg_addr); + sbdev->nr_ports = ((portnum_hex/16)*10) + (portnum_hex % 16); + } + break; +#ifdef CONFIG_PARPORT_PC + case PCI_DEVICE_ID_MP2S1P : + sbdev->nr_ports = 2; + + /* SB16C1053APCI */ + if (sbdev->revision == 0xc0) + { + int prev_port_addr = 0; + + pci_read_config_dword(pcidev, PCI_BASE_ADDRESS_0, &prev_port_addr); + pci_remap_base(pcidev, PCI_BASE_ADDRESS_1, prev_port_addr + 8, 8); + } + + /* add PC compatible parallel port */ + parport_pc_probe_port(pcidev->resource[2].start, pcidev->resource[3].start, PARPORT_IRQ_NONE, PARPORT_DMA_NONE, &pcidev->dev, 0); + break; + case PCI_DEVICE_ID_MP1P : + /* add PC compatible parallel port */ + parport_pc_probe_port(pcidev->resource[2].start, pcidev->resource[3].start, PARPORT_IRQ_NONE, PARPORT_DMA_NONE, &pcidev->dev, 0); + break; +#endif + } + + ret = request_region(sbdev->uart_access_addr, (8*sbdev->nr_ports), sbdev->name); + + if (sbdev->revision == 0xc0) + { + ret = request_region(sbdev->option_reg_addr, 0x40, sbdev->name); + } + else + { + ret = request_region(sbdev->option_reg_addr, 0x20, sbdev->name); + } + + + NR_BOARD++; + NR_PORTS += sbdev->nr_ports; + + /* Enable PCI interrupt */ + addr = sbdev->option_reg_addr + MP_OPTR_IMR0; + for(j=0; j < (sbdev->nr_ports/8)+1; j++) + { + if (sbdev->poll_type == TYPE_INTERRUPT) + { + outb(0xff,addr +j); + } + } + sbdev++; + + return 0; +} + +static int __init multi_init(void) +{ + int ret, i; + struct pci_dev *dev = NULL; + + if(fcr_count==0) + { + for(i=0;i<256;i++) + { + fcr_arr[i] = 0x01; + + } + } + if(deep_count==0) + { + for(i=0;i<256;i++) + { + deep[i] = 1; + + } + } + if(rtr_count==0) + { + for(i=0;i<256;i++) + { + rtr[i] = 0x10; + } + } + if(ttr_count==0) + { + for(i=0;i<256;i++) + { + ttr[i] = 0x38; + } + } + + +printk("MULTI INIT\n"); + for( i=0; i< mp_nrpcibrds; i++) + { + + while( (dev = pci_get_device(mp_pciboards[i].vendor_id, mp_pciboards[i].device_id, dev) ) ) + + { +printk("FOUND~~~\n"); +// Cent OS bug fix +// if (mp_pciboards[i].device_id & 0x0800) + { + int status; + + if (pci_is_enabled(dev)) + pci_disable_device(dev); + + status = pci_enable_device(dev); + + if (status != 0) + { + printk("Multiport Board Enable Fail !\n\n"); + status = -ENXIO; + return status; + } + } + + init_mp_dev(dev, mp_pciboards[i]); + } + } + + for (i = 0; i < NR_IRQS; i++) + spin_lock_init(&irq_lists[i].lock); + + ret = mp_register_driver(&multi_reg); + + if (ret >= 0) + multi_register_ports(&multi_reg); + + return ret; +} + +static void __exit multi_exit(void) +{ + int i; + + for (i = 0; i < NR_PORTS; i++) + mp_remove_one_port(&multi_reg, &multi_ports[i].port); + + mp_unregister_driver(&multi_reg); +} + +module_init(multi_init); +module_exit(multi_exit); + +MODULE_DESCRIPTION("SystemBase Multiport PCI/PCIe CORE"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/sb105x/sb_pci_mp.h b/drivers/staging/sb105x/sb_pci_mp.h new file mode 100644 index 0000000000000..0920beb31c15e --- /dev/null +++ b/drivers/staging/sb105x/sb_pci_mp.h @@ -0,0 +1,290 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +#include +#include +#include + + +#define MP_TERMIOS ktermios + +#include "sb_mp_register.h" +#include "sb_ser_core.h" + +#define DRIVER_VERSION "1.1" +#define DRIVER_DATE "2012/01/05" +#define DRIVER_AUTHOR "SYSTEMBASE" +#define DRIVER_DESC "SystemBase PCI/PCIe Multiport Core" + +#define SB_TTY_MP_MAJOR 54 +#define PCI_VENDOR_ID_MULTIPORT 0x14A1 + +#define PCI_DEVICE_ID_MP1 0x4d01 +#define PCI_DEVICE_ID_MP2 0x4d02 +#define PCI_DEVICE_ID_MP4 0x4d04 +#define PCI_DEVICE_ID_MP4A 0x4d54 +#define PCI_DEVICE_ID_MP6 0x4d06 +#define PCI_DEVICE_ID_MP6A 0x4d56 +#define PCI_DEVICE_ID_MP8 0x4d08 +#define PCI_DEVICE_ID_MP32 0x4d32 +/* Parallel port */ +#define PCI_DEVICE_ID_MP1P 0x4301 +#define PCI_DEVICE_ID_MP2S1P 0x4303 + +#define PCIE_DEVICE_ID_MP1 0x4501 +#define PCIE_DEVICE_ID_MP2 0x4502 +#define PCIE_DEVICE_ID_MP4 0x4504 +#define PCIE_DEVICE_ID_MP8 0x4508 +#define PCIE_DEVICE_ID_MP32 0x4532 + +#define PCIE_DEVICE_ID_MP1E 0x4e01 +#define PCIE_DEVICE_ID_MP2E 0x4e02 +#define PCIE_DEVICE_ID_MP2B 0x4b02 +#define PCIE_DEVICE_ID_MP4B 0x4b04 +#define PCIE_DEVICE_ID_MP8B 0x4b08 + +#define PCI_DEVICE_ID_GT_MP4 0x0004 +#define PCI_DEVICE_ID_GT_MP4A 0x0054 +#define PCI_DEVICE_ID_GT_MP6 0x0006 +#define PCI_DEVICE_ID_GT_MP6A 0x0056 +#define PCI_DEVICE_ID_GT_MP8 0x0008 +#define PCI_DEVICE_ID_GT_MP32 0x0032 + +#define PCIE_DEVICE_ID_GT_MP1 0x1501 +#define PCIE_DEVICE_ID_GT_MP2 0x1502 +#define PCIE_DEVICE_ID_GT_MP4 0x1504 +#define PCIE_DEVICE_ID_GT_MP8 0x1508 +#define PCIE_DEVICE_ID_GT_MP32 0x1532 + +#define PCI_DEVICE_ID_MP4M 0x4604 //modem + +#define MAX_MP_DEV 8 +#define BD_MAX_PORT 32 /* Max serial port in one board */ +#define MAX_MP_PORT 256 /* Max serial port in one PC */ + +#define PORT_16C105XA 3 +#define PORT_16C105X 2 +#define PORT_16C55X 1 + +#define ENABLE 1 +#define DISABLE 0 + +/* ioctls */ +#define TIOCGNUMOFPORT 0x545F +#define TIOCSMULTIECHO 0x5440 +#define TIOCSPTPNOECHO 0x5441 + +#define TIOCGOPTIONREG 0x5461 +#define TIOCGDISABLEIRQ 0x5462 +#define TIOCGENABLEIRQ 0x5463 +#define TIOCGSOFTRESET 0x5464 +#define TIOCGSOFTRESETR 0x5465 +#define TIOCGREGINFO 0x5466 +#define TIOCGGETLSR 0x5467 +#define TIOCGGETDEVID 0x5468 +#define TIOCGGETBDNO 0x5469 +#define TIOCGGETINTERFACE 0x546A +#define TIOCGGETREV 0x546B +#define TIOCGGETNRPORTS 0x546C +#define TIOCGGETPORTTYPE 0x546D +#define GETDEEPFIFO 0x54AA +#define SETDEEPFIFO 0x54AB +#define SETFCR 0x54BA +#define SETTTR 0x54B1 +#define SETRTR 0x54B2 +#define GETTTR 0x54B3 +#define GETRTR 0x54B4 + +/* multi-drop mode related ioctl commands */ +#define TIOCSMULTIDROP 0x5470 +#define TIOCSMDADDR 0x5471 +#define TIOCGMDADDR 0x5472 +#define TIOCSENDADDR 0x5473 + + +/* serial interface */ +#define RS232 1 +#define RS422PTP 2 +#define RS422MD 3 +#define RS485NE 4 +#define RS485ECHO 5 + +#define serial_inp(up, offset) serial_in(up, offset) +#define serial_outp(up, offset, value) serial_out(up, offset, value) + +#define PASS_LIMIT 256 +#define is_real_interrupt(irq) ((irq) != 0) + +#define PROBE_ANY (~0) + +static DEFINE_MUTEX(mp_mutex); +#define MP_MUTEX_LOCK(x) mutex_lock(&(x)) +#define MP_MUTEX_UNLOCK(x) mutex_unlock(&(x)) +#define MP_STATE_LOCK(x) mutex_lock(&((x)->mutex)) +#define MP_STATE_UNLOCK(x) mutex_unlock(&((x)->mutex)) + + +#define UART_LSR_SPECIAL 0x1E + +#define HIGH_BITS_OFFSET ((sizeof(long)-sizeof(int))*8) +#define uart_users(state) ((state)->count + ((state)->info ? (state)->info->blocked_open : 0)) + + +//#define MP_DEBUG 1 +#undef MP_DEBUG + +#ifdef MP_DEBUG +#define DPRINTK(x...) printk(x) +#else +#define DPRINTK(x...) do { } while (0) +#endif + +#ifdef MP_DEBUG +#define DEBUG_AUTOCONF(fmt...) printk(fmt) +#else +#define DEBUG_AUTOCONF(fmt...) do { } while (0) +#endif + +#ifdef MP_DEBUG +#define DEBUG_INTR(fmt...) printk(fmt) +#else +#define DEBUG_INTR(fmt...) do { } while (0) +#endif + +#if defined(__i386__) && defined(CONFIG_M486) +#define SERIAL_INLINE +#endif +#ifdef SERIAL_INLINE +#define _INLINE_ inline +#else +#define _INLINE_ +#endif + +#define TYPE_POLL 1 +#define TYPE_INTERRUPT 2 + + +struct mp_device_t { + unsigned short device_id; + unsigned char revision; + char *name; + unsigned long uart_access_addr; + unsigned long option_reg_addr; + unsigned long reserved_addr[4]; + int irq; + int nr_ports; + int poll_type; +}; + +typedef struct mppcibrd { + char *name; + unsigned short vendor_id; + unsigned short device_id; +} mppcibrd_t; + +static mppcibrd_t mp_pciboards[] = { + + { "Multi-1 PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_MP1} , + { "Multi-2 PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_MP2} , + { "Multi-4 PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_MP4} , + { "Multi-4 PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_MP4A} , + { "Multi-6 PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_MP6} , + { "Multi-6 PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_MP6A} , + { "Multi-8 PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_MP8} , + { "Multi-32 PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_MP32} , + + { "Multi-1P PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_MP1P} , + { "Multi-2S1P PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_MP2S1P} , + + { "Multi-4(GT) PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_GT_MP4} , + { "Multi-4(GT) PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_GT_MP4A} , + { "Multi-6(GT) PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_GT_MP6} , + { "Multi-6(GT) PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_GT_MP6A} , + { "Multi-8(GT) PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_GT_MP8} , + { "Multi-32(GT) PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_GT_MP32} , + + { "Multi-1 PCIe", PCI_VENDOR_ID_MULTIPORT , PCIE_DEVICE_ID_MP1} , + { "Multi-2 PCIe", PCI_VENDOR_ID_MULTIPORT , PCIE_DEVICE_ID_MP2} , + { "Multi-4 PCIe", PCI_VENDOR_ID_MULTIPORT , PCIE_DEVICE_ID_MP4} , + { "Multi-8 PCIe", PCI_VENDOR_ID_MULTIPORT , PCIE_DEVICE_ID_MP8} , + { "Multi-32 PCIe", PCI_VENDOR_ID_MULTIPORT , PCIE_DEVICE_ID_MP32} , + + { "Multi-1 PCIe E", PCI_VENDOR_ID_MULTIPORT , PCIE_DEVICE_ID_MP1E} , + { "Multi-2 PCIe E", PCI_VENDOR_ID_MULTIPORT , PCIE_DEVICE_ID_MP2E} , + { "Multi-2 PCIe B", PCI_VENDOR_ID_MULTIPORT , PCIE_DEVICE_ID_MP2B} , + { "Multi-4 PCIe B", PCI_VENDOR_ID_MULTIPORT , PCIE_DEVICE_ID_MP4B} , + { "Multi-8 PCIe B", PCI_VENDOR_ID_MULTIPORT , PCIE_DEVICE_ID_MP8B} , + + { "Multi-1(GT) PCIe", PCI_VENDOR_ID_MULTIPORT , PCIE_DEVICE_ID_GT_MP1} , + { "Multi-2(GT) PCIe", PCI_VENDOR_ID_MULTIPORT , PCIE_DEVICE_ID_GT_MP2} , + { "Multi-4(GT) PCIe", PCI_VENDOR_ID_MULTIPORT , PCIE_DEVICE_ID_GT_MP4} , + { "Multi-8(GT) PCIe", PCI_VENDOR_ID_MULTIPORT , PCIE_DEVICE_ID_GT_MP8} , + { "Multi-32(GT) PCIe", PCI_VENDOR_ID_MULTIPORT , PCIE_DEVICE_ID_GT_MP32} , + + { "Multi-4M PCI", PCI_VENDOR_ID_MULTIPORT , PCI_DEVICE_ID_MP4M} , +}; + +struct mp_port { + struct sb_uart_port port; + + struct timer_list timer; /* "no irq" timer */ + struct list_head list; /* ports on this IRQ */ + unsigned int capabilities; /* port capabilities */ + unsigned short rev; + unsigned char acr; + unsigned char ier; + unsigned char lcr; + unsigned char mcr; + unsigned char mcr_mask; /* mask of user bits */ + unsigned char mcr_force; /* mask of forced bits */ + unsigned char lsr_break_flag; + + void (*pm)(struct sb_uart_port *port, + unsigned int state, unsigned int old); + struct mp_device_t *device; + unsigned long interface_config_addr; + unsigned long option_base_addr; + unsigned char interface; + unsigned char poll_type; +}; + +struct irq_info { + spinlock_t lock; + struct list_head *head; +}; + +struct sb105x_uart_config { + char *name; + int dfl_xmit_fifo_size; + int flags; +}; + +static const struct sb105x_uart_config uart_config[] = { + { "unknown", 1, 0 }, + { "16550A", 16, UART_CLEAR_FIFO | UART_USE_FIFO }, + { "SB16C1050", 128, UART_CLEAR_FIFO | UART_USE_FIFO | UART_STARTECH }, + { "SB16C1050A", 128, UART_CLEAR_FIFO | UART_USE_FIFO | UART_STARTECH }, +}; + +static struct tty_port tt_port[MAX_MP_PORT]; diff --git a/drivers/staging/sb105x/sb_ser_core.h b/drivers/staging/sb105x/sb_ser_core.h new file mode 100644 index 0000000000000..978b4b26ddbb1 --- /dev/null +++ b/drivers/staging/sb105x/sb_ser_core.h @@ -0,0 +1,368 @@ +#include + +#define UART_CONFIG_TYPE (1 << 0) +#define UART_CONFIG_IRQ (1 << 1) +#define UPIO_PORT (0) +#define UPIO_HUB6 (1) +#define UPIO_MEM (2) +#define UPIO_MEM32 (3) +#define UPIO_AU (4) /* Au1x00 type IO */ +#define UPIO_TSI (5) /* Tsi108/109 type IO */ +#define UPF_FOURPORT (1 << 1) +#define UPF_SAK (1 << 2) +#define UPF_SPD_MASK (0x1030) +#define UPF_SPD_HI (0x0010) +#define UPF_SPD_VHI (0x0020) +#define UPF_SPD_CUST (0x0030) +#define UPF_SPD_SHI (0x1000) +#define UPF_SPD_WARP (0x1010) +#define UPF_SKIP_TEST (1 << 6) +#define UPF_AUTO_IRQ (1 << 7) +#define UPF_HARDPPS_CD (1 << 11) +#define UPF_LOW_LATENCY (1 << 13) +#define UPF_BUGGY_UART (1 << 14) +#define UPF_MAGIC_MULTIPLIER (1 << 16) +#define UPF_CONS_FLOW (1 << 23) +#define UPF_SHARE_IRQ (1 << 24) +#define UPF_BOOT_AUTOCONF (1 << 28) +#define UPF_DEAD (1 << 30) +#define UPF_IOREMAP (1 << 31) +#define UPF_CHANGE_MASK (0x17fff) +#define UPF_USR_MASK (UPF_SPD_MASK|UPF_LOW_LATENCY) +#define USF_CLOSING_WAIT_INF (0) +#define USF_CLOSING_WAIT_NONE (~0U) + +#define UART_XMIT_SIZE PAGE_SIZE + +#define UIF_CHECK_CD (1 << 25) +#define UIF_CTS_FLOW (1 << 26) +#define UIF_NORMAL_ACTIVE (1 << 29) +#define UIF_INITIALIZED (1 << 31) +#define UIF_SUSPENDED (1 << 30) + +#define WAKEUP_CHARS 256 + +#define uart_circ_empty(circ) ((circ)->head == (circ)->tail) +#define uart_circ_clear(circ) ((circ)->head = (circ)->tail = 0) + +#define uart_circ_chars_pending(circ) \ + (CIRC_CNT((circ)->head, (circ)->tail, UART_XMIT_SIZE)) + +#define uart_circ_chars_free(circ) \ + (CIRC_SPACE((circ)->head, (circ)->tail, UART_XMIT_SIZE)) + +#define uart_tx_stopped(port) \ + ((port)->info->tty->flow.stopped || (port)->info->tty->hw_stopped) + +#define UART_ENABLE_MS(port,cflag) ((port)->flags & UPF_HARDPPS_CD || \ + (cflag) & CRTSCTS || \ + !((cflag) & CLOCAL)) + + +struct sb_uart_port; +struct sb_uart_info; +struct serial_struct; +struct device; + +struct sb_uart_ops { + unsigned int (*tx_empty)(struct sb_uart_port *); + void (*set_mctrl)(struct sb_uart_port *, unsigned int mctrl); + unsigned int (*get_mctrl)(struct sb_uart_port *); + void (*stop_tx)(struct sb_uart_port *); + void (*start_tx)(struct sb_uart_port *); + void (*send_xchar)(struct sb_uart_port *, char ch); + void (*stop_rx)(struct sb_uart_port *); + void (*enable_ms)(struct sb_uart_port *); + void (*break_ctl)(struct sb_uart_port *, int ctl); + int (*startup)(struct sb_uart_port *); + void (*shutdown)(struct sb_uart_port *); + void (*set_termios)(struct sb_uart_port *, struct MP_TERMIOS *new, + const struct MP_TERMIOS *old); + void (*pm)(struct sb_uart_port *, unsigned int state, + unsigned int oldstate); + int (*set_wake)(struct sb_uart_port *, unsigned int state); + + const char *(*type)(struct sb_uart_port *); + + void (*release_port)(struct sb_uart_port *); + + int (*request_port)(struct sb_uart_port *); + void (*config_port)(struct sb_uart_port *, int); + int (*verify_port)(struct sb_uart_port *, struct serial_struct *); + int (*ioctl)(struct sb_uart_port *, unsigned int, unsigned long); +}; + + +struct sb_uart_icount { + __u32 cts; + __u32 dsr; + __u32 rng; + __u32 dcd; + __u32 rx; + __u32 tx; + __u32 frame; + __u32 overrun; + __u32 parity; + __u32 brk; + __u32 buf_overrun; +}; +typedef unsigned int upf_t; + +struct sb_uart_port { + spinlock_t lock; /* port lock */ + unsigned int iobase; /* in/out[bwl] */ + unsigned char __iomem *membase; /* read/write[bwl] */ + unsigned int irq; /* irq number */ + unsigned int uartclk; /* base uart clock */ + unsigned int fifosize; /* tx fifo size */ + unsigned char x_char; /* xon/xoff char */ + unsigned char regshift; /* reg offset shift */ + unsigned char iotype; /* io access style */ + unsigned char unused1; + + + unsigned int read_status_mask; /* driver specific */ + unsigned int ignore_status_mask; /* driver specific */ + struct sb_uart_info *info; /* pointer to parent info */ + struct sb_uart_icount icount; /* statistics */ + + struct console *cons; /* struct console, if any */ +#ifdef CONFIG_SERIAL_CORE_CONSOLE + unsigned long sysrq; /* sysrq timeout */ +#endif + + upf_t flags; + + unsigned int mctrl; /* current modem ctrl settings */ + unsigned int timeout; /* character-based timeout */ + unsigned int type; /* port type */ + const struct sb_uart_ops *ops; + unsigned int custom_divisor; + unsigned int line; /* port index */ + unsigned long mapbase; /* for ioremap */ + struct device *dev; /* parent device */ + unsigned char hub6; /* this should be in the 8250 driver */ + unsigned char unused[3]; +}; + +#define mdmode unused[2] +#define MDMODE_ADDR 0x1 +#define MDMODE_ENABLE 0x2 +#define MDMODE_AUTO 0x4 +#define MDMODE_ADDRSEND 0x8 + +struct sb_uart_state { + unsigned int close_delay; /* msec */ + unsigned int closing_wait; /* msec */ + + + int count; + int pm_state; + struct sb_uart_info *info; + struct sb_uart_port *port; + + struct mutex mutex; +}; + +typedef unsigned int uif_t; + +struct sb_uart_info { + struct tty_struct *tty; + struct circ_buf xmit; + uif_t flags; + + int blocked_open; + + struct tasklet_struct tlet; + + wait_queue_head_t open_wait; + wait_queue_head_t delta_msr_wait; +}; + + +struct module; +struct tty_driver; + +struct uart_driver { + struct module *owner; + const char *driver_name; + const char *dev_name; + int major; + int minor; + int nr; + struct console *cons; + + struct sb_uart_state *state; + struct tty_driver *tty_driver; +}; + +static void sb_uart_write_wakeup(struct sb_uart_port *port) +{ + struct sb_uart_info *info = port->info; + tasklet_schedule(&info->tlet); +} + +static void sb_uart_update_timeout(struct sb_uart_port *port, unsigned int cflag, + unsigned int baud) +{ + unsigned int bits; + + switch (cflag & CSIZE) + { + case CS5: + bits = 7; + break; + + case CS6: + bits = 8; + break; + + case CS7: + bits = 9; + break; + + default: + bits = 10; + break; + } + + if (cflag & CSTOPB) + { + bits++; + } + + if (cflag & PARENB) + { + bits++; + } + + bits = bits * port->fifosize; + + port->timeout = (HZ * bits) / baud + HZ/50; +} +static unsigned int sb_uart_get_baud_rate(struct sb_uart_port *port, struct MP_TERMIOS *termios, + const struct MP_TERMIOS *old, unsigned int min, + unsigned int max) +{ + unsigned int try, baud, altbaud = 38400; + upf_t flags = port->flags & UPF_SPD_MASK; + + if (flags == UPF_SPD_HI) + altbaud = 57600; + if (flags == UPF_SPD_VHI) + altbaud = 115200; + if (flags == UPF_SPD_SHI) + altbaud = 230400; + if (flags == UPF_SPD_WARP) + altbaud = 460800; + + for (try = 0; try < 2; try++) { + + switch (termios->c_cflag & (CBAUD | CBAUDEX)) + { + case B921600 : baud = 921600; break; + case B460800 : baud = 460800; break; + case B230400 : baud = 230400; break; + case B115200 : baud = 115200; break; + case B57600 : baud = 57600; break; + case B38400 : baud = 38400; break; + case B19200 : baud = 19200; break; + case B9600 : baud = 9600; break; + case B4800 : baud = 4800; break; + case B2400 : baud = 2400; break; + case B1800 : baud = 1800; break; + case B1200 : baud = 1200; break; + case B600 : baud = 600; break; + case B300 : baud = 300; break; + case B200 : baud = 200; break; + case B150 : baud = 150; break; + case B134 : baud = 134; break; + case B110 : baud = 110; break; + case B75 : baud = 75; break; + case B50 : baud = 50; break; + default : baud = 9600; break; + } + + if (baud == 38400) + baud = altbaud; + + if (baud == 0) + baud = 9600; + + if (baud >= min && baud <= max) + return baud; + + termios->c_cflag &= ~CBAUD; + if (old) { + termios->c_cflag |= old->c_cflag & CBAUD; + old = NULL; + continue; + } + + termios->c_cflag |= B9600; + } + + return 0; +} +static unsigned int sb_uart_get_divisor(struct sb_uart_port *port, unsigned int baud) +{ + unsigned int quot; + + if (baud == 38400 && (port->flags & UPF_SPD_MASK) == UPF_SPD_CUST) + quot = port->custom_divisor; + else + quot = (port->uartclk + (8 * baud)) / (16 * baud); + + return quot; +} + + + +static inline int sb_uart_handle_break(struct sb_uart_port *port) +{ + struct sb_uart_info *info = port->info; + + if (port->flags & UPF_SAK) + do_SAK(info->tty); + return 0; +} + +static inline void sb_uart_handle_dcd_change(struct sb_uart_port *port, unsigned int status) +{ + struct sb_uart_info *info = port->info; + + port->icount.dcd++; + + if (info->flags & UIF_CHECK_CD) { + if (status) + wake_up_interruptible(&info->open_wait); + else if (info->tty) + tty_hangup(info->tty); + } +} + +static inline void sb_uart_handle_cts_change(struct sb_uart_port *port, unsigned int status) +{ + struct sb_uart_info *info = port->info; + struct tty_struct *tty = info->tty; + + port->icount.cts++; + + if (info->flags & UIF_CTS_FLOW) { + if (tty->hw_stopped) { + if (status) { + tty->hw_stopped = 0; + port->ops->start_tx(port); + sb_uart_write_wakeup(port); + } + } else { + if (!status) { + tty->hw_stopped = 1; + port->ops->stop_tx(port); + } + } + } +} + + + diff --git a/drivers/staging/sm750fb/sm750.c b/drivers/staging/sm750fb/sm750.c index 79bcd5bd49380..22032a8753dde 100644 --- a/drivers/staging/sm750fb/sm750.c +++ b/drivers/staging/sm750fb/sm750.c @@ -30,11 +30,11 @@ */ /* common var for all device */ -static int g_hwcursor = 1; +static int g_hwcursor; static int g_noaccel; static int g_nomtrr; static const char *g_fbmode[] = {NULL, NULL}; -static const char *g_def_fbmode = "1024x768-32@60"; +static const char *g_def_fbmode = "800x600-32@60"; static char *g_settings; static int g_dualview; static char *g_option; @@ -880,7 +880,7 @@ static void sm750fb_setup(struct sm750_dev *sm750_dev, char *src) sm750_dev->initParm.resetMemory = 1; /* defaultly turn g_hwcursor on for both view */ - g_hwcursor = 3; + g_hwcursor = 0; if (!src || !*src) { dev_warn(&sm750_dev->pdev->dev, "no specific g_option.\n"); diff --git a/drivers/tty/serial/8250/8250_pci.c b/drivers/tty/serial/8250/8250_pci.c index bbd7914ddc9ad..94decedf572fa 100644 --- a/drivers/tty/serial/8250/8250_pci.c +++ b/drivers/tty/serial/8250/8250_pci.c @@ -258,6 +258,12 @@ static int pci_plx9050_init(struct pci_dev *dev) * deep FIFOs */ irq_config = 0x5b; + + if ((dev->vendor == PCI_VENDOR_ID_PLX) && + (dev->device == PCI_DEVICE_ID_PLX_9050) && + (dev->subsystem_vendor == PCI_VENDOR_ID_PLX) && + (dev->subsystem_device == PCI_DEVICE_ID_PLX_9050)) + irq_config = 0x53; /* * enable/disable interrupts */ @@ -1955,6 +1961,7 @@ pci_moxa_setup(struct serial_private *priv, #define PCIE_VENDOR_ID_WCH 0x1c00 #define PCIE_DEVICE_ID_WCH_CH382_2S1P 0x3250 #define PCIE_DEVICE_ID_WCH_CH384_4S 0x3470 +#define PCIE_DEVICE_ID_WCH_CH384_4S1P 0x3450 #define PCIE_DEVICE_ID_WCH_CH384_8S 0x3853 #define PCIE_DEVICE_ID_WCH_CH382_2S 0x3253 @@ -2246,6 +2253,15 @@ static struct pci_serial_quirk pci_serial_quirks[] = { .setup = pci_default_setup, .exit = pci_plx9050_exit, }, + { + .vendor = PCI_VENDOR_ID_PLX, + .device = PCI_DEVICE_ID_PLX_9050, + .subvendor = PCI_VENDOR_ID_PLX, + .subdevice = PCI_DEVICE_ID_PLX_9050, + .init = pci_plx9050_init, + .setup = pci_default_setup, + .exit = pci_plx9050_exit, + }, { .vendor = PCI_VENDOR_ID_PLX, .device = PCI_DEVICE_ID_PLX_9050, @@ -2738,6 +2754,14 @@ static struct pci_serial_quirk pci_serial_quirks[] = { .subdevice = PCI_ANY_ID, .init = pci_wch_ch38x_init, .exit = pci_wch_ch38x_exit, + .setup = pci_wch_ch38x_setup, + }, + /* WCH CH384 4S1P card (16850 clone) */ + { + .vendor = PCIE_VENDOR_ID_WCH, + .device = PCIE_DEVICE_ID_WCH_CH384_4S1P, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, .setup = pci_wch_ch38x_setup, }, /* @@ -3814,6 +3838,7 @@ static const struct pci_device_id blacklist[] = { { PCI_DEVICE(0x4348, 0x5053), 0, 0, REPORT_CONFIG(PARPORT_SERIAL), }, /* WCH CH382 2S1P */ { PCI_DEVICE(0x1c00, 0x3250), 0, 0, REPORT_CONFIG(PARPORT_SERIAL), }, + { PCI_DEVICE(0x1c00, 0x3450), }, /* WCH CH384 4S1P */ /* Intel platforms with MID UART */ { PCI_VDEVICE(INTEL, 0x081b), REPORT_8250_CONFIG(MID), }, @@ -4349,6 +4374,10 @@ static const struct pci_device_id serial_pci_tbl[] = { PCI_SUBDEVICE_ID_UNKNOWN_0x1584, 0, 0, pbn_b2_4_115200 }, /* Unknown card - subdevice 0x1588 */ + { PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9050, + PCI_VENDOR_ID_PLX, + PCI_DEVICE_ID_PLX_9050, 0, 0, + pbn_b2_8_921600 }, { PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9050, PCI_VENDOR_ID_PLX, PCI_SUBDEVICE_ID_UNKNOWN_0x1588, 0, 0, diff --git a/drivers/tty/serial/8250/8250_pcilib.c b/drivers/tty/serial/8250/8250_pcilib.c index d234e9194feb5..18a04874ec468 100644 --- a/drivers/tty/serial/8250/8250_pcilib.c +++ b/drivers/tty/serial/8250/8250_pcilib.c @@ -18,6 +18,12 @@ int serial8250_pci_setup_port(struct pci_dev *dev, struct uart_8250_port *port, if (bar >= PCI_STD_NUM_BARS) return -EINVAL; + if ((dev->vendor == PCI_VENDOR_ID_PLX) && + (dev->device == PCI_DEVICE_ID_PLX_9050) && + (dev->subsystem_vendor == PCI_VENDOR_ID_PLX) && + (dev->subsystem_device == PCI_DEVICE_ID_PLX_9050)) + offset += 0x80; + if (pci_resource_flags(dev, bar) & IORESOURCE_MEM) { if (!pcim_iomap(dev, bar, 0) && !pcim_iomap_table(dev)) return -ENOMEM; diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 8ff0efac6aa0d..ef0a27870e548 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -1999,6 +1999,8 @@ static int uart_open(struct tty_struct *tty, struct file *filp) int retval; retval = tty_port_open(&state->port, tty, filp); + if (!retval) + clear_bit(TTY_IO_ERROR, &tty->flags); if (retval > 0) retval = 0; diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index 077dfe48d01c1..295d763d95e81 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -1996,7 +1996,7 @@ int usb_set_configuration(struct usb_device *dev, int configuration) struct usb_host_config *cp = NULL; struct usb_interface **new_interfaces = NULL; struct usb_hcd *hcd = bus_to_hcd(dev->bus); - int n, nintf; + int n, nintf, retried = 0; if (dev->authorized == 0 || configuration == -1) configuration = 0; @@ -2145,6 +2145,7 @@ int usb_set_configuration(struct usb_device *dev, int configuration) } kfree(new_interfaces); +retry: ret = usb_control_msg_send(dev, 0, USB_REQ_SET_CONFIGURATION, 0, configuration, 0, NULL, 0, USB_CTRL_SET_TIMEOUT, GFP_NOIO); @@ -2153,6 +2154,12 @@ int usb_set_configuration(struct usb_device *dev, int configuration) * All the old state is gone, so what else can we do? * The device is probably useless now anyway. */ + if (!retried) { + retried = 1; + printk("Retry to configure %d-%s!\n", dev->bus->busnum, dev->devpath); + goto retry; + } + usb_hcd_alloc_bandwidth(dev, NULL, NULL, NULL); for (i = 0; i < nintf; ++i) { usb_disable_interface(dev, cp->interface[i], true); diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index 5cec7640e913c..67335c98f717e 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -1138,8 +1138,10 @@ int ohci_resume(struct usb_hcd *hcd, bool hibernated) set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); /* Make sure resume from hibernation re-enumerates everything */ - if (hibernated) + if (hibernated) { + ohci_writel(ohci, OHCI_INTR_MIE, &ohci->regs->intrdisable); ohci_usb_reset(ohci); + } /* See if the controller is already running or has been reset */ ohci->hc_control = ohci_readl(ohci, &ohci->regs->control); diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 0df5d807a77e8..26487a660729e 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -361,6 +361,9 @@ static void xhci_usb3_hub_descriptor(struct usb_hcd *hcd, struct xhci_hcd *xhci, } desc->u.ss.DeviceRemovable = cpu_to_le16(port_removable); + + if (xhci->quirks & XHCI_ETRON_HOST) + desc->bPwrOn2PwrGood = 255; } static void xhci_hub_descriptor(struct usb_hcd *hcd, struct xhci_hcd *xhci, @@ -889,6 +892,10 @@ static void xhci_hub_report_usb3_link_state(struct xhci_hcd *xhci, if ((xhci->quirks & XHCI_COMP_MODE_QUIRK) && (pls == USB_SS_PORT_LS_COMP_MOD)) pls |= USB_PORT_STAT_CONNECTION; + + if ((xhci->quirks & XHCI_ETRON_HOST) && + (pls == USB_SS_PORT_LS_COMP_MOD)) + pls |= USB_PORT_STAT_CONNECTION; } /* update status field */ diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 54c47463c215c..0ad216fcd2178 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -1409,9 +1409,15 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, unsigned int mult; unsigned int avg_trb_len; unsigned int err_count = 0; + u8 reg8; + unsigned long flags; + struct usb_hcd *hcd; + struct xhci_virt_ep *virt_ep; + hcd = xhci_to_hcd(xhci); ep_index = xhci_get_endpoint_index(&ep->desc); ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->in_ctx, ep_index); + virt_ep = &virt_dev->eps[ep_index]; endpoint_type = xhci_get_endpoint_type(ep); if (!endpoint_type) @@ -1464,6 +1470,19 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, if ((xhci->hci_version > 0x100) && HCC2_LEC(xhci->hcc_params2)) mult = 0; + if (xhci->quirks & XHCI_ETRON_HOST) { + if (udev->speed == USB_SPEED_LOW && usb_endpoint_is_int_in(&ep->desc)) { + spin_lock_irqsave(&xhci->lock, flags); + reg8 = readb(hcd->regs + 0x4300); + reg8 |= 0x04; + writeb(reg8, hcd->regs + 0x4300); + spin_unlock_irqrestore(&xhci->lock, flags); + + max_packet = 9; + virt_ep->ep_state |= EP_EJ188_FIX; + } + } + /* Set up the endpoint ring */ virt_dev->eps[ep_index].new_ring = xhci_ring_alloc(xhci, 2, 1, ring_type, max_packet, mem_flags); diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 50f5880114004..4d387eb5409b5 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -2548,6 +2548,7 @@ static int process_bulk_intr_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, struct xhci_slot_ctx *slot_ctx; u32 trb_comp_code; u32 remaining, requested, ep_trb_len; + union xhci_trb *cur_trb; slot_ctx = xhci_get_slot_ctx(xhci, ep->vdev->out_ctx); trb_comp_code = GET_COMP_CODE(le32_to_cpu(event->transfer_len)); @@ -2595,9 +2596,15 @@ static int process_bulk_intr_td(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, break; } - if (ep_trb == td->last_trb) + if (ep_trb == td->last_trb) { td->urb->actual_length = requested - remaining; - else + if ((xhci->quirks & XHCI_ETRON_HOST) && (ep->ep_state & EP_EJ188_FIX)) { + cur_trb = ep_ring->dequeue; + td->urb->actual_length = + TRB_LEN(le32_to_cpu(cur_trb->generic.field[2])) - + EVENT_TRB_LEN(le32_to_cpu(event->transfer_len)); + } + } else td->urb->actual_length = sum_trb_lengths(xhci, ep_ring, ep_trb) + ep_trb_len - remaining; @@ -3660,7 +3667,9 @@ int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, int sent_len, ret; u32 field, length_field, remainder; u64 addr, send_addr; + struct xhci_virt_ep *ep; + ep = &xhci->devs[slot_id]->eps[ep_index]; ring = xhci_urb_to_transfer_ring(xhci, urb); if (!ring) return -EINVAL; @@ -3762,6 +3771,13 @@ int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, TRB_TD_SIZE(remainder) | TRB_INTR_TARGET(0); + if ((xhci->quirks & XHCI_ETRON_HOST) && + (ep->ep_state & EP_EJ188_FIX)) { + length_field = TRB_LEN(9) | + TRB_TD_SIZE(remainder) | + TRB_INTR_TARGET(0); + } + queue_trb(xhci, ring, more_trbs_coming | need_zero_pkt, lower_32_bits(send_addr), upper_32_bits(send_addr), diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 3bd70e6ad64ba..dee9965e2f045 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -2898,6 +2898,8 @@ int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) struct xhci_input_control_ctx *ctrl_ctx; struct xhci_slot_ctx *slot_ctx; struct xhci_command *command; + u8 reg8; + unsigned long flags; ret = xhci_check_args(hcd, udev, NULL, 0, true, __func__); if (ret <= 0) @@ -2981,6 +2983,17 @@ int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) xhci_debugfs_create_endpoint(xhci, virt_dev, i); } command_cleanup: + if (xhci->quirks & XHCI_ETRON_HOST) { + spin_lock_irqsave(&xhci->lock, flags); + hcd = xhci_to_hcd(xhci); + reg8 = readb(hcd->regs + 0x4300); + if (reg8 & 0x04) { + reg8 &= ~0x04; + writeb(reg8, hcd->regs + 0x4300); + } + spin_unlock_irqrestore(&xhci->lock, flags); + } + kfree(command->completion); kfree(command); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 4bbd12db7239a..eefcfd40848fc 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -697,6 +697,7 @@ struct xhci_virt_ep { #define EP_SOFT_CLEAR_TOGGLE (1 << 7) /* usb_hub_clear_tt_buffer is in progress */ #define EP_CLEARING_TT (1 << 8) +#define EP_EJ188_FIX (1 << 9) /* ---- Related to URB cancellation ---- */ struct list_head cancelled_td_list; struct xhci_hcd *xhci; diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index 04f511adc0025..955ded694fd09 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -2059,6 +2059,7 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_AND_INTERFACE_INFO(ALINK_VENDOR_ID, ALINK_PRODUCT_3GU, 0xff, 0xff, 0xff) }, { USB_DEVICE(ALINK_VENDOR_ID, SIMCOM_PRODUCT_SIM7100E), .driver_info = RSVD(5) | RSVD(6) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x19f5, 0x9909, 0xff, 0xff, 0xff)}, { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9003, 0xff) }, /* Simcom SIM7500/SIM7600 MBIM mode */ { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9011, 0xff), /* Simcom SIM7500/SIM7600 RNDIS mode */ .driver_info = RSVD(7) }, diff --git a/include/drm/ttm/ttm_bo.h b/include/drm/ttm/ttm_bo.h index 0223a41a64b24..0db017e3be8c3 100644 --- a/include/drm/ttm/ttm_bo.h +++ b/include/drm/ttm/ttm_bo.h @@ -316,6 +316,22 @@ static inline void ttm_bo_move_null(struct ttm_buffer_object *bo, ttm_bo_assign_mem(bo, new_mem); } +/** + * ttm_bo_lock_delayed_workqueue + * + * Prevent the delayed workqueue from running. + * Returns + * True if the workqueue was queued at the time + */ +int ttm_bo_lock_delayed_workqueue(struct ttm_device *bdev); + +/** + * ttm_bo_unlock_delayed_workqueue + * + * Allows the delayed workqueue to run. + */ +void ttm_bo_unlock_delayed_workqueue(struct ttm_device *bdev, int resched); + /** * ttm_bo_unreserve * diff --git a/include/drm/ttm/ttm_device.h b/include/drm/ttm/ttm_device.h index c22f30535c848..1116429c20007 100644 --- a/include/drm/ttm/ttm_device.h +++ b/include/drm/ttm/ttm_device.h @@ -266,6 +266,11 @@ struct ttm_device { * @wq: Work queue structure for the delayed delete workqueue. */ struct workqueue_struct *wq; + + /** + * @dwork: Delayed work in the workqueue. + */ + struct delayed_work dwork; }; int ttm_global_swapout(struct ttm_operation_ctx *ctx, gfp_t gfp_flags); diff --git a/include/linux/audit_arch.h b/include/linux/audit_arch.h index 0e34d673ef171..0436757bfc33e 100644 --- a/include/linux/audit_arch.h +++ b/include/linux/audit_arch.h @@ -17,6 +17,9 @@ enum auditsc_class_t { AUDITSC_SOCKETCALL, AUDITSC_EXECVE, AUDITSC_OPENAT2, +#ifdef CONFIG_MIPS + AUDITSC_MIPS_N32, +#endif AUDITSC_NVALS /* count */ }; diff --git a/include/sound/hda_codec.h b/include/sound/hda_codec.h index 5497dc9c396a5..940f5be340ff3 100644 --- a/include/sound/hda_codec.h +++ b/include/sound/hda_codec.h @@ -371,6 +371,8 @@ struct hda_verb { u32 param; }; +int snd_hda_codec_exec_verb(struct hda_codec *codec, unsigned int cmd, + unsigned int *res); void snd_hda_sequence_write(struct hda_codec *codec, const struct hda_verb *seq); diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h index d676ed2b246ec..747fd2789f964 100644 --- a/include/uapi/linux/audit.h +++ b/include/uapi/linux/audit.h @@ -185,7 +185,11 @@ * AUDIT_LIST commands must be implemented. */ #define AUDIT_MAX_FIELDS 64 #define AUDIT_MAX_KEY_LEN 256 +#ifdef __mips__ +#define AUDIT_BITMASK_SIZE 256 +#else #define AUDIT_BITMASK_SIZE 64 +#endif #define AUDIT_WORD(nr) ((__u32)((nr)/32)) #define AUDIT_BIT(nr) (1U << ((nr) - AUDIT_WORD(nr)*32)) @@ -201,6 +205,18 @@ #define AUDIT_CLASS_SIGNAL 8 #define AUDIT_CLASS_SIGNAL_32 9 +/* + * WARNING: Not officially assigned by upstream yet; the names of these + * constants might change breaking source compatibility. The values might + * change breaking binary compatibility. With the audit package being the + * only known user at this time the potencial problem is small + */ +#define AUDIT_CLASS_DIR_WRITE_N32 10 +#define AUDIT_CLASS_CHATTR_N32 11 +#define AUDIT_CLASS_READ_N32 12 +#define AUDIT_CLASS_WRITE_N32 13 +#define AUDIT_CLASS_SIGNAL_N32 14 + /* This bitmask is used to validate user input. It represents all bits that * are currently used in an audit field constant understood by the kernel. * If you are adding a new #define AUDIT_, please ensure that diff --git a/init/Kconfig b/init/Kconfig index 578b0a68a1042..50798796b6163 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -911,6 +911,7 @@ config NUMA_BALANCING depends on ARCH_SUPPORTS_NUMA_BALANCING depends on !ARCH_WANT_NUMA_VARIABLE_LOCALITY depends on SMP && NUMA && MIGRATION && !PREEMPT_RT + depends on TRANSPARENT_HUGEPAGE || !MIPS help This option adds support for automatic NUMA aware memory/task placement. The mechanism is quite primitive and is based on migrating memory when diff --git a/kernel/auditsc.c b/kernel/auditsc.c index 6f0d6fb6523fa..ba2b1882a4c94 100644 --- a/kernel/auditsc.c +++ b/kernel/auditsc.c @@ -189,6 +189,19 @@ static int audit_match_perm(struct audit_context *ctx, int mask) return mask & AUDIT_PERM_EXEC; case AUDITSC_OPENAT2: return mask & ACC_MODE((u32)ctx->openat2.flags); +#ifdef CONFIG_MIPS + case AUDITSC_MIPS_N32: /* for N32 */ + if ((mask & AUDIT_PERM_WRITE) && + audit_match_class(AUDIT_CLASS_WRITE_N32, n)) + return 1; + if ((mask & AUDIT_PERM_READ) && + audit_match_class(AUDIT_CLASS_READ_N32, n)) + return 1; + if ((mask & AUDIT_PERM_ATTR) && + audit_match_class(AUDIT_CLASS_CHATTR_N32, n)) + return 1; + return 0; +#endif default: return 0; } diff --git a/mm/page-writeback.c b/mm/page-writeback.c index e632ec9b64210..71cc3ba716c52 100644 --- a/mm/page-writeback.c +++ b/mm/page-writeback.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -2335,6 +2336,38 @@ static struct ctl_table vm_page_writeback_sysctls[] = { */ void __init page_writeback_init(void) { +#ifdef CONFIG_CPU_LOONGSON64 + uint64_t x, d; + unsigned long ratio = dirty_background_ratio; + unsigned long avail = global_dirtyable_memory(); + unsigned long limit = 2*800*1024*1024UL / PAGE_SIZE; + + if (avail*ratio > limit*100) { + dirty_background_ratio = 0; + vm_dirty_ratio = 0; + dirty_background_bytes = limit * PAGE_SIZE; + vm_dirty_bytes = 2 * dirty_background_bytes; + } + dirty_expire_interval = 10 * 100; + + avail = avail * PAGE_SIZE / SZ_1G; + + switch (avail) { + case 0 ... 89: + d = avail / 2; + break; + case 90 ... 269: + x = avail / 4; + x = x*x*x*x*x*x*x*x*x*x; + d = fls64(x) - 1; + break; + default: + /* 270 ... ULONG_MAX */ + d = vm_swappiness; + } + vm_swappiness -= d; +#endif + BUG_ON(wb_domain_init(&global_wb_domain, GFP_KERNEL)); cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "mm/writeback:online", diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 33af707a65ab1..2031360133790 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -68,6 +68,24 @@ static int codec_exec_verb(struct hdac_device *dev, unsigned int cmd, return err; } +int snd_hda_codec_exec_verb(struct hda_codec *codec, unsigned int raw_cmd, + unsigned int *res) +{ + unsigned cmd; + unsigned int _res; + int r; + + cmd = (u32)codec->addr << 28; + cmd |= raw_cmd; + + r = codec_exec_verb(&codec->core, cmd, 0, (res || codec->bus->core.sync_write) ? &_res : NULL); + if (res) + *res = _res; + + return r; +} +EXPORT_SYMBOL_GPL(snd_hda_codec_exec_verb); + /** * snd_hda_sequence_write - sequence writes * @codec: the HDA codec diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c index b3208b068dd80..37f1d5a1dea4c 100644 --- a/sound/pci/hda/patch_conexant.c +++ b/sound/pci/hda/patch_conexant.c @@ -43,6 +43,9 @@ struct conexant_spec { unsigned int gpio_mute_led_mask; unsigned int gpio_mic_led_mask; bool is_cx8070_sn6140; + + const unsigned int *raw_init_verbs[5]; + unsigned int num_raw_init_verbs; }; @@ -187,7 +190,13 @@ static void cx_fixup_headset_recog(struct hda_codec *codec) static int cx_auto_init(struct hda_codec *codec) { + int i, j; struct conexant_spec *spec = codec->spec; + + for (i = 0; i < spec->num_raw_init_verbs; i++) + for (j = 0; spec->raw_init_verbs[i][j] != -1; j++) + snd_hda_codec_exec_verb(codec, spec->raw_init_verbs[i][j], NULL); + snd_hda_gen_init(codec); if (!spec->dynamic_eapd) cx_auto_turn_eapd(codec, spec->num_eapds, spec->eapds, true); @@ -290,6 +299,7 @@ enum { CXT_FIXUP_STEREO_DMIC, CXT_PINCFG_LENOVO_NOTEBOOK, CXT_FIXUP_INC_MIC_BOOST, + CXT_FIXUP_SET_RAW_VERBS, CXT_FIXUP_HEADPHONE_MIC_PIN, CXT_FIXUP_HEADPHONE_MIC, CXT_FIXUP_GPIO1, @@ -316,6 +326,12 @@ enum { /* for hda_fixup_thinkpad_acpi() */ #include "thinkpad_helper.c" +static unsigned int cxt5066_raw_init_verbs_lemote_aio_a1205[] = { + 0x273f01c, /* Set speaker power to 1.5W@8ohm */ + 0x2729003, /* Set High pass filter to 90Hz */ + -1 /* end */ +}; + static void cxt_fixup_stereo_dmic(struct hda_codec *codec, const struct hda_fixup *fix, int action) { @@ -349,6 +365,19 @@ static void cxt5066_increase_mic_boost(struct hda_codec *codec, (0 << AC_AMPCAP_MUTE_SHIFT)); } +static void cxt5066_set_raw_verbs(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct conexant_spec *spec = codec->spec; + + if (action != HDA_FIXUP_ACT_PRE_PROBE) + return; + + spec->raw_init_verbs[spec->num_raw_init_verbs] = + cxt5066_raw_init_verbs_lemote_aio_a1205; + spec->num_raw_init_verbs++; +} + static void cxt_update_headset_mode(struct hda_codec *codec) { /* The verbs used in this function were tested on a Conexant CX20751/2 codec. */ @@ -851,6 +880,8 @@ static const struct hda_fixup cxt_fixups[] = { }, [CXT_PINCFG_LEMOTE_A1205] = { .type = HDA_FIXUP_PINS, + .chained = true, + .chain_id = CXT_FIXUP_SET_RAW_VERBS, .v.pins = cxt_pincfg_lemote, }, [CXT_PINCFG_COMPAQ_CQ60] = { @@ -878,6 +909,10 @@ static const struct hda_fixup cxt_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = cxt5066_increase_mic_boost, }, + [CXT_FIXUP_SET_RAW_VERBS] = { + .type = HDA_FIXUP_FUNC, + .v.func = cxt5066_set_raw_verbs, + }, [CXT_FIXUP_HEADPHONE_MIC_PIN] = { .type = HDA_FIXUP_PINS, .chained = true, @@ -1142,6 +1177,78 @@ static void add_cx5051_fake_mutes(struct hda_codec *codec) spec->gen.dac_min_mute = true; } +#ifdef CONFIG_PROC_FS +static void cxt5066_proc_hook(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid) +{ + if (nid == codec->core.afg) { + unsigned int res; + const struct { + unsigned int val; + const char *desc; + } speaker_power_map[] = { + { 0x10, "2.00 1.00 N/A N/A (Unit:W)" }, + { 0x14, "1.80 0.90 N/A N/A (Unit:W)" }, + { 0x1c, "1.50 1.00 N/A N/A (Unit:W)" }, + { 0x20, "1.40 1.00 N/A N/A (Unit:W)" }, + { 0x24, "1.20 1.00 N/A N/A (Unit:W)" }, + { 0x28, "1.00 0.50 N/A N/A (Unit:W)" }, + { 0x2c, "0.80 0.40 N/A N/A (Unit:W)" }, + { 0x30, "0.60 0.30 N/A N/A (Unit:W)" }, + { 0x34, "0.50 0.25 2.00 1.00 (Unit:W)" }, + { 0x38, "0.40 0.20 1.60 0.80 (Unit:W)" }, + { 0x3c, "0.25 0.13 1.00 0.50 (Unit:W)" }, + { -1, "???" } + }; + int i; + + snd_iprintf(buffer, "Node 0x27 [Vendor Defined Widget]:\n"); + + snd_hda_codec_exec_verb(codec, 0x27a2000, &res); + snd_iprintf(buffer, " PC beep: %s\n", + res == 0x0 ? "independent mode(default)" : + res == 0x2 ? "mixed mode" : "???"); + + snd_hda_codec_exec_verb(codec, 0x27a7000, &res); + snd_iprintf(buffer, " Class D: %s\n", + res == 0x0 ? "stereo mode(default)" : + res == 0x10 ? "mono mode" : "???"); + + snd_hda_codec_exec_verb(codec, 0x27bf000, &res); + for (i = 0; + speaker_power_map[i].val != -1 && + speaker_power_map[i].val != res; + i++) {} + snd_iprintf(buffer, " Speaker power: %s\n", + speaker_power_map[i].desc); + + snd_hda_codec_exec_verb(codec, 0x01f1f00, &res); + snd_iprintf(buffer, " GS Mark: %s\n", + res == 0x0 ? "Disabled" : + res == 0x1 ? "Enabled" : "???"); + + snd_hda_codec_exec_verb(codec, 0x27a9000, &res); + if (res > 0x0 && res <= 0x3f) + snd_iprintf(buffer, " High pass filter: %dHz\n", res*30); + else + snd_iprintf(buffer, " High pass filter: %s\n", + res == 0x0 ? "120Hz(default)" : "???"); + + snd_hda_codec_exec_verb(codec, 0x27aa008, &res); + if (res > 0x0 && res <= 0x3f) + snd_iprintf(buffer, " Low pass filter: %dHz\n", res*15); + else + snd_iprintf(buffer, " Low pass filter: %s\n", + res == 0x0 ? "Disabled" : "???"); + + snd_hda_codec_exec_verb(codec, 0x27b4100, &res); + snd_iprintf(buffer, " Adjust 3.3V LDO voltage: 0x%x\n", res); + } +} +#else +#define cxt5066_proc_hook NULL +#endif + static int patch_conexant_auto(struct hda_codec *codec) { struct conexant_spec *spec; @@ -1165,6 +1272,8 @@ static int patch_conexant_auto(struct hda_codec *codec) break; } + codec->proc_widget_hook = cxt5066_proc_hook; + cx_auto_parse_eapd(codec); spec->gen.own_eapd_ctl = 1; @@ -1189,6 +1298,14 @@ static int patch_conexant_auto(struct hda_codec *codec) snd_hda_pick_fixup(codec, cxt5051_fixup_models, cxt5051_fixups, cxt_fixups); break; + /* CX20631/CX20641 node1b's default value is not same as datasheet, cause rear mic not work */ + case 0x14f15097: + case 0x14f150a1: + snd_hda_codec_set_pincfg(codec, 0x1b, 0x01a190f0); + codec->pin_amp_workaround = 1; + snd_hda_pick_fixup(codec, cxt5066_fixup_models, + cxt5066_fixups, cxt_fixups); + break; case 0x14f15098: codec->pin_amp_workaround = 1; spec->gen.mixer_nid = 0x22;