diff options
author | Christian Lamparter <chunkeey@googlemail.com> | 2017-02-10 17:44:43 +0100 |
---|---|---|
committer | Felix Fietkau <nbd@nbd.name> | 2017-02-11 20:57:56 +0100 |
commit | 9a9f2f97e6b66cc88866b3522372d4caa59b26de (patch) | |
tree | f05b66ca99f795a300efd2cea7b6dce0df8b365a /target | |
parent | 9827c3e9b9d366be2edc8f1d27a1b84d03cfa97d (diff) | |
download | upstream-9a9f2f97e6b66cc88866b3522372d4caa59b26de.tar.gz upstream-9a9f2f97e6b66cc88866b3522372d4caa59b26de.tar.bz2 upstream-9a9f2f97e6b66cc88866b3522372d4caa59b26de.zip |
apm821xx: add linux 4.9 apm821xx patches
This patch updates the apm821xx target to use the 4.9 kernel.
Signed-off-by: Christian Lamparter <chunkeey@googlemail.com>
Diffstat (limited to 'target')
14 files changed, 2573 insertions, 1 deletions
diff --git a/target/linux/apm821xx/Makefile b/target/linux/apm821xx/Makefile index 026466d86d..6d711543c4 100644 --- a/target/linux/apm821xx/Makefile +++ b/target/linux/apm821xx/Makefile @@ -13,7 +13,7 @@ MAINTAINER:=Chris Blake <chrisrblake93@gmail.com>, \ Christian Lamparter <chunkeey@gmail.com> SUBTARGETS:=nand sata -KERNEL_PATCHVER:=4.4 +KERNEL_PATCHVER:=4.9 define Target/Description Build images for AppliedMicro APM821xx based boards. diff --git a/target/linux/apm821xx/config-4.9 b/target/linux/apm821xx/config-4.9 new file mode 100644 index 0000000000..d9b3283f29 --- /dev/null +++ b/target/linux/apm821xx/config-4.9 @@ -0,0 +1,327 @@ +# CONFIG_40x is not set +CONFIG_44x=y +CONFIG_460EX=y +CONFIG_4xx=y +CONFIG_4xx_SOC=y +# CONFIG_ADVANCED_OPTIONS is not set +CONFIG_APM821xx=y +CONFIG_APOLLO3G=y +# CONFIG_ARCHES is not set +CONFIG_ARCH_DMA_ADDR_T_64BIT=y +CONFIG_ARCH_ENABLE_MEMORY_HOTPLUG=y +CONFIG_ARCH_ENABLE_MEMORY_HOTREMOVE=y +CONFIG_ARCH_HAS_DMA_SET_COHERENT_MASK=y +CONFIG_ARCH_HAS_ELF_RANDOMIZE=y +CONFIG_ARCH_HAS_GCOV_PROFILE_ALL=y +CONFIG_ARCH_HAS_ILOG2_U32=y +CONFIG_ARCH_HAS_SG_CHAIN=y +CONFIG_ARCH_HAS_WALK_MEMORY=y +CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG=y +CONFIG_ARCH_HIBERNATION_POSSIBLE=y +CONFIG_ARCH_MAY_HAVE_PC_FDC=y +CONFIG_ARCH_MIGHT_HAVE_PC_PARPORT=y +CONFIG_ARCH_MIGHT_HAVE_PC_SERIO=y +CONFIG_ARCH_PHYS_ADDR_T_64BIT=y +# CONFIG_ARCH_RANDOM is not set +CONFIG_ARCH_SUPPORTS_ATOMIC_RMW=y +CONFIG_ARCH_SUPPORTS_DEBUG_PAGEALLOC=y +CONFIG_ARCH_SUPPORTS_DEFERRED_STRUCT_PAGE_INIT=y +CONFIG_ARCH_SUPPORTS_UPROBES=y +CONFIG_ARCH_SUSPEND_POSSIBLE=y +CONFIG_ARCH_USE_BUILTIN_BSWAP=y +CONFIG_ARCH_WANT_IPC_PARSE_VERSION=y +CONFIG_AUDIT_ARCH=y +# CONFIG_BAMBOO is not set +CONFIG_BCH=y +CONFIG_BLK_MQ_PCI=y +# CONFIG_BLUESTONE is not set +CONFIG_BOOKE=y +CONFIG_BOOKE_WDT=y +# CONFIG_BOUNCE is not set +CONFIG_BUCKMINSTER=y +# CONFIG_CANYONLANDS is not set +CONFIG_CLONE_BACKWARDS=y +CONFIG_CMDLINE="rootfstype=squashfs noinitrd" +CONFIG_CMDLINE_BOOL=y +CONFIG_CONSISTENT_SIZE=0x00200000 +CONFIG_CPU_BIG_ENDIAN=y +CONFIG_CRC16=y +CONFIG_CRYPTO_DEFLATE=y +CONFIG_CRYPTO_DEV_PPC4XX=y +CONFIG_CRYPTO_HASH=y +CONFIG_CRYPTO_HASH2=y +CONFIG_CRYPTO_HW=y +CONFIG_CRYPTO_LZO=y +CONFIG_CRYPTO_MD5_PPC=y +CONFIG_CRYPTO_RNG2=y +CONFIG_CRYPTO_SHA1_PPC=y +CONFIG_CRYPTO_WORKQUEUE=y +CONFIG_DECOMPRESS_GZIP=y +# CONFIG_DEFAULT_UIMAGE is not set +CONFIG_DTC=y +# CONFIG_E200 is not set +CONFIG_EARLY_PRINTK=y +# CONFIG_EBONY is not set +CONFIG_EDAC_ATOMIC_SCRUB=y +CONFIG_EDAC_SUPPORT=y +# CONFIG_EIGER is not set +# CONFIG_EPAPR_BOOT is not set +CONFIG_EXTRA_TARGETS="uImage" +CONFIG_FIXED_PHY=y +CONFIG_FREEZER=y +# CONFIG_FSL_LBC is not set +# CONFIG_FSL_ULI1575 is not set +CONFIG_GENERIC_ATOMIC64=y +CONFIG_GENERIC_BUG=y +CONFIG_GENERIC_CLOCKEVENTS=y +CONFIG_GENERIC_CMOS_UPDATE=y +CONFIG_GENERIC_CPU_AUTOPROBE=y +# CONFIG_GENERIC_CSUM is not set +CONFIG_GENERIC_IO=y +CONFIG_GENERIC_IRQ_SHOW=y +CONFIG_GENERIC_IRQ_SHOW_LEVEL=y +CONFIG_GENERIC_ISA_DMA=y +CONFIG_GENERIC_MSI_IRQ=y +CONFIG_GENERIC_NVRAM=y +CONFIG_GENERIC_PCI_IOMAP=y +CONFIG_GENERIC_SMP_IDLE_THREAD=y +CONFIG_GENERIC_STRNCPY_FROM_USER=y +CONFIG_GENERIC_STRNLEN_USER=y +# CONFIG_GENERIC_TBSYNC is not set +CONFIG_GENERIC_TIME_VSYSCALL_OLD=y +# CONFIG_GEN_RTC is not set +# CONFIG_GE_FPGA is not set +# CONFIG_GLACIER is not set +CONFIG_GPIOLIB=y +CONFIG_GPIO_GENERIC=y +CONFIG_GPIO_GENERIC_PLATFORM=y +CONFIG_GPIO_SYSFS=y +CONFIG_HAS_DMA=y +CONFIG_HAS_IOMEM=y +CONFIG_HAS_IOPORT_MAP=y +# CONFIG_HAS_RAPIDIO is not set +# CONFIG_HAVE_64BIT_ALIGNED_ACCESS is not set +CONFIG_HAVE_ARCH_AUDITSYSCALL=y +# CONFIG_HAVE_ARCH_BITREVERSE is not set +CONFIG_HAVE_ARCH_JUMP_LABEL=y +CONFIG_HAVE_ARCH_KGDB=y +CONFIG_HAVE_ARCH_SECCOMP_FILTER=y +CONFIG_HAVE_ARCH_TRACEHOOK=y +# CONFIG_HAVE_BOOTMEM_INFO_NODE is not set +CONFIG_HAVE_CBPF_JIT=y +CONFIG_HAVE_DEBUG_KMEMLEAK=y +CONFIG_HAVE_DEBUG_STACKOVERFLOW=y +CONFIG_HAVE_DMA_API_DEBUG=y +CONFIG_HAVE_DYNAMIC_FTRACE=y +CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y +CONFIG_HAVE_FTRACE_MCOUNT_RECORD=y +CONFIG_HAVE_FUNCTION_GRAPH_TRACER=y +CONFIG_HAVE_FUNCTION_TRACER=y +# CONFIG_HAVE_GENERIC_DMA_COHERENT is not set +CONFIG_HAVE_GENERIC_RCU_GUP=y +CONFIG_HAVE_IDE=y +CONFIG_HAVE_IOREMAP_PROT=y +CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK=y +CONFIG_HAVE_MEMBLOCK=y +CONFIG_HAVE_MEMBLOCK_NODE_MAP=y +CONFIG_HAVE_MOD_ARCH_SPECIFIC=y +CONFIG_HAVE_NET_DSA=y +CONFIG_HAVE_OPROFILE=y +CONFIG_HAVE_PERF_EVENTS=y +CONFIG_HAVE_PERF_REGS=y +CONFIG_HAVE_PERF_USER_STACK_DUMP=y +CONFIG_HAVE_REGS_AND_STACK_ACCESS_API=y +# CONFIG_HAVE_SETUP_PER_CPU_AREA is not set +CONFIG_HAVE_SYSCALL_TRACEPOINTS=y +CONFIG_HAVE_VIRT_CPU_ACCOUNTING=y +CONFIG_HW_RANDOM=y +CONFIG_HW_RANDOM_PPC4XX=y +CONFIG_HZ=1000 +# CONFIG_HZ_100 is not set +CONFIG_HZ_1000=y +CONFIG_HZ_PERIODIC=y +CONFIG_I2C=y +CONFIG_I2C_BOARDINFO=y +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_IBM_IIC=y +CONFIG_IBM_EMAC=y +CONFIG_IBM_EMAC_EMAC4=y +CONFIG_IBM_EMAC_POLL_WEIGHT=32 +CONFIG_IBM_EMAC_RGMII=y +CONFIG_IBM_EMAC_RXB=128 +CONFIG_IBM_EMAC_RX_COPY_THRESHOLD=256 +CONFIG_IBM_EMAC_RX_SKB_HEADROOM=0 +CONFIG_IBM_EMAC_TAH=y +CONFIG_IBM_EMAC_TXB=128 +# CONFIG_ICON is not set +CONFIG_IKAREM=y +CONFIG_INITRAMFS_SOURCE="" +# CONFIG_IOMMU_HELPER is not set +# CONFIG_IPIC is not set +CONFIG_IRQCHIP=y +CONFIG_IRQ_DOMAIN=y +CONFIG_IRQ_FORCED_THREADING=y +CONFIG_IRQ_WORK=y +CONFIG_ISA_DMA_API=y +# CONFIG_JFFS2_FS is not set +# CONFIG_KATMAI is not set +CONFIG_KERNEL_GZIP=y +CONFIG_KERNEL_START=0xc0000000 +CONFIG_LEDS_TRIGGER_MTD=y +CONFIG_LIBFDT=y +CONFIG_LOWMEM_SIZE=0x30000000 +CONFIG_LZO_COMPRESS=y +CONFIG_LZO_DECOMPRESS=y +# CONFIG_MATH_EMULATION is not set +CONFIG_MDIO_BOARDINFO=y +# CONFIG_MDIO_HISI_FEMAC is not set +# CONFIG_MFD_ACT8945A is not set +# CONFIG_MFD_MAX77620 is not set +# CONFIG_MMIO_NVRAM is not set +CONFIG_MODULES_USE_ELF_RELA=y +# CONFIG_MPIC is not set +# CONFIG_MPIC_U3_HT_IRQS is not set +# CONFIG_MPIC_WEIRD is not set +CONFIG_MTD_CFI_ADV_OPTIONS=y +# CONFIG_MTD_CFI_GEOMETRY is not set +CONFIG_MTD_NAND=y +CONFIG_MTD_NAND_BCH=y +CONFIG_MTD_NAND_ECC=y +CONFIG_MTD_NAND_ECC_BCH=y +CONFIG_MTD_NAND_ECC_SMC=y +CONFIG_MTD_NAND_NDFC=y +# CONFIG_MTD_SPLIT is not set +# CONFIG_MTD_SPLIT_SQUASHFS_ROOT is not set +CONFIG_NEED_DMA_MAP_STATE=y +# CONFIG_NEED_PER_CPU_EMBED_FIRST_CHUNK is not set +CONFIG_NEED_PER_CPU_KM=y +CONFIG_NEED_SG_DMA_LENGTH=y +# CONFIG_NONSTATIC_KERNEL is not set +CONFIG_NOT_COHERENT_CACHE=y +CONFIG_NO_BOOTMEM=y +CONFIG_NR_IRQS=512 +CONFIG_OF=y +CONFIG_OF_ADDRESS=y +CONFIG_OF_ADDRESS_PCI=y +CONFIG_OF_EARLY_FLATTREE=y +CONFIG_OF_FLATTREE=y +CONFIG_OF_GPIO=y +CONFIG_OF_IRQ=y +CONFIG_OF_MDIO=y +CONFIG_OF_NET=y +CONFIG_OF_PCI=y +CONFIG_OF_PCI_IRQ=y +CONFIG_OF_RESERVED_MEM=y +CONFIG_OLD_SIGACTION=y +CONFIG_OLD_SIGSUSPEND=y +CONFIG_PAGE_OFFSET=0xc0000000 +CONFIG_PCI=y +CONFIG_PCIEAER=y +CONFIG_PCIEPORTBUS=y +CONFIG_PCIE_PME=y +CONFIG_PCI_BUS_ADDR_T_64BIT=y +CONFIG_PCI_DISABLE_COMMON_QUIRKS=y +CONFIG_PCI_DOMAINS=y +CONFIG_PCI_MSI=y +# CONFIG_PCI_MSI_IRQ_DOMAIN is not set +CONFIG_PGTABLE_LEVELS=2 +CONFIG_PHYLIB=y +CONFIG_PHYSICAL_START=0x00000000 +CONFIG_PHYS_64BIT=y +CONFIG_PHYS_ADDR_T_64BIT=y +CONFIG_PM=y +CONFIG_PM_AUTOSLEEP=y +# CONFIG_PM_DEBUG is not set +CONFIG_PM_SLEEP=y +CONFIG_PM_WAKELOCKS=y +CONFIG_PM_WAKELOCKS_GC=y +CONFIG_PM_WAKELOCKS_LIMIT=100 +CONFIG_PPC=y +CONFIG_PPC32=y +CONFIG_PPC44x_SIMPLE=y +CONFIG_PPC4xx_CPM=y +CONFIG_PPC4xx_GPIO=y +# CONFIG_PPC4xx_HSTA_MSI is not set +CONFIG_PPC4xx_MSI=y +CONFIG_PPC4xx_OCM=y +CONFIG_PPC4xx_PCI_EXPRESS=y +# CONFIG_PPC64 is not set +# CONFIG_PPC_47x is not set +# CONFIG_PPC_85xx is not set +# CONFIG_PPC_8xx is not set +# CONFIG_PPC_970_NAP is not set +CONFIG_PPC_ADV_DEBUG_DACS=2 +CONFIG_PPC_ADV_DEBUG_DAC_RANGE=y +CONFIG_PPC_ADV_DEBUG_DVCS=2 +CONFIG_PPC_ADV_DEBUG_IACS=4 +CONFIG_PPC_ADV_DEBUG_REGS=y +# CONFIG_PPC_BOOK3S_32 is not set +# CONFIG_PPC_CELL is not set +# CONFIG_PPC_CELL_NATIVE is not set +# CONFIG_PPC_COPRO_BASE is not set +CONFIG_PPC_DCR=y +# CONFIG_PPC_DCR_MMIO is not set +CONFIG_PPC_DCR_NATIVE=y +# CONFIG_PPC_DOORBELL is not set +# CONFIG_PPC_EARLY_DEBUG is not set +# CONFIG_PPC_EPAPR_HV_PIC is not set +CONFIG_PPC_FPU=y +# CONFIG_PPC_I8259 is not set +# CONFIG_PPC_ICP_HV is not set +# CONFIG_PPC_ICP_NATIVE is not set +# CONFIG_PPC_ICS_RTAS is not set +CONFIG_PPC_INDIRECT_PCI=y +CONFIG_PPC_LIB_RHEAP=y +CONFIG_PPC_MMU_NOHASH=y +# CONFIG_PPC_MM_SLICES is not set +# CONFIG_PPC_MPC106 is not set +CONFIG_PPC_MSI_BITMAP=y +# CONFIG_PPC_P7_NAP is not set +CONFIG_PPC_PCI_CHOICE=y +# CONFIG_PPC_RTAS is not set +CONFIG_PPC_UDBG_16550=y +CONFIG_PPC_WERROR=y +# CONFIG_PPC_XICS is not set +# CONFIG_PQ2ADS is not set +CONFIG_PTE_64BIT=y +# CONFIG_QORIQ_THERMAL is not set +# CONFIG_RAINIER is not set +CONFIG_RAS=y +# CONFIG_RCU_STALL_COMMON is not set +CONFIG_RD_GZIP=y +# CONFIG_RELOCATABLE is not set +CONFIG_RWSEM_XCHGADD_ALGORITHM=y +# CONFIG_SAM440EP is not set +CONFIG_SCHED_HRTICK=y +# CONFIG_SCHED_INFO is not set +# CONFIG_SCSI_DMA is not set +# CONFIG_SEQUOIA is not set +CONFIG_SERIAL_8250_EXTENDED=y +CONFIG_SERIAL_8250_FSL=y +CONFIG_SERIAL_8250_SHARE_IRQ=y +CONFIG_SERIAL_OF_PLATFORM=y +# CONFIG_SG_POOL is not set +CONFIG_SIMPLE_GPIO=y +CONFIG_SPARSE_IRQ=y +CONFIG_SRCU=y +CONFIG_SUSPEND=y +CONFIG_SUSPEND_FREEZER=y +# CONFIG_SWIOTLB is not set +CONFIG_SWPHY=y +CONFIG_SYSCTL_EXCEPTION_TRACE=y +# CONFIG_TAISHAN is not set +CONFIG_TASK_SIZE=0xc0000000 +CONFIG_TICK_CPU_ACCOUNTING=y +CONFIG_USB_SUPPORT=y +CONFIG_VDSO32=y +# CONFIG_VIRT_CPU_ACCOUNTING_NATIVE is not set +# CONFIG_WARP is not set +CONFIG_WATCHDOG_CORE=y +CONFIG_WNDR4700=y +# CONFIG_XILINX_SYSACE is not set +# CONFIG_XILINX_VIRTEX440_GENERIC_BOARD is not set +CONFIG_XZ_DEC_BCJ=y +CONFIG_XZ_DEC_POWERPC=y +# CONFIG_YOSEMITE is not set +CONFIG_ZLIB_DEFLATE=y +CONFIG_ZLIB_INFLATE=y diff --git a/target/linux/apm821xx/patches-4.9/200-add-meraki-mr24-ikarem-support.patch b/target/linux/apm821xx/patches-4.9/200-add-meraki-mr24-ikarem-support.patch new file mode 100644 index 0000000000..018bcf21a0 --- /dev/null +++ b/target/linux/apm821xx/patches-4.9/200-add-meraki-mr24-ikarem-support.patch @@ -0,0 +1,32 @@ +--- a/arch/powerpc/platforms/44x/Kconfig ++++ b/arch/powerpc/platforms/44x/Kconfig +@@ -40,6 +40,19 @@ config EBONY + help + This option enables support for the IBM PPC440GP evaluation board. + ++config IKAREM ++ bool "Ikarem" ++ depends on 44x ++ default n ++ select PPC44x_SIMPLE ++ select APM821xx ++ select PCI_MSI ++ select PPC4xx_MSI ++ select PPC4xx_PCI_EXPRESS ++ select IBM_EMAC_RGMII ++ help ++ This option enables support for the Cisco Meraki MR24 (Ikarem) Access Point. ++ + config SAM440EP + bool "Sam440ep" + depends on 44x +--- a/arch/powerpc/platforms/44x/ppc44x_simple.c ++++ b/arch/powerpc/platforms/44x/ppc44x_simple.c +@@ -62,6 +62,7 @@ static char *board[] __initdata = { + "amcc,sequoia", + "amcc,taishan", + "amcc,yosemite", ++ "meraki,ikarem", + "mosaixtech,icon" + }; + diff --git a/target/linux/apm821xx/patches-4.9/201-add-amcc-apollo3g-support.patch b/target/linux/apm821xx/patches-4.9/201-add-amcc-apollo3g-support.patch new file mode 100644 index 0000000000..0581a43e33 --- /dev/null +++ b/target/linux/apm821xx/patches-4.9/201-add-amcc-apollo3g-support.patch @@ -0,0 +1,30 @@ +--- a/arch/powerpc/platforms/44x/Kconfig ++++ b/arch/powerpc/platforms/44x/Kconfig +@@ -143,6 +143,17 @@ config CANYONLANDS + help + This option enables support for the AMCC PPC460EX evaluation board. + ++config APOLLO3G ++ bool "Apollo3G" ++ depends on 44x ++ default n ++ select PPC44x_SIMPLE ++ select APM821xx ++ select IBM_EMAC_RGMII ++ select 460EX ++ help ++ This option enables support for the AMCC Apollo 3G board. ++ + config GLACIER + bool "Glacier" + depends on 44x +--- a/arch/powerpc/platforms/44x/ppc44x_simple.c ++++ b/arch/powerpc/platforms/44x/ppc44x_simple.c +@@ -50,6 +50,7 @@ machine_device_initcall(ppc44x_simple, p + * board.c file for it rather than adding it to this list. + */ + static char *board[] __initdata = { ++ "amcc,apollo3g", + "amcc,arches", + "amcc,bamboo", + "apm,bluestone", diff --git a/target/linux/apm821xx/patches-4.9/202-add-netgear-wndr4700-support.patch b/target/linux/apm821xx/patches-4.9/202-add-netgear-wndr4700-support.patch new file mode 100644 index 0000000000..6b6db1dddb --- /dev/null +++ b/target/linux/apm821xx/patches-4.9/202-add-netgear-wndr4700-support.patch @@ -0,0 +1,32 @@ +--- a/arch/powerpc/platforms/44x/Makefile ++++ b/arch/powerpc/platforms/44x/Makefile +@@ -3,6 +3,7 @@ ifneq ($(CONFIG_PPC4xx_CPM),y) + obj-$(CONFIG_44x) += idle.o + endif + obj-$(CONFIG_PPC44x_SIMPLE) += ppc44x_simple.o ++obj-$(CONFIG_WNDR4700) += wndr4700.o + obj-$(CONFIG_EBONY) += ebony.o + obj-$(CONFIG_SAM440EP) += sam440ep.o + obj-$(CONFIG_WARP) += warp.o +--- a/arch/powerpc/platforms/44x/Kconfig ++++ b/arch/powerpc/platforms/44x/Kconfig +@@ -260,6 +260,19 @@ config ICON + help + This option enables support for the AMCC PPC440SPe evaluation board. + ++config WNDR4700 ++ bool "WNDR4700" ++ depends on 44x ++ default n ++ select APM821xx ++ select PCI_MSI ++ select PPC4xx_MSI ++ select PPC4xx_PCI_EXPRESS ++ select IBM_EMAC_RGMII ++ select 460EX ++ help ++ This option enables support for the Netgear WNDR4700/WNDR4720 board. ++ + config XILINX_VIRTEX440_GENERIC_BOARD + bool "Generic Xilinx Virtex 5 FXT board support" + depends on 44x diff --git a/target/linux/apm821xx/patches-4.9/203-add-meraki-mx60-buckminster-support.patch b/target/linux/apm821xx/patches-4.9/203-add-meraki-mx60-buckminster-support.patch new file mode 100644 index 0000000000..8c072387d4 --- /dev/null +++ b/target/linux/apm821xx/patches-4.9/203-add-meraki-mx60-buckminster-support.patch @@ -0,0 +1,32 @@ +--- a/arch/powerpc/platforms/44x/Kconfig ++++ b/arch/powerpc/platforms/44x/Kconfig +@@ -30,6 +30,19 @@ config BLUESTONE + help + This option enables support for the APM APM821xx Evaluation board. + ++config BUCKMINSTER ++ bool "Buckminster" ++ depends on 44x ++ default n ++ select APM821xx ++ select PCI_MSI ++ select PPC4xx_MSI ++ select PPC4xx_PCI_EXPRESS ++ select IBM_EMAC_RGMII ++ select 460EX ++ help ++ This option enables support for the Cisco Meraki MX60/MX60W (Buckminster) Security Appliance ++ + config EBONY + bool "Ebony" + depends on 44x +--- a/arch/powerpc/platforms/44x/ppc44x_simple.c ++++ b/arch/powerpc/platforms/44x/ppc44x_simple.c +@@ -63,6 +63,7 @@ static char *board[] __initdata = { + "amcc,sequoia", + "amcc,taishan", + "amcc,yosemite", ++ "meraki,buckminster", + "meraki,ikarem", + "mosaixtech,icon" + }; diff --git a/target/linux/apm821xx/patches-4.9/300-fix-atheros-nics-on-apm82181.patch b/target/linux/apm821xx/patches-4.9/300-fix-atheros-nics-on-apm82181.patch new file mode 100644 index 0000000000..0b1affcfe7 --- /dev/null +++ b/target/linux/apm821xx/patches-4.9/300-fix-atheros-nics-on-apm82181.patch @@ -0,0 +1,51 @@ +--- a/arch/powerpc/sysdev/ppc4xx_pci.c ++++ b/arch/powerpc/sysdev/ppc4xx_pci.c +@@ -1066,15 +1066,24 @@ static int __init apm821xx_pciex_init_po + u32 val; + + /* +- * Do a software reset on PCIe ports. +- * This code is to fix the issue that pci drivers doesn't re-assign +- * bus number for PCIE devices after Uboot +- * scanned and configured all the buses (eg. PCIE NIC IntelPro/1000 +- * PT quad port, SAS LSI 1064E) ++ * Only reset the PHY when no link is currently established. ++ * This is for the Atheros PCIe board which has problems to establish ++ * the link (again) after this PHY reset. All other currently tested ++ * PCIe boards don't show this problem. + */ +- +- mtdcri(SDR0, PESDR0_460EX_PHY_CTL_RST, 0x0); +- mdelay(10); ++ val = mfdcri(SDR0, port->sdr_base + PESDRn_LOOP); ++ if (!(val & 0x00001000)) { ++ /* ++ * Do a software reset on PCIe ports. ++ * This code is to fix the issue that pci drivers doesn't re-assign ++ * bus number for PCIE devices after Uboot ++ * scanned and configured all the buses (eg. PCIE NIC IntelPro/1000 ++ * PT quad port, SAS LSI 1064E) ++ */ ++ ++ mtdcri(SDR0, PESDR0_460EX_PHY_CTL_RST, 0x0); ++ mdelay(10); ++ } + + if (port->endpoint) + val = PTYPE_LEGACY_ENDPOINT << 20; +@@ -1091,9 +1100,12 @@ static int __init apm821xx_pciex_init_po + mtdcri(SDR0, PESDR0_460EX_L0DRV, 0x00000130); + mtdcri(SDR0, PESDR0_460EX_L0CLK, 0x00000006); + +- mtdcri(SDR0, PESDR0_460EX_PHY_CTL_RST, 0x10000000); +- mdelay(50); +- mtdcri(SDR0, PESDR0_460EX_PHY_CTL_RST, 0x30000000); ++ val = mfdcri(SDR0, port->sdr_base + PESDRn_LOOP); ++ if (!(val & 0x00001000)) { ++ mtdcri(SDR0, PESDR0_460EX_PHY_CTL_RST, 0x10000000); ++ mdelay(50); ++ mtdcri(SDR0, PESDR0_460EX_PHY_CTL_RST, 0x30000000); ++ } + + mtdcri(SDR0, port->sdr_base + PESDRn_RCSSET, + mfdcri(SDR0, port->sdr_base + PESDRn_RCSSET) | diff --git a/target/linux/apm821xx/patches-4.9/301-fix-memory-map-wndr4700.patch b/target/linux/apm821xx/patches-4.9/301-fix-memory-map-wndr4700.patch new file mode 100644 index 0000000000..d6a1006177 --- /dev/null +++ b/target/linux/apm821xx/patches-4.9/301-fix-memory-map-wndr4700.patch @@ -0,0 +1,14 @@ +--- a/arch/powerpc/sysdev/ppc4xx_pci.c ++++ b/arch/powerpc/sysdev/ppc4xx_pci.c +@@ -1913,9 +1913,9 @@ static void __init ppc4xx_configure_pcie + * if it works + */ + out_le32(mbase + PECFG_PIM0LAL, 0x00000000); +- out_le32(mbase + PECFG_PIM0LAH, 0x00000000); ++ out_le32(mbase + PECFG_PIM0LAH, 0x00000008); + out_le32(mbase + PECFG_PIM1LAL, 0x00000000); +- out_le32(mbase + PECFG_PIM1LAH, 0x00000000); ++ out_le32(mbase + PECFG_PIM1LAH, 0x0000000c); + out_le32(mbase + PECFG_PIM01SAH, 0xffff0000); + out_le32(mbase + PECFG_PIM01SAL, 0x00000000); + diff --git a/target/linux/apm821xx/patches-4.9/701-powerpc_ibm_apm82181_phyclk_fix.patch b/target/linux/apm821xx/patches-4.9/701-powerpc_ibm_apm82181_phyclk_fix.patch new file mode 100644 index 0000000000..93a0858b26 --- /dev/null +++ b/target/linux/apm821xx/patches-4.9/701-powerpc_ibm_apm82181_phyclk_fix.patch @@ -0,0 +1,52 @@ +--- a/drivers/net/ethernet/ibm/emac/core.c ++++ b/drivers/net/ethernet/ibm/emac/core.c +@@ -129,6 +129,7 @@ static inline void emac_report_timeout_e + { + if (emac_has_feature(dev, EMAC_FTR_440GX_PHY_CLK_FIX | + EMAC_FTR_460EX_PHY_CLK_FIX | ++ EMAC_FTR_APM821XX_PHY_CLK_FIX | + EMAC_FTR_440EP_PHY_CLK_FIX)) + DBG(dev, "%s" NL, error); + else if (net_ratelimit()) +@@ -146,6 +147,10 @@ static inline void emac_rx_clk_tx(struct + if (emac_has_feature(dev, EMAC_FTR_440EP_PHY_CLK_FIX)) + dcri_clrset(SDR0, SDR0_MFR, + 0, SDR0_MFR_ECS >> dev->cell_index); ++ ++ if (emac_has_feature(dev, EMAC_FTR_APM821XX_PHY_CLK_FIX)) ++ dcri_clrset(SDR0, SDR0_ETH_CFG, ++ 0, 0x00000100 >> dev->cell_index); + #endif + } + +@@ -155,6 +160,10 @@ static inline void emac_rx_clk_default(s + if (emac_has_feature(dev, EMAC_FTR_440EP_PHY_CLK_FIX)) + dcri_clrset(SDR0, SDR0_MFR, + SDR0_MFR_ECS >> dev->cell_index, 0); ++ ++ if (emac_has_feature(dev, EMAC_FTR_APM821XX_PHY_CLK_FIX)) ++ dcri_clrset(SDR0, SDR0_ETH_CFG, ++ 0x00000100 >> dev->cell_index, 0); + #endif + } + +@@ -2617,7 +2626,7 @@ static int emac_init_config(struct emac_ + if (of_device_is_compatible(np, "ibm,emac-apm821xx")) { + dev->features |= (EMAC_APM821XX_REQ_JUMBO_FRAME_SIZE | + EMAC_FTR_APM821XX_NO_HALF_DUPLEX | +- EMAC_FTR_460EX_PHY_CLK_FIX); ++ EMAC_FTR_APM821XX_PHY_CLK_FIX); + } + } else if (of_device_is_compatible(np, "ibm,emac4")) { + dev->features |= EMAC_FTR_EMAC4; +--- a/drivers/net/ethernet/ibm/emac/core.h ++++ b/drivers/net/ethernet/ibm/emac/core.h +@@ -333,6 +333,8 @@ struct emac_instance { + */ + #define EMAC_FTR_APM821XX_NO_HALF_DUPLEX 0x00001000 + ++#define EMAC_FTR_APM821XX_PHY_CLK_FIX 0x000002000 ++ + /* Right now, we don't quite handle the always/possible masks on the + * most optimal way as we don't have a way to say something like + * always EMAC4. Patches welcome. diff --git a/target/linux/apm821xx/patches-4.9/702-powerpc_ibm_phy_add_dt_parser.patch b/target/linux/apm821xx/patches-4.9/702-powerpc_ibm_phy_add_dt_parser.patch new file mode 100644 index 0000000000..f1edb9cc7a --- /dev/null +++ b/target/linux/apm821xx/patches-4.9/702-powerpc_ibm_phy_add_dt_parser.patch @@ -0,0 +1,328 @@ +From b1c54da602ae9215cfbde1c3ed3b6296b76f07fc Mon Sep 17 00:00:00 2001 +Message-Id: <b1c54da602ae9215cfbde1c3ed3b6296b76f07fc.1486337989.git.chunkeey@googlemail.com> +In-Reply-To: <246bd6614529d28dc48b11981ab5dae7a7364fc2.1486337989.git.chunkeey@googlemail.com> +References: <246bd6614529d28dc48b11981ab5dae7a7364fc2.1486337989.git.chunkeey@googlemail.com> +From: Christian Lamparter <chunkeey@gmail.com> +Date: Mon, 13 Jun 2016 15:42:21 +0200 +Subject: [RFC 2/2] net: emac: add support for device-tree based PHY discovery + and setup +To: netdev@vger.kernel.org, + devicetree@vger.kernel.org +Cc: David S. Miller <davem@davemloft.net>, + Ivan Mikhaylov <ivan@de.ibm.com>, + Mark Rutland <mark.rutland@arm.com>, + Rob Herring <robh+dt@kernel.org> + +This patch adds glue-code that allows the EMAC driver to interface +with the existing dt-supported PHYs in drivers/net/phy. + +Because currently, the emac driver maintains a small library of +supported phys for in a private phy.c file located in the drivers +directory. + +The support is limited to mostly single ethernet transceiver like the: +CIS8201, BCM5248, ET1011C, Marvell 88E1111 and 88E1112, AR8035. +However, routers like the Netgear WNDR4700 and Cisco Meraki MX60(W) +have a 5-port switch (QCA8327N) attached to the MDIO of the EMAC. +The switch chip has already a proper phy-driver (ar8216) that uses +the generic phy library. + +Signed-off-by: Christian Lamparter <chunkeey@googlemail.com> +--- +--- a/drivers/net/ethernet/ibm/emac/core.c ++++ b/drivers/net/ethernet/ibm/emac/core.c +@@ -42,6 +42,7 @@ + #include <linux/of_address.h> + #include <linux/of_irq.h> + #include <linux/of_net.h> ++#include <linux/of_mdio.h> + #include <linux/slab.h> + + #include <asm/processor.h> +@@ -2422,6 +2423,229 @@ static int emac_read_uint_prop(struct de + return 0; + } + ++static void emac_adjust_link(struct net_device *ndev) ++{ ++ struct emac_instance *dev = netdev_priv(ndev); ++ struct phy_device *phy = dev->phy_dev; ++ ++ dev->phy.autoneg = phy->autoneg; ++ dev->phy.speed = phy->speed; ++ dev->phy.duplex = phy->duplex; ++ dev->phy.pause = phy->pause; ++ dev->phy.asym_pause = phy->asym_pause; ++ dev->phy.advertising = phy->advertising; ++} ++ ++static int emac_mii_bus_read(struct mii_bus *bus, int addr, int regnum) ++{ ++ return emac_mdio_read(bus->priv, addr, regnum); ++} ++ ++static int emac_mii_bus_write(struct mii_bus *bus, int addr, int regnum, u16 val) ++{ ++ emac_mdio_write(bus->priv, addr, regnum, val); ++ return 0; ++} ++ ++static int emac_mii_bus_reset(struct mii_bus *bus) ++{ ++ struct emac_instance *dev = netdev_priv(bus->priv); ++ int err; ++ ++ err = emac_reset(dev); ++ if (err) ++ return err; ++ /* Meraki MX60(W)'s uboot will disable the switch and ++ * a bus reset won't do anything. */ ++ emac_mii_reset_phy(&dev->phy); ++ return 0; ++} ++ ++static int emac_mdio_setup_aneg(struct mii_phy *phy, u32 advertise) ++{ ++ struct net_device *ndev = phy->dev; ++ struct emac_instance *dev = netdev_priv(ndev); ++ ++ dev->phy.autoneg = AUTONEG_ENABLE; ++ dev->phy.speed = SPEED_1000; ++ dev->phy.duplex = DUPLEX_FULL; ++ dev->phy.advertising = advertise; ++ phy->autoneg = AUTONEG_ENABLE; ++ phy->speed = dev->phy.speed; ++ phy->duplex = dev->phy.duplex; ++ phy->advertising = advertise; ++ return phy_start_aneg(dev->phy_dev); ++} ++ ++static int emac_mdio_setup_forced(struct mii_phy *phy, int speed, int fd) ++{ ++ struct net_device *ndev = phy->dev; ++ struct emac_instance *dev = netdev_priv(ndev); ++ ++ dev->phy.autoneg = AUTONEG_DISABLE; ++ dev->phy.speed = speed; ++ dev->phy.duplex = fd; ++ phy->autoneg = AUTONEG_DISABLE; ++ phy->speed = speed; ++ phy->duplex = fd; ++ return phy_start_aneg(dev->phy_dev); ++} ++ ++static int emac_mdio_poll_link(struct mii_phy *phy) ++{ ++ struct net_device *ndev = phy->dev; ++ struct emac_instance *dev = netdev_priv(ndev); ++ int res; ++ ++ res = phy_read_status(dev->phy_dev); ++ if (res) { ++ dev_err(&dev->ofdev->dev, "link update failed (%d).", res); ++ return ethtool_op_get_link(ndev); ++ } ++ ++ return dev->phy_dev->link; ++} ++ ++static int emac_mdio_read_link(struct mii_phy *phy) ++{ ++ struct net_device *ndev = phy->dev; ++ struct emac_instance *dev = netdev_priv(ndev); ++ int res; ++ ++ res = phy_read_status(dev->phy_dev); ++ if (res) ++ return res; ++ ++ dev->phy.speed = phy->speed; ++ dev->phy.duplex = phy->duplex; ++ dev->phy.pause = phy->pause; ++ dev->phy.asym_pause = phy->asym_pause; ++ return 0; ++} ++ ++static int emac_mdio_init_phy(struct mii_phy *phy) ++{ ++ struct net_device *ndev = phy->dev; ++ struct emac_instance *dev = netdev_priv(ndev); ++ ++ phy_start(dev->phy_dev); ++ dev->phy.autoneg = phy->autoneg; ++ dev->phy.speed = phy->speed; ++ dev->phy.duplex = phy->duplex; ++ dev->phy.advertising = phy->advertising; ++ dev->phy.pause = phy->pause; ++ dev->phy.asym_pause = phy->asym_pause; ++ ++ return phy_init_hw(dev->phy_dev); ++} ++ ++static const struct mii_phy_ops emac_dt_mdio_phy_ops = { ++ .init = emac_mdio_init_phy, ++ .setup_aneg = emac_mdio_setup_aneg, ++ .setup_forced = emac_mdio_setup_forced, ++ .poll_link = emac_mdio_poll_link, ++ .read_link = emac_mdio_read_link, ++}; ++ ++static int emac_dt_mdio_probe(struct emac_instance *dev) ++{ ++ struct device_node *mii_np; ++ int res; ++ ++ mii_np = of_get_child_by_name(dev->ofdev->dev.of_node, "mdio"); ++ if (!mii_np) { ++ dev_err(&dev->ofdev->dev, "no mdio definition found."); ++ return -ENODEV; ++ } ++ ++ if (!of_device_is_available(mii_np)) { ++ res = 1; ++ goto put_node; ++ } ++ ++ dev->mii_bus = devm_mdiobus_alloc(&dev->ofdev->dev); ++ if (!dev->mii_bus) { ++ res = -ENOMEM; ++ goto put_node; ++ } ++ ++ dev->mii_bus->priv = dev->ndev; ++ dev->mii_bus->parent = dev->ndev->dev.parent; ++ dev->mii_bus->name = "emac_mdio"; ++ dev->mii_bus->read = &emac_mii_bus_read; ++ dev->mii_bus->write = &emac_mii_bus_write; ++ dev->mii_bus->reset = &emac_mii_bus_reset; ++ snprintf(dev->mii_bus->id, MII_BUS_ID_SIZE, "%s", dev->ofdev->name); ++ res = of_mdiobus_register(dev->mii_bus, mii_np); ++ if (res) { ++ dev_err(&dev->ofdev->dev, "cannot register MDIO bus %s (%d)", ++ dev->mii_bus->name, res); ++ } ++ ++ put_node: ++ of_node_put(mii_np); ++ return res; ++} ++ ++static int emac_dt_phy_probe(struct emac_instance *dev, ++ struct device_node *phy_handle) ++{ ++ u32 phy_flags = 0; ++ int res; ++ ++ res = of_property_read_u32(phy_handle, "phy-flags", &phy_flags); ++ if (res < 0 && res != -EINVAL) ++ return res; ++ ++ dev->phy.def = devm_kzalloc(&dev->ofdev->dev, sizeof(*dev->phy.def), ++ GFP_KERNEL); ++ if (!dev->phy.def) ++ return -ENOMEM; ++ ++ dev->phy_dev = of_phy_connect(dev->ndev, phy_handle, ++ &emac_adjust_link, phy_flags, ++ PHY_INTERFACE_MODE_RGMII); ++ if (!dev->phy_dev) { ++ dev_err(&dev->ofdev->dev, "failed to connect to PHY.\n"); ++ return -ENODEV; ++ } ++ ++ dev->phy.def->phy_id = dev->phy_dev->drv->phy_id; ++ dev->phy.def->phy_id_mask = dev->phy_dev->drv->phy_id_mask; ++ dev->phy.def->name = dev->phy_dev->drv->name; ++ dev->phy.def->ops = &emac_dt_mdio_phy_ops; ++ dev->phy.features = dev->phy_dev->supported; ++ dev->phy.address = dev->phy_dev->mdio.addr; ++ dev->phy.mode = dev->phy_dev->interface; ++ return 0; ++} ++ ++static int emac_probe_dt_phy(struct emac_instance *dev) ++{ ++ struct device_node *np = dev->ofdev->dev.of_node; ++ struct device_node *phy_handle; ++ int res = 0; ++ ++ phy_handle = of_parse_phandle(np, "phy-handle", 0); ++ ++ if (phy_handle) { ++ res = emac_dt_mdio_probe(dev); ++ if (!res) { ++ res = emac_dt_phy_probe(dev, phy_handle); ++ if (!res) ++ res = 1; ++ else ++ mdiobus_unregister(dev->mii_bus); ++ } ++ } ++ ++ of_node_put(phy_handle); ++ /* if no phy device was specified in the device tree, then we fallback ++ * to the old emac_phy.c probe code for compatibility reasons. ++ */ ++ return res; ++} ++ + static int emac_init_phy(struct emac_instance *dev) + { + struct device_node *np = dev->ofdev->dev.of_node; +@@ -2492,6 +2716,22 @@ static int emac_init_phy(struct emac_ins + + emac_configure(dev); + ++ if (emac_has_feature(dev, EMAC_FTR_HAS_RGMII)) { ++ int res = emac_probe_dt_phy(dev); ++ ++ if (res == 1) { ++ mutex_unlock(&emac_phy_map_lock); ++ goto init_phy; ++ } else if (res < 0) { ++ mutex_unlock(&emac_phy_map_lock); ++ dev_err(&dev->ofdev->dev, "failed to attach dt phy (%d).\n", ++ res); ++ return res; ++ } ++ ++ /* continue with old code */ ++ } ++ + if (dev->phy_address != 0xffffffff) + phy_map = ~(1 << dev->phy_address); + +@@ -2519,6 +2759,7 @@ static int emac_init_phy(struct emac_ins + return -ENXIO; + } + ++ init_phy: + /* Init PHY */ + if (dev->phy.def->ops->init) + dev->phy.def->ops->init(&dev->phy); +@@ -2987,6 +3228,12 @@ static int emac_remove(struct platform_d + if (emac_has_feature(dev, EMAC_FTR_HAS_ZMII)) + zmii_detach(dev->zmii_dev, dev->zmii_port); + ++ if (dev->phy_dev) ++ phy_disconnect(dev->phy_dev); ++ ++ if (dev->mii_bus) ++ mdiobus_unregister(dev->mii_bus); ++ + busy_phy_map &= ~(1 << dev->phy.address); + DBG(dev, "busy_phy_map now %#x" NL, busy_phy_map); + +--- a/drivers/net/ethernet/ibm/emac/core.h ++++ b/drivers/net/ethernet/ibm/emac/core.h +@@ -199,6 +199,10 @@ struct emac_instance { + struct emac_instance *mdio_instance; + struct mutex mdio_lock; + ++ /* Device-tree based phy configuration */ ++ struct mii_bus *mii_bus; ++ struct phy_device *phy_dev; ++ + /* ZMII infos if any */ + u32 zmii_ph; + u32 zmii_port; diff --git a/target/linux/apm821xx/patches-4.9/801-usb-xhci-add-firmware-loader-for-uPD720201-and-uPD72.patch b/target/linux/apm821xx/patches-4.9/801-usb-xhci-add-firmware-loader-for-uPD720201-and-uPD72.patch new file mode 100644 index 0000000000..be5cb838aa --- /dev/null +++ b/target/linux/apm821xx/patches-4.9/801-usb-xhci-add-firmware-loader-for-uPD720201-and-uPD72.patch @@ -0,0 +1,545 @@ +From 419992bae5aaa4e06402e0b7c79fcf7bcb6b4764 Mon Sep 17 00:00:00 2001 +From: Christian Lamparter <chunkeey@googlemail.com> +Date: Thu, 2 Jun 2016 00:48:46 +0200 +Subject: [PATCH] usb: xhci: add firmware loader for uPD720201 and uPD720202 + w/o ROM + +This patch adds a firmware loader for the uPD720201K8-711-BAC-A +and uPD720202K8-711-BAA-A variant. Both of these chips are listed +in Renesas' R19UH0078EJ0500 Rev.5.00 "User's Manual: Hardware" as +devices which need the firmware loader on page 2 in order to +work as they "do not support the External ROM". + +The "Firmware Download Sequence" is describe in chapter +"7.1 FW Download Interface" R19UH0078EJ0500 Rev.5.00 page 131. + +The firmware "K2013080.mem" is available from a USB3.0 Host to +PCIe Adapter (PP2U-E card) "Firmware download" archive. An +alternative version can be sourced from Netgear's WNDR4700 GPL +archives. + +The release notes of the PP2U-E's "Firmware Download" ver 2.0.1.3 +(2012-06-15) state that the firmware is for the following devices: + - uPD720201 ES 2.0 sample whose revision ID is 2. + - uPD720201 ES 2.1 sample & CS sample & Mass product, ID is 3. + - uPD720202 ES 2.0 sample & CS sample & Mass product, ID is 2. + +If someone from Renesas is listening: It would be great, if these +firmwares could be added to linux-firmware.git. + +Cc: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> +Signed-off-by: Christian Lamparter <chunkeey@googlemail.com> +--- + drivers/usb/host/xhci-pci.c | 492 ++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 492 insertions(+) + +--- a/drivers/usb/host/xhci-pci.c ++++ b/drivers/usb/host/xhci-pci.c +@@ -24,6 +24,8 @@ + #include <linux/slab.h> + #include <linux/module.h> + #include <linux/acpi.h> ++#include <linux/firmware.h> ++#include <asm/unaligned.h> + + #include "xhci.h" + #include "xhci-trace.h" +@@ -221,6 +223,458 @@ static void xhci_pme_acpi_rtd3_enable(st + static void xhci_pme_acpi_rtd3_enable(struct pci_dev *dev) { } + #endif /* CONFIG_ACPI */ + ++static const struct renesas_fw_entry { ++ const char *firmware_name; ++ u16 device; ++ u8 revision; ++ u16 expected_version; ++} renesas_fw_table[] = { ++ /* ++ * Only the uPD720201K8-711-BAC-A or uPD720202K8-711-BAA-A ++ * are listed in R19UH0078EJ0500 Rev.5.00 as devices which ++ * need the software loader. ++ * ++ * PP2U/ReleaseNote_USB3-201-202-FW.txt: ++ * ++ * Note: This firmware is for the following devices. ++ * - uPD720201 ES 2.0 sample whose revision ID is 2. ++ * - uPD720201 ES 2.1 sample & CS sample & Mass product, ID is 3. ++ * - uPD720202 ES 2.0 sample & CS sample & Mass product, ID is 2. ++ */ ++ { "K2013080.mem", 0x0014, 0x02, 0x2013 }, ++ { "K2013080.mem", 0x0014, 0x03, 0x2013 }, ++ { "K2013080.mem", 0x0015, 0x02, 0x2013 }, ++}; ++ ++static const struct renesas_fw_entry *renesas_needs_fw_dl(struct pci_dev *dev) ++{ ++ const struct renesas_fw_entry *entry; ++ size_t i; ++ ++ /* This loader will only work with a RENESAS device. */ ++ if (!(dev->vendor == PCI_VENDOR_ID_RENESAS)) ++ return NULL; ++ ++ for (i = 0; i < ARRAY_SIZE(renesas_fw_table); i++) { ++ entry = &renesas_fw_table[i]; ++ if (entry->device == dev->device && ++ entry->revision == dev->revision) ++ return entry; ++ } ++ ++ return NULL; ++} ++ ++static int renesas_fw_download_image(struct pci_dev *dev, ++ const u32 *fw, ++ size_t step) ++{ ++ size_t i; ++ int err; ++ u8 fw_status; ++ bool data0_or_data1; ++ ++ /* ++ * The hardware does alternate between two 32-bit pages. ++ * (This is because each row of the firmware is 8 bytes). ++ * ++ * for even steps we use DATA0, for odd steps DATA1. ++ */ ++ data0_or_data1 = (step & 1) == 1; ++ ++ /* step+1. Read "Set DATAX" and confirm it is cleared. */ ++ for (i = 0; i < 10000; i++) { ++ err = pci_read_config_byte(dev, 0xF5, &fw_status); ++ if (err) ++ return pcibios_err_to_errno(err); ++ if (!(fw_status & BIT(data0_or_data1))) ++ break; ++ ++ udelay(1); ++ } ++ if (i == 10000) ++ return -ETIMEDOUT; ++ ++ /* ++ * step+2. Write FW data to "DATAX". ++ * "LSB is left" => force little endian ++ */ ++ err = pci_write_config_dword(dev, data0_or_data1 ? 0xFC : 0xF8, ++ (__force u32) cpu_to_le32(fw[step])); ++ if (err) ++ return pcibios_err_to_errno(err); ++ ++ udelay(100); ++ ++ /* step+3. Set "Set DATAX". */ ++ err = pci_write_config_byte(dev, 0xF5, BIT(data0_or_data1)); ++ if (err) ++ return pcibios_err_to_errno(err); ++ ++ return 0; ++} ++ ++static int renesas_fw_verify(struct pci_dev *dev, ++ const void *fw_data, ++ size_t length) ++{ ++ const struct renesas_fw_entry *entry = renesas_needs_fw_dl(dev); ++ u16 fw_version_pointer; ++ u16 fw_version; ++ ++ if (!entry) ++ return -EINVAL; ++ ++ /* ++ * The Firmware's Data Format is describe in ++ * "6.3 Data Format" R19UH0078EJ0500 Rev.5.00 page 124 ++ */ ++ ++ /* "Each row is 8 bytes". => firmware size must be a multiple of 8. */ ++ if (length % 8 != 0) { ++ dev_err(&dev->dev, "firmware size is not a multipe of 8."); ++ return -EINVAL; ++ } ++ ++ /* ++ * The bootrom chips of the big brother have sizes up to 64k, let's ++ * assume that's the biggest the firmware can get. ++ */ ++ if (length < 0x1000 || length >= 0x10000) { ++ dev_err(&dev->dev, "firmware is size %zd is not (4k - 64k).", ++ length); ++ return -EINVAL; ++ } ++ ++ /* The First 2 bytes are fixed value (55aa). "LSB on Left" */ ++ if (get_unaligned_le16(fw_data) != 0x55aa) { ++ dev_err(&dev->dev, "no valid firmware header found."); ++ return -EINVAL; ++ } ++ ++ /* verify the firmware version position and print it. */ ++ fw_version_pointer = get_unaligned_le16(fw_data + 4); ++ if (fw_version_pointer + 2 >= length) { ++ dev_err(&dev->dev, "firmware version pointer is outside of the firmware image."); ++ return -EINVAL; ++ } ++ ++ fw_version = get_unaligned_le16(fw_data + fw_version_pointer); ++ dev_dbg(&dev->dev, "got firmware version: %02x.", fw_version); ++ ++ if (fw_version != entry->expected_version) { ++ dev_err(&dev->dev, "firmware version mismatch, expected version: %02x.", ++ entry->expected_version); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int renesas_fw_check_running(struct pci_dev *pdev) ++{ ++ int err; ++ u8 fw_state; ++ ++ /* ++ * Test if the device is actually needing the firmware. As most ++ * BIOSes will initialize the device for us. If the device is ++ * initialized. ++ */ ++ err = pci_read_config_byte(pdev, 0xF4, &fw_state); ++ if (err) ++ return pcibios_err_to_errno(err); ++ ++ /* ++ * Check if "FW Download Lock" is locked. If it is and the FW is ++ * ready we can simply continue. If the FW is not ready, we have ++ * to give up. ++ */ ++ if (fw_state & BIT(1)) { ++ dev_dbg(&pdev->dev, "FW Download Lock is engaged."); ++ ++ if (fw_state & BIT(4)) ++ return 0; ++ ++ dev_err(&pdev->dev, "FW Download Lock is set and FW is not ready. Giving Up."); ++ return -EIO; ++ } ++ ++ /* ++ * Check if "FW Download Enable" is set. If someone (us?) tampered ++ * with it and it can't be resetted, we have to give up too... and ++ * ask for a forgiveness and a reboot. ++ */ ++ if (fw_state & BIT(0)) { ++ dev_err(&pdev->dev, "FW Download Enable is stale. Giving Up (poweroff/reboot needed)."); ++ return -EIO; ++ } ++ ++ /* Otherwise, Check the "Result Code" Bits (6:4) and act accordingly */ ++ switch ((fw_state & 0x70)) { ++ case 0: /* No result yet */ ++ dev_dbg(&pdev->dev, "FW is not ready/loaded yet."); ++ ++ /* tell the caller, that this device needs the firmware. */ ++ return 1; ++ ++ case BIT(4): /* Success, device should be working. */ ++ dev_dbg(&pdev->dev, "FW is ready."); ++ return 0; ++ ++ case BIT(5): /* Error State */ ++ dev_err(&pdev->dev, "hardware is in an error state. Giving up (poweroff/reboot needed)."); ++ return -ENODEV; ++ ++ default: /* All other states are marked as "Reserved states" */ ++ dev_err(&pdev->dev, "hardware is in an invalid state %x. Giving up (poweroff/reboot needed).", ++ (fw_state & 0x70) >> 4); ++ return -EINVAL; ++ } ++} ++ ++static int renesas_hw_check_run_stop_busy(struct pci_dev *pdev) ++{ ++#if 0 ++ u32 val; ++ ++ /* ++ * 7.1.3 Note 3: "... must not set 'FW Download Enable' when ++ * 'RUN/STOP' of USBCMD Register is set" ++ */ ++ val = readl(hcd->regs + 0x20); ++ if (val & BIT(0)) { ++ dev_err(&pdev->dev, "hardware is busy and can't receive a FW."); ++ return -EBUSY; ++ } ++#endif ++ return 0; ++} ++ ++static int renesas_fw_download(struct pci_dev *pdev, ++ const struct firmware *fw, unsigned int retry_counter) ++{ ++ const u32 *fw_data = (const u32 *) fw->data; ++ size_t i; ++ int err; ++ u8 fw_status; ++ ++ /* ++ * For more information and the big picture: please look at the ++ * "Firmware Download Sequence" in "7.1 FW Download Interface" ++ * of R19UH0078EJ0500 Rev.5.00 page 131 ++ */ ++ err = renesas_hw_check_run_stop_busy(pdev); ++ if (err) ++ return err; ++ ++ /* ++ * 0. Set "FW Download Enable" bit in the ++ * "FW Download Control & Status Register" at 0xF4 ++ */ ++ err = pci_write_config_byte(pdev, 0xF4, BIT(0)); ++ if (err) ++ return pcibios_err_to_errno(err); ++ ++ /* 1 - 10 follow one step after the other. */ ++ for (i = 0; i < fw->size / 4; i++) { ++ err = renesas_fw_download_image(pdev, fw_data, i); ++ if (err) { ++ dev_err(&pdev->dev, "Firmware Download Step %zd failed at position %zd bytes with (%d).", ++ i, i * 4, err); ++ return err; ++ } ++ } ++ ++ /* ++ * This sequence continues until the last data is written to ++ * "DATA0" or "DATA1". Naturally, we wait until "SET DATA0/1" ++ * is cleared by the hardware beforehand. ++ */ ++ for (i = 0; i < 10000; i++) { ++ err = pci_read_config_byte(pdev, 0xF5, &fw_status); ++ if (err) ++ return pcibios_err_to_errno(err); ++ if (!(fw_status & (BIT(0) | BIT(1)))) ++ break; ++ ++ udelay(1); ++ } ++ if (i == 10000) ++ dev_warn(&pdev->dev, "Final Firmware Download step timed out."); ++ ++ /* ++ * 11. After finishing writing the last data of FW, the ++ * System Software must clear "FW Download Enable" ++ */ ++ err = pci_write_config_byte(pdev, 0xF4, 0); ++ if (err) ++ return pcibios_err_to_errno(err); ++ ++ /* 12. Read "Result Code" and confirm it is good. */ ++ for (i = 0; i < 10000; i++) { ++ err = pci_read_config_byte(pdev, 0xF4, &fw_status); ++ if (err) ++ return pcibios_err_to_errno(err); ++ if (fw_status & BIT(4)) ++ break; ++ ++ udelay(1); ++ } ++ if (i == 10000) { ++ /* Timed out / Error - let's see if we can fix this */ ++ err = renesas_fw_check_running(pdev); ++ switch (err) { ++ case 0: /* ++ * we shouldn't end up here. ++ * maybe it took a little bit longer. ++ * But all should be well? ++ */ ++ break; ++ ++ case 1: /* (No result yet? - we can try to retry) */ ++ if (retry_counter < 10) { ++ retry_counter++; ++ dev_warn(&pdev->dev, "Retry Firmware download: %d try.", ++ retry_counter); ++ return renesas_fw_download(pdev, fw, ++ retry_counter); ++ } ++ return -ETIMEDOUT; ++ ++ default: ++ return err; ++ } ++ } ++ /* ++ * Optional last step: Engage Firmware Lock ++ * ++ * err = pci_write_config_byte(pdev, 0xF4, BIT(2)); ++ * if (err) ++ * return pcibios_err_to_errno(err); ++ */ ++ ++ return 0; ++} ++ ++struct renesas_fw_ctx { ++ struct pci_dev *pdev; ++ const struct pci_device_id *id; ++ bool resume; ++}; ++ ++static int xhci_pci_probe(struct pci_dev *pdev, ++ const struct pci_device_id *id); ++ ++static void renesas_fw_callback(const struct firmware *fw, ++ void *context) ++{ ++ struct renesas_fw_ctx *ctx = context; ++ struct pci_dev *pdev = ctx->pdev; ++ struct device *parent = pdev->dev.parent; ++ int err = -ENOENT; ++ ++ if (fw) { ++ err = renesas_fw_verify(pdev, fw->data, fw->size); ++ if (!err) { ++ err = renesas_fw_download(pdev, fw, 0); ++ release_firmware(fw); ++ if (!err) { ++ if (ctx->resume) ++ return; ++ ++ err = xhci_pci_probe(pdev, ctx->id); ++ if (!err) { ++ /* everything worked */ ++ devm_kfree(&pdev->dev, ctx); ++ return; ++ } ++ ++ /* in case of an error - fall through */ ++ } else { ++ dev_err(&pdev->dev, "firmware failed to download (%d).", ++ err); ++ } ++ } ++ } else { ++ dev_err(&pdev->dev, "firmware failed to load (%d).", err); ++ } ++ ++ dev_info(&pdev->dev, "Unloading driver"); ++ ++ if (parent) ++ device_lock(parent); ++ ++ device_release_driver(&pdev->dev); ++ ++ if (parent) ++ device_unlock(parent); ++ ++ pci_dev_put(pdev); ++} ++ ++static int renesas_fw_alive_check(struct pci_dev *pdev) ++{ ++ const struct renesas_fw_entry *entry; ++ int err; ++ ++ /* check if we have a eligible RENESAS' uPD720201/2 w/o FW. */ ++ entry = renesas_needs_fw_dl(pdev); ++ if (!entry) ++ return 0; ++ ++ err = renesas_fw_check_running(pdev); ++ /* Also go ahead, if the firmware is running */ ++ if (err == 0) ++ return 0; ++ ++ /* At this point, we can be sure that the FW isn't ready. */ ++ return err; ++} ++ ++static int renesas_fw_download_to_hw(struct pci_dev *pdev, ++ const struct pci_device_id *id, ++ bool do_resume) ++{ ++ const struct renesas_fw_entry *entry; ++ struct renesas_fw_ctx *ctx; ++ int err; ++ ++ /* check if we have a eligible RENESAS' uPD720201/2 w/o FW. */ ++ entry = renesas_needs_fw_dl(pdev); ++ if (!entry) ++ return 0; ++ ++ err = renesas_fw_check_running(pdev); ++ /* Continue ahead, if the firmware is already running. */ ++ if (err == 0) ++ return 0; ++ ++ if (err != 1) ++ return err; ++ ++ ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); ++ if (!ctx) ++ return -ENOMEM; ++ ctx->pdev = pdev; ++ ctx->resume = do_resume; ++ ctx->id = id; ++ ++ pci_dev_get(pdev); ++ err = request_firmware_nowait(THIS_MODULE, 1, entry->firmware_name, ++ &pdev->dev, GFP_KERNEL, ctx, renesas_fw_callback); ++ if (err) { ++ pci_dev_put(pdev); ++ return err; ++ } ++ ++ /* ++ * The renesas_fw_callback() callback will continue the probe ++ * process, once it aquires the firmware. ++ */ ++ return 1; ++} ++ + /* called during probe() after chip reset completes */ + static int xhci_pci_setup(struct usb_hcd *hcd) + { +@@ -260,6 +714,22 @@ static int xhci_pci_probe(struct pci_dev + struct hc_driver *driver; + struct usb_hcd *hcd; + ++ /* ++ * Check if this device is a RENESAS uPD720201/2 device. ++ * Otherwise, we can continue with xhci_pci_probe as usual. ++ */ ++ retval = renesas_fw_download_to_hw(dev, id, false); ++ switch (retval) { ++ case 0: ++ break; ++ ++ case 1: /* let it load the firmware and recontinue the probe. */ ++ return 0; ++ ++ default: ++ return retval; ++ }; ++ + driver = (struct hc_driver *)id->driver_data; + + /* Prevent runtime suspending between USB-2 and USB-3 initialization */ +@@ -317,6 +787,16 @@ static void xhci_pci_remove(struct pci_d + { + struct xhci_hcd *xhci; + ++ if (renesas_fw_alive_check(dev)) { ++ /* ++ * bail out early, if this was a renesas device w/o FW. ++ * Else we might hit the NMI watchdog in xhci_handsake ++ * during xhci_reset as part of the driver's unloading. ++ * which we forced in the renesas_fw_callback(). ++ */ ++ return; ++ } ++ + xhci = hcd_to_xhci(pci_get_drvdata(dev)); + xhci->xhc_state |= XHCI_STATE_REMOVING; + if (xhci->shared_hcd) { diff --git a/target/linux/apm821xx/patches-4.9/802-usb-xhci-force-msi-renesas-xhci.patch b/target/linux/apm821xx/patches-4.9/802-usb-xhci-force-msi-renesas-xhci.patch new file mode 100644 index 0000000000..a201d79104 --- /dev/null +++ b/target/linux/apm821xx/patches-4.9/802-usb-xhci-force-msi-renesas-xhci.patch @@ -0,0 +1,54 @@ +From a0dc613140bab907a3d5787a7ae7b0638bf674d0 Mon Sep 17 00:00:00 2001 +From: Christian Lamparter <chunkeey@gmail.com> +Date: Thu, 23 Jun 2016 20:28:20 +0200 +Subject: [PATCH] usb: xhci: force MSI for uPD720201 and + uPD720202 + +The APM82181 does not support MSI-X. When probed, it will +produce a noisy warning. + +--- + drivers/usb/host/pci-quirks.c | 362 ++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 362 insertions(+) + +--- a/drivers/usb/host/xhci-pci.c ++++ b/drivers/usb/host/xhci-pci.c +@@ -188,7 +188,7 @@ static void xhci_pci_quirks(struct devic + } + if (pdev->vendor == PCI_VENDOR_ID_RENESAS && + pdev->device == 0x0015) +- xhci->quirks |= XHCI_RESET_ON_RESUME; ++ xhci->quirks |= XHCI_RESET_ON_RESUME | XHCI_FORCE_MSI; + if (pdev->vendor == PCI_VENDOR_ID_VIA) + xhci->quirks |= XHCI_RESET_ON_RESUME; + +--- a/drivers/usb/host/xhci.c ++++ b/drivers/usb/host/xhci.c +@@ -387,10 +387,14 @@ static int xhci_try_enable_msi(struct us + free_irq(hcd->irq, hcd); + hcd->irq = 0; + +- ret = xhci_setup_msix(xhci); +- if (ret) +- /* fall back to msi*/ ++ if (xhci->quirks & XHCI_FORCE_MSI) { + ret = xhci_setup_msi(xhci); ++ } else { ++ ret = xhci_setup_msix(xhci); ++ if (ret) ++ /* fall back to msi*/ ++ ret = xhci_setup_msi(xhci); ++ } + + if (!ret) + /* hcd->irq is 0, we have MSI */ +--- a/drivers/usb/host/xhci.h ++++ b/drivers/usb/host/xhci.h +@@ -1674,6 +1674,7 @@ struct xhci_hcd { + /* support xHCI 0.96 spec USB2 software LPM */ + unsigned sw_lpm_support:1; + /* support xHCI 1.0 spec USB2 hardware LPM */ ++#define XHCI_FORCE_MSI (1 << 24) + unsigned hw_lpm_support:1; + /* cached usb2 extened protocol capabilites */ + u32 *ext_caps; diff --git a/target/linux/apm821xx/patches-4.9/804-usb-dwc2-add-amcc-usb-otg-405ex.patch b/target/linux/apm821xx/patches-4.9/804-usb-dwc2-add-amcc-usb-otg-405ex.patch new file mode 100644 index 0000000000..3dd2ed5734 --- /dev/null +++ b/target/linux/apm821xx/patches-4.9/804-usb-dwc2-add-amcc-usb-otg-405ex.patch @@ -0,0 +1,48 @@ +--- a/drivers/usb/dwc2/platform.c ++++ b/drivers/usb/dwc2/platform.c +@@ -279,6 +279,37 @@ static int dwc2_get_dr_mode(struct dwc2_ + return 0; + } + ++static const struct dwc2_core_params params_amcc_dwc_otg = { ++ .otg_cap = DWC2_CAP_PARAM_HNP_SRP_CAPABLE, ++ .otg_ver = -1, ++ .dma_enable = -1, ++ .dma_desc_enable = -1, ++ .speed = -1, ++ .enable_dynamic_fifo = -1, ++ .en_multiple_tx_fifo = -1, ++ .host_rx_fifo_size = -1, ++ .host_nperio_tx_fifo_size = -1, ++ .host_perio_tx_fifo_size = -1, ++ .max_transfer_size = -1, ++ .max_packet_count = -1, ++ .host_channels = -1, ++ .phy_type = -1, ++ .phy_utmi_width = -1, ++ .phy_ulpi_ddr = -1, ++ .phy_ulpi_ext_vbus = -1, ++ .i2c_enable = -1, ++ .ulpi_fs_ls = -1, ++ .host_support_fs_ls_low_power = -1, ++ .host_ls_low_power_phy_clk = -1, ++ .ts_dline = -1, ++ .reload_ctl = -1, ++ .ahbcfg = GAHBCFG_HBSTLEN_INCR16 << ++ GAHBCFG_HBSTLEN_SHIFT, ++ .uframe_sched = -1, ++ .external_id_pin_ctl = -1, ++ .hibernation = -1, ++}; ++ + static int __dwc2_lowlevel_hw_enable(struct dwc2_hsotg *hsotg) + { + struct platform_device *pdev = to_platform_device(hsotg->dev); +@@ -511,6 +542,7 @@ static void dwc2_driver_shutdown(struct + } + + static const struct of_device_id dwc2_of_match_table[] = { ++ { .compatible = "amcc,usb-otg-405ex", .data = ¶ms_amcc_dwc_otg }, + { .compatible = "brcm,bcm2835-usb", .data = ¶ms_bcm2835 }, + { .compatible = "hisilicon,hi6220-usb", .data = ¶ms_hi6220 }, + { .compatible = "rockchip,rk3066-usb", .data = ¶ms_rk3066 }, diff --git a/target/linux/apm821xx/patches-4.9/901-hwmon-add-driver-for-Microchip-TC654-TC655-PWM-fan-c.patch b/target/linux/apm821xx/patches-4.9/901-hwmon-add-driver-for-Microchip-TC654-TC655-PWM-fan-c.patch new file mode 100644 index 0000000000..ceacde9dda --- /dev/null +++ b/target/linux/apm821xx/patches-4.9/901-hwmon-add-driver-for-Microchip-TC654-TC655-PWM-fan-c.patch @@ -0,0 +1,1027 @@ +From 5ea2e152d846bf60901107fefd81a58f792f3bc2 Mon Sep 17 00:00:00 2001 +From: Christian Lamparter <chunkeey@gmail.com> +Date: Fri, 10 Jun 2016 03:00:46 +0200 +Subject: [PATCH] hwmon: add driver for Microchip TC654/TC655 PWM fan + controllers + +This patch adds a hwmon driver for the Microchip TC654 and TC655 +Dual SMBus PWM Fan Speed Controllers with Fan Fault detection. + +The chip is described in the DS2001734C Spec Document from Microchip. +It supports: + - Shared PWM Fan Drive for two fans + - Provides RPM + - automatic PWM controller (needs additional + NTC/PTC Thermistors.) + - Overtemperature alarm (when using NTC/PTC + Thermistors) + +Signed-off-by: Christian Lamparter <chunkeey@gmail.com> +--- + drivers/hwmon/Kconfig | 10 + + drivers/hwmon/Makefile | 1 + + drivers/hwmon/tc654.c | 969 +++++++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 980 insertions(+) + create mode 100644 drivers/hwmon/tc654.c + +--- a/drivers/hwmon/Kconfig ++++ b/drivers/hwmon/Kconfig +@@ -1549,6 +1549,16 @@ config SENSORS_INA3221 + This driver can also be built as a module. If so, the module + will be called ina3221. + ++config SENSORS_TC654 ++ tristate "Microchip TC654 and TC655" ++ depends on I2C ++ help ++ If you say yes here you get support for Microchip TC655 and TC654 ++ Dual PWM Fan Speed Controllers and sensor chips. ++ ++ This driver can also be built as a module. If so, the module ++ will be called tc654. ++ + config SENSORS_TC74 + tristate "Microchip TC74" + depends on I2C +--- a/drivers/hwmon/Makefile ++++ b/drivers/hwmon/Makefile +@@ -148,6 +148,7 @@ obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc4 + obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o + obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o + obj-$(CONFIG_SENSORS_AMC6821) += amc6821.o ++obj-$(CONFIG_SENSORS_TC654) += tc654.o + obj-$(CONFIG_SENSORS_TC74) += tc74.o + obj-$(CONFIG_SENSORS_THMC50) += thmc50.o + obj-$(CONFIG_SENSORS_TMP102) += tmp102.o +--- /dev/null ++++ b/drivers/hwmon/tc654.c +@@ -0,0 +1,969 @@ ++/* ++ * tc654.c - Support for Microchip TC654/TC655 ++ * "A Dual SMBus PWM FAN Speed Controllers with Fan Fault Detection" ++ * ++ * Copyright (c) 2016 Christian Lamparter <chunkeey@gmail.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 version 2 of the License. ++ * ++ * 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. ++ * ++ * The chip is described in the DS2001734C Spec Document from Microchip. ++ */ ++ ++#include <linux/module.h> ++#include <linux/init.h> ++#include <linux/slab.h> ++#include <linux/jiffies.h> ++#include <linux/i2c.h> ++#include <linux/hwmon.h> ++#include <linux/hwmon-sysfs.h> ++#include <linux/err.h> ++#include <linux/mutex.h> ++#include <linux/thermal.h> ++ ++/* Hardware definitions */ ++/* 5.1.4 Address Byte stats that TC654/TC655 are fixed at 0x1b */ ++static const unsigned short normal_i2c[] = { 0x1b, I2C_CLIENT_END }; ++ ++enum TC654_REGS { ++ TC654_REG_RPM1 = 0x00, ++ TC654_REG_RPM2, ++ TC654_REG_FAN1_FAULT_THRESH, ++ TC654_REG_FAN2_FAULT_THRESH, ++ TC654_REG_CONFIG, ++ TC654_REG_STATUS, ++ TC654_REG_DUTY_CYCLE, ++ TC654_REG_MFR_ID, ++ TC654_REG_VER_ID, ++ ++ /* keep last */ ++ __TC654_REG_NUM, ++}; ++ ++#define TC654_MFR_ID_MICROCHIP 0x84 ++#define TC654_VER_ID 0x00 ++#define TC655_VER_ID 0x01 ++ ++enum TC654_CONTROL_BITS { ++ TC654_CTRL_SDM = BIT(0), ++ TC654_CTRL_F1PPR_S = 1, ++ TC654_CTRL_F1PPR_M = (BIT(1) | BIT(2)), ++ TC654_CTRL_F2PPR_S = 3, ++ TC654_CTRL_F2PPR_M = (BIT(3) | BIT(4)), ++ TC654_CTRL_DUTYC = BIT(5), ++ TC654_CTRL_RES = BIT(6), ++ TC654_CTRL_FFCLR = BIT(7), ++}; ++ ++enum TC654_STATUS_BITS { ++ TC654_STATUS_F1F = BIT(0), ++ TC654_STATUS_F2F = BIT(1), ++ TC654_STATUS_VSTAT = BIT(2), ++ TC654_STATUS_R1CO = BIT(3), ++ TC654_STATUS_R2CO = BIT(4), ++ TC654_STATUS_OTF = BIT(5), ++}; ++ ++enum TC654_FAN { ++ TC654_FAN1 = 0, ++ TC654_FAN2, ++ ++ /* keep last */ ++ __NUM_TC654_FAN, ++}; ++ ++enum TC654_FAN_MODE { ++ TC654_PWM_OFF, /* Shutdown Mode - switch of both fans */ ++ TC654_PWM_VIN, /* Fans will be controlled via V_in analog input pin */ ++ TC654_PWM_3000, /* sets fans to 30% duty cycle */ ++ TC654_PWM_3467, ++ TC654_PWM_3933, /* default case - if V_in pin is open */ ++ TC654_PWM_4400, ++ TC654_PWM_4867, ++ TC654_PWM_5333, ++ TC654_PWM_5800, ++ TC654_PWM_6267, ++ TC654_PWM_6733, ++ TC654_PWM_7200, ++ TC654_PWM_7667, ++ TC654_PWM_8133, ++ TC654_PWM_8600, ++ TC654_PWM_9067, ++ TC654_PWM_9533, ++ TC654_PWM_10000, /* sets fans to 100% duty cycle */ ++}; ++ ++enum TC654_ALARMS { ++ TC654_ALARM_FAN1_FAULT, ++ TC654_ALARM_FAN2_FAULT, ++ TC654_ALARM_FAN1_COUNTER_OVERFLOW, ++ TC654_ALARM_FAN2_COUNTER_OVERFLOW, ++ TC654_ALARM_OVER_TEMPERATURE, ++ ++ /* KEEP LAST */ ++ __NUM_TC654_ALARMS, ++}; ++ ++static const struct pwm_table_entry { ++ u8 min; ++ enum TC654_FAN_MODE mode; ++} pwm_table[] = { ++ { 0, TC654_PWM_OFF }, ++ { 1, TC654_PWM_3000 }, ++ { 88, TC654_PWM_3467 }, ++ {101, TC654_PWM_3933 }, ++ {113, TC654_PWM_4400 }, ++ {125, TC654_PWM_4867 }, ++ {137, TC654_PWM_5333 }, ++ {148, TC654_PWM_5800 }, ++ {160, TC654_PWM_6267 }, ++ {172, TC654_PWM_6733 }, ++ {184, TC654_PWM_7200 }, ++ {196, TC654_PWM_7667 }, ++ {208, TC654_PWM_8133 }, ++ {220, TC654_PWM_8600 }, ++ {232, TC654_PWM_9067 }, ++ {244, TC654_PWM_9533 }, ++ {255, TC654_PWM_10000 }, ++}; ++ ++/* driver context */ ++struct tc654 { ++ struct i2c_client *client; ++ ++ struct mutex update_lock; ++ ++ unsigned long last_updated; /* in jiffies */ ++ u8 cached_regs[__TC654_REG_NUM]; ++ ++ bool valid; /* monitored registers are valid */ ++ u16 fan_input[__NUM_TC654_FAN]; ++ bool alarms[__NUM_TC654_ALARMS]; ++ bool vin_status; ++ bool pwm_manual; ++ ++ /* optional cooling device */ ++ struct thermal_cooling_device *cdev; ++}; ++ ++/* hardware accessors and functions */ ++static int read_tc(struct tc654 *tc, u8 reg) ++{ ++ s32 status; ++ ++ if (reg <= TC654_REG_VER_ID) { ++ /* Table 6.1 states that all registers are readable */ ++ status = i2c_smbus_read_byte_data(tc->client, reg); ++ } else ++ status = -EINVAL; ++ ++ if (status < 0) { ++ dev_warn(&tc->client->dev, "can't read register 0x%02x due to error (%d)", ++ reg, status); ++ } else { ++ tc->cached_regs[reg] = status; ++ } ++ ++ return status; ++} ++ ++static int write_tc(struct tc654 *tc, u8 i2c_reg, u8 val) ++{ ++ s32 status; ++ ++ /* ++ * Table 6.1 states that both fan threshold registers, ++ * the Config and Duty Cycle are writeable. ++ */ ++ switch (i2c_reg) { ++ case TC654_REG_FAN1_FAULT_THRESH: ++ case TC654_REG_FAN2_FAULT_THRESH: ++ case TC654_REG_DUTY_CYCLE: ++ case TC654_REG_CONFIG: ++ status = i2c_smbus_write_byte_data(tc->client, i2c_reg, val); ++ break; ++ ++ default: ++ return -EINVAL; ++ } ++ ++ if (status < 0) { ++ dev_warn(&tc->client->dev, "can't write register 0x%02x with value 0x%02x due to error (%d)", ++ i2c_reg, val, status); ++ } else { ++ tc->cached_regs[i2c_reg] = val; ++ } ++ ++ return status; ++} ++ ++static int mod_config(struct tc654 *tc, u8 set, u8 clear) ++{ ++ u8 val = 0; ++ ++ /* a bit can't be set and cleared on the same time. */ ++ if (set & clear) ++ return -EINVAL; ++ ++ /* invalidate data to force re-read from hardware */ ++ tc->valid = false; ++ val = (tc->cached_regs[TC654_REG_CONFIG] | set) & (~clear); ++ return write_tc(tc, TC654_REG_CONFIG, val); ++} ++ ++static int read_fan_rpm(struct tc654 *tc, enum TC654_FAN fan) ++{ ++ int ret; ++ ++ /* 6.1 RPM-OUTPUT1 and RPM-OUTPUT2 registers */ ++ ret = read_tc(tc, fan == TC654_FAN1 ? TC654_REG_RPM1 : TC654_REG_RPM2); ++ if (ret < 0) ++ return ret; ++ ++ /* ++ * The Resolution Selection Bit in 6.3 CONFIGURATION REGISTER ++ * is needed to convert the raw value to the RPM. ++ * 0 = RPM1 and RPM2 use (8-Bit) resolution => * 50 RPM ++ * 1 = RPM1 and RPM2 use (9-Bit) resolution => * 25 RPM ++ */ ++ return ret * (25 << ++ !(tc->cached_regs[TC654_REG_CONFIG] & TC654_CTRL_RES)); ++} ++ ++static int write_fan_fault_thresh(struct tc654 *tc, enum TC654_FAN fan, ++ u16 rpm) ++{ ++ u8 converted_rpm; ++ ++ if (rpm > 12750) ++ return -EINVAL; ++ ++ /* ++ * 6.2 FAN_FAULT1 and FAN_FAULT2 Threshold registers ++ * ++ * Both registers operate in 50 RPM mode exclusively. ++ */ ++ converted_rpm = rpm / 50; ++ ++ /* invalidate data to force re-read from hardware */ ++ tc->valid = false; ++ return write_tc(tc, fan == TC654_FAN1 ? TC654_REG_FAN1_FAULT_THRESH : ++ TC654_REG_FAN2_FAULT_THRESH, converted_rpm); ++} ++ ++ ++static int read_fan_fault_thresh(struct tc654 *tc, enum TC654_FAN fan) ++{ ++ /* ++ * 6.2 FAN_FAULT1 and FAN_FAULT2 Threshold registers ++ * ++ * Both registers operate in 50 RPM mode exclusively. ++ */ ++ return read_tc(tc, fan == TC654_FAN1 ? TC654_REG_FAN1_FAULT_THRESH : ++ TC654_REG_FAN2_FAULT_THRESH) * 50; ++} ++ ++static enum TC654_FAN_MODE get_fan_mode(struct tc654 *tc) ++{ ++ if (tc->cached_regs[TC654_REG_CONFIG] & TC654_CTRL_SDM) { ++ return TC654_PWM_OFF; ++ } else if (tc->cached_regs[TC654_REG_CONFIG] & TC654_CTRL_DUTYC) { ++ return TC654_PWM_3000 + tc->cached_regs[TC654_REG_DUTY_CYCLE]; ++ } else if (tc->vin_status == 0) ++ return TC654_PWM_VIN; ++ ++ return -EINVAL; ++} ++ ++static int write_fan_mode(struct tc654 *tc, enum TC654_FAN_MODE mode) ++{ ++ int err; ++ u8 pwm_mode; ++ bool in_sdm; ++ ++ in_sdm = !!(tc->cached_regs[TC654_REG_CONFIG] & ++ TC654_CTRL_SDM); ++ ++ switch (mode) { ++ case TC654_PWM_OFF: ++ if (in_sdm) ++ return 0; ++ ++ /* Enter Shutdown Mode - Switches off all fans */ ++ err = mod_config(tc, TC654_CTRL_SDM, TC654_CTRL_DUTYC); ++ if (err) ++ return err; ++ ++ return 0; ++ ++ case TC654_PWM_VIN: ++ if (tc->vin_status) { ++ dev_err(&tc->client->dev, ++ "V_in pin is open, can't enable automatic mode."); ++ return -EINVAL; ++ } ++ ++ err = mod_config(tc, 0, TC654_CTRL_SDM | TC654_CTRL_DUTYC); ++ if (err) ++ return err; ++ ++ tc->pwm_manual = false; ++ return 0; ++ ++ case TC654_PWM_3000: ++ case TC654_PWM_3467: ++ case TC654_PWM_3933: ++ case TC654_PWM_4400: ++ case TC654_PWM_4867: ++ case TC654_PWM_5333: ++ case TC654_PWM_5800: ++ case TC654_PWM_6267: ++ case TC654_PWM_6733: ++ case TC654_PWM_7200: ++ case TC654_PWM_7667: ++ case TC654_PWM_8133: ++ case TC654_PWM_8600: ++ case TC654_PWM_9067: ++ case TC654_PWM_9533: ++ case TC654_PWM_10000: ++ pwm_mode = mode - TC654_PWM_3000; ++ if (!in_sdm) { ++ err = write_tc(tc, TC654_REG_DUTY_CYCLE, pwm_mode); ++ if (err) ++ return err; ++ } ++ ++ err = mod_config(tc, TC654_CTRL_DUTYC, TC654_CTRL_SDM); ++ if (err) ++ return err; ++ ++ tc->pwm_manual = true; ++ ++ if (in_sdm) { ++ /* ++ * In case the TC654/TC655 was in SDM mode, the write ++ * above into the TC654_REG_DUTY_CYCLE register will ++ * have no effect because the chip was switched off. ++ * ++ * Note: The TC654/TC655 have a special "power-on" ++ * feature where the PWM will be forced to 100% for ++ * one full second in order to spin-up a resting fan. ++ */ ++ err = write_tc(tc, TC654_REG_DUTY_CYCLE, pwm_mode); ++ if (err) ++ return err; ++ } ++ ++ return 0; ++ ++ default: ++ return -EINVAL; ++ } ++} ++ ++static struct tc654 *tc654_update_device(struct device *dev) ++{ ++ struct tc654 *tc = dev_get_drvdata(dev); ++ ++ mutex_lock(&tc->update_lock); ++ ++ /* ++ * In Chapter "1.0 Electrical Characteristics", ++ * the "Fault Output Response Time" is specified as 2.4 seconds. ++ */ ++ if (time_after(jiffies, tc->last_updated + 2 * HZ + (HZ * 2) / 5) ++ || !tc->valid) { ++ size_t i; ++ int ret; ++ bool alarm_triggered; ++ ++ tc->valid = false; ++ ++ for (i = 0; i < __NUM_TC654_FAN; i++) { ++ ret = read_fan_rpm(tc, i); ++ if (ret < 0) ++ goto out; ++ ++ tc->fan_input[i] = ret; ++ } ++ ++ ret = read_tc(tc, TC654_REG_STATUS); ++ if (ret < 0) ++ goto out; ++ ++ alarm_triggered = !!(ret & (TC654_STATUS_F1F | ++ TC654_STATUS_F2F | TC654_STATUS_R1CO | ++ TC654_STATUS_R2CO | TC654_STATUS_OTF)); ++ ++ tc->alarms[TC654_ALARM_FAN1_FAULT] = !!(ret & TC654_STATUS_F1F); ++ tc->alarms[TC654_ALARM_FAN2_FAULT] = !!(ret & TC654_STATUS_F2F); ++ tc->alarms[TC654_ALARM_FAN1_COUNTER_OVERFLOW] = ++ !!(ret & TC654_STATUS_R1CO); ++ tc->alarms[TC654_ALARM_FAN2_COUNTER_OVERFLOW] = ++ !!(ret & TC654_STATUS_R2CO); ++ tc->alarms[TC654_ALARM_OVER_TEMPERATURE] = ++ !!(ret & TC654_STATUS_OTF); ++ tc->vin_status = !!(ret & TC654_STATUS_VSTAT); ++ ++ /* ++ * From 4.5 and 6.3 ++ * ++ * ... "If the V_in pin is open when TC654_CTRL_DUTYC is not ++ * selected, then V_out duty cycle will default to 39.33%.". ++ * ++ * and most importantly 6.5: ++ * ... "V_in pin is open, the duty cycle will go to the default ++ * setting of this register, which is 0010 (39.33%)." ++ */ ++ tc->pwm_manual |= tc->vin_status && ++ (tc->cached_regs[TC654_REG_CONFIG] & ++ TC654_CTRL_DUTYC); ++ ++ if (alarm_triggered) { ++ /* ++ * as the name implies, this FLAG needs to be ++ * set in order to clear the FAN Fault error. ++ */ ++ ret = mod_config(tc, TC654_CTRL_FFCLR, 0); ++ if (ret < 0) ++ goto out; ++ } ++ ++ tc->last_updated = jiffies; ++ tc->valid = true; ++ } ++ ++out: ++ mutex_unlock(&tc->update_lock); ++ return tc; ++} ++ ++static u8 get_fan_pulse(struct tc654 *tc, enum TC654_FAN fan) ++{ ++ u8 fan_pulse_mask = fan == TC654_FAN1 ? ++ TC654_CTRL_F1PPR_M : TC654_CTRL_F2PPR_M; ++ u8 fan_pulse_shift = fan == TC654_FAN1 ? ++ TC654_CTRL_F1PPR_S : TC654_CTRL_F2PPR_S; ++ ++ return 1 << ((tc->cached_regs[TC654_REG_CONFIG] & fan_pulse_mask) >> ++ fan_pulse_shift); ++} ++ ++static int ++set_fan_pulse(struct tc654 *tc, enum TC654_FAN fan, int pulses) ++{ ++ int old_pulses; ++ int err; ++ u8 new_pulse_per_rotation; ++ u8 fan_pulse_mask = fan == TC654_FAN1 ? ++ TC654_CTRL_F1PPR_M : TC654_CTRL_F2PPR_M; ++ u8 fan_pulse_shift = fan == TC654_FAN1 ? ++ TC654_CTRL_F1PPR_S : TC654_CTRL_F2PPR_S; ++ ++ switch (pulses) { ++ case 1: ++ new_pulse_per_rotation = 0; ++ break; ++ case 2: ++ new_pulse_per_rotation = 1; ++ break; ++ case 4: ++ new_pulse_per_rotation = 2; ++ break; ++ case 8: ++ new_pulse_per_rotation = 3; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ new_pulse_per_rotation <<= fan_pulse_shift; ++ new_pulse_per_rotation &= fan_pulse_mask; ++ ++ old_pulses = tc->cached_regs[TC654_REG_CONFIG]; ++ old_pulses &= fan_pulse_mask; ++ ++ if (new_pulse_per_rotation == old_pulses) ++ return 0; ++ ++ mutex_lock(&tc->update_lock); ++ err = mod_config(tc, new_pulse_per_rotation, ++ old_pulses & (~new_pulse_per_rotation)); ++ mutex_unlock(&tc->update_lock); ++ ++ /* invalidate RPM data to force re-read from hardware */ ++ tc->valid = false; ++ ++ return err; ++} ++ ++static int get_fan_speed(struct tc654 *tc) ++{ ++ enum TC654_FAN_MODE mode; ++ size_t i; ++ ++ mode = get_fan_mode(tc); ++ for (i = 0; i < ARRAY_SIZE(pwm_table); i++) { ++ if (mode == pwm_table[i].mode) ++ return pwm_table[i].min; ++ } ++ ++ return -EINVAL; ++} ++ ++static int set_fan_speed(struct tc654 *tc, int new_value) ++{ ++ int result; ++ size_t i; ++ ++ if (new_value > pwm_table[ARRAY_SIZE(pwm_table) - 1].min || ++ new_value < pwm_table[0].min) ++ return -EINVAL; ++ ++ for (i = 0; i < ARRAY_SIZE(pwm_table); i++) { ++ /* exact match */ ++ if (pwm_table[i].min == new_value) ++ break; ++ ++ /* a little bit too big - go with the previous entry */ ++ if (pwm_table[i].min > new_value) { ++ --i; ++ break; ++ } ++ } ++ ++ mutex_lock(&tc->update_lock); ++ result = write_fan_mode(tc, pwm_table[i].mode); ++ mutex_unlock(&tc->update_lock); ++ if (result < 0) ++ return result; ++ ++ return 0; ++} ++ ++/* sysfs */ ++ ++static ssize_t ++show_fan_input(struct device *dev, struct device_attribute *da, char *buf) ++{ ++ struct tc654 *tc = tc654_update_device(dev); ++ int nr = to_sensor_dev_attr(da)->index; ++ ++ return sprintf(buf, "%d\n", tc->fan_input[nr]); ++} ++ ++static ssize_t ++show_fan_min(struct device *dev, struct device_attribute *da, char *buf) ++{ ++ struct tc654 *tc = dev_get_drvdata(dev); ++ int nr = to_sensor_dev_attr(da)->index; ++ ++ return sprintf(buf, "%d\n", read_fan_fault_thresh(tc, nr)); ++} ++ ++static ssize_t ++show_fan_min_alarm(struct device *dev, struct device_attribute *da, char *buf) ++{ ++ struct tc654 *tc = tc654_update_device(dev); ++ int nr = to_sensor_dev_attr(da)->index; ++ ++ return sprintf(buf, "%d\n", nr == TC654_FAN1 ? ++ tc->alarms[TC654_ALARM_FAN1_FAULT] : ++ tc->alarms[TC654_ALARM_FAN2_FAULT]); ++} ++ ++static ssize_t ++show_fan_max_alarm(struct device *dev, struct device_attribute *da, char *buf) ++{ ++ struct tc654 *tc = tc654_update_device(dev); ++ int nr = to_sensor_dev_attr(da)->index; ++ ++ return sprintf(buf, "%d\n", nr == TC654_FAN1 ? ++ tc->alarms[TC654_ALARM_FAN1_COUNTER_OVERFLOW] : ++ tc->alarms[TC654_ALARM_FAN2_COUNTER_OVERFLOW]); ++} ++ ++static ssize_t ++set_fan_min(struct device *dev, struct device_attribute *da, ++ const char *buf, size_t count) ++{ ++ struct tc654 *tc = dev_get_drvdata(dev); ++ long new_min; ++ int nr = to_sensor_dev_attr(da)->index; ++ int old_min = read_fan_fault_thresh(tc, nr); ++ int status = kstrtol(buf, 10, &new_min); ++ ++ if (status < 0) ++ return status; ++ ++ new_min = (new_min / 50) * 50; ++ if (new_min == old_min) /* No change */ ++ return count; ++ ++ if (new_min < 0 || new_min > 12750) ++ return -EINVAL; ++ ++ mutex_lock(&tc->update_lock); ++ status = write_fan_fault_thresh(tc, nr, new_min); ++ mutex_unlock(&tc->update_lock); ++ return count; ++} ++ ++static ssize_t ++show_fan_max(struct device *dev, struct device_attribute *da, char *buf) ++{ ++ struct tc654 *tc = dev_get_drvdata(dev); ++ int max_rpm = tc->cached_regs[TC654_REG_CONFIG] & TC654_CTRL_RES ? ++ (((1 << 9) - 1) * 25) /* ((2**9) - 1) * 25 RPM */: ++ (((1 << 8) - 1) * 50) /* ((2**8) - 1) * 50 RPM */; ++ ++ return sprintf(buf, "%d\n", max_rpm); ++} ++ ++static ssize_t ++show_fan_fault(struct device *dev, struct device_attribute *da, char *buf) ++{ ++ struct tc654 *tc = tc654_update_device(dev); ++ int nr = to_sensor_dev_attr(da)->index; ++ u8 fan_fault_mask = nr == TC654_FAN1 ? ++ TC654_STATUS_F1F : TC654_STATUS_F2F; ++ ++ return sprintf(buf, "%d\n", ++ !!(tc->cached_regs[TC654_REG_STATUS] & fan_fault_mask)); ++} ++ ++static ssize_t ++show_fan_pulses(struct device *dev, struct device_attribute *da, char *buf) ++{ ++ struct tc654 *tc = dev_get_drvdata(dev); ++ int nr = to_sensor_dev_attr(da)->index; ++ ++ return sprintf(buf, "%d\n", get_fan_pulse(tc, nr)); ++} ++ ++static ssize_t ++set_fan_pulses(struct device *dev, struct device_attribute *da, ++ const char *buf, size_t count) ++{ ++ struct tc654 *tc = dev_get_drvdata(dev); ++ long new_pulse; ++ int nr = to_sensor_dev_attr(da)->index; ++ int status = kstrtol(buf, 10, &new_pulse); ++ ++ if (status < 0) ++ return status; ++ ++ status = set_fan_pulse(tc, nr, new_pulse); ++ if (status < 0) ++ return status; ++ ++ return count; ++} ++ ++static ssize_t ++show_pwm_enable(struct device *dev, struct device_attribute *da, char *buf) ++{ ++ struct tc654 *tc = tc654_update_device(dev); ++ int pwm_enabled; ++ ++ if ((tc->cached_regs[TC654_REG_CONFIG] & TC654_CTRL_SDM) && ++ !tc->pwm_manual) { ++ pwm_enabled = 0; /* full off */ ++ } else { ++ if (tc->valid && tc->vin_status == 0) ++ pwm_enabled = 2; /* automatic fan speed control */ ++ ++ pwm_enabled = 1; /* PWM Mode */ ++ } ++ ++ return sprintf(buf, "%d\n", pwm_enabled); ++} ++ ++static ssize_t ++set_pwm_enable(struct device *dev, struct device_attribute *da, ++ const char *buf, size_t count) ++{ ++ struct tc654 *tc = dev_get_drvdata(dev); ++ long new_value; ++ ++ int result = kstrtol(buf, 10, &new_value); ++ ++ if (result < 0) ++ return result; ++ ++ mutex_lock(&tc->update_lock); ++ switch (new_value) { ++ case 0: /* no fan control (i.e. is OFF) */ ++ result = write_fan_mode(tc, TC654_PWM_OFF); ++ tc->pwm_manual = false; ++ break; ++ ++ case 1: /* manual fan control enabled (using pwm) */ ++ result = write_fan_mode(tc, TC654_PWM_10000); ++ break; ++ ++ case 2: /* automatic fan speed control enabled */ ++ result = write_fan_mode(tc, TC654_PWM_VIN); ++ break; ++ ++ default: ++ result = -EINVAL; ++ } ++ ++ mutex_unlock(&tc->update_lock); ++ return result < 0 ? result : count; ++} ++ ++static ssize_t ++show_pwm(struct device *dev, struct device_attribute *da, char *buf) ++{ ++ struct tc654 *tc = tc654_update_device(dev); ++ int ret; ++ ++ ret = get_fan_speed(tc); ++ if (ret < 0) ++ return ret; ++ ++ return sprintf(buf, "%d\n", ret); ++} ++ ++static ssize_t ++set_pwm(struct device *dev, struct device_attribute *da, ++ const char *buf, size_t count) ++{ ++ struct tc654 *tc = dev_get_drvdata(dev); ++ long new_value = -1; ++ int result = kstrtol(buf, 10, &new_value); ++ ++ if (result < 0) ++ return result; ++ ++ if (new_value < 0 || new_value > INT_MAX) ++ return -EINVAL; ++ ++ if (!tc->pwm_manual) ++ return -EINVAL; ++ ++ result = set_fan_speed(tc, new_value); ++ if (result < 0) ++ return result; ++ ++ return count; ++} ++ ++static ssize_t ++show_temp_alarm_otf(struct device *dev, struct device_attribute *da, char *buf) ++{ ++ struct tc654 *tc = tc654_update_device(dev); ++ ++ return sprintf(buf, "%d\n", tc->alarms[TC654_ALARM_OVER_TEMPERATURE]); ++} ++ ++static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan_input, ++ NULL, TC654_FAN1); ++static SENSOR_DEVICE_ATTR(fan1_min, S_IRUGO | S_IWUSR, show_fan_min, ++ set_fan_min, TC654_FAN1); ++static SENSOR_DEVICE_ATTR(fan1_min_alarm, S_IRUGO, show_fan_min_alarm, ++ NULL, TC654_FAN1); ++static SENSOR_DEVICE_ATTR(fan1_max_alarm, S_IRUGO, show_fan_max_alarm, ++ NULL, TC654_FAN1); ++static SENSOR_DEVICE_ATTR(fan1_max, S_IRUGO, show_fan_max, NULL, TC654_FAN1); ++static SENSOR_DEVICE_ATTR(fan1_fault, S_IRUGO, show_fan_fault, ++ NULL, TC654_FAN1); ++static SENSOR_DEVICE_ATTR(fan1_pulses, S_IRUGO | S_IWUSR, show_fan_pulses, ++ set_fan_pulses, TC654_FAN1); ++static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan_input, ++ NULL, TC654_FAN2); ++static SENSOR_DEVICE_ATTR(fan2_min, S_IRUGO | S_IWUSR, show_fan_min, ++ set_fan_min, TC654_FAN2); ++static SENSOR_DEVICE_ATTR(fan2_max, S_IRUGO, show_fan_max, ++ NULL, TC654_FAN2); ++static SENSOR_DEVICE_ATTR(fan2_min_alarm, S_IRUGO, show_fan_min_alarm, ++ NULL, TC654_FAN2); ++static SENSOR_DEVICE_ATTR(fan2_max_alarm, S_IRUGO, show_fan_max_alarm, ++ NULL, TC654_FAN2); ++static SENSOR_DEVICE_ATTR(fan2_fault, S_IRUGO, show_fan_fault, ++ NULL, TC654_FAN2); ++static SENSOR_DEVICE_ATTR(fan2_pulses, S_IRUGO | S_IWUSR, show_fan_pulses, ++ set_fan_pulses, TC654_FAN2); ++ ++static DEVICE_ATTR(pwm1_enable, S_IRUGO | S_IWUSR, show_pwm_enable, ++ set_pwm_enable); ++static DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, show_pwm, set_pwm); ++ ++static DEVICE_ATTR(temp1_emergency_alarm, S_IRUGO, show_temp_alarm_otf, NULL); ++ ++/* sensors present on all models */ ++static struct attribute *tc654_attrs[] = { ++ &sensor_dev_attr_fan1_input.dev_attr.attr, ++ &sensor_dev_attr_fan1_min.dev_attr.attr, ++ &sensor_dev_attr_fan1_max.dev_attr.attr, ++ &sensor_dev_attr_fan1_min_alarm.dev_attr.attr, ++ &sensor_dev_attr_fan1_max_alarm.dev_attr.attr, ++ &sensor_dev_attr_fan1_fault.dev_attr.attr, ++ &sensor_dev_attr_fan1_pulses.dev_attr.attr, ++ &sensor_dev_attr_fan2_input.dev_attr.attr, ++ &sensor_dev_attr_fan2_min.dev_attr.attr, ++ &sensor_dev_attr_fan2_max.dev_attr.attr, ++ &sensor_dev_attr_fan2_min_alarm.dev_attr.attr, ++ &sensor_dev_attr_fan2_max_alarm.dev_attr.attr, ++ &sensor_dev_attr_fan2_fault.dev_attr.attr, ++ &sensor_dev_attr_fan2_pulses.dev_attr.attr, ++ ++ &dev_attr_pwm1_enable.attr, ++ &dev_attr_pwm1.attr, ++ ++ &dev_attr_temp1_emergency_alarm.attr, ++ NULL ++}; ++ ++ATTRIBUTE_GROUPS(tc654); ++ ++/* cooling device */ ++ ++static int tc654_get_max_state(struct thermal_cooling_device *cdev, ++ unsigned long *state) ++{ ++ *state = 255; ++ return 0; ++} ++ ++static int tc654_get_cur_state(struct thermal_cooling_device *cdev, ++ unsigned long *state) ++{ ++ struct tc654 *tc = cdev->devdata; ++ int ret; ++ ++ if (!tc) ++ return -EINVAL; ++ ++ ret = get_fan_speed(tc); ++ if (ret < 0) ++ return ret; ++ ++ *state = ret; ++ return 0; ++} ++ ++static int tc654_set_cur_state(struct thermal_cooling_device *cdev, ++ unsigned long state) ++{ ++ struct tc654 *tc = cdev->devdata; ++ ++ if (!tc) ++ return -EINVAL; ++ ++ if (state > INT_MAX) ++ return -EINVAL; ++ ++ return set_fan_speed(tc, state); ++} ++ ++static const struct thermal_cooling_device_ops tc654_fan_cool_ops = { ++ .get_max_state = tc654_get_max_state, ++ .get_cur_state = tc654_get_cur_state, ++ .set_cur_state = tc654_set_cur_state, ++}; ++ ++ ++/* hardware probe and detection */ ++ ++static int ++tc654_probe(struct i2c_client *client, const struct i2c_device_id *id) ++{ ++ struct tc654 *tc; ++ struct device *hwmon_dev; ++ int ret, i; ++ ++ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) ++ return -EIO; ++ ++ tc = devm_kzalloc(&client->dev, sizeof(*tc), GFP_KERNEL); ++ if (!tc) ++ return -ENOMEM; ++ ++ i2c_set_clientdata(client, tc); ++ tc->client = client; ++ mutex_init(&tc->update_lock); ++ ++ /* cache all 8 registers */ ++ for (i = 0; i < __TC654_REG_NUM; i++) { ++ ret = read_tc(tc, i); ++ if (ret < 0) ++ return ret; ++ } ++ ++ /* sysfs hooks */ ++ hwmon_dev = devm_hwmon_device_register_with_groups(&client->dev, ++ client->name, tc, ++ tc654_groups); ++ if (IS_ERR(hwmon_dev)) ++ return PTR_ERR(hwmon_dev); ++ ++#if IS_ENABLED(CONFIG_OF) ++ /* Optional cooling device register for Device tree platforms */ ++ tc->cdev = thermal_of_cooling_device_register(client->dev.of_node, ++ "tc654", tc, ++ &tc654_fan_cool_ops); ++#else /* CONFIG_OF */ ++ /* Optional cooling device register for non Device tree platforms */ ++ tc->cdev = thermal_cooling_device_register("tc654", tc, ++ &tc654_fan_cool_ops); ++#endif /* CONFIG_OF */ ++ ++ dev_info(&client->dev, "%s: sensor '%s'\n", ++ dev_name(hwmon_dev), client->name); ++ ++ return 0; ++} ++ ++static const struct i2c_device_id tc654_ids[] = { ++ { "tc654", 0, }, ++ { } ++}; ++MODULE_DEVICE_TABLE(i2c, tc654_ids); ++ ++/* Return 0 if detection is successful, -ENODEV otherwise */ ++static int ++tc654_detect(struct i2c_client *new_client, struct i2c_board_info *info) ++{ ++ struct i2c_adapter *adapter = new_client->adapter; ++ int manufacturer, product; ++ ++ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) ++ return -ENODEV; ++ ++ manufacturer = i2c_smbus_read_byte_data(new_client, TC654_REG_MFR_ID); ++ if (manufacturer != TC654_MFR_ID_MICROCHIP) ++ return -ENODEV; ++ ++ product = i2c_smbus_read_byte_data(new_client, TC654_REG_VER_ID); ++ if (!((product == TC654_VER_ID) || (product == TC655_VER_ID))) ++ return -ENODEV; ++ ++ strlcpy(info->type, "tc654", I2C_NAME_SIZE); ++ return 0; ++} ++ ++static struct i2c_driver tc654_driver = { ++ .class = I2C_CLASS_HWMON, ++ .driver = { ++ .name = "tc654", ++ }, ++ .probe = tc654_probe, ++ .id_table = tc654_ids, ++ .detect = tc654_detect, ++ .address_list = normal_i2c, ++}; ++ ++module_i2c_driver(tc654_driver); ++ ++MODULE_AUTHOR("Christian Lamparter <chunkeey@gmail.com>"); ++MODULE_DESCRIPTION("Microchip TC654/TC655 hwmon driver"); ++MODULE_LICENSE("GPL"); |