diff options
author | James <> | 2015-11-04 11:49:21 +0000 |
---|---|---|
committer | James <> | 2015-11-04 11:49:21 +0000 |
commit | 716ca530e1c4515d8683c9d5be3d56b301758b66 (patch) | |
tree | 700eb5bcc1a462a5f21dcec15ce7c97ecfefa772 /target/linux/mcs814x/files-3.18 | |
download | trunk-47381-master.tar.gz trunk-47381-master.tar.bz2 trunk-47381-master.zip |
Diffstat (limited to 'target/linux/mcs814x/files-3.18')
33 files changed, 3484 insertions, 0 deletions
diff --git a/target/linux/mcs814x/files-3.18/arch/arm/boot/dts/dlan-usb-extender.dts b/target/linux/mcs814x/files-3.18/arch/arm/boot/dts/dlan-usb-extender.dts new file mode 100644 index 0000000..59830e8 --- /dev/null +++ b/target/linux/mcs814x/files-3.18/arch/arm/boot/dts/dlan-usb-extender.dts @@ -0,0 +1,68 @@ +/* + * dlan-usb-extender.dts - Device Tree file for Devolo dLAN USB Extender + * + * Copyright (C) 2012, Florian Fainelli <florian@openwrt.org> + * + * Licensed under GPLv2 + */ + +/dts-v1/; +/include/ "mcs8140.dtsi" + +/ { + model = "Devolo dLAN USB Extender"; + compatible = "devolo,dlan-usb-extender", "moschip,mcs8140", "moschip,mcs814x"; + + chosen { + bootargs = "mem=16M console=ttyS0,57600 earlyprintk"; + }; + + ahb { + vci { + eth0: ethernet@40084000 { + phy = <&phy0>; + phy-mode = "mii"; + + phy0: ethernet-phy@0 { + reg = <8>; + }; + }; + + adc { + sdram: memory@0,0 { + reg = <0 0 0x1000000>; + }; + + nor: flash@7,0 { + + partition@0 { + label = "ArmBoot"; + reg = <0 0x30000>; + }; + partition@30000 { + label = "Config1"; + reg = <0x30000 0x10000>; + }; + partition@40000 { + label = "Config2"; + reg = <0x40000 0x10000>; + }; + partition@50000 { + label = "linux"; + reg = <0x50000 0x4C0000>; + }; + }; + }; + + leds { + compatible = "gpio-leds"; + + usb { + label = "dlan-usb-extender:green:usb"; + gpios = <&gpio 19 0>; // gpio 19 active high + }; + }; + }; + }; +}; + diff --git a/target/linux/mcs814x/files-3.18/arch/arm/boot/dts/mcs8140.dtsi b/target/linux/mcs814x/files-3.18/arch/arm/boot/dts/mcs8140.dtsi new file mode 100644 index 0000000..b7e8eb4 --- /dev/null +++ b/target/linux/mcs814x/files-3.18/arch/arm/boot/dts/mcs8140.dtsi @@ -0,0 +1,189 @@ +/* + * mcs8140.dtsi - Device Tree Include file for Moschip MCS8140 family SoC + * + * Copyright (C) 2012, Florian Fainelli <florian@openwrt.org> + * + * Licensed under GPLv2. + */ + +/include/ "skeleton.dtsi" + +/ { + model = "Moschip MCS8140 family SoC"; + compatible = "moschip,mcs8140"; + interrupt-parent = <&intc>; + + aliases { + serial0 = &uart0; + eth0 = ð0; + }; + + cpus { + cpu@0 { + compatible = "arm,arm926ejs"; + }; + }; + + ahb { + compatible = "simple-bus"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + vci { + compatible = "simple-bus"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + eth0: ethernet@40084000 { + #address-cells = <1>; + #size-cells = <0>; + + compatible = "moschip,nuport-mac"; + reg = <0x40084000 0xd8 // mac + 0x40080000 0x58>; // dma channels + interrupts = <4 5 29>; /* tx, rx, link */ + nuport-mac,buffer-shifting; + nuport-mac,link-activity = <0>; + }; + + tso@40088000 { + reg = <0x40088000 0x1c>; + interrupts = <7>; + }; + + i2s@4008c000 { + compatible = "moschip,mcs814x-i2s"; + reg = <0x4008c000 0x18>; + interrupts = <8>; + }; + + ipsec@40094000 { + compatible = "moschip,mcs814x-ipsec"; + reg = <0x40094000 0x1d8>; + interrupts = <16>; + }; + + rng@4009c000 { + compatible = "moschip,mcs814x-rng"; + reg = <0x4009c000 0x8>; + }; + + memc@400a8000 { + reg = <0x400a8000 0x58>; + }; + + list-proc@400ac0c0 { + reg = <0x400ac0c0 0x38>; + interrupts = <19 27>; // done, error + }; + + gpio: gpio@400d0000 { + compatible = "moschip,mcs814x-gpio"; + reg = <0x400d0000 0x670>; + interrupts = <10>; + #gpio-cells = <2>; + gpio-controller; + num-gpios = <20>; + }; + + eepio: gpio@400d4000 { + compatible = "moschip,mcs814x-gpio"; + reg = <0x400d4000 0x470>; + #gpio-cells = <2>; + gpio-controller; + num-gpios = <4>; + }; + + uart0: serial@400dc000 { + compatible = "ns16550"; + reg = <0x400dc000 0x20>; + clock-frequency = <50000000>; + reg-shift = <2>; + interrupts = <21>; + status = "okay"; + }; + + intc: interrupt-controller@400e4000 { + #interrupt-cells = <1>; + compatible = "moschip,mcs814x-intc"; + interrupt-controller; + interrupt-parent; + reg = <0x400e4000 0x48>; + }; + + m2m@400e8000 { + reg = <0x400e8000 0x24>; + interrupts = <17>; + }; + + eth-filters@400ec000 { + reg = <0x400ec000 0x80>; + }; + + timer: timer@400f800c { + compatible = "moschip,mcs814x-timer"; + interrupts = <0>; + reg = <0x400f800c 0x8>; + }; + + watchdog@400f8014 { + compatible = "moschip,mcs814x-wdt"; + reg = <0x400f8014 0x8>; + }; + + adc { + compatible = "simple-bus"; + #address-cells = <2>; + #size-cells = <1>; + // 8 64MB chip-selects + ranges = <0 0 0x00000000 0x4000000 // sdram + 1 0 0x04000000 0x4000000 // sdram + 2 0 0x08000000 0x4000000 // reserved + 3 0 0x0c000000 0x4000000 // flash/localbus + 4 0 0x10000000 0x4000000 // flash/localbus + 5 0 0x14000000 0x4000000 // flash/localbus + 6 0 0x18000000 0x4000000 // flash/localbus + 7 0 0x1c000000 0x4000000>; // flash/localbus + + sdram: memory@0,0 { + reg = <0 0 0>; + }; + + nor: flash@7,0 { + reg = <7 0 0x4000000>; + compatible = "cfi-flash"; + bank-width = <1>; // 8-bit external flash + #address-cells = <1>; + #size-cells = <1>; + }; + }; + + usb0: ehci@400fc000 { + compatible = "moschip,mcs814x-ehci", "usb-ehci"; + reg = <0x400fc000 0x74>; + interrupts = <2>; + }; + + usb1: ohci@400fd000 { + compatible = "moschip,mcs814x-ohci", "ohci-le"; + reg = <0x400fd000 0x74>; + interrupts = <11>; + }; + + usb2: ohci@400fe000 { + compatible = "moschip,mcs814x-ohci", "ohci-le"; + reg = <0x400fe000 0x74>; + interrupts = <12>; + }; + + usb3: otg@400ff000 { + compatible = "moschip,msc814x-otg", "usb-otg"; + reg = <0x400ff000 0x1000>; + interrupts = <13>; + }; + }; + + }; +}; diff --git a/target/linux/mcs814x/files-3.18/arch/arm/boot/dts/rbt-832.dts b/target/linux/mcs814x/files-3.18/arch/arm/boot/dts/rbt-832.dts new file mode 100644 index 0000000..9949c8e --- /dev/null +++ b/target/linux/mcs814x/files-3.18/arch/arm/boot/dts/rbt-832.dts @@ -0,0 +1,89 @@ +/* + * rbt-832.dts - Device Tree file for Tigal RBT-832 + * + * Copyright (C) 2012, Florian Fainelli <florian@openwrt.org> + * + * Licensed under GPLv2 + */ + +/dts-v1/; +/include/ "mcs8140.dtsi" + +/ { + model = "Tigal RBT-832"; + compatible = "tigal,rbt-832", "moschip,mcs8140", "moschip,mcs814x"; + + chosen { + bootargs = "mem=32M console=ttyS0,115200 earlyprintk"; + }; + + ahb { + vci { + eth0: ethernet@40084000 { + nuport-mac,link-activity = <0x01>; + phy = <&phy0>; + phy-mode = "mii"; + + phy0: ethernet-phy@0 { + reg = <1>; + }; + }; + + adc { + sdram: memory@0,0 { + reg = <0 0 0x2000000>; + }; + + nor: flash@7,0 { + + partition@0 { + label = "ArmBoot"; + reg = <0 0x40000>; + }; + partition@30000 { + label = "Enviroment"; + reg = <0x40000 0x20000>; + }; + partition@50000 { + label = "bZimage"; + reg = <0x60000 0x1a0000>; + }; + partition@150000 { + label = "UserFS"; + reg = <0x200000 0x600000>; + }; + }; + }; + + leds { + compatible = "gpio-leds"; + + ethernet { + label = "rbt-832:red:ethernet"; + gpios = <&gpio 0 0>; // gpio 0 active high + }; + + usb0 { + label = "rbt-832:red:usb0"; + gpios = <&gpio 4 0>; // gpio 4 active high + }; + + usb1 { + label = "rbt-832:red:usb1"; + gpios = <&gpio 3 0>; // gpio 3 active high + }; + + usb2 { + label = "rbt-832:red:usb2"; + gpios = <&gpio 2 0>; // gpio 2 active high + }; + + usb3 { + label = "rbt-832:red:usb3"; + gpios = <&gpio 1 0>; // gpio 1 active high + }; + }; + }; + }; +}; + diff --git a/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/Kconfig b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/Kconfig new file mode 100644 index 0000000..372c9b8 --- /dev/null +++ b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/Kconfig @@ -0,0 +1,29 @@ +if ARCH_MCS814X + +config MCS8140 + bool + select CPU_ARM926T + +menu "Moschip MCS8140 boards" + +config MACH_DLAN_USB_EXT + bool "Devolo dLAN USB Extender" + select MCS8140 + select NEW_LEDS + select LEDS_CLASS + select LEDS_GPIO + help + Machine support for the Devolo dLAN USB Extender + +config MACH_RBT_832 + bool "Tigal RBT-832" + select MCS8140 + select NEW_LEDS + select LEDS_CLASS + select LEDS_GPIO + help + Machine support for the Tigal RBT-832 board + +endmenu + +endif diff --git a/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/Makefile b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/Makefile new file mode 100644 index 0000000..68c5f4a --- /dev/null +++ b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/Makefile @@ -0,0 +1,5 @@ +obj-y += clock.o +obj-y += common.o +obj-y += irq.o +obj-y += timer.o +obj-y += board-mcs8140-dt.o diff --git a/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/Makefile.boot b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/Makefile.boot new file mode 100644 index 0000000..b572507 --- /dev/null +++ b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/Makefile.boot @@ -0,0 +1,4 @@ + zreladdr-y := 0x00008000 + +dtb-$(CONFIG_MACH_DLAN_USB_EXT) += dlan-usb-extender.dtb +dtb-$(CONFIG_MACH_RBT_832) += rbt-832.dtb diff --git a/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/board-mcs8140-dt.c b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/board-mcs8140-dt.c new file mode 100644 index 0000000..0d11450 --- /dev/null +++ b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/board-mcs8140-dt.c @@ -0,0 +1,45 @@ +/* + * Setup code for Moschip MCS8140-based board using Device Tree + * + * Copyright (C) 2012, Florian Fainelli <florian@openwrt.org> + * + * Licensed under GPLv2. + */ +#include <linux/types.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/gpio.h> +#include <linux/irqdomain.h> +#include <linux/of_platform.h> + +#include "common.h" + +#include <asm/setup.h> +#include <asm/irq.h> +#include <asm/mach/arch.h> +#include <asm/mach/map.h> +#include <asm/mach/irq.h> + +static void __init mcs814x_dt_device_init(void) +{ + of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL); + mcs814x_init_machine(); +} + +static const char *mcs8140_dt_board_compat[] __initdata = { + "moschip,mcs8140", + NULL, /* sentinel */ +}; + +DT_MACHINE_START(mcs8140_dt, "Moschip MCS8140 board") + /* Maintainer: Florian Fainelli <florian@openwrt.org> */ + .map_io = mcs814x_map_io, + .init_early = mcs814x_clk_init, + .init_irq = mcs814x_of_irq_init, + .init_time = mcs814x_timer_init, + .init_machine = mcs814x_dt_device_init, + .restart = mcs814x_restart, + .dt_compat = mcs8140_dt_board_compat, + .handle_irq = mcs814x_handle_irq, +MACHINE_END + diff --git a/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/clock.c b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/clock.c new file mode 100644 index 0000000..413bfec --- /dev/null +++ b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/clock.c @@ -0,0 +1,271 @@ +/* + * Moschip MCS814x clock routines + * + * Copyright (C) 2012, Florian Fainelli <florian@openwrt.org> + * + * Licensed under GPLv2 + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/export.h> +#include <linux/spinlock.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/clkdev.h> +#include <linux/clk.h> + +#include <mach/mcs814x.h> + +#include "common.h" + +#define KHZ 1000 +#define MHZ (KHZ * KHZ) + +struct clk_ops { + unsigned long (*get_rate)(struct clk *clk); + int (*set_rate)(struct clk *clk, unsigned long rate); + struct clk *(*get_parent)(struct clk *clk); + int (*enable)(struct clk *clk, int enable); +}; + +struct clk { + struct clk *parent; /* parent clk */ + unsigned long rate; /* clock rate in Hz */ + unsigned long divider; /* clock divider */ + u32 usecount; /* reference count */ + struct clk_ops *ops; /* clock operation */ + u32 enable_reg; /* clock enable register */ + u32 enable_mask; /* clock enable mask */ +}; + +static unsigned long clk_divide_parent(struct clk *clk) +{ + if (clk->parent && clk->divider) + return clk_get_rate(clk->parent) / clk->divider; + else + return 0; +} + +static int clk_local_onoff_enable(struct clk *clk, int enable) +{ + u32 tmp; + + /* no enable_reg means the clock is always enabled */ + if (!clk->enable_reg) + return 0; + + tmp = readl_relaxed(mcs814x_sysdbg_base + clk->enable_reg); + if (!enable) + tmp &= ~clk->enable_mask; + else + tmp |= clk->enable_mask; + + writel_relaxed(tmp, mcs814x_sysdbg_base + clk->enable_reg); + + return 0; +} + +static struct clk_ops default_clk_ops = { + .get_rate = clk_divide_parent, + .enable = clk_local_onoff_enable, +}; + +static DEFINE_SPINLOCK(clocks_lock); + +static const unsigned long cpu_freq_table[] = { + 175000, + 300000, + 125000, + 137500, + 212500, + 250000, + 162500, + 187500, + 162500, + 150000, + 225000, + 237500, + 200000, + 262500, + 275000, + 287500 +}; + +static struct clk clk_cpu; + +/* System clock is fixed at 50Mhz */ +static struct clk clk_sys = { + .rate = 50 * MHZ, +}; + +static struct clk clk_sdram; + +static struct clk clk_timer0 = { + .parent = &clk_sdram, + .divider = 2, + .ops = &default_clk_ops, +}; + +static struct clk clk_timer1_2 = { + .parent = &clk_sys, +}; + +/* Watchdog clock is system clock / 128 */ +static struct clk clk_wdt = { + .parent = &clk_sys, + .divider = 128, + .ops = &default_clk_ops, +}; + +static struct clk clk_emac = { + .ops = &default_clk_ops, + .enable_reg = SYSDBG_SYSCTL, + .enable_mask = SYSCTL_EMAC, +}; + +static struct clk clk_ephy = { + .ops = &default_clk_ops, + .enable_reg = SYSDBG_PLL_CTL, + .enable_mask = ~SYSCTL_EPHY, /* active low */ +}; + +static struct clk clk_cipher = { + .ops = &default_clk_ops, + .enable_reg = SYSDBG_SYSCTL, + .enable_mask = SYSCTL_CIPHER, +}; + +#define CLK(_dev, _con, _clk) \ +{ .dev_id = (_dev), .con_id = (_con), .clk = (_clk) }, + +static struct clk_lookup mcs814x_chip_clks[] = { + CLK("cpu", NULL, &clk_cpu) + CLK("sys", NULL, &clk_sys) + CLK("sdram", NULL, &clk_sdram) + /* 32-bits timer0 */ + CLK("timer0", NULL, &clk_timer0) + /* 16-bits timer1 */ + CLK("timer1", NULL, &clk_timer1_2) + /* 64-bits timer2, same as timer 1 */ + CLK("timer2", NULL, &clk_timer1_2) + CLK(NULL, "wdt", &clk_wdt) + CLK(NULL, "emac", &clk_emac) + CLK(NULL, "ephy", &clk_ephy) + CLK(NULL, "cipher", &clk_cipher) +}; + +static void local_clk_disable(struct clk *clk) +{ + WARN_ON(!clk->usecount); + + if (clk->usecount > 0) { + clk->usecount--; + + if ((clk->usecount == 0) && (clk->ops->enable)) + clk->ops->enable(clk, 0); + + if (clk->parent) + local_clk_disable(clk->parent); + } +} + +static int local_clk_enable(struct clk *clk) +{ + int ret = 0; + + if (clk->parent) + ret = local_clk_enable(clk->parent); + + if (ret) + return ret; + + if ((clk->usecount == 0) && (clk->ops->enable)) + ret = clk->ops->enable(clk, 1); + + if (!ret) + clk->usecount++; + else if (clk->parent && clk->parent->ops->enable) + local_clk_disable(clk->parent); + + return ret; +} + +int clk_enable(struct clk *clk) +{ + int ret; + unsigned long flags; + + spin_lock_irqsave(&clocks_lock, flags); + ret = local_clk_enable(clk); + spin_unlock_irqrestore(&clocks_lock, flags); + + return ret; +} +EXPORT_SYMBOL(clk_enable); + +void clk_disable(struct clk *clk) +{ + unsigned long flags; + + spin_lock_irqsave(&clocks_lock, flags); + local_clk_disable(clk); + spin_unlock_irqrestore(&clocks_lock, flags); +} +EXPORT_SYMBOL(clk_disable); + +unsigned long clk_get_rate(struct clk *clk) +{ + if (unlikely(IS_ERR_OR_NULL(clk))) + return 0; + + if (clk->rate) + return clk->rate; + + if (clk->ops && clk->ops->get_rate) + return clk->ops->get_rate(clk); + + return clk_get_rate(clk->parent); +} +EXPORT_SYMBOL(clk_get_rate); + +struct clk *clk_get_parent(struct clk *clk) +{ + unsigned long flags; + + if (unlikely(IS_ERR_OR_NULL(clk))) + return NULL; + + if (!clk->ops || !clk->ops->get_parent) + return clk->parent; + + spin_lock_irqsave(&clocks_lock, flags); + clk->parent = clk->ops->get_parent(clk); + spin_unlock_irqrestore(&clocks_lock, flags); + + return clk->parent; +} +EXPORT_SYMBOL(clk_get_parent); + +void __init mcs814x_clk_init(void) +{ + u32 bs1; + u8 cpu_freq; + + clkdev_add_table(mcs814x_chip_clks, ARRAY_SIZE(mcs814x_chip_clks)); + + /* read the bootstrap registers to know the exact clocking scheme */ + bs1 = readl_relaxed(mcs814x_sysdbg_base + SYSDBG_BS1); + cpu_freq = (bs1 >> CPU_FREQ_SHIFT) & CPU_FREQ_MASK; + + pr_info("CPU frequency: %lu (kHz)\n", cpu_freq_table[cpu_freq]); + clk_cpu.rate = cpu_freq * KHZ; + + /* read SDRAM frequency */ + if (bs1 & SDRAM_FREQ_BIT) + clk_sdram.rate = 100 * MHZ; + else + clk_sdram.rate = 133 * MHZ; + + pr_info("SDRAM frequency: %lu (MHz)\n", clk_sdram.rate / MHZ); +} + diff --git a/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/common.c b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/common.c new file mode 100644 index 0000000..3f1be1f --- /dev/null +++ b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/common.c @@ -0,0 +1,166 @@ +/* + * arch/arm/mach-mcs814x/common.c + * + * Core functions for Moschip MCS814x SoCs + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/gpio.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/reboot.h> + +#include <asm/setup.h> +#include <asm/mach-types.h> +#include <asm/mach/arch.h> +#include <mach/mcs814x.h> +#include <mach/cpu.h> +#include <asm/pgtable.h> +#include <asm/mach/map.h> + +void __iomem *mcs814x_sysdbg_base; + +static struct map_desc mcs814x_io_desc[] __initdata = { + { + .virtual = MCS814X_IO_BASE, + .pfn = __phys_to_pfn(MCS814X_IO_START), + .length = MCS814X_IO_SIZE, + .type = MT_DEVICE + }, +}; + +struct cpu_mode { + const char *name; + int gpio_start; + int gpio_end; +}; + +static const struct cpu_mode cpu_modes[] = { + { + .name = "I2S", + .gpio_start = 4, + .gpio_end = 8, + }, + { + .name = "UART", + .gpio_start = 4, + .gpio_end = 9, + }, + { + .name = "External MII", + .gpio_start = 0, + .gpio_end = 16, + }, + { + .name = "Normal", + .gpio_start = -1, + .gpio_end = -1, + }, +}; + +static void mcs814x_eth_hardware_filter_set(u8 value) +{ + u32 reg; + + reg = readl_relaxed(MCS814X_VIRT_BASE + MCS814X_DBGLED); + if (value) + reg |= 0x80; + else + reg &= ~0x80; + writel_relaxed(reg, MCS814X_VIRT_BASE + MCS814X_DBGLED); +} + +static void mcs814x_eth_led_cfg_set(u8 cfg) +{ + u32 reg; + + reg = readl_relaxed(mcs814x_sysdbg_base + SYSDBG_BS2); + reg &= ~LED_CFG_MASK; + reg |= cfg; + writel_relaxed(reg, mcs814x_sysdbg_base + SYSDBG_BS2); +} + +static void mcs814x_eth_buffer_shifting_set(u8 value) +{ + u8 reg; + + reg = readb_relaxed(mcs814x_sysdbg_base + SYSDBG_SYSCTL_MAC); + if (value) + reg |= BUF_SHIFT_BIT; + else + reg &= ~BUF_SHIFT_BIT; + writeb_relaxed(reg, mcs814x_sysdbg_base + SYSDBG_SYSCTL_MAC); +} + +static struct of_device_id mcs814x_eth_ids[] __initdata = { + { .compatible = "moschip,nuport-mac", }, + { /* sentinel */ }, +}; + +/* Configure platform specific knobs based on ethernet device node + * properties */ +static void mcs814x_eth_init(void) +{ + struct device_node *np; + const unsigned int *intspec; + + np = of_find_matching_node(NULL, mcs814x_eth_ids); + if (!np) + return; + + /* hardware filter must always be enabled */ + mcs814x_eth_hardware_filter_set(1); + + intspec = of_get_property(np, "nuport-mac,buffer-shifting", NULL); + if (!intspec) + mcs814x_eth_buffer_shifting_set(0); + else + mcs814x_eth_buffer_shifting_set(1); + + intspec = of_get_property(np, "nuport-mac,link-activity", NULL); + if (intspec) + mcs814x_eth_led_cfg_set(be32_to_cpup(intspec)); + + of_node_put(np); +} + +void __init mcs814x_init_machine(void) +{ + u32 bs2, cpu_mode; + int gpio; + + bs2 = readl_relaxed(mcs814x_sysdbg_base + SYSDBG_BS2); + cpu_mode = (bs2 >> CPU_MODE_SHIFT) & CPU_MODE_MASK; + + pr_info("CPU mode: %s\n", cpu_modes[cpu_mode].name); + + /* request the gpios since the pins are muxed for functionnality */ + for (gpio = cpu_modes[cpu_mode].gpio_start; + gpio == cpu_modes[cpu_mode].gpio_end; gpio++) { + if (gpio != -1) + gpio_request(gpio, cpu_modes[cpu_mode].name); + } + + mcs814x_eth_init(); +} + +void __init mcs814x_map_io(void) +{ + iotable_init(mcs814x_io_desc, ARRAY_SIZE(mcs814x_io_desc)); + + mcs814x_sysdbg_base = ioremap(MCS814X_IO_START + MCS814X_SYSDBG, + MCS814X_SYSDBG_SIZE); + if (!mcs814x_sysdbg_base) + panic("unable to remap sysdbg base"); +} + +void mcs814x_restart(enum reboot_mode mode, const char *cmd) +{ + writel_relaxed(~(1 << 31), mcs814x_sysdbg_base); +} diff --git a/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/common.h b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/common.h new file mode 100644 index 0000000..4a6c9ad --- /dev/null +++ b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/common.h @@ -0,0 +1,16 @@ +#ifndef __ARCH_MCS814X_COMMON_H +#define __ARCH_MCS814X_COMMON_H + +#include <linux/reboot.h> +#include <asm/mach/time.h> + +void mcs814x_map_io(void); +void mcs814x_clk_init(void); +void mcs814x_of_irq_init(void); +void mcs814x_init_machine(void); +void mcs814x_handle_irq(struct pt_regs *regs); +void mcs814x_restart(enum reboot_mode mode, const char *cmd); +void mcs814x_timer_init(void); +extern void __iomem *mcs814x_sysdbg_base; + +#endif /* __ARCH_MCS814X_COMMON_H */ diff --git a/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/cpu.h b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/cpu.h new file mode 100644 index 0000000..1ef3c4a --- /dev/null +++ b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/cpu.h @@ -0,0 +1,16 @@ +#ifndef __ASM_ARCH_CPU_H__ +#define __ASM_ARCH_CPU_H__ + +#include <asm/cputype.h> + +#define MCS8140_ID 0x41069260 /* ARM926EJ-S */ +#define MCS814X_MASK 0xff0ffff0 + +#ifdef CONFIG_MCS8140 +/* Moschip MCS8140 is based on an ARM926EJ-S core */ +#define soc_is_mcs8140() ((read_cpuid_id() & MCS814X_MASK) == MCS8140_ID) +#else +#define soc_is_mcs8140() (0) +#endif /* !CONFIG_MCS8140 */ + +#endif /* __ASM_ARCH_CPU_H__ */ diff --git a/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/debug-macro.S b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/debug-macro.S new file mode 100644 index 0000000..93ecea4 --- /dev/null +++ b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/debug-macro.S @@ -0,0 +1,11 @@ +#include <mach/mcs814x.h> + + .macro addruart, rp, rv, tmp + ldr \rp, =MCS814X_PHYS_BASE + ldr \rv, =MCS814X_VIRT_BASE + orr \rp, \rp, #MCS814X_UART + orr \rv, \rv, #MCS814X_UART + .endm + +#define UART_SHIFT 2 +#include <asm/hardware/debug-8250.S> diff --git a/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/entry-macro.S b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/entry-macro.S new file mode 100644 index 0000000..16d2d6d --- /dev/null +++ b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/entry-macro.S @@ -0,0 +1,6 @@ +#include <mach/mcs814x.h> + .macro disable_fiq + .endm + + .macro arch_ret_to_user, tmp1, tmp2 + .endm diff --git a/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/gpio.h b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/gpio.h new file mode 100644 index 0000000..20240c2 --- /dev/null +++ b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/gpio.h @@ -0,0 +1,21 @@ +#ifndef __ASM_ARCH_GPIO_H +#define __ASM_ARCH_GPIO_H + +/* new generic GPIO API */ +#include <asm-generic/gpio.h> + +#define gpio_get_value __gpio_get_value +#define gpio_set_value __gpio_set_value +#define gpio_cansleep __gpio_cansleep + +static inline int gpio_to_irq(unsigned gpio) +{ + return -EINVAL; +} + +static inline int irq_to_gpio(unsigned irq) +{ + return -EINVAL; +} + +#endif diff --git a/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/hardware.h b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/hardware.h new file mode 100644 index 0000000..529f648 --- /dev/null +++ b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/hardware.h @@ -0,0 +1,16 @@ +/* + * Copyright (C) 2003 Artec Design Ltd. + * + * 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. + * + */ + +#ifndef __ASM_ARCH_HARDWARE_H +#define __ASM_ARCH_HARDWARE_H + +#include "mcs814x.h" + +#endif + diff --git a/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/io.h b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/io.h new file mode 100644 index 0000000..80e56f6 --- /dev/null +++ b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/io.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003 Artec Design Ltd. + * Copyright (C) 2012, Florian Fainelli <florian@openwrt.org> + * + * 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. + * + */ + +#ifndef __ASM_ARM_ARCH_IO_H +#define __ASM_ARM_ARCH_IO_H + +#define IO_SPACE_LIMIT 0xffffffff + +/* + * We don't support ins[lb]/outs[lb]. Make them fault. + */ +#define __raw_readsb(p, d, l) do { *(int *)0 = 0; } while (0) +#define __raw_readsl(p, d, l) do { *(int *)0 = 0; } while (0) +#define __raw_writesb(p, d, l) do { *(int *)0 = 0; } while (0) +#define __raw_writesl(p, d, l) do { *(int *)0 = 0; } while (0) + +#define __io(a) __typesafe_io(a) +#define __mem_pci(a) (a) + +#endif diff --git a/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/irqs.h b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/irqs.h new file mode 100644 index 0000000..7b9e07c --- /dev/null +++ b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/irqs.h @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2003 Artec Design Ltd. + * + * 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. + * + */ + +#ifndef __ASM_ARCH_IRQS_H +#define __ASM_ARCH_IRQS_H + +#define FIQ_START 0 + +#define NR_IRQS 32 + +#endif diff --git a/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/mcs814x.h b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/mcs814x.h new file mode 100644 index 0000000..8252d33 --- /dev/null +++ b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/mcs814x.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2003 Artec Design Ltd. + * + * 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. + * + */ + +#ifndef __ASM_ARCH_MCS814X_H +#define __ASM_ARCH_MCS814X_H + +#define MCS814X_IO_BASE 0xF0000000 +#define MCS814X_IO_START 0x40000000 +#define MCS814X_IO_SIZE 0x00100000 + +/* IRQ controller register offset */ +#define MCS814X_IRQ_ICR 0x00 +#define MCS814X_IRQ_ISR 0x04 +#define MCS814X_IRQ_MASK 0x20 +#define MCS814X_IRQ_STS0 0x40 + +#define MCS814X_PHYS_BASE 0x40000000 +#define MCS814X_VIRT_BASE MCS814X_IO_BASE + +#define MCS814X_UART 0x000DC000 +#define MCS814X_DBGLED 0x000EC000 +#define MCS814X_SYSDBG 0x000F8000 +#define MCS814X_SYSDBG_SIZE 0x50 + +/* System configuration and bootstrap registers */ +#define SYSDBG_BS1 0x00 +#define CPU_FREQ_SHIFT 27 +#define CPU_FREQ_MASK 0x0F +#define SDRAM_FREQ_BIT (1 << 22) + +#define SYSDBG_BS2 0x04 +#define LED_CFG_MASK 0x03 +#define CPU_MODE_SHIFT 23 +#define CPU_MODE_MASK 0x03 + +#define SYSDBG_SYSCTL_MAC 0x1d +#define BUF_SHIFT_BIT (1 << 0) + +#define SYSDBG_SYSCTL 0x08 +#define SYSCTL_EMAC (1 << 0) +#define SYSCTL_EPHY (1 << 0) /* active low */ +#define SYSCTL_CIPHER (1 << 16) + +#define SYSDBG_PLL_CTL 0x3C + +#endif /* __ASM_ARCH_MCS814X_H */ + diff --git a/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/param.h b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/param.h new file mode 100644 index 0000000..7ffe70b --- /dev/null +++ b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/param.h @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2003 Artec Design Ltd. + * + * 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. + * + */ + +#ifndef __ASM_ARCH_PARAM_H +#define __ASM_ARCH_PARAM_H + +#define HZ 100 + +#endif diff --git a/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/system.h b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/system.h new file mode 100644 index 0000000..cf5453d --- /dev/null +++ b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/system.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2003 Artec Design Ltd. + * Copyright (C) 2012 Florian Fainelli <florian@openwrt.org> + * + * 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. + * + */ + +#ifndef __ASM_ARCH_SYSTEM_H +#define __ASM_ARCH_SYSTEM_H + +static inline void arch_idle(void) +{ + cpu_do_idle(); +} +#endif diff --git a/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/timex.h b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/timex.h new file mode 100644 index 0000000..f05c8ee --- /dev/null +++ b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/timex.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2003 Artec Design Ltd. + * + * 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. + * + */ + +#ifndef __ASM_ARCH_TIMEX_H +#define __ASM_ARCH_TIMEX_H + +/* + * Timex specification for MCS814X + */ +#define CLOCK_TICK_RATE 100 + +#endif diff --git a/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/uncompress.h b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/uncompress.h new file mode 100644 index 0000000..cf3ed9a --- /dev/null +++ b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/include/mach/uncompress.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2012, Florian Fainelli <florian@openwrt.org> + * + * 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. + */ + +#ifndef __ASM_ARCH_UNCOMPRESS_H +#define __ASM_ARCH_UNCOMPRESS_H + +#include <linux/serial_reg.h> +#include <asm/io.h> +#include <mach/mcs814x.h> +#include <mach/cpu.h> + +#define UART_SHIFT (2) + +/* cannot be static because the code will be inlined */ +void __iomem *uart_base; + +static inline void putc(int c) +{ + while (!(__raw_readb(uart_base + (UART_LSR << UART_SHIFT)) & UART_LSR_TEMT)); + __raw_writeb(c, uart_base + (UART_TX << UART_SHIFT)); +} + +static inline void flush(void) +{ +} + +static inline void arch_decomp_setup(void) +{ + if (soc_is_mcs8140()) + uart_base = (void __iomem *)(MCS814X_PHYS_BASE +MCS814X_UART); +} + +#define arch_decomp_wdog() + +#endif diff --git a/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/irq.c b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/irq.c new file mode 100644 index 0000000..d1cab68 --- /dev/null +++ b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/irq.c @@ -0,0 +1,94 @@ +/* + * Moschip MCS814x generic interrupt controller routines + * + * Copyright (C) 2012, Florian Fainelli <florian@openwrt.org> + * + * Licensed under the GPLv2 + */ +#include <linux/init.h> +#include <linux/irq.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/irqdomain.h> + +#include <asm/exception.h> +#include <asm/mach/irq.h> +#include <mach/mcs814x.h> + +static void __iomem *mcs814x_intc_base; +static struct irq_domain *domain; + +static void __init mcs814x_alloc_gc(void __iomem *base, unsigned int irq_start, + unsigned int num) +{ + struct irq_chip_generic *gc; + struct irq_chip_type *ct; + + if (irq_alloc_domain_generic_chips(domain, num, 1, "mcs814x-intc", handle_level_irq, + IRQ_GC_INIT_MASK_CACHE, IRQ_NOREQUEST, 0)) + panic("unable to allocate domain generic irq chip"); + + gc = irq_get_domain_generic_chip(domain, irq_start); + if (!gc) + panic("unable to get generic irq chip"); + + gc->reg_base = base; + ct = gc->chip_types; + ct->chip.irq_ack = irq_gc_unmask_enable_reg; + ct->chip.irq_mask = irq_gc_mask_clr_bit; + ct->chip.irq_unmask = irq_gc_mask_set_bit; + ct->regs.mask = MCS814X_IRQ_MASK; + ct->regs.enable = MCS814X_IRQ_ICR; + + /* Clear all interrupts */ + writel_relaxed(0xffffffff, base + MCS814X_IRQ_ICR); +} + +asmlinkage void __exception_irq_entry mcs814x_handle_irq(struct pt_regs *regs) +{ + u32 status, irq; + + do { + /* read the status register */ + status = __raw_readl(mcs814x_intc_base + MCS814X_IRQ_STS0); + if (!status) + break; + + irq = ffs(status) - 1; + status |= (1 << irq); + /* clear the interrupt */ + __raw_writel(status, mcs814x_intc_base + MCS814X_IRQ_STS0); + /* call the generic handler */ + handle_domain_irq(domain, irq, regs); + + } while (1); +} + +static const struct of_device_id mcs814x_intc_ids[] = { + { .compatible = "moschip,mcs814x-intc" }, + { /* sentinel */ }, +}; + +void __init mcs814x_of_irq_init(void) +{ + struct device_node *np; + + np = of_find_matching_node(NULL, mcs814x_intc_ids); + if (!np) + panic("unable to find compatible intc node in dtb\n"); + + mcs814x_intc_base = of_iomap(np, 0); + if (!mcs814x_intc_base) + panic("unable to map intc cpu registers\n"); + + domain = irq_domain_add_linear(np, 32, &irq_generic_chip_ops, NULL); + if (!domain) + panic("unable to add irq domain\n"); + irq_set_default_host(domain); + + of_node_put(np); + + mcs814x_alloc_gc(mcs814x_intc_base, 0, 32); +} + diff --git a/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/timer.c b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/timer.c new file mode 100644 index 0000000..31d0ba6 --- /dev/null +++ b/target/linux/mcs814x/files-3.18/arch/arm/mach-mcs814x/timer.c @@ -0,0 +1,132 @@ +/* + * Moschip MCS814x timer routines + * + * Copyright (C) 2012, Florian Fainelli <florian@openwrt.org> + * + * Licensed under GPLv2 + */ +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/time.h> +#include <linux/timex.h> +#include <linux/irq.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> + +#include <asm/mach/time.h> +#include <mach/mcs814x.h> + +/* Timer block registers */ +#define TIMER_VAL 0x00 +#define TIMER_CTL 0x04 +#define TIMER_CTL_EN 0x01 +#define TIMER_CTL_DBG 0x02 + +static u32 last_reload; +static u32 timer_correct; +static u32 clock_rate; +static u32 timer_reload_value; +static void __iomem *mcs814x_timer_base; + +static inline u32 ticks2usecs(u32 x) +{ + return x / (clock_rate / 1000000); +} + +/* + * Returns number of ms since last clock interrupt. Note that interrupts + * will have been disabled by do_gettimeoffset() + */ +static u32 mcs814x_gettimeoffset(void) +{ + u32 ticks = readl_relaxed(mcs814x_timer_base + TIMER_VAL); + + if (ticks < last_reload) + return ticks2usecs(ticks + (u32)(0xffffffff - last_reload)); + else + return ticks2usecs(ticks - last_reload); +} + + +static irqreturn_t mcs814x_timer_interrupt(int irq, void *dev_id) +{ + u32 count = readl_relaxed(mcs814x_timer_base + TIMER_VAL); + + /* take into account delay up to this moment */ + last_reload = count + timer_correct + timer_reload_value; + + if (last_reload < timer_reload_value) { + last_reload = timer_reload_value; + } else { + if (timer_correct == 0) + timer_correct = readl_relaxed(mcs814x_timer_base + TIMER_VAL) - count; + } + writel_relaxed(last_reload, mcs814x_timer_base + TIMER_VAL); + + timer_tick(); + + return IRQ_HANDLED; +} + +static struct of_device_id mcs814x_timer_ids[] = { + { .compatible = "moschip,mcs814x-timer" }, + { /* sentinel */ }, +}; + +static int __init mcs814x_of_timer_init(void) +{ + struct device_node *np; + int irq; + + np = of_find_matching_node(NULL, mcs814x_timer_ids); + if (!np) + panic("unable to find compatible timer node in dtb"); + + mcs814x_timer_base = of_iomap(np, 0); + if (!mcs814x_timer_base) + panic("unable to remap timer cpu registers"); + + irq = irq_of_parse_and_map(np, 0); + if (!irq) + panic("no interrupts property/mapping failed for timer"); + + return irq; +} + +void __init mcs814x_timer_init(void) +{ + struct clk *clk; + int irq; + + arch_gettimeoffset = mcs814x_gettimeoffset; + + clk = clk_get_sys("timer0", NULL); + if (IS_ERR_OR_NULL(clk)) + panic("unable to get timer0 clock"); + + clock_rate = clk_get_rate(clk); + + irq = mcs814x_of_timer_init(); + + pr_info("Timer frequency: %d (kHz)\n", clock_rate / 1000); + + timer_reload_value = 0xffffffff - (clock_rate / HZ); + + /* disable timer */ + writel_relaxed(~TIMER_CTL_EN, mcs814x_timer_base + TIMER_CTL); + writel_relaxed(timer_reload_value, mcs814x_timer_base + TIMER_VAL); + last_reload = timer_reload_value; + + if (request_irq(irq, mcs814x_timer_interrupt, + IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL, + "mcs814x-timer", NULL)) + panic("unable to request timer0 irq %d", irq); + + /* enable timer, stop timer in debug mode */ + writel_relaxed(TIMER_CTL_EN | TIMER_CTL_DBG, + mcs814x_timer_base + TIMER_CTL); +} diff --git a/target/linux/mcs814x/files-3.18/drivers/char/hw_random/mcs814x-rng.c b/target/linux/mcs814x/files-3.18/drivers/char/hw_random/mcs814x-rng.c new file mode 100644 index 0000000..cb3f339 --- /dev/null +++ b/target/linux/mcs814x/files-3.18/drivers/char/hw_random/mcs814x-rng.c @@ -0,0 +1,121 @@ +/* + * RNG driver for Moschip MCS814x SoC + * + * Copyright 2012 (C), Florian Fainelli <florian@openwrt.org> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/platform_device.h> +#include <linux/hw_random.h> +#include <linux/io.h> +#include <linux/of.h> + +#define STAT 0x00 +#define RND 0x04 + +struct mcs814x_rng_priv { + void __iomem *regs; +}; + +static int mcs814x_rng_data_read(struct hwrng *rng, u32 *buffer) +{ + struct mcs814x_rng_priv *priv = (struct mcs814x_rng_priv *)rng->priv; + + *buffer = readl_relaxed(priv->regs + RND); + + return 4; +} + +static int mcs814x_rng_probe(struct platform_device *pdev) +{ + struct resource *res; + struct mcs814x_rng_priv *priv; + struct hwrng *rng; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + ret = -ENOMEM; + goto out; + } + + rng = kzalloc(sizeof(*rng), GFP_KERNEL); + if (!rng) { + ret = -ENOMEM; + goto out_priv; + } + + platform_set_drvdata(pdev, rng); + rng->priv = (unsigned long)priv; + rng->name = pdev->name; + rng->data_read = mcs814x_rng_data_read; + + priv->regs = devm_ioremap_resource(&pdev->dev, res); + if (!priv->regs) { + ret = -ENOMEM; + goto out_rng; + } + + ret = hwrng_register(rng); + if (ret) { + dev_err(&pdev->dev, "failed to register hwrng driver\n"); + goto out; + } + + dev_info(&pdev->dev, "registered\n"); + + return ret; + +out_rng: + platform_set_drvdata(pdev, NULL); + kfree(rng); +out_priv: + kfree(priv); +out: + return ret; +} + +static int mcs814x_rng_remove(struct platform_device *pdev) +{ + struct hwrng *rng = platform_get_drvdata(pdev); + struct mcs814x_rng_priv *priv = (struct mcs814x_rng_priv *)rng->priv; + + hwrng_unregister(rng); + kfree(priv); + kfree(rng); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static const struct of_device_id mcs814x_rng_ids[] = { + { .compatible = "moschip,mcs814x-rng", }, + { /* sentinel */ }, +}; + +static struct platform_driver mcs814x_rng_driver = { + .driver = { + .name = "mcs814x-rng", + .owner = THIS_MODULE, + .of_match_table = mcs814x_rng_ids, + }, + .probe = mcs814x_rng_probe, + .remove = mcs814x_rng_remove, +}; + +module_platform_driver(mcs814x_rng_driver); + +MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>"); +MODULE_DESCRIPTION("H/W Random Number Generator (RNG) for Moschip MCS814x"); +MODULE_LICENSE("GPL"); diff --git a/target/linux/mcs814x/files-3.18/drivers/gpio/gpio-mcs814x.c b/target/linux/mcs814x/files-3.18/drivers/gpio/gpio-mcs814x.c new file mode 100644 index 0000000..c8a6509 --- /dev/null +++ b/target/linux/mcs814x/files-3.18/drivers/gpio/gpio-mcs814x.c @@ -0,0 +1,148 @@ +/* + * Moschip MCS814x GPIO support + * + * Copyright (C) 2012, Florian Fainelli <florian@openwrt.org> + * + * Licensed under the GPLv2 + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_address.h> + +struct mcs814x_gpio_chip { + void __iomem *regs; + struct gpio_chip chip; +}; + +#define GPIO_PIN 0x00 +#define GPIO_DIR 0x04 + +#define to_mcs814x_gpio_chip(x) container_of(x, struct mcs814x_gpio_chip, chip) + +static int mcs814x_gpio_get(struct gpio_chip *chip, unsigned offset) +{ + struct mcs814x_gpio_chip *mcs814x = to_mcs814x_gpio_chip(chip); + + return readl_relaxed(mcs814x->regs + GPIO_PIN) & (1 << offset); +} + +static void mcs814x_gpio_set(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct mcs814x_gpio_chip *mcs814x = to_mcs814x_gpio_chip(chip); + u32 mask; + + mask = readl_relaxed(mcs814x->regs + GPIO_PIN); + if (value) + mask |= (1 << offset); + else + mask &= ~(1 << offset); + writel_relaxed(mask, mcs814x->regs + GPIO_PIN); +} + +static int mcs814x_gpio_direction_output(struct gpio_chip *chip, + unsigned offset, int value) +{ + struct mcs814x_gpio_chip *mcs814x = to_mcs814x_gpio_chip(chip); + u32 mask; + + mask = readl_relaxed(mcs814x->regs + GPIO_DIR); + mask &= ~(1 << offset); + writel_relaxed(mask, mcs814x->regs + GPIO_DIR); + + return 0; +} + +static int mcs814x_gpio_direction_input(struct gpio_chip *chip, + unsigned offset) +{ + struct mcs814x_gpio_chip *mcs814x = to_mcs814x_gpio_chip(chip); + u32 mask; + + mask = readl_relaxed(mcs814x->regs + GPIO_DIR); + mask |= (1 << offset); + writel_relaxed(mask, mcs814x->regs + GPIO_DIR); + + return 0; +} + +static int mcs814x_gpio_probe(struct platform_device *pdev) +{ + struct resource *res; + struct mcs814x_gpio_chip *mcs814x_chip; + int ret; + const unsigned int *num_gpios; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + num_gpios = of_get_property(pdev->dev.of_node, "num-gpios", NULL); + if (!num_gpios) + dev_err(&pdev->dev, "FIXME: no num-gpios property\n"); + + mcs814x_chip = kzalloc(sizeof(*mcs814x_chip), GFP_KERNEL); + if (!mcs814x_chip) + return -ENOMEM; + + mcs814x_chip->regs = devm_ioremap_resource(&pdev->dev, res); + if (!mcs814x_chip->regs) { + ret = -ENOMEM; + goto out; + } + + platform_set_drvdata(pdev, mcs814x_chip); + +#ifdef CONFIG_OF_GPIO + mcs814x_chip->chip.of_node = pdev->dev.of_node; +#endif + + mcs814x_chip->chip.label = pdev->name; + mcs814x_chip->chip.get = mcs814x_gpio_get; + mcs814x_chip->chip.set = mcs814x_gpio_set; + mcs814x_chip->chip.direction_input = mcs814x_gpio_direction_input; + mcs814x_chip->chip.direction_output = mcs814x_gpio_direction_output; + mcs814x_chip->chip.ngpio = be32_to_cpup(num_gpios); + /* we want dynamic base allocation */ + mcs814x_chip->chip.base = -1; + + ret = gpiochip_add(&mcs814x_chip->chip); + if (ret) { + dev_err(&pdev->dev, "failed to register gpiochip\n"); + goto out; + } + + return 0; + +out: + platform_set_drvdata(pdev, NULL); + kfree(mcs814x_chip); + return ret; +} + +static struct of_device_id mcs814x_gpio_ids[] = { + { .compatible = "moschip,mcs814x-gpio" }, + { /* sentinel */ }, +}; + +static struct platform_driver mcs814x_gpio_driver = { + .driver = { + .name = "mcs814x-gpio", + .owner = THIS_MODULE, + .of_match_table = mcs814x_gpio_ids, + }, + .probe = mcs814x_gpio_probe, +}; + +int __init mcs814x_gpio_init(void) +{ + return platform_driver_register(&mcs814x_gpio_driver); +} +postcore_initcall(mcs814x_gpio_init); diff --git a/target/linux/mcs814x/files-3.18/drivers/net/ethernet/mcs8140/Kconfig b/target/linux/mcs814x/files-3.18/drivers/net/ethernet/mcs8140/Kconfig new file mode 100644 index 0000000..8fa38a4 --- /dev/null +++ b/target/linux/mcs814x/files-3.18/drivers/net/ethernet/mcs8140/Kconfig @@ -0,0 +1,4 @@ +config NUPORT_ETHERNET_DRIVER + tristate "MCS8140 Ethernet driver" + depends on ETHERNET && ARCH_MCS814X + help diff --git a/target/linux/mcs814x/files-3.18/drivers/net/ethernet/mcs8140/Makefile b/target/linux/mcs814x/files-3.18/drivers/net/ethernet/mcs8140/Makefile new file mode 100644 index 0000000..9719c51 --- /dev/null +++ b/target/linux/mcs814x/files-3.18/drivers/net/ethernet/mcs8140/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_NUPORT_ETHERNET_DRIVER) += mcs8140.o + +mcs8140-objs := nuport_mac.o diff --git a/target/linux/mcs814x/files-3.18/drivers/net/ethernet/mcs8140/nuport_mac.c b/target/linux/mcs814x/files-3.18/drivers/net/ethernet/mcs8140/nuport_mac.c new file mode 100644 index 0000000..a02144e --- /dev/null +++ b/target/linux/mcs814x/files-3.18/drivers/net/ethernet/mcs8140/nuport_mac.c @@ -0,0 +1,1206 @@ +/* + * Moschip MCS8140 Ethernet MAC driver + * + * Copyright (C) 2003, Moschip Semiconductors + * Copyright (C) 2012, Florian Fainelli <florian@openwrt.org> + * + * Licensed under GPLv2 + */ +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/delay.h> +#include <linux/ethtool.h> +#include <linux/mii.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_mdio.h> +#include <linux/of_net.h> +#include <linux/irq.h> +#include <linux/err.h> +#include <linux/phy.h> +#include <linux/clk.h> +#include <linux/dma-mapping.h> + +#include <asm/unaligned.h> +#include <asm/sizes.h> + +/* Hardware registers */ +#define MAC_BASE_ADDR ((priv->mac_base)) + +#define CTRL_REG (MAC_BASE_ADDR) +#define MII_BUSY (1 << 0) +#define MII_WRITE (1 << 1) +#define RX_ENABLE (1 << 2) +#define TX_ENABLE (1 << 3) +#define DEFER_CHECK (1 << 5) +#define STRIP_PAD (1 << 8) +#define DRTRY_DISABLE (1 << 10) +#define FULL_DUPLEX (1 << 20) +#define HBD_DISABLE (1 << 28) +#define MAC_ADDR_HIGH_REG (MAC_BASE_ADDR + 0x04) +#define MAC_ADDR_LOW_REG (MAC_BASE_ADDR + 0x08) +#define MII_ADDR_REG (MAC_BASE_ADDR + 0x14) +#define MII_ADDR_SHIFT (11) +#define MII_REG_SHIFT (6) +#define MII_DATA_REG (MAC_BASE_ADDR + 0x18) +/* Link interrupt registers */ +#define LINK_INT_CSR (MAC_BASE_ADDR + 0xD0) +#define LINK_INT_EN (1 << 0) +#define LINK_PHY_ADDR_SHIFT (1) +#define LINK_PHY_REG_SHIFT (6) +#define LINK_BIT_UP_SHIFT (11) +#define LINK_UP (1 << 16) +#define LINK_INT_POLL_TIME (MAC_BASE_ADDR + 0xD4) +#define LINK_POLL_MASK ((1 << 20) - 1) + +#define DMA_CHAN_WIDTH 32 +#define DMA_RX_CHAN 0 +#define DMA_TX_CHAN 2 + +/* Receive DMA registers */ +#define RX_DMA_BASE ((priv->dma_base) + \ + (DMA_CHAN_WIDTH * DMA_RX_CHAN)) +#define RX_BUFFER_ADDR (RX_DMA_BASE + 0x00) +#define RX_MAX_BYTES (RX_DMA_BASE + 0x04) +#define RX_ACT_BYTES (RX_DMA_BASE + 0x08) +#define RX_START_DMA (RX_DMA_BASE + 0x0C) +#define RX_DMA_ENABLE (1 << 0) +#define RX_DMA_RESET (1 << 1) +#define RX_DMA_STATUS_FIFO (1 << 12) +#define RX_DMA_ENH (RX_DMA_BASE + 0x14) +#define RX_DMA_INT_ENABLE (1 << 1) + +/* Transmit DMA registers */ +#define TX_DMA_BASE ((priv->dma_base) + \ + (DMA_CHAN_WIDTH * DMA_TX_CHAN)) +#define TX_BUFFER_ADDR (TX_DMA_BASE + 0x00) +#define TX_PKT_BYTES (TX_DMA_BASE + 0x04) +#define TX_BYTES_SENT (TX_DMA_BASE + 0x08) +#define TX_START_DMA (TX_DMA_BASE + 0x0C) +#define TX_DMA_ENABLE (1 << 0) +#define TX_DMA_START_FRAME (1 << 2) +#define TX_DMA_END_FRAME (1 << 3) +#define TX_DMA_PAD_DISABLE (1 << 8) +#define TX_DMA_CRC_DISABLE (1 << 9) +#define TX_DMA_FIFO_FULL (1 << 16) +#define TX_DMA_FIFO_EMPTY (1 << 17) +#define TX_DMA_STATUS_AVAIL (1 << 18) +#define TX_DMA_RESET (1 << 24) +#define TX_DMA_STATUS (TX_DMA_BASE + 0x10) +#define TX_DMA_ENH (TX_DMA_BASE + 0x14) +#define TX_DMA_ENH_ENABLE (1 << 0) +#define TX_DMA_INT_FIFO (1 << 1) + +#define RX_ALLOC_SIZE SZ_2K +#define MAX_ETH_FRAME_SIZE 1536 +#define RX_SKB_TAILROOM 128 +#define RX_SKB_HEADROOM (RX_ALLOC_SIZE - \ + (MAX_ETH_FRAME_SIZE + RX_SKB_TAILROOM) + 0) + + /* WDT Late COL Lenght COL Type */ +#define ERROR_FILTER_MASK ((1<<14) | (1<<15) | (1<<16) | (1<<17) | (0<<18) | \ + /* MII Dribbling CRC Len/type Control */\ + (1<<19) | (1<<20) | (1<<21) | (0<<24) | (1<<25) | \ + /* Unsup Missed */\ + (1<<26) | (0<<31)) +#define TX_RING_SIZE 30 +#define RX_RING_SIZE 30 + +static inline u32 nuport_mac_readl(void __iomem *reg) +{ + return readl_relaxed(reg); +} + +static inline u8 nuport_mac_readb(void __iomem *reg) +{ + return readb_relaxed(reg); +} + +static inline void nuport_mac_writel(u32 value, void __iomem *reg) +{ + writel_relaxed(value, reg); +} + +static inline void nuport_mac_writeb(u8 value, void __iomem *reg) +{ + writel_relaxed(value, reg); +} + +/* MAC private data */ +struct nuport_mac_priv { + spinlock_t lock; + + void __iomem *mac_base; + void __iomem *dma_base; + + int rx_irq; + int tx_irq; + int link_irq; + struct clk *emac_clk; + struct clk *ephy_clk; + + /* Transmit buffers */ + struct sk_buff *tx_skb[TX_RING_SIZE]; + dma_addr_t tx_addr; + unsigned int valid_txskb[TX_RING_SIZE]; + unsigned int cur_tx; + unsigned int dma_tx; + unsigned int tx_full; + + /* Receive buffers */ + struct sk_buff *rx_skb[RX_RING_SIZE]; + dma_addr_t rx_addr; + unsigned int irq_rxskb[RX_RING_SIZE]; + int pkt_len[RX_RING_SIZE]; + unsigned int cur_rx; + unsigned int dma_rx; + unsigned int rx_full; + + unsigned int first_pkt; + + /* Private data */ + struct napi_struct napi; + struct net_device *dev; + struct platform_device *pdev; + struct mii_bus *mii_bus; + struct phy_device *phydev; + struct device_node *phy_node; + phy_interface_t phy_interface; + int old_link; + int old_duplex; + u32 msg_level; + unsigned int buffer_shifting_len; +}; + +static inline int nuport_mac_mii_busy_wait(struct nuport_mac_priv *priv) +{ + unsigned long curr; + unsigned long finish = jiffies + 3 * HZ; + + do { + curr = jiffies; + if (!(nuport_mac_readl(MII_ADDR_REG) & MII_BUSY)) + return 0; + cpu_relax(); + } while (!time_after_eq(curr, finish)); + + return -EBUSY; +} + +/* Read from PHY registers */ +static int nuport_mac_mii_read(struct mii_bus *bus, + int mii_id, int regnum) +{ + struct net_device *dev = bus->priv; + struct nuport_mac_priv *priv = netdev_priv(dev); + int ret; + u32 val = 0; + + ret = nuport_mac_mii_busy_wait(priv); + if (ret) + return ret; + + val |= (mii_id << MII_ADDR_SHIFT) | (regnum << MII_REG_SHIFT) | MII_BUSY; + nuport_mac_writel(val, MII_ADDR_REG); + ret = nuport_mac_mii_busy_wait(priv); + if (ret) + return ret; + + return nuport_mac_readl(MII_DATA_REG); +} + +static int nuport_mac_mii_write(struct mii_bus *bus, int mii_id, + int regnum, u16 value) +{ + struct net_device *dev = bus->priv; + struct nuport_mac_priv *priv = netdev_priv(dev); + int ret; + u32 val = 0; + + ret = nuport_mac_mii_busy_wait(priv); + if (ret) + return ret; + + val |= (mii_id << MII_ADDR_SHIFT) | (regnum << MII_REG_SHIFT); + val |= MII_BUSY | MII_WRITE; + nuport_mac_writel(value, MII_DATA_REG); + nuport_mac_writel(val, MII_ADDR_REG); + + return nuport_mac_mii_busy_wait(priv); +} + +static int nuport_mac_mii_reset(struct mii_bus *bus) +{ + return 0; +} + +static int nuport_mac_start_tx_dma(struct nuport_mac_priv *priv, + struct sk_buff *skb) +{ + u32 reg; + unsigned int timeout = 2048; + + while (timeout--) { + reg = nuport_mac_readl(TX_START_DMA); + if (!(reg & TX_DMA_ENABLE)) { + netdev_dbg(priv->dev, "dma ready\n"); + break; + } + cpu_relax(); + } + + if (!timeout) + return -EBUSY; + + priv->tx_addr = dma_map_single(&priv->pdev->dev, skb->data, + skb->len, DMA_TO_DEVICE); + if (dma_mapping_error(&priv->pdev->dev, priv->tx_addr)) + return -ENOMEM; + + /* enable enhanced mode */ + nuport_mac_writel(TX_DMA_ENH_ENABLE, TX_DMA_ENH); + nuport_mac_writel(priv->tx_addr, TX_BUFFER_ADDR); + nuport_mac_writel((skb->len) - 1, TX_PKT_BYTES); + wmb(); + reg = TX_DMA_ENABLE | TX_DMA_START_FRAME | TX_DMA_END_FRAME; + nuport_mac_writel(reg, TX_START_DMA); + + return 0; +} + +static void nuport_mac_reset_tx_dma(struct nuport_mac_priv *priv) +{ + u32 reg; + + reg = nuport_mac_readl(TX_START_DMA); + reg |= TX_DMA_RESET; + nuport_mac_writel(reg, TX_START_DMA); +} + +static int nuport_mac_start_rx_dma(struct nuport_mac_priv *priv, + struct sk_buff *skb) +{ + u32 reg; + unsigned int timeout = 2048; + + while (timeout--) { + reg = nuport_mac_readl(RX_START_DMA); + if (!(reg & RX_DMA_ENABLE)) { + netdev_dbg(priv->dev, "dma ready\n"); + break; + } + cpu_relax(); + } + + if (!timeout) + return -EBUSY; + + priv->rx_addr = dma_map_single(&priv->pdev->dev, skb->data, + RX_ALLOC_SIZE, DMA_FROM_DEVICE); + if (dma_mapping_error(&priv->pdev->dev, priv->rx_addr)) + return -ENOMEM; + + nuport_mac_writel(priv->rx_addr, RX_BUFFER_ADDR); + wmb(); + nuport_mac_writel(RX_DMA_ENABLE, RX_START_DMA); + + return 0; +} + +static void nuport_mac_reset_rx_dma(struct nuport_mac_priv *priv) +{ + u32 reg; + + reg = nuport_mac_readl(RX_START_DMA); + reg |= RX_DMA_RESET; + nuport_mac_writel(reg, RX_START_DMA); +} + +/* I suppose this might do something, but I am not sure actually */ +static void nuport_mac_disable_rx_dma(struct nuport_mac_priv *priv) +{ + u32 reg; + + reg = nuport_mac_readl(RX_DMA_ENH); + reg &= ~RX_DMA_INT_ENABLE; + nuport_mac_writel(reg, RX_DMA_ENH); +} + +static void nuport_mac_enable_rx_dma(struct nuport_mac_priv *priv) +{ + u32 reg; + + reg = nuport_mac_readl(RX_DMA_ENH); + reg |= RX_DMA_INT_ENABLE; + nuport_mac_writel(reg, RX_DMA_ENH); +} + +/* Add packets to the transmit queue */ +static int nuport_mac_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + unsigned long flags; + struct nuport_mac_priv *priv = netdev_priv(dev); + int ret; + + if (netif_queue_stopped(dev)) { + netdev_warn(dev, "netif queue was stopped, restarting\n"); + netif_start_queue(dev); + } + + spin_lock_irqsave(&priv->lock, flags); + if (priv->first_pkt) { + ret = nuport_mac_start_tx_dma(priv, skb); + if (ret) { + netif_stop_queue(dev); + spin_unlock_irqrestore(&priv->lock, flags); + netdev_err(dev, "transmit path busy\n"); + return NETDEV_TX_BUSY; + } + priv->first_pkt = 0; + } + + priv->tx_skb[priv->cur_tx] = skb; + dev->stats.tx_bytes += skb->len; + dev->stats.tx_packets++; + priv->valid_txskb[priv->cur_tx] = 1; + priv->cur_tx++; + dev->trans_start = jiffies; + + if (priv->cur_tx >= TX_RING_SIZE) + priv->cur_tx = 0; + + spin_unlock_irqrestore(&priv->lock, flags); + + if (priv->valid_txskb[priv->cur_tx]) { + priv->tx_full = 1; + netdev_err(dev, "stopping queue\n"); + netif_stop_queue(dev); + } + + return NETDEV_TX_OK; +} + +static void nuport_mac_adjust_link(struct net_device *dev) +{ + struct nuport_mac_priv *priv = netdev_priv(dev); + struct phy_device *phydev = priv->phydev; + unsigned int status_changed = 0; + u32 reg; + + BUG_ON(!phydev); + + if (priv->old_link != phydev->link) { + status_changed = 1; + priv->old_link = phydev->link; + } + + if (phydev->link && (priv->old_duplex != phydev->duplex)) { + reg = nuport_mac_readl(CTRL_REG); + if (phydev->duplex == DUPLEX_FULL) + reg |= DUPLEX_FULL; + else + reg &= ~DUPLEX_FULL; + nuport_mac_writel(reg, CTRL_REG); + + status_changed = 1; + priv->old_duplex = phydev->duplex; + } + + if (!status_changed) + return; + + pr_info("%s: link %s", dev->name, phydev->link ? + "UP" : "DOWN"); + if (phydev->link) { + pr_cont(" - %d/%s", phydev->speed, + phydev->duplex == DUPLEX_FULL ? "full" : "half"); + } + pr_cont("\n"); +} + +static irqreturn_t nuport_mac_link_interrupt(int irq, void *dev_id) +{ + struct net_device *dev = dev_id; + struct nuport_mac_priv *priv = netdev_priv(dev); + u32 reg; + u8 phy_addr; + unsigned long flags; + irqreturn_t ret = IRQ_HANDLED; + + spin_lock_irqsave(&priv->lock, flags); + reg = nuport_mac_readl(LINK_INT_CSR); + phy_addr = (reg >> LINK_PHY_ADDR_SHIFT) & (PHY_MAX_ADDR - 1); + + if (phy_addr != priv->phydev->addr) { + netdev_err(dev, "spurious PHY irq (phy: %d)\n", phy_addr); + ret = IRQ_NONE; + goto out; + } + + priv->phydev->link = (reg & LINK_UP); + nuport_mac_adjust_link(dev); + +out: + spin_unlock_irqrestore(&priv->lock, flags); + return ret; +} + +static irqreturn_t nuport_mac_tx_interrupt(int irq, void *dev_id) +{ + struct net_device *dev = (struct net_device *)dev_id; + struct nuport_mac_priv *priv = netdev_priv(dev); + struct sk_buff *skb; + unsigned long flags; + int ret; + u32 reg; + + spin_lock_irqsave(&priv->lock, flags); + /* clear status word available if ready */ + reg = nuport_mac_readl(TX_START_DMA); + if (reg & TX_DMA_STATUS_AVAIL) { + nuport_mac_writel(reg, TX_START_DMA); + reg = nuport_mac_readl(TX_DMA_STATUS); + + if (reg & 1) + dev->stats.tx_errors++; + } else + netdev_dbg(dev, "no status word: %08x\n", reg); + + skb = priv->tx_skb[priv->dma_tx]; + priv->tx_skb[priv->dma_tx] = NULL; + priv->valid_txskb[priv->dma_tx] = 0; + dma_unmap_single(&priv->pdev->dev, priv->rx_addr, skb->len, + DMA_TO_DEVICE); + dev_kfree_skb_irq(skb); + + priv->dma_tx++; + if (priv->dma_tx >= TX_RING_SIZE) + priv->dma_tx = 0; + + if (!priv->valid_txskb[priv->dma_tx]) + priv->first_pkt = 1; + else { + ret = nuport_mac_start_tx_dma(priv, priv->tx_skb[priv->dma_tx]); + if (ret) + netdev_err(dev, "failed to restart TX dma\n"); + } + + if (priv->tx_full) { + netdev_dbg(dev, "restarting transmit queue\n"); + netif_wake_queue(dev); + priv->tx_full = 0; + } + + spin_unlock_irqrestore(&priv->lock, flags); + + return IRQ_HANDLED; +} + +static unsigned int nuport_mac_has_work(struct nuport_mac_priv *priv) +{ + unsigned int i; + + for (i = 0; i < RX_RING_SIZE; i++) + if (priv->rx_skb[i]) + return 1; + + return 0; +} + +static irqreturn_t nuport_mac_rx_interrupt(int irq, void *dev_id) +{ + struct net_device *dev = (struct net_device *)dev_id; + struct nuport_mac_priv *priv = netdev_priv(dev); + unsigned long flags; + int ret; + + spin_lock_irqsave(&priv->lock, flags); + if (!priv->rx_full) { + priv->pkt_len[priv->dma_rx] = nuport_mac_readl(RX_ACT_BYTES) - 4; + priv->irq_rxskb[priv->dma_rx] = 0; + priv->dma_rx++; + + if (priv->dma_rx >= RX_RING_SIZE) + priv->dma_rx = 0; + } else + priv->rx_full = 0; + + if (priv->irq_rxskb[priv->dma_rx] == 1) { + ret = nuport_mac_start_rx_dma(priv, priv->rx_skb[priv->dma_rx]); + if (ret) + netdev_err(dev, "failed to start rx dma\n"); + } else { + priv->rx_full = 1; + netdev_dbg(dev, "RX ring full\n"); + } + + if (likely(nuport_mac_has_work(priv))) { + /* find a way to disable DMA rx irq */ + nuport_mac_disable_rx_dma(priv); + napi_schedule(&priv->napi); + } + spin_unlock_irqrestore(&priv->lock, flags); + + return IRQ_HANDLED; +} + +/* Process received packets in tasklet */ +static int nuport_mac_rx(struct net_device *dev, int limit) +{ + struct nuport_mac_priv *priv = netdev_priv(dev); + struct sk_buff *skb; + int len, status; + int count = 0; + + while (count < limit && !priv->irq_rxskb[priv->cur_rx]) { + skb = priv->rx_skb[priv->cur_rx]; + len = priv->pkt_len[priv->cur_rx]; + + /* Remove 2 bytes added by RX buffer shifting */ + len = len - priv->buffer_shifting_len; + skb->data = skb->data + priv->buffer_shifting_len; + + /* Get packet status */ + status = get_unaligned((u32 *) (skb->data + len)); + + dma_unmap_single(&priv->pdev->dev, priv->rx_addr, skb->len, + DMA_FROM_DEVICE); + + /* packet filter failed */ + if (!(status & (1 << 30))) { + dev_kfree_skb_irq(skb); + goto exit; + } + + /* missed frame */ + if (status & (1 << 31)) { + dev->stats.rx_missed_errors++; + dev_kfree_skb_irq(skb); + goto exit; + } + + /* Not ethernet type */ + if ((!(status & (1 << 18))) || (status & ERROR_FILTER_MASK)) + dev->stats.rx_errors++; + + if (len > MAX_ETH_FRAME_SIZE) { + dev_kfree_skb_irq(skb); + goto exit; + } else + skb_put(skb, len); + + skb->protocol = eth_type_trans(skb, dev); + dev->stats.rx_packets++; + + if (status & (1 << 29)) + skb->pkt_type = PACKET_OTHERHOST; + if (status & (1 << 27)) + skb->pkt_type = PACKET_MULTICAST; + if (status & (1 << 28)) + skb->pkt_type = PACKET_BROADCAST; + + skb->ip_summed = CHECKSUM_UNNECESSARY; + + /* Pass the received packet to network layer */ + status = netif_receive_skb(skb); + if (status != NET_RX_DROP) + dev->stats.rx_bytes += len - 4; /* Without CRC */ + else + dev->stats.rx_dropped++; + + dev->last_rx = jiffies; + +exit: + skb = netdev_alloc_skb(dev, RX_ALLOC_SIZE); + if (!skb) + goto out; + + skb_reserve(skb, RX_SKB_HEADROOM); + priv->rx_skb[priv->cur_rx] = skb; + priv->irq_rxskb[priv->cur_rx] = 1; + priv->cur_rx++; + + if (priv->cur_rx >= RX_RING_SIZE) + priv->cur_rx = 0; + count++; + } +out: + return count; +} + +static int nuport_mac_poll(struct napi_struct *napi, int budget) +{ + struct nuport_mac_priv *priv = + container_of(napi, struct nuport_mac_priv, napi); + struct net_device *dev = priv->dev; + int work_done; + + work_done = nuport_mac_rx(dev, budget); + + if (work_done < budget) { + napi_complete(napi); + nuport_mac_enable_rx_dma(priv); + } + + return work_done; +} + +static void nuport_mac_init_tx_ring(struct nuport_mac_priv *priv) +{ + int i; + + priv->cur_tx = priv->dma_tx = priv->tx_full = 0; + for (i = 0; i < TX_RING_SIZE; i++) { + priv->tx_skb[i] = NULL; + priv->valid_txskb[i] = 0; + } + priv->first_pkt = 1; +} + +static int nuport_mac_init_rx_ring(struct net_device *dev) +{ + struct nuport_mac_priv *priv = netdev_priv(dev); + struct sk_buff *skb; + int i; + + priv->cur_rx = priv->dma_rx = priv->rx_full = 0; + + for (i = 0; i < RX_RING_SIZE; i++) { + skb = netdev_alloc_skb(dev, RX_ALLOC_SIZE); + if (!skb) + return -ENOMEM; + skb_reserve(skb, RX_SKB_HEADROOM); + priv->rx_skb[i] = skb; + priv->irq_rxskb[i] = 1; + } + + return 0; +} + +static void nuport_mac_free_rx_ring(struct nuport_mac_priv *priv) +{ + int i; + + for (i = 0; i < RX_RING_SIZE; i++) { + if (!priv->rx_skb[i]) + continue; + + dev_kfree_skb(priv->rx_skb[i]); + priv->rx_skb[i] = NULL; + } + + if (priv->rx_addr) + dma_unmap_single(&priv->pdev->dev, priv->rx_addr, RX_ALLOC_SIZE, + DMA_TO_DEVICE); +} + +static void nuport_mac_read_mac_address(struct net_device *dev) +{ + struct nuport_mac_priv *priv = netdev_priv(dev); + int i; + + for (i = 0; i < 4; i++) + dev->dev_addr[i] = nuport_mac_readb(MAC_ADDR_LOW_REG + i); + dev->dev_addr[4] = nuport_mac_readb(MAC_ADDR_HIGH_REG); + dev->dev_addr[5] = nuport_mac_readb(MAC_ADDR_HIGH_REG + 1); + + if (!is_valid_ether_addr(dev->dev_addr)) { + dev_info(&priv->pdev->dev, "using random address\n"); + random_ether_addr(dev->dev_addr); + } +} + +static int nuport_mac_change_mac_address(struct net_device *dev, void *mac_addr) +{ + struct sockaddr *addr = mac_addr; + struct nuport_mac_priv *priv = netdev_priv(dev); + unsigned long *temp = (unsigned long *)dev->dev_addr; + u32 high, low; + + if (netif_running(dev)) + return -EBUSY; + + memcpy(dev->dev_addr, addr->sa_data, ETH_ALEN); + + spin_lock_irq(&priv->lock); + + nuport_mac_writel(*temp, MAC_ADDR_LOW_REG); + temp = (unsigned long *)(dev->dev_addr + 4); + nuport_mac_writel(*temp, MAC_ADDR_HIGH_REG); + + low = nuport_mac_readl(MAC_ADDR_LOW_REG); + high = nuport_mac_readl(MAC_ADDR_HIGH_REG); + + spin_unlock_irq(&priv->lock); + + return 0; +} + +static int nuport_mac_open(struct net_device *dev) +{ + int ret; + struct nuport_mac_priv *priv = netdev_priv(dev); + unsigned long flags; + u32 reg = 0; + + ret = clk_enable(priv->emac_clk); + if (ret) { + netdev_err(dev, "failed to enable EMAC clock\n"); + return ret; + } + + /* Set MAC into full duplex mode by default */ + reg |= RX_ENABLE | TX_ENABLE; + reg |= DEFER_CHECK | STRIP_PAD | DRTRY_DISABLE; + reg |= FULL_DUPLEX | HBD_DISABLE; + nuport_mac_writel(reg, CTRL_REG); + + /* set mac address in hardware in case it was not already */ + nuport_mac_change_mac_address(dev, dev->dev_addr); + + ret = request_irq(priv->link_irq, &nuport_mac_link_interrupt, + 0, dev->name, dev); + if (ret) { + netdev_err(dev, "unable to request link interrupt\n"); + goto out_emac_clk; + } + + ret = request_irq(priv->tx_irq, &nuport_mac_tx_interrupt, + 0, dev->name, dev); + if (ret) { + netdev_err(dev, "unable to request rx interrupt\n"); + goto out_link_irq; + } + + /* Enable link interrupt monitoring for our PHY address */ + reg = LINK_INT_EN | (priv->phydev->addr << LINK_PHY_ADDR_SHIFT); + /* MII_BMSR register to be watched */ + reg |= (1 << LINK_PHY_REG_SHIFT); + /* BMSR_STATUS to be watched in particular */ + reg |= (2 << LINK_BIT_UP_SHIFT); + + spin_lock_irqsave(&priv->lock, flags); + nuport_mac_writel(reg, LINK_INT_CSR); + nuport_mac_writel(LINK_POLL_MASK, LINK_INT_POLL_TIME); + spin_unlock_irqrestore(&priv->lock, flags); + + phy_start(priv->phydev); + + ret = request_irq(priv->rx_irq, &nuport_mac_rx_interrupt, + 0, dev->name, dev); + if (ret) { + netdev_err(dev, "unable to request tx interrupt\n"); + goto out_tx_irq; + } + + netif_start_queue(dev); + + nuport_mac_init_tx_ring(priv); + + ret = nuport_mac_init_rx_ring(dev); + if (ret) { + netdev_err(dev, "rx ring init failed\n"); + goto out_rx_skb; + } + + nuport_mac_reset_tx_dma(priv); + nuport_mac_reset_rx_dma(priv); + + /* Start RX DMA */ + spin_lock_irqsave(&priv->lock, flags); + ret = nuport_mac_start_rx_dma(priv, priv->rx_skb[0]); + spin_unlock_irqrestore(&priv->lock, flags); + + napi_enable(&priv->napi); + + return ret; + +out_rx_skb: + nuport_mac_free_rx_ring(priv); + free_irq(priv->rx_irq, dev); +out_tx_irq: + free_irq(priv->tx_irq, dev); +out_link_irq: + free_irq(priv->link_irq, dev); +out_emac_clk: + clk_disable(priv->emac_clk); + return ret; +} + +static int nuport_mac_close(struct net_device *dev) +{ + u32 reg; + struct nuport_mac_priv *priv = netdev_priv(dev); + + spin_lock_irq(&priv->lock); + reg = nuport_mac_readl(CTRL_REG); + reg &= ~(RX_ENABLE | TX_ENABLE); + nuport_mac_writel(reg, CTRL_REG); + + napi_disable(&priv->napi); + netif_stop_queue(dev); + + free_irq(priv->link_irq, dev); + /* disable PHY polling */ + nuport_mac_writel(0, LINK_INT_CSR); + nuport_mac_writel(0, LINK_INT_POLL_TIME); + phy_stop(priv->phydev); + + free_irq(priv->tx_irq, dev); + free_irq(priv->rx_irq, dev); + spin_unlock_irq(&priv->lock); + + nuport_mac_free_rx_ring(priv); + + clk_disable(priv->emac_clk); + + return 0; +} + +static void nuport_mac_tx_timeout(struct net_device *dev) +{ + struct nuport_mac_priv *priv = netdev_priv(dev); + unsigned int i; + + netdev_warn(dev, "transmit timeout, attempting recovery\n"); + + netdev_info(dev, "TX DMA regs\n"); + for (i = 0; i < DMA_CHAN_WIDTH; i += 4) + netdev_info(dev, "[%02x]: 0x%08x\n", i, nuport_mac_readl(TX_DMA_BASE + i)); + netdev_info(dev, "RX DMA regs\n"); + for (i = 0; i < DMA_CHAN_WIDTH; i += 4) + netdev_info(dev, "[%02x]: 0x%08x\n", i, nuport_mac_readl(RX_DMA_BASE + i)); + + nuport_mac_init_tx_ring(priv); + nuport_mac_reset_tx_dma(priv); + + netif_wake_queue(dev); +} + +static int nuport_mac_mii_probe(struct net_device *dev) +{ + struct nuport_mac_priv *priv = netdev_priv(dev); + struct phy_device *phydev = NULL; + int ret; + + ret = clk_enable(priv->ephy_clk); + if (ret) { + netdev_err(dev, "unable to enable ePHY clk\n"); + return ret; + } + + phydev = phy_find_first(priv->mii_bus); + if (!phydev) { + netdev_err(dev, "no PHYs found\n"); + ret = -ENODEV; + goto out; + } + + phydev = of_phy_connect(dev, priv->phy_node, + nuport_mac_adjust_link, 0, + priv->phy_interface); + if (IS_ERR(phydev)) { + netdev_err(dev, "could not attach PHY\n"); + ret = PTR_ERR(phydev); + goto out; + } + + phydev->supported &= PHY_BASIC_FEATURES; + phydev->advertising = phydev->supported; + priv->phydev = phydev; + priv->old_link = 1; + priv->old_duplex = DUPLEX_FULL; + + dev_info(&priv->pdev->dev, "attached PHY driver [%s] " + "(mii_bus:phy_addr=%d)\n", + phydev->drv->name, phydev->addr); + + return 0; + +out: + /* disable the Ethernet PHY clock for the moment */ + clk_disable(priv->ephy_clk); + + return ret; +} + +static void nuport_mac_ethtool_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + strncpy(info->driver, "nuport-mac", sizeof(info->driver)); + strncpy(info->version, "0.1", sizeof(info->version)); + strncpy(info->fw_version, "N/A", sizeof(info->fw_version)); + strncpy(info->bus_info, "internal", sizeof(info->bus_info)); + info->n_stats = 0; + info->testinfo_len = 0; + info->regdump_len = 0; + info->eedump_len = 0; +} + +static int nuport_mac_ethtool_get_settings(struct net_device *dev, + struct ethtool_cmd *cmd) +{ + struct nuport_mac_priv *priv = netdev_priv(dev); + + if (priv->phydev) + return phy_ethtool_gset(priv->phydev, cmd); + + return -EINVAL; +} + +static int nuport_mac_ethtool_set_settings(struct net_device *dev, + struct ethtool_cmd *cmd) +{ + struct nuport_mac_priv *priv = netdev_priv(dev); + + if (priv->phydev) + return phy_ethtool_sset(priv->phydev, cmd); + + return -EINVAL; +} + +static void nuport_mac_set_msglevel(struct net_device *dev, u32 msg_level) +{ + struct nuport_mac_priv *priv = netdev_priv(dev); + + priv->msg_level = msg_level; +} + +static u32 nuport_mac_get_msglevel(struct net_device *dev) +{ + struct nuport_mac_priv *priv = netdev_priv(dev); + + return priv->msg_level; +} + +static const struct ethtool_ops nuport_mac_ethtool_ops = { + .get_drvinfo = nuport_mac_ethtool_drvinfo, + .get_link = ethtool_op_get_link, + .get_settings = nuport_mac_ethtool_get_settings, + .set_settings = nuport_mac_ethtool_set_settings, + .set_msglevel = nuport_mac_set_msglevel, + .get_msglevel = nuport_mac_get_msglevel, +}; + +static const struct net_device_ops nuport_mac_ops = { + .ndo_open = nuport_mac_open, + .ndo_stop = nuport_mac_close, + .ndo_start_xmit = nuport_mac_start_xmit, + .ndo_change_mtu = eth_change_mtu, + .ndo_validate_addr = eth_validate_addr, + .ndo_set_mac_address = nuport_mac_change_mac_address, + .ndo_tx_timeout = nuport_mac_tx_timeout, +}; + +static int __init nuport_mac_probe(struct platform_device *pdev) +{ + struct net_device *dev; + struct nuport_mac_priv *priv = NULL; + struct resource *regs, *dma; + int ret = 0; + int rx_irq, tx_irq, link_irq; + int i; + const unsigned int *intspec; + + dev = alloc_etherdev(sizeof(struct nuport_mac_priv)); + if (!dev) { + dev_err(&pdev->dev, "no memory for net_device\n"); + return -ENOMEM; + } + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dma = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!regs || !dma) { + dev_err(&pdev->dev, "failed to get regs resources\n"); + ret = -ENODEV; + goto out; + } + + rx_irq = platform_get_irq(pdev, 0); + tx_irq = platform_get_irq(pdev, 1); + link_irq = platform_get_irq(pdev, 2); + if (rx_irq < 0 || tx_irq < 0 || link_irq < 0) { + ret = -ENODEV; + goto out; + } + + platform_set_drvdata(pdev, dev); + SET_NETDEV_DEV(dev, &pdev->dev); + priv = netdev_priv(dev); + priv->pdev = pdev; + priv->dev = dev; + spin_lock_init(&priv->lock); + + intspec = of_get_property(pdev->dev.of_node, + "nuport-mac,buffer-shifting", NULL); + if (!intspec) + priv->buffer_shifting_len = 0; + else + priv->buffer_shifting_len = 2; + + priv->mac_base = devm_ioremap_resource(&pdev->dev, regs); + if (!priv->mac_base) { + dev_err(&pdev->dev, "failed to remap regs\n"); + ret = -ENOMEM; + goto out_platform; + } + + priv->dma_base = devm_ioremap_resource(&pdev->dev, dma); + if (!priv->dma_base) { + dev_err(&pdev->dev, "failed to remap dma-regs\n"); + ret = -ENOMEM; + goto out_platform; + } + + priv->emac_clk = clk_get(&pdev->dev, "emac"); + if (IS_ERR_OR_NULL(priv->emac_clk)) { + dev_err(&pdev->dev, "failed to get emac clk\n"); + ret = PTR_ERR(priv->emac_clk); + goto out_platform; + } + + priv->ephy_clk = clk_get(&pdev->dev, "ephy"); + if (IS_ERR_OR_NULL(priv->ephy_clk)) { + dev_err(&pdev->dev, "failed to get ephy clk\n"); + ret = PTR_ERR(priv->ephy_clk); + goto out_platform; + } + + priv->link_irq = link_irq; + priv->rx_irq = rx_irq; + priv->tx_irq = tx_irq; + priv->msg_level = NETIF_MSG_DRV | NETIF_MSG_PROBE | NETIF_MSG_LINK; + dev->netdev_ops = &nuport_mac_ops; + dev->ethtool_ops = &nuport_mac_ethtool_ops; + dev->watchdog_timeo = HZ; + dev->flags = IFF_BROADCAST; /* Supports Broadcast */ + dev->tx_queue_len = TX_RING_SIZE / 2; + + netif_napi_add(dev, &priv->napi, nuport_mac_poll, 64); + + priv->phy_node = of_parse_phandle(pdev->dev.of_node, "phy", 0); + if (!priv->phy_node) { + dev_err(&pdev->dev, "no associated PHY\n"); + ret = -ENODEV; + goto out; + } + + priv->phy_interface = of_get_phy_mode(pdev->dev.of_node); + if (priv->phy_interface < 0) { + dev_err(&pdev->dev, "invalid PHY mode\n"); + ret = -EINVAL; + goto out; + } + + priv->mii_bus = mdiobus_alloc(); + if (!priv->mii_bus) { + dev_err(&pdev->dev, "mii bus allocation failed\n"); + goto out; + } + + priv->mii_bus->priv = dev; + priv->mii_bus->read = nuport_mac_mii_read; + priv->mii_bus->write = nuport_mac_mii_write; + priv->mii_bus->reset = nuport_mac_mii_reset; + priv->mii_bus->name = "nuport-mac-mii"; + priv->mii_bus->phy_mask = (1 << 0); + snprintf(priv->mii_bus->id, MII_BUS_ID_SIZE, "%s", pdev->name); + priv->mii_bus->irq = kzalloc(PHY_MAX_ADDR * sizeof(int), GFP_KERNEL); + if (!priv->mii_bus->irq) { + dev_err(&pdev->dev, "failed to allocate mii_bus irqs\n"); + ret = -ENOMEM; + goto out_mdio; + } + + /* We support PHY interrupts routed back to the MAC */ + for (i = 0; i < PHY_MAX_ADDR; i++) + priv->mii_bus->irq[i] = PHY_IGNORE_INTERRUPT; + + ret = of_mdiobus_register(priv->mii_bus, pdev->dev.of_node); + if (ret) { + dev_err(&pdev->dev, "failed to register mii_bus\n"); + goto out_mdio_irq; + } + + ret = nuport_mac_mii_probe(dev); + if (ret) { + dev_err(&pdev->dev, "failed to probe MII bus\n"); + goto out_mdio_unregister; + } + + ret = register_netdev(dev); + if (ret) { + dev_err(&pdev->dev, "failed to register net_device\n"); + goto out_mdio_probe; + } + + /* read existing mac address */ + nuport_mac_read_mac_address(dev); + + dev_info(&pdev->dev, "registered (MAC: %pM)\n", dev->dev_addr); + + return ret; + +out_mdio_probe: + phy_disconnect(priv->phydev); +out_mdio_unregister: + mdiobus_unregister(priv->mii_bus); +out_mdio_irq: + kfree(priv->mii_bus->irq); +out_mdio: + mdiobus_free(priv->mii_bus); +out_platform: + platform_set_drvdata(pdev, NULL); +out: + clk_put(priv->ephy_clk); + clk_put(priv->emac_clk); + free_netdev(dev); + platform_set_drvdata(pdev, NULL); + return ret; +} + +static int nuport_mac_remove(struct platform_device *pdev) +{ + struct net_device *dev = platform_get_drvdata(pdev); + struct nuport_mac_priv *priv = netdev_priv(dev); + + unregister_netdev(dev); + phy_disconnect(priv->phydev); + mdiobus_unregister(priv->mii_bus); + kfree(priv->mii_bus->irq); + mdiobus_free(priv->mii_bus); + clk_put(priv->ephy_clk); + clk_put(priv->emac_clk); + free_netdev(dev); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct of_device_id nuport_eth_ids[] __initdata = { + {.compatible = "moschip,nuport-mac",}, + { /* sentinel */ }, +}; + +static struct platform_driver nuport_eth_driver = { + .driver = { + .name = "nuport-mac", + .owner = THIS_MODULE, + .of_match_table = nuport_eth_ids, + }, + .probe = nuport_mac_probe, + .remove = nuport_mac_remove, +}; + +module_platform_driver(nuport_eth_driver); + +MODULE_AUTHOR("Moschip Semiconductors Ltd."); +MODULE_DESCRIPTION("Moschip MCS8140 Ethernet MAC driver"); +MODULE_LICENSE("GPL"); diff --git a/target/linux/mcs814x/files-3.18/drivers/net/phy/mcs814x.c b/target/linux/mcs814x/files-3.18/drivers/net/phy/mcs814x.c new file mode 100644 index 0000000..e92176e --- /dev/null +++ b/target/linux/mcs814x/files-3.18/drivers/net/phy/mcs814x.c @@ -0,0 +1,64 @@ +/* + * Driver for Moschip MCS814x internal PHY + * + * Copyright (c) 2012 Florian Fainelli <florian@openwrt.org> + * + * 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 <linux/kernel.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/unistd.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/spinlock.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/mii.h> +#include <linux/ethtool.h> +#include <linux/phy.h> + +MODULE_DESCRIPTION("Moschip MCS814x PHY driver"); +MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>"); +MODULE_LICENSE("GPL"); + +/* Nothing special about this PHY but its OUI (O) */ +static struct phy_driver mcs8140_driver = { + .phy_id = 0, + .name = "Moschip MCS8140", + .phy_id_mask = 0x02, + .features = PHY_BASIC_FEATURES, + .config_aneg = &genphy_config_aneg, + .read_status = &genphy_read_status, + .suspend = genphy_suspend, + .resume = genphy_resume, + .driver = { .owner = THIS_MODULE,}, +}; + +static int __init mcs814x_phy_init(void) +{ + return phy_driver_register(&mcs8140_driver); +} + +static void __exit mcs814x_phy_exit(void) +{ + phy_driver_unregister(&mcs8140_driver); +} + +module_init(mcs814x_phy_init); +module_exit(mcs814x_phy_exit); + +static struct mdio_device_id __maybe_unused mcs814x_phy_tbl[] = { + { 0x0, 0x0ffffff0 }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, mcs814x_phy_tbl); diff --git a/target/linux/mcs814x/files-3.18/drivers/usb/host/ehci-mcs814x.c b/target/linux/mcs814x/files-3.18/drivers/usb/host/ehci-mcs814x.c new file mode 100644 index 0000000..23928f2 --- /dev/null +++ b/target/linux/mcs814x/files-3.18/drivers/usb/host/ehci-mcs814x.c @@ -0,0 +1,163 @@ +/* + * MCS814X EHCI Host Controller Driver + * + * Based on "ehci-fsl.c" by Randy Vinson <rvinson@mvista.com> + * + * 2007 (c) MontaVista Software, Inc. This file is licensed under + * the terms of the GNU General Public License version 2. This program + * is licensed "as is" without any warranty of any kind, whether express + * or implied. + */ + +#include <linux/platform_device.h> +#include <linux/of.h> + +#define MCS814X_EHCI_CAPS_OFFSET 0x68 + +static int mcs814x_ehci_init(struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + int retval = 0; + + ehci->caps = hcd->regs + MCS814X_EHCI_CAPS_OFFSET; + ehci->regs = hcd->regs + + HC_LENGTH(ehci, ehci_readl(ehci, &ehci->caps->hc_capbase)); + ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params); + ehci_reset(ehci); + + retval = ehci_init(hcd); + if (retval) { + pr_err("ehci_init failed\n"); + return retval; + } + + return retval; +} + +static const struct hc_driver mcs814x_ehci_hc_driver = { + .description = hcd_name, + .product_desc = "MCS814X EHCI Host Controller", + .hcd_priv_size = sizeof(struct ehci_hcd), + .irq = ehci_irq, + .flags = HCD_MEMORY | HCD_USB2, + .reset = mcs814x_ehci_init, + .start = ehci_run, + .stop = ehci_stop, + .shutdown = ehci_shutdown, + .urb_enqueue = ehci_urb_enqueue, + .urb_dequeue = ehci_urb_dequeue, + .endpoint_disable = ehci_endpoint_disable, + .get_frame_number = ehci_get_frame, + .hub_status_data = ehci_hub_status_data, + .hub_control = ehci_hub_control, +#if defined(CONFIG_PM) + .bus_suspend = ehci_bus_suspend, + .bus_resume = ehci_bus_resume, +#endif + .relinquish_port = ehci_relinquish_port, + .port_handed_over = ehci_port_handed_over, + + .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, +}; + +static int mcs814x_ehci_probe(struct platform_device *pdev) +{ + struct usb_hcd *hcd; + const struct hc_driver *driver = &mcs814x_ehci_hc_driver; + struct resource *res; + int irq; + int retval; + + if (usb_disabled()) + return -ENODEV; + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(&pdev->dev, + "Found HC with no IRQ. Check %s setup!\n", + dev_name(&pdev->dev)); + return -ENODEV; + } + irq = res->start; + + pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask; + + hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev)); + if (!hcd) { + retval = -ENOMEM; + goto fail_create_hcd; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, + "Found HC with no register addr. Check %s setup!\n", + dev_name(&pdev->dev)); + retval = -ENODEV; + goto fail_request_resource; + } + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, + driver->description)) { + dev_dbg(&pdev->dev, "controller already in use\n"); + retval = -EBUSY; + goto fail_request_resource; + } + + hcd->regs = ioremap_nocache(hcd->rsrc_start, hcd->rsrc_len); + if (hcd->regs == NULL) { + dev_dbg(&pdev->dev, "error mapping memory\n"); + retval = -EFAULT; + goto fail_ioremap; + } + + retval = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (retval) + goto fail_add_hcd; + + dev_info(&pdev->dev, "added MCS814X EHCI driver\n"); + + return retval; + +fail_add_hcd: + iounmap(hcd->regs); +fail_ioremap: + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); +fail_request_resource: + usb_put_hcd(hcd); +fail_create_hcd: + dev_err(&pdev->dev, "init %s fail, %d\n", dev_name(&pdev->dev), retval); + return retval; +} + +static int mcs814x_ehci_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + usb_remove_hcd(hcd); + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); + + return 0; +} + +MODULE_ALIAS("platform:mcs814x-ehci"); + +static const struct of_device_id mcs814x_ehci_id[] = { + { .compatible = "moschip,mcs814x-ehci" }, + { .compatible = "usb-ehci" }, + { /* sentinel */ }, +}; + +static struct platform_driver mcs814x_ehci_driver = { + .probe = mcs814x_ehci_probe, + .remove = mcs814x_ehci_remove, + .driver = { + .name = "mcs814x-ehci", + .of_match_table = mcs814x_ehci_id, + }, +}; diff --git a/target/linux/mcs814x/files-3.18/drivers/usb/host/ohci-mcs814x.c b/target/linux/mcs814x/files-3.18/drivers/usb/host/ohci-mcs814x.c new file mode 100644 index 0000000..161176b --- /dev/null +++ b/target/linux/mcs814x/files-3.18/drivers/usb/host/ohci-mcs814x.c @@ -0,0 +1,202 @@ +/* + * OHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> + * (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net> + * (C) Copyright 2002 Hewlett-Packard Company + * + * Bus Glue for Moschip MCS814x. + * + * Written by Christopher Hoover <ch@hpl.hp.com> + * Based on fragments of previous driver by Russell King et al. + * + * Modified for LH7A404 from ohci-sa1111.c + * by Durgesh Pattamatta <pattamattad@sharpsec.com> + * + * Modified for pxa27x from ohci-lh7a404.c + * by Nick Bane <nick@cecomputing.co.uk> 26-8-2004 + * + * Modified for mcs814x from ohci-mcs814x.c + * by Lennert Buytenhek <buytenh@wantstofly.org> 28-2-2006 + * Based on an earlier driver by Ray Lehtiniemi + * + * This file is licenced under the GPL. + */ + +#include <linux/device.h> +#include <linux/signal.h> +#include <linux/platform_device.h> +#include <linux/of.h> + +static int usb_hcd_mcs814x_probe(const struct hc_driver *driver, + struct platform_device *pdev) +{ + int retval; + struct usb_hcd *hcd; + + if (pdev->resource[1].flags != IORESOURCE_IRQ) { + pr_debug("resource[1] is not IORESOURCE_IRQ"); + return -ENOMEM; + } + + pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask; + + hcd = usb_create_hcd(driver, &pdev->dev, "mcs814x"); + if (hcd == NULL) + return -ENOMEM; + + hcd->rsrc_start = pdev->resource[0].start; + hcd->rsrc_len = pdev->resource[0].end - pdev->resource[0].start + 1; + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) { + usb_put_hcd(hcd); + retval = -EBUSY; + goto err1; + } + + hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); + if (hcd->regs == NULL) { + pr_debug("ioremap failed"); + retval = -ENOMEM; + goto err2; + } + + ohci_hcd_init(hcd_to_ohci(hcd)); + + retval = usb_add_hcd(hcd, pdev->resource[1].start, IRQF_DISABLED); + if (retval == 0) + return retval; + + iounmap(hcd->regs); +err2: + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); +err1: + usb_put_hcd(hcd); + + return retval; +} + +static void usb_hcd_mcs814x_remove(struct usb_hcd *hcd, + struct platform_device *pdev) +{ + usb_remove_hcd(hcd); + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); +} + +static int ohci_mcs814x_start(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + int ret; + + ret = ohci_init(ohci); + if (ret < 0) + return ret; + + ret = ohci_run(ohci); + if (ret < 0) { + ohci_err(ohci, "can't start %s", hcd->self.bus_name); + ohci_stop(hcd); + return ret; + } + + return 0; +} + +static struct hc_driver ohci_mcs814x_hc_driver = { + .description = hcd_name, + .product_desc = "MCS814X OHCI", + .hcd_priv_size = sizeof(struct ohci_hcd), + .irq = ohci_irq, + .flags = HCD_USB11 | HCD_MEMORY, + .start = ohci_mcs814x_start, + .stop = ohci_stop, + .shutdown = ohci_shutdown, + .urb_enqueue = ohci_urb_enqueue, + .urb_dequeue = ohci_urb_dequeue, + .endpoint_disable = ohci_endpoint_disable, + .get_frame_number = ohci_get_frame, + .hub_status_data = ohci_hub_status_data, + .hub_control = ohci_hub_control, +#ifdef CONFIG_PM + .bus_suspend = ohci_bus_suspend, + .bus_resume = ohci_bus_resume, +#endif + .start_port_reset = ohci_start_port_reset, +}; + +extern int usb_disabled(void); + +static int ohci_hcd_mcs814x_drv_probe(struct platform_device *pdev) +{ + int ret; + + ret = -ENODEV; + if (!usb_disabled()) + ret = usb_hcd_mcs814x_probe(&ohci_mcs814x_hc_driver, pdev); + + return ret; +} + +static int ohci_hcd_mcs814x_drv_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + usb_hcd_mcs814x_remove(hcd, pdev); + + return 0; +} + +#ifdef CONFIG_PM +static int ohci_hcd_mcs814x_drv_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + + if (time_before(jiffies, ohci->next_statechange)) + msleep(5); + ohci->next_statechange = jiffies; + + hcd->state = HC_STATE_SUSPENDED; + + return 0; +} + +static int ohci_hcd_mcs814x_drv_resume(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + int status; + + if (time_before(jiffies, ohci->next_statechange)) + msleep(5); + ohci->next_statechange = jiffies; + + ohci_finish_controller_resume(hcd); + return 0; +} +#endif + +static const struct of_device_id mcs814x_ohci_id[] = { + { .compatible = "moschip,mcs814x-ohci" }, + { .compatible = "ohci-le" }, + { /* sentinel */ }, +}; + +static struct platform_driver ohci_hcd_mcs814x_driver = { + .probe = ohci_hcd_mcs814x_drv_probe, + .remove = ohci_hcd_mcs814x_drv_remove, + .shutdown = usb_hcd_platform_shutdown, +#ifdef CONFIG_PM + .suspend = ohci_hcd_mcs814x_drv_suspend, + .resume = ohci_hcd_mcs814x_drv_resume, +#endif + .driver = { + .name = "mcs814x-ohci", + .owner = THIS_MODULE, + .of_match_table = mcs814x_ohci_id, + }, +}; + +MODULE_ALIAS("platform:mcs814x-ohci"); diff --git a/target/linux/mcs814x/files-3.18/drivers/watchdog/mcs814x_wdt.c b/target/linux/mcs814x/files-3.18/drivers/watchdog/mcs814x_wdt.c new file mode 100644 index 0000000..7bcded5 --- /dev/null +++ b/target/linux/mcs814x/files-3.18/drivers/watchdog/mcs814x_wdt.c @@ -0,0 +1,207 @@ +/* + * Moschip MCS814x Watchdog driver + * + * Copyright (C) 2012, Florian Fainelli <florian@openwrt.org> + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/of.h> + +#define WDT_COUNT 0x00 +#define WDT_CTRL 0x04 +#define WDT_CTRL_EN 0x1 + +/* watchdog frequency */ +#define WDT_MAX_VALUE (0xffffffff) + +struct mcs814x_wdt { + void __iomem *regs; + spinlock_t lock; + struct watchdog_device wdt_dev; + struct clk *clk; +}; + +static int mcs814x_wdt_start(struct watchdog_device *dev) +{ + struct mcs814x_wdt *wdt = watchdog_get_drvdata(dev); + u32 reg; + + spin_lock(&wdt->lock); + reg = readl_relaxed(wdt->regs + WDT_CTRL); + reg |= WDT_CTRL_EN; + writel_relaxed(reg, wdt->regs + WDT_CTRL); + spin_unlock(&wdt->lock); + + return 0; +} + +static int mcs814x_wdt_stop(struct watchdog_device *dev) +{ + struct mcs814x_wdt *wdt = watchdog_get_drvdata(dev); + u32 reg; + + spin_lock(&wdt->lock); + reg = readl_relaxed(wdt->regs + WDT_CTRL); + reg &= ~WDT_CTRL_EN; + writel_relaxed(reg, wdt->regs + WDT_CTRL); + spin_unlock(&wdt->lock); + + return 0; +} + +static int mcs814x_wdt_set_timeout(struct watchdog_device *dev, + unsigned int new_timeout) +{ + struct mcs814x_wdt *wdt = watchdog_get_drvdata(dev); + + spin_lock(&wdt->lock); + /* watchdog counts upward and rollover (0xfffffff -> 0) + * triggers the reboot + */ + writel_relaxed(WDT_MAX_VALUE - (new_timeout * clk_get_rate(wdt->clk)), + wdt->regs + WDT_COUNT); + spin_unlock(&wdt->lock); + + return 0; +} + +static int mcs814x_wdt_ping(struct watchdog_device *dev) +{ + /* restart the watchdog */ + mcs814x_wdt_stop(dev); + mcs814x_wdt_set_timeout(dev, dev->timeout); + mcs814x_wdt_start(dev); + + return 0; +} + +static const struct watchdog_info mcs814x_wdt_ident = { + .options = WDIOF_CARDRESET | WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING, + .identity = "MCS814x Watchdog", +}; + +static struct watchdog_ops mcs814x_wdt_ops = { + .owner = THIS_MODULE, + .start = mcs814x_wdt_start, + .stop = mcs814x_wdt_stop, + .set_timeout = mcs814x_wdt_set_timeout, + .ping = mcs814x_wdt_ping, +}; + +static int mcs814x_wdt_probe(struct platform_device *pdev) +{ + struct resource *res; + struct mcs814x_wdt *wdt; + int ret; + struct clk *clk; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + clk = clk_get(NULL, "wdt"); + if (IS_ERR_OR_NULL(clk)) { + dev_err(&pdev->dev, "failed to get watchdog clock\n"); + return PTR_ERR(clk); + } + + wdt = kzalloc(sizeof(*wdt), GFP_KERNEL); + if (!wdt) { + ret = -ENOMEM; + goto out_clk; + } + + spin_lock_init(&wdt->lock); + wdt->clk = clk; + wdt->wdt_dev.info = &mcs814x_wdt_ident; + wdt->wdt_dev.ops = &mcs814x_wdt_ops; + wdt->wdt_dev.min_timeout = 1; + /* approximately 10995 secs */ + wdt->wdt_dev.max_timeout = (WDT_MAX_VALUE / clk_get_rate(clk)); + + platform_set_drvdata(pdev, wdt); + + /* only ioremap registers, because the register is shared */ + wdt->regs = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!wdt->regs) { + ret = -ENOMEM; + goto out; + } + + watchdog_set_drvdata(&wdt->wdt_dev, wdt); + + ret = watchdog_register_device(&wdt->wdt_dev); + if (ret) { + dev_err(&pdev->dev, "cannot register watchdog: %d\n", ret); + goto out; + } + + dev_info(&pdev->dev, "registered\n"); + return 0; + +out: + platform_set_drvdata(pdev, NULL); + kfree(wdt); +out_clk: + clk_put(clk); + return ret; +} + +static int mcs814x_wdt_remove(struct platform_device *pdev) +{ + struct mcs814x_wdt *wdt = platform_get_drvdata(pdev); + + clk_put(wdt->clk); + watchdog_unregister_device(&wdt->wdt_dev); + watchdog_set_drvdata(&wdt->wdt_dev, NULL); + kfree(wdt); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static const struct of_device_id mcs814x_wdt_ids[] = { + { .compatible = "moschip,mcs814x-wdt", }, + { /* sentinel */ }, +}; + +static struct platform_driver mcs814x_wdt_driver = { + .driver = { + .name = "mcs814x-wdt", + .owner = THIS_MODULE, + .of_match_table = mcs814x_wdt_ids, + }, + .probe = mcs814x_wdt_probe, + .remove = mcs814x_wdt_remove, +}; + +module_platform_driver(mcs814x_wdt_driver); + +MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>"); +MODULE_DESCRIPTION("Moschip MCS814x Watchdog driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS("platform:mcs814x-wdt"); |