diff options
author | Pedro Perez de Heredia <pedro.perez@digi.com> | 2009-12-29 22:00:04 +0100 |
---|---|---|
committer | Alejandro Gonzalez <alex.gonzalez@digi.com> | 2010-02-12 18:29:11 +0100 |
commit | 2db9e9c3d4eee57ef498916d7130e7d28dad0630 (patch) | |
tree | 8bb9cd5913513c5d1144f48c261550778fe144bd | |
parent | 4c84fa570af23da568fdc3aab90e71f587564b01 (diff) |
ccwmx51js: add first support for CCWi-MX51 platform on a JSK board
This commit provides the first support for the Embedded module
ConnectCore Wi-MX51 on a JSK board.
Supported interfaces are:
-Serial
-Internal Ethernet controller (FEC)
-External Ethernet controller.
-NAND flash
Signed-off-by: Pedro Perez de Heredia <pedro.perez@digi.com>
-rw-r--r-- | arch/arm/mach-mx51/Kconfig | 30 | ||||
-rw-r--r-- | arch/arm/mach-mx51/Makefile | 1 | ||||
-rw-r--r-- | arch/arm/mach-mx51/mx51_ccwmx51js.c | 352 | ||||
-rw-r--r-- | arch/arm/mach-mx51/mx51_ccwmx51js_gpio.c | 77 | ||||
-rw-r--r-- | arch/arm/mach-mx51/mx51_ccwmx51js_pmic_mc13892.c | 361 | ||||
-rw-r--r-- | arch/arm/tools/mach-types | 317 | ||||
-rw-r--r-- | drivers/mtd/nand/mxc_nd2.c | 4 | ||||
-rw-r--r-- | drivers/net/Kconfig | 9 | ||||
-rw-r--r-- | drivers/net/fec.c | 23 | ||||
-rw-r--r-- | drivers/net/smsc9118/smsc911x.c | 2711 |
10 files changed, 3882 insertions, 3 deletions
diff --git a/arch/arm/mach-mx51/Kconfig b/arch/arm/mach-mx51/Kconfig index f6a0598dcf39..bbb6311a1c69 100644 --- a/arch/arm/mach-mx51/Kconfig +++ b/arch/arm/mach-mx51/Kconfig @@ -25,6 +25,34 @@ config MACH_MX51_BABBAGE Include support for MX51 Babbage platform. This includes specific configurations for the board and its peripherals. +config MACH_CCWMX51JS + bool "Support for the ConnectCore Wi-MX51 module, on the JSK base board" + help + Include support for the Digi ConnectCore Wi-MX51 Embedded Module, on the + JumpStart Kit base board. This includes specific configurations for the + peripherals on that base board. + +config MACH_CCMX51JS + bool "Support for the ConnectCore MX51 module, on the JSK base board" + help + Include support for the Digi ConnectCore MX51 Embedded Module, on the + JumpStart Kit base board. This includes specific configurations for the + peripherals on that base board. + +config MACH_CCWMX51 + bool "Support for the ConnectCore Wi-MX51 module" + help + Include support for the Digi ConnectCore Wi-MX51 Embedded Module, on a + custom board. The machine file should be modified to include support for + the interfaces available in that board. + +config MACH_CCMX51 + bool "Support for the ConnectCore MX51 module" + help + Include support for the Digi ConnectCore Wi-MX51 Embedded Module, on a + custom board. The machine file should be modified to include support for + the interfaces available in that board. + config MXC_SDMA_API bool "Use SDMA API" default y @@ -45,7 +73,7 @@ config ARCH_MXC_HAS_NFC_V3_2 depends on ARCH_MXC_HAS_NFC_V3 default y help - This selects the Freescale MXC Nand Flash Controller Hardware Version 3.1 + This selects the Freescale MXC Nand Flash Controller Hardware Version 3.2 If unsure, say N. menu "SDMA options" diff --git a/arch/arm/mach-mx51/Makefile b/arch/arm/mach-mx51/Makefile index a86947a79faa..55cfb0b058a4 100644 --- a/arch/arm/mach-mx51/Makefile +++ b/arch/arm/mach-mx51/Makefile @@ -12,6 +12,7 @@ obj-y += dummy_gpio.o obj-$(CONFIG_CPU_V7) += wfi.o suspend.o obj-$(CONFIG_MACH_MX51_3DS) += mx51_3stack.o mx51_3stack_gpio.o mx51_3stack_pmic_mc13892.o obj-$(CONFIG_MACH_MX51_BABBAGE) += mx51_babbage.o mx51_babbage_gpio.o mx51_babbage_pmic_mc13892.o +obj-$(CONFIG_MACH_CCWMX51JS) += mx51_ccwmx51js.o mx51_ccwmx51js_gpio.o mx51_ccwmx51js_pmic_mc13892.o obj-$(CONFIG_USB_EHCI_ARC_H1) += usb_h1.o obj-$(CONFIG_USB_EHCI_ARC_H2) += usb_h2.o diff --git a/arch/arm/mach-mx51/mx51_ccwmx51js.c b/arch/arm/mach-mx51/mx51_ccwmx51js.c new file mode 100644 index 000000000000..d1001335de98 --- /dev/null +++ b/arch/arm/mach-mx51/mx51_ccwmx51js.c @@ -0,0 +1,352 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2009 Digi International, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/types.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/nodemask.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/fsl_devices.h> +#include <linux/spi/spi.h> +#include <linux/i2c.h> +#include <linux/ata.h> +#include <linux/regulator/consumer.h> +#include <linux/pmic_external.h> +#include <linux/pmic_status.h> +#include <linux/ipu.h> +#include <linux/mxcfb.h> +#include <mach/common.h> +#include <mach/hardware.h> +#include <mach/spba.h> +#include <asm/irq.h> +#include <asm/setup.h> +#include <asm/mach-types.h> +#include <asm/mach/arch.h> +#include <asm/mach/time.h> +#include <mach/memory.h> +#include <mach/gpio.h> +#include <mach/mmc.h> +#include "board-mx51_babbage.h" +#include "iomux.h" +#include "crm_regs.h" +#include <mach/mxc_edid.h> + +#include <linux/smc911x.h> + + +#if defined(CONFIG_MTD) || defined(CONFIG_MTD_MODULE) +#include <linux/mtd/mtd.h> +#include <linux/mtd/map.h> +#include <linux/mtd/partitions.h> +#include <asm/mach/flash.h> +#endif + +extern void __init ccwmx51_io_init(void); +extern int __init ccwmx51_init_mc13892(void); +extern struct cpu_wp *(*get_cpu_wp)(int *wp); +extern void (*set_num_cpu_wp)(int num); +static int num_cpu_wp = 3; + +/* working point(wp): 0 - 800MHz; 1 - 166.25MHz; */ +static struct cpu_wp cpu_wp_auto[] = { + { + .pll_rate = 1000000000, + .cpu_rate = 1000000000, + .pdf = 0, + .mfi = 10, + .mfd = 11, + .mfn = 5, + .cpu_podf = 0, + .cpu_voltage = 1175000,}, + { + .pll_rate = 800000000, + .cpu_rate = 800000000, + .pdf = 0, + .mfi = 8, + .mfd = 2, + .mfn = 1, + .cpu_podf = 0, + .cpu_voltage = 1100000,}, + { + .pll_rate = 800000000, + .cpu_rate = 166250000, + .pdf = 4, + .mfi = 8, + .mfd = 2, + .mfn = 1, + .cpu_podf = 4, + .cpu_voltage = 850000,}, +}; + +struct cpu_wp *mx51_get_cpu_wp(int *wp) +{ + *wp = num_cpu_wp; + return cpu_wp_auto; +} + +void mx51_set_num_cpu_wp(int num) +{ + num_cpu_wp = num; + return; +} +static void mxc_nop_release(struct device *dev) +{ + /* Nothing */ +} + +/* MTD NAND flash */ +#if defined(CONFIG_MTD_NAND_MXC) \ + || defined(CONFIG_MTD_NAND_MXC_MODULE) \ + || defined(CONFIG_MTD_NAND_MXC_V2) \ + || defined(CONFIG_MTD_NAND_MXC_V2_MODULE) \ + || defined(CONFIG_MTD_NAND_MXC_V3) \ + || defined(CONFIG_MTD_NAND_MXC_V3_MODULE) + +extern void gpio_nand_active(void); +extern void gpio_nand_inactive(void); + +static int nand_init(void) +{ + /* Configure the pins */ + gpio_nand_active(); + return 0; +} + +static void nand_exit(void) +{ + /* Free the pins */ + gpio_nand_inactive(); +} + +static struct flash_platform_data mxc_nand_data = { + .width = 1, + .init = nand_init, + .exit = nand_exit, +}; + +static struct platform_device mxc_nandv2_mtd_device = { + .name = "mxc_nandv2_flash", + .id = 0, + .dev = { + .release = mxc_nop_release, + .platform_data = &mxc_nand_data, + }, +}; + +static void ccwmx51_init_nand_mtd(void) +{ + (void)platform_device_register(&mxc_nandv2_mtd_device); +} +#else +static inline void ccwmx51_init_nand_mtd(void) { } +#endif + +#if defined(CONFIG_SMSC9118) +static struct resource smsc911x_device_resources[] = { + [0] = { + .name = "smsc911x-memory", + .start = CS5_BASE_ADDR, + .end = CS5_BASE_ADDR + SZ_4K - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = IOMUX_TO_IRQ(MX51_PIN_GPIO1_9), + .end = IOMUX_TO_IRQ(MX51_PIN_GPIO1_9), + .flags = IORESOURCE_IRQ, + }, +}; + +static struct smc911x_platdata ccwmx51_smsc9118 = { + .flags = SMC911X_USE_16BIT, + .irq_flags = IRQF_DISABLED | IRQF_TRIGGER_FALLING, + .irq_polarity = 0, +}; + +static struct platform_device smsc911x_device = { + .name = "smsc911x", + .id = -1, + .num_resources = ARRAY_SIZE(smsc911x_device_resources), + .resource = smsc911x_device_resources, + .dev = { + .platform_data = &ccwmx51_smsc9118, + } +}; + +/* WEIM registers */ +#define CSGCR1 0x00 +#define CSGCR2 0x04 +#define CSRCR1 0x08 +#define CSRCR2 0x0C +#define CSWCR1 0x10 + +static void ccwmx51_init_ext_eth_mac(void) +{ + __iomem u32 *weim_vbaddr; + + weim_vbaddr = ioremap(WEIM_BASE_ADDR, SZ_4K); + if (weim_vbaddr == 0) { + printk(KERN_ERR "Unable to ioremap 0x%08x in %s\n", WEIM_BASE_ADDR, __func__); + return; + } + + /** Configure the CS timming, bus width, etc. + * 16 bit on DATA[31..16], not multiplexed, async + * RWSC=50, RADVA=2, RADVN=6, OEA=0, OEN=0, RCSA=0, RCSN=0, APR=0 + * WAL=0, WBED=1, WWSC=50, WADVA=2, WADVN=6, WEA=0, WEN=0, WCSA=0 + */ + __raw_writel(0x00420081, weim_vbaddr + 0x78 + CSGCR1); + __raw_writel(0, weim_vbaddr + 0x78 + CSGCR2); + __raw_writel(0x32260000, weim_vbaddr + 0x78 + CSRCR1); + __raw_writel(0, weim_vbaddr + 0x78 + CSRCR2); + __raw_writel(0x72080f00, weim_vbaddr + 0x78 + CSWCR1); + + iounmap(weim_vbaddr); + + /* Configure interrupt line as GPIO input, the iomux should be already setup */ + gpio_request(IOMUX_TO_GPIO(MX51_PIN_GPIO1_9), "LAN2-irq"); + gpio_direction_input(IOMUX_TO_GPIO(MX51_PIN_GPIO1_9)); + + (void)platform_device_register(&smsc911x_device); +} +#else +static void mxc_init_ext_eth_mac(void) { } +#endif + + +#if defined(CONFIG_FEC) || defined(CONFIG_FEC_MODULE) +unsigned int expio_intr_fec; + +EXPORT_SYMBOL(expio_intr_fec); +#endif + + +/*! + * Board specific fixup function. It is called by \b setup_arch() in + * setup.c file very early on during kernel starts. It allows the user to + * statically fill in the proper values for the passed-in parameters. None of + * the parameters is used currently. + * + * @param desc pointer to \b struct \b machine_desc + * @param tags pointer to \b struct \b tag + * @param cmdline pointer to the command line + * @param mi pointer to \b struct \b meminfo + */ +static void __init fixup_mxc_board(struct machine_desc *desc, struct tag *tags, + char **cmdline, struct meminfo *mi) +{ + char *str; + int size = SZ_512M - SZ_32M; + struct tag *t; + + mxc_cpu_init(); + + get_cpu_wp = mx51_get_cpu_wp; + set_num_cpu_wp = mx51_set_num_cpu_wp; + + for_each_tag(t, tags) { + if (t->hdr.tag != ATAG_CMDLINE) + continue; + str = t->u.cmdline.cmdline; + str = strstr(str, "mem="); + if (str != NULL) { + str += 4; + size = memparse(str, &str); + if (size == 0 || size == SZ_512M) + return; + } + } + + for_each_tag(t, tags) { + if (t->hdr.tag != ATAG_MEM) + continue; + + t->u.mem.size = size; +#if defined(CONFIG_FB_MXC_SYNC_PANEL) || \ + defined(CONFIG_FB_MXC_SYNC_PANEL_MODULE) + mxcfb_resources[0].start = t->u.mem.start + size; + mxcfb_resources[0].end = t->u.mem.start + SZ_512M - 1; +#endif + } +} + +#define PWGT1SPIEN (1<<15) +#define PWGT2SPIEN (1<<16) +#define USEROFFSPI (1<<3) + +static void mxc_power_off(void) +{ + /* We can do power down one of two ways: + Set the power gating + Set USEROFFSPI */ + + /* Set the power gate bits to power down */ + pmic_write_reg(REG_POWER_MISC, (PWGT1SPIEN|PWGT2SPIEN), + (PWGT1SPIEN|PWGT2SPIEN)); +} + + + +/*! + * Board specific initialization. + */ +static void __init mxc_board_init(void) +{ + mxc_cpu_common_init(); + mxc_register_gpios(); + ccwmx51_io_init(); + early_console_setup(saved_command_line); + + mxc_init_devices(); + + ccwmx51_init_nand_mtd(); + ccwmx51_init_ext_eth_mac(); + ccwmx51_init_mc13892(); + + pm_power_off = mxc_power_off; +} + +static void __init ccwmx51_timer_init(void) +{ + /* Change the CPU voltages for TO2*/ + if (cpu_is_mx51_rev(CHIP_REV_2_0) <= 1) { + cpu_wp_auto[0].cpu_voltage = 1175000; + cpu_wp_auto[1].cpu_voltage = 1100000; + cpu_wp_auto[2].cpu_voltage = 1000000; + } + + mxc_clocks_init(32768, 24000000, 22579200, 24576000); + mxc_timer_init("gpt_clk.0"); +} + +static struct sys_timer mxc_timer = { + .init = ccwmx51_timer_init, +}; + +MACHINE_START(CCWMX51JS, "ConnectCore Wi-MX51 on a JSK board") + /* Maintainer: Digi International, Inc. */ + .phys_io = AIPS1_BASE_ADDR, + .io_pg_offst = ((AIPS1_BASE_ADDR_VIRT) >> 18) & 0xfffc, + .boot_params = PHYS_OFFSET + 0x100, + .fixup = fixup_mxc_board, + .map_io = mxc_map_io, + .init_irq = mxc_init_irq, + .init_machine = mxc_board_init, + .timer = &mxc_timer, +MACHINE_END diff --git a/arch/arm/mach-mx51/mx51_ccwmx51js_gpio.c b/arch/arm/mach-mx51/mx51_ccwmx51js_gpio.c new file mode 100644 index 000000000000..bbe8901bc662 --- /dev/null +++ b/arch/arm/mach-mx51/mx51_ccwmx51js_gpio.c @@ -0,0 +1,77 @@ +/* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2009 Digi International, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <mach/hardware.h> +#include <mach/gpio.h> + +#include "iomux.h" + +/** + * iomux settings for the external ethernet mac + */ +#if defined(CONFIG_SMSC9118) +static struct mxc_iomux_pin_cfg __initdata ccwmx51_iomux_ext_eth_pins[] = { + {MX51_PIN_EIM_CS5, IOMUX_CONFIG_ALT0, + (PAD_CTL_HYS_ENABLE | PAD_CTL_PUE_KEEPER | PAD_CTL_DRV_MEDIUM), }, + {MX51_PIN_EIM_OE, IOMUX_CONFIG_ALT0,}, + {MX51_PIN_EIM_DA0, IOMUX_CONFIG_ALT0,}, + {MX51_PIN_EIM_DA1, IOMUX_CONFIG_ALT0,}, + {MX51_PIN_EIM_DA2, IOMUX_CONFIG_ALT0,}, + {MX51_PIN_EIM_DA3, IOMUX_CONFIG_ALT0,}, + {MX51_PIN_EIM_DA4, IOMUX_CONFIG_ALT0,}, + {MX51_PIN_EIM_DA5, IOMUX_CONFIG_ALT0,}, + {MX51_PIN_EIM_DA6, IOMUX_CONFIG_ALT0,}, + {MX51_PIN_EIM_DA7, IOMUX_CONFIG_ALT0,}, + {MX51_PIN_EIM_D16, IOMUX_CONFIG_ALT0,}, + {MX51_PIN_EIM_D17, IOMUX_CONFIG_ALT0,}, + {MX51_PIN_EIM_D18, IOMUX_CONFIG_ALT0,}, + {MX51_PIN_EIM_D19, IOMUX_CONFIG_ALT0,}, + {MX51_PIN_EIM_D20, IOMUX_CONFIG_ALT0,}, + {MX51_PIN_EIM_D21, IOMUX_CONFIG_ALT0,}, + {MX51_PIN_EIM_D22, IOMUX_CONFIG_ALT0,}, + {MX51_PIN_EIM_D23, IOMUX_CONFIG_ALT0,}, + {MX51_PIN_EIM_D24, IOMUX_CONFIG_ALT0,}, + {MX51_PIN_EIM_D25, IOMUX_CONFIG_ALT0,}, + {MX51_PIN_EIM_D26, IOMUX_CONFIG_ALT0,}, + {MX51_PIN_EIM_D27, IOMUX_CONFIG_ALT0,}, + {MX51_PIN_EIM_D28, IOMUX_CONFIG_ALT0,}, + {MX51_PIN_EIM_D29, IOMUX_CONFIG_ALT0,}, + {MX51_PIN_EIM_D30, IOMUX_CONFIG_ALT0,}, + {MX51_PIN_EIM_D31, IOMUX_CONFIG_ALT0,}, + {MX51_PIN_GPIO1_9, IOMUX_CONFIG_ALT0, (PAD_CTL_HYS_ENABLE | PAD_CTL_100K_PU), }, +}; +#endif + +void __init ccwmx51_io_init(void) +{ + int i; + +#if defined(CONFIG_SMSC9118) + for (i = 0; i < ARRAY_SIZE(ccwmx51_iomux_ext_eth_pins); i++) { + mxc_request_iomux(ccwmx51_iomux_ext_eth_pins[i].pin, + ccwmx51_iomux_ext_eth_pins[i].mux_mode); + if (ccwmx51_iomux_ext_eth_pins[i].pad_cfg) + mxc_iomux_set_pad(ccwmx51_iomux_ext_eth_pins[i].pin, + ccwmx51_iomux_ext_eth_pins[i].pad_cfg); + if (ccwmx51_iomux_ext_eth_pins[i].in_select) + mxc_iomux_set_input(ccwmx51_iomux_ext_eth_pins[i].in_select, + ccwmx51_iomux_ext_eth_pins[i].in_mode); + } +#endif +} + diff --git a/arch/arm/mach-mx51/mx51_ccwmx51js_pmic_mc13892.c b/arch/arm/mach-mx51/mx51_ccwmx51js_pmic_mc13892.c new file mode 100644 index 000000000000..84eed5e88666 --- /dev/null +++ b/arch/arm/mach-mx51/mx51_ccwmx51js_pmic_mc13892.c @@ -0,0 +1,361 @@ + /* + * Copyright 2009 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2009 Digi International, Inc. All Rights Reserved. + */ + + /* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> +#include <linux/err.h> +#include <linux/pmic_external.h> +#include <linux/regulator/machine.h> +#include <linux/mfd/mc13892/core.h> +#include "iomux.h" + +/* + * Convenience conversion. + * Here atm, maybe there is somewhere better for this. + */ +#define mV_to_uV(mV) (mV * 1000) +#define uV_to_mV(uV) (uV / 1000) +#define V_to_uV(V) (mV_to_uV(V * 1000)) +#define uV_to_V(uV) (uV_to_mV(uV) / 1000) + +/* Coin cell charger enable */ +#define CIONCHEN_LSH 23 +#define CIONCHEN_WID 1 +/* Coin cell charger voltage setting */ +#define VCOIN_LSH 20 +#define VCOIN_WID 3 + +/* Coin Charger voltage */ +#define VCOIN_2_5V 0x0 +#define VCOIN_2_7V 0x1 +#define VCOIN_2_8V 0x2 +#define VCOIN_2_9V 0x3 +#define VCOIN_3_0V 0x4 +#define VCOIN_3_1V 0x5 +#define VCOIN_3_2V 0x6 +#define VCOIN_3_3V 0x7 + +/* Keeps VSRTC and CLK32KMCU on for all states */ +#define DRM_LSH 4 +#define DRM_WID 1 + +/* regulator standby mask */ +#define GEN1_STBY_MASK (1 << 1) +#define IOHI_STBY_MASK (1 << 4) +#define DIG_STBY_MASK (1 << 10) +#define GEN2_STBY_MASK (1 << 13) +#define PLL_STBY_MASK (1 << 16) +#define USB2_STBY_MASK (1 << 19) + +#define GEN3_STBY_MASK (1 << 1) +#define CAM_STBY_MASK (1 << 7) +#define VIDEO_STBY_MASK (1 << 13) +#define AUDIO_STBY_MASK (1 << 16) +#define SD_STBY_MASK (1 << 19) + +/* 0x92412 */ +#define REG_MODE_0_ALL_MASK (GEN1_STBY_MASK |\ + DIG_STBY_MASK | GEN2_STBY_MASK |\ + PLL_STBY_MASK | USB2_STBY_MASK) +/* 0x92082 */ +#define REG_MODE_1_ALL_MASK (GEN3_STBY_MASK | CAM_STBY_MASK |\ + VIDEO_STBY_MASK | AUDIO_STBY_MASK |\ + SD_STBY_MASK) + +/* CPU */ +static struct regulator_consumer_supply sw1_consumers[] = { + { + .supply = "cpu_vcc", + } +}; + +struct mc13892; + +static struct regulator_init_data sw1_init = { + .constraints = { + .name = "SW1", + .min_uV = mV_to_uV(600), + .max_uV = mV_to_uV(1375), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .valid_modes_mask = 0, + .always_on = 1, + .boot_on = 1, + .initial_state = PM_SUSPEND_MEM, + .state_mem = { + .uV = 850000, + .mode = REGULATOR_MODE_NORMAL, + .enabled = 1, + }, + }, + .num_consumer_supplies = ARRAY_SIZE(sw1_consumers), + .consumer_supplies = sw1_consumers, +}; + +static struct regulator_init_data sw2_init = { + .constraints = { + .name = "SW2", + .min_uV = mV_to_uV(900), + .max_uV = mV_to_uV(1850), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .always_on = 1, + .boot_on = 1, + .initial_state = PM_SUSPEND_MEM, + .state_mem = { + .uV = 950000, + .mode = REGULATOR_MODE_NORMAL, + .enabled = 1, + }, + } +}; + +static struct regulator_init_data sw3_init = { + .constraints = { + .name = "SW3", + .min_uV = mV_to_uV(1100), + .max_uV = mV_to_uV(1850), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .always_on = 1, + .boot_on = 1, + } +}; + +static struct regulator_init_data sw4_init = { + .constraints = { + .name = "SW4", + .min_uV = mV_to_uV(1100), + .max_uV = mV_to_uV(1850), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .always_on = 1, + .boot_on = 1, + } +}; + +static struct regulator_init_data viohi_init = { + .constraints = { + .name = "VIOHI", + .boot_on = 1, + } +}; + +static struct regulator_init_data vusb_init = { + .constraints = { + .name = "VUSB", + .boot_on = 1, + } +}; + +static struct regulator_init_data swbst_init = { + .constraints = { + .name = "SWBST", + } +}; + +static struct regulator_init_data vdig_init = { + .constraints = { + .name = "VDIG", + .min_uV = mV_to_uV(1050), + .max_uV = mV_to_uV(1800), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .boot_on = 1, + } +}; + +static struct regulator_init_data vpll_init = { + .constraints = { + .name = "VPLL", + .min_uV = mV_to_uV(1050), + .max_uV = mV_to_uV(1800), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .boot_on = 1, + } +}; + +static struct regulator_init_data vusb2_init = { + .constraints = { + .name = "VUSB2", + .min_uV = mV_to_uV(2400), + .max_uV = mV_to_uV(2775), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .boot_on = 1, + } +}; + +static struct regulator_init_data vvideo_init = { + .constraints = { + .name = "VVIDEO", + .min_uV = mV_to_uV(2775), + .max_uV = mV_to_uV(2775), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + .always_on = 1, + .apply_uV =1, + } +}; + +static struct regulator_init_data vaudio_init = { + .constraints = { + .name = "VAUDIO", + .min_uV = mV_to_uV(2300), + .max_uV = mV_to_uV(3000), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + } +}; + +static struct regulator_init_data vsd_init = { + .constraints = { + .name = "VSD", + .min_uV = mV_to_uV(1800), + .max_uV = mV_to_uV(3150), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + } +}; + +static struct regulator_init_data vcam_init = { + .constraints = { + .name = "VCAM", + .min_uV = mV_to_uV(2500), + .max_uV = mV_to_uV(3000), + .valid_ops_mask = + REGULATOR_CHANGE_VOLTAGE | REGULATOR_CHANGE_MODE, + .valid_modes_mask = REGULATOR_MODE_FAST | REGULATOR_MODE_NORMAL, + } +}; + +static struct regulator_init_data vgen1_init = { + .constraints = { + .name = "VGEN1", + .min_uV = mV_to_uV(1200), + .max_uV = mV_to_uV(3150), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + } +}; + +static struct regulator_init_data vgen2_init = { + .constraints = { + .name = "VGEN2", + .min_uV = mV_to_uV(1200), + .max_uV = mV_to_uV(3150), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + } +}; + +static struct regulator_init_data vgen3_init = { + .constraints = { + .name = "VGEN3", + .min_uV = mV_to_uV(1800), + .max_uV = mV_to_uV(2900), + .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE, + } +}; + +static struct regulator_init_data gpo1_init = { + .constraints = { + .name = "GPO1", + } +}; + +static struct regulator_init_data gpo2_init = { + .constraints = { + .name = "GPO2", + } +}; + +static struct regulator_init_data gpo3_init = { + .constraints = { + .name = "GPO3", + } +}; + +static struct regulator_init_data gpo4_init = { + .constraints = { + .name = "GPO4", + } +}; + +static int mc13892_regulator_init(struct mc13892 *mc13892) +{ + unsigned int value, register_mask; + printk("Initializing regulators for Babbage.\n"); + if (mxc_cpu_is_rev(CHIP_REV_2_0) < 0) + sw2_init.constraints.state_mem.uV = 1100000; + else if (mxc_cpu_is_rev(CHIP_REV_2_0) == 1) { + sw2_init.constraints.state_mem.uV = 1250000; + sw1_init.constraints.state_mem.uV = 1000000; + } + + /* enable standby controll for all regulators */ + pmic_read_reg(REG_MODE_0, &value, 0xffffff); + value |= REG_MODE_0_ALL_MASK; + pmic_write_reg(REG_MODE_0, value, 0xffffff); + + pmic_read_reg(REG_MODE_1, &value, 0xffffff); + value |= REG_MODE_1_ALL_MASK; + pmic_write_reg(REG_MODE_1, value, 0xffffff); + + /* Enable coin cell charger */ + value = BITFVAL(CIONCHEN, 1) | BITFVAL(VCOIN, VCOIN_3_0V); + register_mask = BITFMASK(CIONCHEN) | BITFMASK(VCOIN); + pmic_write_reg(REG_POWER_CTL0, value, register_mask); + +#if defined(CONFIG_RTC_DRV_MXC_V2) || defined(CONFIG_RTC_DRV_MXC_V2_MODULE) + value = BITFVAL(DRM, 1); + register_mask = BITFMASK(DRM); + pmic_write_reg(REG_POWER_CTL0, value, register_mask); +#endif + + mc13892_register_regulator(mc13892, MC13892_SW1, &sw1_init); + mc13892_register_regulator(mc13892, MC13892_SW2, &sw2_init); + mc13892_register_regulator(mc13892, MC13892_SW3, &sw3_init); + mc13892_register_regulator(mc13892, MC13892_SW4, &sw4_init); + mc13892_register_regulator(mc13892, MC13892_SWBST, &swbst_init); + mc13892_register_regulator(mc13892, MC13892_VIOHI, &viohi_init); + mc13892_register_regulator(mc13892, MC13892_VPLL, &vpll_init); + mc13892_register_regulator(mc13892, MC13892_VDIG, &vdig_init); + mc13892_register_regulator(mc13892, MC13892_VSD, &vsd_init); + mc13892_register_regulator(mc13892, MC13892_VUSB2, &vusb2_init); + mc13892_register_regulator(mc13892, MC13892_VVIDEO, &vvideo_init); + mc13892_register_regulator(mc13892, MC13892_VAUDIO, &vaudio_init); + mc13892_register_regulator(mc13892, MC13892_VCAM, &vcam_init); + mc13892_register_regulator(mc13892, MC13892_VGEN1, &vgen1_init); + mc13892_register_regulator(mc13892, MC13892_VGEN2, &vgen2_init); + mc13892_register_regulator(mc13892, MC13892_VGEN3, &vgen3_init); + mc13892_register_regulator(mc13892, MC13892_VUSB, &vusb_init); + mc13892_register_regulator(mc13892, MC13892_GPO1, &gpo1_init); + mc13892_register_regulator(mc13892, MC13892_GPO2, &gpo2_init); + mc13892_register_regulator(mc13892, MC13892_GPO3, &gpo3_init); + mc13892_register_regulator(mc13892, MC13892_GPO4, &gpo4_init); + + return 0; +} + +static struct mc13892_platform_data mc13892_plat = { + .init = mc13892_regulator_init, +}; + +static struct spi_board_info __initdata mc13892_spi_device = { + .modalias = "pmic_spi", + .irq = IOMUX_TO_IRQ(MX51_PIN_GPIO1_8), + .max_speed_hz = 1000000, /* max spi SCK clock speed in HZ */ + .bus_num = 1, + .chip_select = 0, + .platform_data = &mc13892_plat, +}; + + +int __init ccwmx51_init_mc13892(void) +{ + return spi_register_board_info(&mc13892_spi_device, 1); +} + diff --git a/arch/arm/tools/mach-types b/arch/arm/tools/mach-types index 33026eff2aa4..febc40fd8456 100644 --- a/arch/arm/tools/mach-types +++ b/arch/arm/tools/mach-types @@ -12,7 +12,7 @@ # # http://www.arm.linux.org.uk/developer/machines/?action=new # -# Last update: Sat Jun 20 22:28:39 2009 +# Last update: Tue Dec 29 20:50:49 2009 # # machine_is_xxx CONFIG_xxxx MACH_TYPE_xxx number # @@ -928,7 +928,7 @@ palmt5 MACH_PALMT5 PALMT5 917 palmtc MACH_PALMTC PALMTC 918 omap_apollon MACH_OMAP_APOLLON OMAP_APOLLON 919 mxc30030evb MACH_MXC30030EVB MXC30030EVB 920 -rea_2d MACH_REA_2D REA_2D 921 +rea_cpu2 MACH_REA_2D REA_2D 921 eti3e524 MACH_TI3E524 TI3E524 922 ateb9200 MACH_ATEB9200 ATEB9200 923 auckland MACH_AUCKLAND AUCKLAND 924 @@ -1776,6 +1776,7 @@ cybook3 MACH_CYBOOK3 CYBOOK3 1784 wdg002 MACH_WDG002 WDG002 1785 sg560adsl MACH_SG560ADSL SG560ADSL 1786 nextio_n2800_ica MACH_NEXTIO_N2800_ICA NEXTIO_N2800_ICA 1787 +dove_db MACH_DOVE_DB DOVE_DB 1788 marvell_newdb MACH_MARVELL_NEWDB MARVELL_NEWDB 1789 vandihud MACH_VANDIHUD VANDIHUD 1790 magx_e8 MACH_MAGX_E8 MAGX_E8 1791 @@ -2280,3 +2281,315 @@ htcrhodium MACH_HTCRHODIUM HTCRHODIUM 2292 htctopaz MACH_HTCTOPAZ HTCTOPAZ 2293 matrix504 MACH_MATRIX504 MATRIX504 2294 mrfsa MACH_MRFSA MRFSA 2295 +sc_p270 MACH_SC_P270 SC_P270 2296 +atlas5_evb MACH_ATLAS5_EVB ATLAS5_EVB 2297 +pelco_lobox MACH_PELCO_LOBOX PELCO_LOBOX 2298 +dilax_pcu200 MACH_DILAX_PCU200 DILAX_PCU200 2299 +leonardo MACH_LEONARDO LEONARDO 2300 +zoran_approach7 MACH_ZORAN_APPROACH7 ZORAN_APPROACH7 2301 +dp6xx MACH_DP6XX DP6XX 2302 +bcm2153_vesper MACH_BCM2153_VESPER BCM2153_VESPER 2303 +mahimahi MACH_MAHIMAHI MAHIMAHI 2304 +clickc MACH_CLICKC CLICKC 2305 +zb_gateway MACH_ZB_GATEWAY ZB_GATEWAY 2306 +tazcard MACH_TAZCARD TAZCARD 2307 +tazdev MACH_TAZDEV TAZDEV 2308 +annax_cb_arm MACH_ANNAX_CB_ARM ANNAX_CB_ARM 2309 +annax_dm3 MACH_ANNAX_DM3 ANNAX_DM3 2310 +cerebric MACH_CEREBRIC CEREBRIC 2311 +orca MACH_ORCA ORCA 2312 +pc9260 MACH_PC9260 PC9260 2313 +ems285a MACH_EMS285A EMS285A 2314 +gec2410 MACH_GEC2410 GEC2410 2315 +gec2440 MACH_GEC2440 GEC2440 2316 +mw903 MACH_ARCH_MW903 ARCH_MW903 2317 +mw2440 MACH_MW2440 MW2440 2318 +ecac2378 MACH_ECAC2378 ECAC2378 2319 +tazkiosk MACH_TAZKIOSK TAZKIOSK 2320 +whiterabbit_mch MACH_WHITERABBIT_MCH WHITERABBIT_MCH 2321 +sbox9263 MACH_SBOX9263 SBOX9263 2322 +oreo MACH_OREO OREO 2323 +smdk6442 MACH_SMDK6442 SMDK6442 2324 +openrd_base MACH_OPENRD_BASE OPENRD_BASE 2325 +incredible MACH_INCREDIBLE INCREDIBLE 2326 +incrediblec MACH_INCREDIBLEC INCREDIBLEC 2327 +heroct MACH_HEROCT HEROCT 2328 +mmnet1000 MACH_MMNET1000 MMNET1000 2329 +devkit8000 MACH_DEVKIT8000 DEVKIT8000 2330 +devkit9000 MACH_DEVKIT9000 DEVKIT9000 2331 +mx31txtr MACH_MX31TXTR MX31TXTR 2332 +u380 MACH_U380 U380 2333 +oamp3_hualu MACH_HUALU_BOARD HUALU_BOARD 2334 +npcmx50 MACH_NPCMX50 NPCMX50 2335 +mx51_lange51 MACH_MX51_LANGE51 MX51_LANGE51 2336 +mx51_lange52 MACH_MX51_LANGE52 MX51_LANGE52 2337 +riom MACH_RIOM RIOM 2338 +comcas MACH_COMCAS COMCAS 2339 +wsi_mx27 MACH_WSI_MX27 WSI_MX27 2340 +cm_t35 MACH_CM_T35 CM_T35 2341 +net2big MACH_NET2BIG NET2BIG 2342 +motorola_a1600 MACH_MOTOROLA_A1600 MOTOROLA_A1600 2343 +igep0020 MACH_IGEP0020 IGEP0020 2344 +igep0010 MACH_IGEP0010 IGEP0010 2345 +mv6281gtwge2 MACH_MV6281GTWGE2 MV6281GTWGE2 2346 +scat100 MACH_SCAT100 SCAT100 2347 +sanmina MACH_SANMINA SANMINA 2348 +momento MACH_MOMENTO MOMENTO 2349 +nuc9xx MACH_NUC9XX NUC9XX 2350 +nuc910evb MACH_NUC910EVB NUC910EVB 2351 +nuc920evb MACH_NUC920EVB NUC920EVB 2352 +nuc950evb MACH_NUC950EVB NUC950EVB 2353 +nuc945evb MACH_NUC945EVB NUC945EVB 2354 +nuc960evb MACH_NUC960EVB NUC960EVB 2355 +nuc932evb MACH_NUC932EVB NUC932EVB 2356 +nuc900 MACH_NUC900 NUC900 2357 +sd1soc MACH_SD1SOC SD1SOC 2358 +ln2440bc MACH_LN2440BC LN2440BC 2359 +rsbc MACH_RSBC RSBC 2360 +openrd_client MACH_OPENRD_CLIENT OPENRD_CLIENT 2361 +hpipaq11x MACH_HPIPAQ11X HPIPAQ11X 2362 +wayland MACH_WAYLAND WAYLAND 2363 +acnbsx102 MACH_ACNBSX102 ACNBSX102 2364 +hwat91 MACH_HWAT91 HWAT91 2365 +at91sam9263cs MACH_AT91SAM9263CS AT91SAM9263CS 2366 +csb732 MACH_CSB732 CSB732 2367 +u8500 MACH_U8500 U8500 2368 +huqiu MACH_HUQIU HUQIU 2369 +mx51_kunlun MACH_MX51_KUNLUN MX51_KUNLUN 2370 +pmt1g MACH_PMT1G PMT1G 2371 +htcelf MACH_HTCELF HTCELF 2372 +armadillo420 MACH_ARMADILLO420 ARMADILLO420 2373 +armadillo440 MACH_ARMADILLO440 ARMADILLO440 2374 +u_chip_dual_arm MACH_U_CHIP_DUAL_ARM U_CHIP_DUAL_ARM 2375 +csr_bdb3 MACH_CSR_BDB3 CSR_BDB3 2376 +dolby_cat1018 MACH_DOLBY_CAT1018 DOLBY_CAT1018 2377 +hy9307 MACH_HY9307 HY9307 2378 +aspire_easystore MACH_A_ES A_ES 2379 +davinci_irif MACH_DAVINCI_IRIF DAVINCI_IRIF 2380 +agama9263 MACH_AGAMA9263 AGAMA9263 2381 +marvell_jasper MACH_MARVELL_JASPER MARVELL_JASPER 2382 +flint MACH_FLINT FLINT 2383 +tavorevb3 MACH_TAVOREVB3 TAVOREVB3 2384 +sch_m490 MACH_SCH_M490 SCH_M490 2386 +rbl01 MACH_RBL01 RBL01 2387 +omnifi MACH_OMNIFI OMNIFI 2388 +otavalo MACH_OTAVALO OTAVALO 2389 +siena MACH_SIENNA SIENNA 2390 +htc_excalibur_s620 MACH_HTC_EXCALIBUR_S620 HTC_EXCALIBUR_S620 2391 +htc_opal MACH_HTC_OPAL HTC_OPAL 2392 +touchbook MACH_TOUCHBOOK TOUCHBOOK 2393 +latte MACH_LATTE LATTE 2394 +xa200 MACH_XA200 XA200 2395 +nimrod MACH_NIMROD NIMROD 2396 +cc9p9215_3g MACH_CC9P9215_3G CC9P9215_3G 2397 +cc9p9215_3gjs MACH_CC9P9215_3GJS CC9P9215_3GJS 2398 +tk71 MACH_TK71 TK71 2399 +comham3525 MACH_COMHAM3525 COMHAM3525 2400 +mx31erebus MACH_MX31EREBUS MX31EREBUS 2401 +mcardmx27 MACH_MCARDMX27 MCARDMX27 2402 +paradise MACH_PARADISE PARADISE 2403 +tide MACH_TIDE TIDE 2404 +wzl2440 MACH_WZL2440 WZL2440 2405 +sdrdemo MACH_SDRDEMO SDRDEMO 2406 +ethercan2 MACH_ETHERCAN2 ETHERCAN2 2407 +ecmimg20 MACH_ECMIMG20 ECMIMG20 2408 +omap_dragon MACH_OMAP_DRAGON OMAP_DRAGON 2409 +halo MACH_HALO HALO 2410 +huangshan MACH_HUANGSHAN HUANGSHAN 2411 +vl_ma2sc MACH_VL_MA2SC VL_MA2SC 2412 +raumfeld_rc MACH_RAUMFELD_RC RAUMFELD_RC 2413 +raumfeld_connector MACH_RAUMFELD_CONNECTOR RAUMFELD_CONNECTOR 2414 +raumfeld_speaker MACH_RAUMFELD_SPEAKER RAUMFELD_SPEAKER 2415 +multibus_master MACH_MULTIBUS_MASTER MULTIBUS_MASTER 2416 +multibus_pbk MACH_MULTIBUS_PBK MULTIBUS_PBK 2417 +tnetv107x MACH_TNETV107X TNETV107X 2418 +snake MACH_SNAKE SNAKE 2419 +cwmx27 MACH_CWMX27 CWMX27 2420 +sch_m480 MACH_SCH_M480 SCH_M480 2421 +platypus MACH_PLATYPUS PLATYPUS 2422 +pss2 MACH_PSS2 PSS2 2423 +davinci_apm150 MACH_DAVINCI_APM150 DAVINCI_APM150 2424 +str9100 MACH_STR9100 STR9100 2425 +net5big MACH_NET5BIG NET5BIG 2426 +seabed9263 MACH_SEABED9263 SEABED9263 2427 +mx51_m2id MACH_MX51_M2ID MX51_M2ID 2428 +octvocplus_eb MACH_OCTVOCPLUS_EB OCTVOCPLUS_EB 2429 +klk_firefox MACH_KLK_FIREFOX KLK_FIREFOX 2430 +klk_wirma_module MACH_KLK_WIRMA_MODULE KLK_WIRMA_MODULE 2431 +klk_wirma_mmi MACH_KLK_WIRMA_MMI KLK_WIRMA_MMI 2432 +supersonic MACH_SUPERSONIC SUPERSONIC 2433 +liberty MACH_LIBERTY LIBERTY 2434 +mh355 MACH_MH355 MH355 2435 +pc7802 MACH_PC7802 PC7802 2436 +gnet_sgc MACH_GNET_SGC GNET_SGC 2437 +einstein15 MACH_EINSTEIN15 EINSTEIN15 2438 +cmpd MACH_CMPD CMPD 2439 +davinci_hase1 MACH_DAVINCI_HASE1 DAVINCI_HASE1 2440 +lgeincitephone MACH_LGEINCITEPHONE LGEINCITEPHONE 2441 +ea313x MACH_EA313X EA313X 2442 +fwbd_39064 MACH_FWBD_39064 FWBD_39064 2443 +fwbd_390128 MACH_FWBD_390128 FWBD_390128 2444 +pelco_moe MACH_PELCO_MOE PELCO_MOE 2445 +minimix27 MACH_MINIMIX27 MINIMIX27 2446 +omap3_thunder MACH_OMAP3_THUNDER OMAP3_THUNDER 2447 +passionc MACH_PASSIONC PASSIONC 2448 +mx27amata MACH_MX27AMATA MX27AMATA 2449 +bgat1 MACH_BGAT1 BGAT1 2450 +buzz MACH_BUZZ BUZZ 2451 +mb9g20 MACH_MB9G20 MB9G20 2452 +yushan MACH_YUSHAN YUSHAN 2453 +lizard MACH_LIZARD LIZARD 2454 +omap3polycom MACH_OMAP3POLYCOM OMAP3POLYCOM 2455 +smdkv210 MACH_SMDKV210 SMDKV210 2456 +bravo MACH_BRAVO BRAVO 2457 +siogentoo1 MACH_SIOGENTOO1 SIOGENTOO1 2458 +siogentoo2 MACH_SIOGENTOO2 SIOGENTOO2 2459 +sm3k MACH_SM3K SM3K 2460 +acer_tempo_f900 MACH_ACER_TEMPO_F900 ACER_TEMPO_F900 2461 +sst61vc010_dev MACH_SST61VC010_DEV SST61VC010_DEV 2462 +glittertind MACH_GLITTERTIND GLITTERTIND 2463 +omap_zoom3 MACH_OMAP_ZOOM3 OMAP_ZOOM3 2464 +omap_3630sdp MACH_OMAP_3630SDP OMAP_3630SDP 2465 +cybook2440 MACH_CYBOOK2440 CYBOOK2440 2466 +torino_s MACH_TORINO_S TORINO_S 2467 +havana MACH_HAVANA HAVANA 2468 +beaumont_11 MACH_BEAUMONT_11 BEAUMONT_11 2469 +vanguard MACH_VANGUARD VANGUARD 2470 +s5pc110_draco MACH_S5PC110_DRACO S5PC110_DRACO 2471 +cartesio_two MACH_CARTESIO_TWO CARTESIO_TWO 2472 +aster MACH_ASTER ASTER 2473 +voguesv210 MACH_VOGUESV210 VOGUESV210 2474 +acm500x MACH_ACM500X ACM500X 2475 +km9260 MACH_KM9260 KM9260 2476 +nideflexg1 MACH_NIDEFLEXG1 NIDEFLEXG1 2477 +ctera_plug_io MACH_CTERA_PLUG_IO CTERA_PLUG_IO 2478 +smartq7 MACH_SMARTQ7 SMARTQ7 2479 +at91sam9g10ek2 MACH_AT91SAM9G10EK2 AT91SAM9G10EK2 2480 +asusp527 MACH_ASUSP527 ASUSP527 2481 +at91sam9g20mpm2 MACH_AT91SAM9G20MPM2 AT91SAM9G20MPM2 2482 +topasa900 MACH_TOPASA900 TOPASA900 2483 +electrum_100 MACH_ELECTRUM_100 ELECTRUM_100 2484 +mx51grb MACH_MX51GRB MX51GRB 2485 +xea300 MACH_XEA300 XEA300 2486 +htcstartrek MACH_HTCSTARTREK HTCSTARTREK 2487 +lima MACH_LIMA LIMA 2488 +csb740 MACH_CSB740 CSB740 2489 +usb_s8815 MACH_USB_S8815 USB_S8815 2490 +watson_efm_plugin MACH_WATSON_EFM_PLUGIN WATSON_EFM_PLUGIN 2491 +milkyway MACH_MILKYWAY MILKYWAY 2492 +g4evm MACH_G4EVM G4EVM 2493 +picomod6 MACH_PICOMOD6 PICOMOD6 2494 +omapl138_hawkboard MACH_OMAPL138_HAWKBOARD OMAPL138_HAWKBOARD 2495 +ip6000 MACH_IP6000 IP6000 2496 +ip6010 MACH_IP6010 IP6010 2497 +utm400 MACH_UTM400 UTM400 2498 +omap3_zybex MACH_OMAP3_ZYBEX OMAP3_ZYBEX 2499 +wireless_space MACH_WIRELESS_SPACE WIRELESS_SPACE 2500 +sx560 MACH_SX560 SX560 2501 +ts41x MACH_TS41X TS41X 2502 +elphel10373 MACH_ELPHEL10373 ELPHEL10373 2503 +rhobot MACH_RHOBOT RHOBOT 2504 +mx51_refresh MACH_MX51_REFRESH MX51_REFRESH 2505 +ls9260 MACH_LS9260 LS9260 2506 +shank MACH_SHANK SHANK 2507 +qsd8x50_st1 MACH_QSD8X50_ST1 QSD8X50_ST1 2508 +at91sam9m10ekes MACH_AT91SAM9M10EKES AT91SAM9M10EKES 2509 +hiram MACH_HIRAM HIRAM 2510 +phy3250 MACH_PHY3250 PHY3250 2511 +ea3250 MACH_EA3250 EA3250 2512 +fdi3250 MACH_FDI3250 FDI3250 2513 +whitestone MACH_WHITESTONE WHITESTONE 2514 +at91sam9263nit MACH_AT91SAM9263NIT AT91SAM9263NIT 2515 +ccmx51 MACH_CCMX51 CCMX51 2516 +ccmx51js MACH_CCMX51JS CCMX51JS 2517 +ccwmx51 MACH_CCWMX51 CCWMX51 2518 +ccwmx51js MACH_CCWMX51JS CCWMX51JS 2519 +mini6410 MACH_MINI6410 MINI6410 2520 +tiny6410 MACH_TINY6410 TINY6410 2521 +nano6410 MACH_NANO6410 NANO6410 2522 +at572d940hfnldb MACH_AT572D940HFNLDB AT572D940HFNLDB 2523 +htcleo MACH_HTCLEO HTCLEO 2524 +avp13 MACH_AVP13 AVP13 2525 +xxsvideod MACH_XXSVIDEOD XXSVIDEOD 2526 +vpnext MACH_VPNEXT VPNEXT 2527 +swarco_itc3 MACH_SWARCO_ITC3 SWARCO_ITC3 2528 +tx51 MACH_TX51 TX51 2529 +dolby_cat1021 MACH_DOLBY_CAT1021 DOLBY_CAT1021 2530 +mx28evk MACH_MX28EVK MX28EVK 2531 +phoenix260 MACH_PHOENIX260 PHOENIX260 2532 +uvaca_stork MACH_UVACA_STORK UVACA_STORK 2533 +smartq5 MACH_SMARTQ5 SMARTQ5 2534 +all3078 MACH_ALL3078 ALL3078 2535 +ctera_2bay_ds MACH_CTERA_2BAY_DS CTERA_2BAY_DS 2536 +siogentoo3 MACH_SIOGENTOO3 SIOGENTOO3 2537 +epb5000 MACH_EPB5000 EPB5000 2538 +hy9263 MACH_HY9263 HY9263 2539 +acer_tempo_m900 MACH_ACER_TEMPO_M900 ACER_TEMPO_M900 2540 +acer_tempo_dx650 MACH_ACER_TEMPO_DX900 ACER_TEMPO_DX900 2541 +acer_tempo_x960 MACH_ACER_TEMPO_X960 ACER_TEMPO_X960 2542 +acer_eten_v900 MACH_ACER_ETEN_V900 ACER_ETEN_V900 2543 +acer_eten_x900 MACH_ACER_ETEN_X900 ACER_ETEN_X900 2544 +bonnell MACH_BONNELL BONNELL 2545 +oht_mx27 MACH_OHT_MX27 OHT_MX27 2546 +htcquartz MACH_HTCQUARTZ HTCQUARTZ 2547 +davinci_dm6467tevm MACH_DAVINCI_DM6467TEVM DAVINCI_DM6467TEVM 2548 +c3ax03 MACH_C3AX03 C3AX03 2549 +mxt_td60 MACH_MXT_TD60 MXT_TD60 2550 +esyx MACH_ESYX ESYX 2551 +dove_db2 MACH_DOVE_DB2 DOVE_DB2 2552 +bulldog MACH_BULLDOG BULLDOG 2553 +derell_me2000 MACH_DERELL_ME2000 DERELL_ME2000 2554 +bcmring_base MACH_BCMRING_BASE BCMRING_BASE 2555 +bcmring_evm MACH_BCMRING_EVM BCMRING_EVM 2556 +bcmring_evm_jazz MACH_BCMRING_EVM_JAZZ BCMRING_EVM_JAZZ 2557 +bcmring_sp MACH_BCMRING_SP BCMRING_SP 2558 +bcmring_sv MACH_BCMRING_SV BCMRING_SV 2559 +bcmring_sv_jazz MACH_BCMRING_SV_JAZZ BCMRING_SV_JAZZ 2560 +bcmring_tablet MACH_BCMRING_TABLET BCMRING_TABLET 2561 +bcmring_vp MACH_BCMRING_VP BCMRING_VP 2562 +bcmring_evm_seikor MACH_BCMRING_EVM_SEIKOR BCMRING_EVM_SEIKOR 2563 +bcmring_sp_wqvga MACH_BCMRING_SP_WQVGA BCMRING_SP_WQVGA 2564 +bcmring_custom MACH_BCMRING_CUSTOM BCMRING_CUSTOM 2565 +acer_s200 MACH_ACER_S200 ACER_S200 2566 +bt270 MACH_BT270 BT270 2567 +iseo MACH_ISEO ISEO 2568 +cezanne MACH_CEZANNE CEZANNE 2569 +lucca MACH_LUCCA LUCCA 2570 +supersmart MACH_SUPERSMART SUPERSMART 2571 +arm11_board MACH_CS_MISANO CS_MISANO 2572 +magnolia2 MACH_MAGNOLIA2 MAGNOLIA2 2573 +emxx MACH_EMXX EMXX 2574 +outlaw MACH_OUTLAW OUTLAW 2575 +riot_bei2 MACH_RIOT_BEI2 RIOT_BEI2 2576 +riot_vox MACH_RIOT_VOX RIOT_VOX 2577 +riot_x37 MACH_RIOT_X37 RIOT_X37 2578 +mega25mx MACH_MEGA25MX MEGA25MX 2579 +benzina2 MACH_BENZINA2 BENZINA2 2580 +ignite MACH_IGNITE IGNITE 2581 +foggia MACH_FOGGIA FOGGIA 2582 +arezzo MACH_AREZZO AREZZO 2583 +leica_skywalker MACH_LEICA_SKYWALKER LEICA_SKYWALKER 2584 +jacinto2_jamr MACH_JACINTO2_JAMR JACINTO2_JAMR 2585 +gts_nova MACH_GTS_NOVA GTS_NOVA 2586 +p3600 MACH_P3600 P3600 2587 +dlt2 MACH_DLT2 DLT2 2588 +df3120 MACH_DF3120 DF3120 2589 +ecucore_9g20 MACH_ECUCORE_9G20 ECUCORE_9G20 2590 +nautel_lpc3240 MACH_NAUTEL_LPC3240 NAUTEL_LPC3240 2591 +glacier MACH_GLACIER GLACIER 2592 +phrazer_bulldog MACH_PHRAZER_BULLDOG PHRAZER_BULLDOG 2593 +omap3_bulldog MACH_OMAP3_BULLDOG OMAP3_BULLDOG 2594 +pca101 MACH_PCA101 PCA101 2595 +buzzc MACH_BUZZC BUZZC 2596 +sasie2 MACH_SASIE2 SASIE2 2597 +davinci_cio MACH_DAVINCI_CIO DAVINCI_CIO 2598 +smartmeter_dl MACH_SMARTMETER_DL SMARTMETER_DL 2599 +wzl6410 MACH_WZL6410 WZL6410 2600 +wzl6410m MACH_WZL6410M WZL6410M 2601 +wzl6410f MACH_WZL6410F WZL6410F 2602 +wzl6410i MACH_WZL6410I WZL6410I 2603 +spacecom1 MACH_SPACECOM1 SPACECOM1 2604 +pingu920 MACH_PINGU920 PINGU920 2605 +bravoc MACH_BRAVOC BRAVOC 2606 +cybo2440 MACH_CYBO2440 CYBO2440 2607 + diff --git a/drivers/mtd/nand/mxc_nd2.c b/drivers/mtd/nand/mxc_nd2.c index db431a46445d..22249a04d620 100644 --- a/drivers/mtd/nand/mxc_nd2.c +++ b/drivers/mtd/nand/mxc_nd2.c @@ -1345,6 +1345,10 @@ static int __init mxcnd_probe(struct platform_device *pdev) /* Register the partitions */ #ifdef CONFIG_MTD_PARTITIONS +#if defined(CONFIG_MACH_CCWMX51JS) || defined(CONFIG_MACH_CCMX51JS) || \ + defined(CONFIG_MACH_CCWMX51) || defined(CONFIG_MACH_CCMX51) + mtd->name= "onboard_boot"; +#endif nr_parts = parse_mtd_partitions(mtd, part_probes, &mxc_nand_data->parts, 0); if (nr_parts > 0) diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index f5544c33b915..eb6f9ef89331 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -1013,6 +1013,15 @@ config SMSC911X <file:Documentation/networking/net-modules.txt>. The module will be called smsc911x. +config SMSC9118 + tristate "SMSC LAN9218 support" + depends on NET_ETHERNET && (MACH_CC9M2443JS || MACH_CCW9M2443JS || MACH_CCWMX51JS || MACH_CCMX51JS) + select CRC32 + select MII + ---help--- + Say Y here if you want support for SMSC LAN921x families + of ethernet controllers. + config NET_VENDOR_RACAL bool "Racal-Interlan (Micom) NI cards" depends on ISA diff --git a/drivers/net/fec.c b/drivers/net/fec.c index b3b2921550e2..74644f02ae2c 100644 --- a/drivers/net/fec.c +++ b/drivers/net/fec.c @@ -1161,6 +1161,28 @@ static phy_info_t phy_info_lan8700 = { { mk_mii_end, } }, }; + +static phy_info_t phy_info_lan8710 = { + 0x0007C0F, + "LAN8710", + (const phy_cmd_t []) { /* config */ + { mk_mii_read(MII_REG_CR), mii_parse_cr }, + { mk_mii_read(MII_REG_ANAR), mii_parse_anar }, + { mk_mii_end, } + }, + (const phy_cmd_t []) { /* startup */ + { mk_mii_write(MII_REG_CR, 0x1200), NULL }, /* autonegotiate */ + { mk_mii_read(MII_REG_SR), mii_parse_sr }, + { mk_mii_end, } + }, + (const phy_cmd_t []) { /* act_int */ + { mk_mii_end, } + }, + (const phy_cmd_t []) { /* shutdown */ + { mk_mii_end, } + }, +}; + /* ------------------------------------------------------------------------- */ static phy_info_t const * const phy_info[] = { @@ -1171,6 +1193,7 @@ static phy_info_t const * const phy_info[] = { &phy_info_ks8721bl, &phy_info_dp83848, &phy_info_lan8700, + &phy_info_lan8710, NULL }; diff --git a/drivers/net/smsc9118/smsc911x.c b/drivers/net/smsc9118/smsc911x.c new file mode 100644 index 000000000000..fa1163f7cc6d --- /dev/null +++ b/drivers/net/smsc9118/smsc911x.c @@ -0,0 +1,2711 @@ +/*************************************************************************** + * + * Copyright (C) 2004-2007 SMSC + * Copyright (C) 2005 ARM + * + * 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. + * + *************************************************************************** + * Rewritten, heavily based on smsc911x simple driver by SMSC. + * Partly uses io macros from smc91x.c by Nicolas Pitre + * + * Supported devices: + * LAN9115, LAN9116, LAN9117, LAN9118 + * LAN9215, LAN9216, LAN9217, LAN9218 + * + * History: + * 05/05/2005 bahadir.balban@arm.com + * - Transition to linux coding style + * - Platform driver and module interface + * + * 17/07/2006 steve.glendinning@smsc.com + * - Added support for LAN921x family + * - Added workaround for multicast filters + * + * 31/07/2006 steve.glendinning@smsc.com + * - Removed tasklet, using NAPI poll instead + * - Multiple device support + * - Large tidy-up following feedback from netdev list + * + * 03/08/2006 steve.glendinning@smsc.com + * - Added ethtool support + * - Convert to use generic MII interface + * + * 04/08/2006 bahadir.balban@arm.com + * - Added ethtool eeprom r/w support + * + * 17/06/2007 steve.glendinning@smsc.com + * - Incorporate changes from Bill Gatliff and Russell King + * + * 04/07/2007 steve.glendinning@smsc.com + * - move irq configuration to platform_device + * - fix link poller after interface is stopped and restarted + * + * 13/07/2007 bahadir.balban@arm.com + * - set irq polarity before requesting irq + * + * 26/06/2007 hennerich@blackfin.uclinux.org + * - Fixed minor style issue to pass checkpatch.pl + */ + +#include <linux/crc32.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/etherdevice.h> +#include <linux/ethtool.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/mii.h> +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/timer.h> +#include <linux/version.h> +#include <linux/bug.h> +#include <linux/bitops.h> +#include <linux/irq.h> +#include <asm/io.h> + +/* For having the same platform-data as in the Vanilla kernel */ +#include <linux/smc911x.h> + +#include "smsc911x.h" + +#define SMSC_CHIPNAME "smsc911x" +#define SMSC_DRV_VERSION "2007-07-13" + +MODULE_LICENSE("GPL"); + + +/* Base address of the connected controller: S3C2410_CS5 = 0x28000000 */ +#define printk_err(fmt, args...) printk(KERN_ERR "[ ERROR ] smsc911x: " fmt, ## args) +#define printk_info(fmt, args...) printk(KERN_INFO "smsc911x: " fmt, ## args) + +#if 0 +#define SMSC911X_DEBUG +#endif + +#ifdef SMSC911X_DEBUG +# define printk_debug(fmt, args...) printk(KERN_DEBUG "smsc911x: " fmt, ## args) +#else +# define printk_debug(fmt, args...) +#endif + +/* Enables the debug messages for the PM-operations (WOL, suspend, etc.) */ +#if 0 +#define SMSC911X_PM_DEBUG +#endif + +#ifdef SMSC911X_PM_DEBUG +# define printk_pmdbg(fmt, args...) printk(KERN_DEBUG "smsc911x: " fmt, ## args) +#else +# define printk_pmdbg(fmt, args...) +#endif + +struct smsc911x_data { + void __iomem *ioaddr; + + unsigned int idrev; + unsigned int generation; /* used to decide which workarounds apply */ + + /* device configuration */ + unsigned int irq_polarity; + unsigned int irq_type; + unsigned int irq_flags; + + /* This needs to be acquired before calling any of below: + * smsc911x_mac_read(), smsc911x_mac_write() + * smsc911x_phy_read(), smsc911x_phy_write() + */ + spinlock_t phy_lock; + + struct mii_if_info mii; + unsigned int using_extphy; + u32 msg_enable; +#ifdef USE_LED1_WORK_AROUND + unsigned int gpio_setting; + unsigned int gpio_orig_setting; +#endif + struct net_device *netdev; + struct napi_struct napi; + struct timer_list link_poll_timer; + unsigned int stop_link_poll; + + unsigned int software_irq_signal; + +#ifdef USE_PHY_WORK_AROUND +#define MIN_PACKET_SIZE (64) + char loopback_tx_pkt[MIN_PACKET_SIZE]; + char loopback_rx_pkt[MIN_PACKET_SIZE]; + unsigned int resetcount; +#endif + + /* Members for Multicast filter workaround */ + unsigned int multicast_update_pending; + unsigned int set_bits_mask; + unsigned int clear_bits_mask; + unsigned int hashhi; + unsigned int hashlo; + unsigned int last_rxstat; + + /* Registers for the internal PM */ + unsigned long mac_wucsr; + unsigned long pmt_ctrl; + unsigned long phy_intmsk; +}; + + +static int smsc911x_set_mac(struct net_device *dev, void *addr); + +#if SMSC_CAN_USE_32BIT + +static inline u32 smsc911x_reg_read(struct smsc911x_data *pdata, u32 reg) +{ + return readl(pdata->ioaddr + reg); +} + +static inline void smsc911x_reg_write(u32 val, struct smsc911x_data *pdata, + u32 reg) +{ + writel(val, pdata->ioaddr + reg); +} + +#else /* SMSC_CAN_USE_32BIT */ + +static inline u32 smsc911x_reg_read(struct smsc911x_data *pdata, u32 reg) +{ + u32 reg_val; + unsigned long flags; + + /* these two 16-bit reads must be performed consecutively, so must + * not be interrupted by our own ISR (which would start another + * read operation) */ + local_irq_save(flags); + reg_val = ((readw(pdata->ioaddr + reg) & 0xFFFF) | + ((readw(pdata->ioaddr + reg + 2) & 0xFFFF) << 16)); + local_irq_restore(flags); + + return reg_val; +} + +static inline void smsc911x_reg_write(u32 val, struct smsc911x_data *pdata, + u32 reg) +{ + unsigned long flags; + + /* these two 16-bit writes must be performed consecutively, so must + * not be interrupted by our own ISR (which would start another + * read operation) */ + local_irq_save(flags); + writew(val & 0xFFFF, pdata->ioaddr + reg); + writew((val >> 16) & 0xFFFF, pdata->ioaddr + reg + 2); + local_irq_restore(flags); +} + +#endif /* SMSC_CAN_USE_32BIT */ + +#ifndef CONFIG_BLACKFIN +/* Writes a packet to the TX_DATA_FIFO */ +static inline void +smsc911x_tx_writefifo(struct smsc911x_data *pdata, unsigned int *buf, + unsigned int wordcount) +{ + while (wordcount--) + smsc911x_reg_write(*buf++, pdata, TX_DATA_FIFO); +} + +/* Reads a packet out of the RX_DATA_FIFO */ +static inline void +smsc911x_rx_readfifo(struct smsc911x_data *pdata, unsigned int *buf, + unsigned int wordcount) +{ + while (wordcount--) + *buf++ = smsc911x_reg_read(pdata, RX_DATA_FIFO); +} +#else +/* Writes a packet to the TX_DATA_FIFO */ +static inline void +smsc911x_tx_writefifo(struct smsc911x_data *pdata, unsigned int *buf, + unsigned int wordcount) +{ + if (wordcount > 24) + dma_outsl((u_long)pdata->ioaddr + TX_DATA_FIFO, buf, wordcount); + else + outsl((u_long)pdata->ioaddr + TX_DATA_FIFO, buf, wordcount); +} + +/* Reads a packet out of the RX_DATA_FIFO */ +static inline void +smsc911x_rx_readfifo(struct smsc911x_data *pdata, unsigned int *buf, + unsigned int wordcount) +{ + if (wordcount > 24) + dma_insl((u_long)pdata->ioaddr + RX_DATA_FIFO, buf, wordcount); + else + insl((u_long)pdata->ioaddr + RX_DATA_FIFO, buf, wordcount); +} +#endif + +/* waits for MAC not busy, with timeout. Only called by smsc911x_mac_read + * and smsc911x_mac_write, so assumes phy_lock is held */ +static int smsc911x_mac_notbusy(struct smsc911x_data *pdata) +{ + int i; + u32 val; + + for (i = 0; i < 40; i++) { + val = smsc911x_reg_read(pdata, MAC_CSR_CMD); + if (!(val & MAC_CSR_CMD_CSR_BUSY_)) + return 1; + } + SMSC_WARNING("Timed out waiting for MAC not BUSY. " + "MAC_CSR_CMD: 0x%08X", val); + return 0; +} + +/* Fetches a MAC register value. Assumes phy_lock is acquired */ +static u32 smsc911x_mac_read(struct smsc911x_data *pdata, unsigned int offset) +{ + unsigned int temp; + +#ifdef CONFIG_DEBUG_SPINLOCK + if (!spin_is_locked(&pdata->phy_lock)) + SMSC_WARNING("phy_lock not held"); +#endif /* CONFIG_DEBUG_SPINLOCK */ + + temp = smsc911x_reg_read(pdata, MAC_CSR_CMD); + if (unlikely(temp & MAC_CSR_CMD_CSR_BUSY_)) { + SMSC_WARNING("smsc911x_mac_read failed, MAC busy at entry"); + return 0xFFFFFFFF; + } + + /* Send the MAC cmd */ + smsc911x_reg_write(((offset & 0xFF) | MAC_CSR_CMD_CSR_BUSY_ + | MAC_CSR_CMD_R_NOT_W_), pdata, MAC_CSR_CMD); + + /* Workaround for hardware read-after-write restriction */ + temp = smsc911x_reg_read(pdata, BYTE_TEST); + + /* Wait for the read to happen */ + if (likely(smsc911x_mac_notbusy(pdata))) + return smsc911x_reg_read(pdata, MAC_CSR_DATA); + + SMSC_WARNING("smsc911x_mac_read failed, MAC busy after read"); + return 0xFFFFFFFF; +} + +/* Set a mac register, phy_lock must be acquired before calling */ +static void smsc911x_mac_write(struct smsc911x_data *pdata, + unsigned int offset, u32 val) +{ + unsigned int temp; + +#ifdef CONFIG_DEBUG_SPINLOCK + if (!spin_is_locked(&pdata->phy_lock)) + SMSC_WARNING("phy_lock not held"); +#endif /* CONFIG_DEBUG_SPINLOCK */ + + temp = smsc911x_reg_read(pdata, MAC_CSR_CMD); + if (unlikely(temp & MAC_CSR_CMD_CSR_BUSY_)) { + SMSC_WARNING("smsc911x_mac_write failed, MAC busy at entry"); + return; + } + + /* Send data to write */ + smsc911x_reg_write(val, pdata, MAC_CSR_DATA); + + /* Write the actual data */ + smsc911x_reg_write(((offset & 0xFF) | MAC_CSR_CMD_CSR_BUSY_), pdata, + MAC_CSR_CMD); + + /* Workaround for hardware read-after-write restriction */ + temp = smsc911x_reg_read(pdata, BYTE_TEST); + + /* Wait for the write to complete */ + if (likely(smsc911x_mac_notbusy(pdata))) + return; + + SMSC_WARNING("smsc911x_mac_write failed, MAC busy after write"); +} + +/* Gets a phy register, phy_lock must be acquired before calling */ +static u16 smsc911x_phy_read(struct smsc911x_data *pdata, unsigned int index) +{ + unsigned int addr; + int i; + +#ifdef CONFIG_DEBUG_SPINLOCK + if (!spin_is_locked(&pdata->phy_lock)) + SMSC_WARNING("phy_lock not held"); +#endif /* CONFIG_DEBUG_SPINLOCK */ + + /* Confirm MII not busy */ + if (unlikely(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) { + SMSC_WARNING("MII is busy in smsc911x_phy_read???"); + return 0; + } + + /* Set the address, index & direction (read from PHY) */ + addr = (((pdata->mii.phy_id) & 0x1F) << 11) + | ((index & 0x1F) << 6); + smsc911x_mac_write(pdata, MII_ACC, addr); + + /* Wait for read to complete w/ timeout */ + for (i = 0; i < 100; i++) { + /* See if MII is finished yet */ + if (!(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) { + return smsc911x_mac_read(pdata, MII_DATA); + } + } + SMSC_WARNING("Timed out waiting for MII write to finish"); + return 0xFFFF; +} + +/* Sets a phy register, phy_lock must be acquired before calling */ +static void smsc911x_phy_write(struct smsc911x_data *pdata, + unsigned int index, u16 val) +{ + unsigned int addr; + int i; + +#ifdef CONFIG_DEBUG_SPINLOCK + if (!spin_is_locked(&pdata->phy_lock)) + SMSC_WARNING("phy_lock not held"); +#endif /* CONFIG_DEBUG_SPINLOCK */ + + /* Confirm MII not busy */ + if (unlikely(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) { + SMSC_WARNING("MII is busy in smsc911x_write_phy???"); + return; + } + + /* Put the data to write in the MAC */ + smsc911x_mac_write(pdata, MII_DATA, val); + + /* Set the address, index & direction (write to PHY) */ + addr = (((pdata->mii.phy_id) & 0x1F) << 11) | + ((index & 0x1F) << 6) | MII_ACC_MII_WRITE_; + smsc911x_mac_write(pdata, MII_ACC, addr); + + /* Wait for write to complete w/ timeout */ + for (i = 0; i < 100; i++) { + /* See if MII is finished yet */ + if (!(smsc911x_mac_read(pdata, MII_ACC) & MII_ACC_MII_BUSY_)) + return; + } + SMSC_WARNING("Timed out waiting for MII write to finish"); +} + +static int smsc911x_mdio_read(struct net_device *dev, int phy_id, int location) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned long flags; + int reg; + + spin_lock_irqsave(&pdata->phy_lock, flags); + reg = smsc911x_phy_read(pdata, location); + spin_unlock_irqrestore(&pdata->phy_lock, flags); + + return reg; +} + +static void smsc911x_mdio_write(struct net_device *dev, int phy_id, + int location, int val) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned long flags; + + spin_lock_irqsave(&pdata->phy_lock, flags); + smsc911x_phy_write(pdata, location, val); + spin_unlock_irqrestore(&pdata->phy_lock, flags); +} + +/* Autodetects and initialises external phy for SMSC9115 and SMSC9117 flavors. + * If something goes wrong, returns -ENODEV to revert back to internal phy. + * Performed at initialisation only, so interrupts are enabled */ +static int smsc911x_phy_initialise_external(struct smsc911x_data *pdata) +{ + unsigned int address; + unsigned int hwcfg; + unsigned int phyid1; + unsigned int phyid2; + + hwcfg = smsc911x_reg_read(pdata, HW_CFG); + + /* External phy is requested, supported, and detected */ + if (hwcfg & HW_CFG_EXT_PHY_DET_) { + + /* Attempt to switch to external phy for auto-detecting + * its address. Assuming tx and rx are stopped because + * smsc911x_phy_initialise is called before + * smsc911x_rx_initialise and tx_initialise. + */ + + /* Disable phy clocks to the MAC */ + hwcfg &= (~HW_CFG_PHY_CLK_SEL_); + hwcfg |= HW_CFG_PHY_CLK_SEL_CLK_DIS_; + smsc911x_reg_write(hwcfg, pdata, HW_CFG); + udelay(10); /* Enough time for clocks to stop */ + + /* Switch to external phy */ + hwcfg |= HW_CFG_EXT_PHY_EN_; + smsc911x_reg_write(hwcfg, pdata, HW_CFG); + + /* Enable phy clocks to the MAC */ + hwcfg &= (~HW_CFG_PHY_CLK_SEL_); + hwcfg |= HW_CFG_PHY_CLK_SEL_EXT_PHY_; + smsc911x_reg_write(hwcfg, pdata, HW_CFG); + udelay(10); /* Enough time for clocks to restart */ + + hwcfg |= HW_CFG_SMI_SEL_; + smsc911x_reg_write(hwcfg, pdata, HW_CFG); + + /* Auto-detect PHY */ + spin_lock_irq(&pdata->phy_lock); + for (address = 0; address <= 31; address++) { + pdata->mii.phy_id = address; + phyid1 = smsc911x_phy_read(pdata, MII_PHYSID1); + phyid2 = smsc911x_phy_read(pdata, MII_PHYSID2); + if ((phyid1 != 0xFFFFU) || (phyid2 != 0xFFFFU)) { + SMSC_TRACE("Detected PHY at address = " + "0x%02X = %d", address, address); + break; + } + } + spin_unlock_irq(&pdata->phy_lock); + + if ((phyid1 == 0xFFFFU) && (phyid2 == 0xFFFFU)) { + SMSC_WARNING("External PHY is not accessable, " + "using internal PHY instead"); + /* Revert back to internal phy settings. */ + + /* Disable phy clocks to the MAC */ + hwcfg &= (~HW_CFG_PHY_CLK_SEL_); + hwcfg |= HW_CFG_PHY_CLK_SEL_CLK_DIS_; + smsc911x_reg_write(hwcfg, pdata, HW_CFG); + udelay(10); /* Enough time for clocks to stop */ + + /* Switch to internal phy */ + hwcfg &= (~HW_CFG_EXT_PHY_EN_); + smsc911x_reg_write(hwcfg, pdata, HW_CFG); + + /* Enable phy clocks to the MAC */ + hwcfg &= (~HW_CFG_PHY_CLK_SEL_); + hwcfg |= HW_CFG_PHY_CLK_SEL_INT_PHY_; + smsc911x_reg_write(hwcfg, pdata, HW_CFG); + udelay(10); /* Enough time for clocks to restart */ + + hwcfg &= (~HW_CFG_SMI_SEL_); + smsc911x_reg_write(hwcfg, pdata, HW_CFG); + /* Use internal phy */ + return -ENODEV; + } else { + SMSC_TRACE("Successfully switched to external PHY"); + pdata->using_extphy = 1; + } + } else { + SMSC_WARNING("No external PHY detected."); + SMSC_WARNING("Using internal PHY instead."); + /* Use internal phy */ + return -ENODEV; + } + return 0; +} + +/* called by phy_initialise and loopback test */ +static int smsc911x_phy_reset(struct smsc911x_data *pdata) +{ + unsigned int temp; + unsigned int i = 100000; + unsigned long flags; + + SMSC_TRACE("Performing PHY BCR Reset"); + spin_lock_irqsave(&pdata->phy_lock, flags); + smsc911x_phy_write(pdata, MII_BMCR, BMCR_RESET); + do { + udelay(10); + temp = smsc911x_phy_read(pdata, MII_BMCR); + } while ((i--) && (temp & BMCR_RESET)); + spin_unlock_irqrestore(&pdata->phy_lock, flags); + + if (temp & BMCR_RESET) { + SMSC_WARNING("PHY reset failed to complete."); + return 0; + } + /* Extra delay required because the phy may not be completed with + * its reset when BMCR_RESET is cleared. Specs say 256 uS is + * enough delay but using 1ms here to be safe + */ + msleep(1); + + return 1; +} + +/* Fetches a tx status out of the status fifo */ +static unsigned int smsc911x_tx_get_txstatus(struct smsc911x_data *pdata) +{ + unsigned int result = + smsc911x_reg_read(pdata, TX_FIFO_INF) & TX_FIFO_INF_TSUSED_; + + if (result != 0) + result = smsc911x_reg_read(pdata, TX_STATUS_FIFO); + + return result; +} + +/* Fetches the next rx status */ +static unsigned int smsc911x_rx_get_rxstatus(struct smsc911x_data *pdata) +{ + unsigned int result = + smsc911x_reg_read(pdata, RX_FIFO_INF) & RX_FIFO_INF_RXSUSED_; + + if (result != 0) + result = smsc911x_reg_read(pdata, RX_STATUS_FIFO); + + return result; +} + +#ifdef USE_PHY_WORK_AROUND +static int smsc911x_phy_check_loopbackpkt(struct smsc911x_data *pdata) +{ + unsigned int tries; + u32 wrsz; + u32 rdsz; + u32 bufp; + + for (tries = 0; tries < 10; tries++) { + unsigned int txcmd_a; + unsigned int txcmd_b; + unsigned int status; + unsigned int pktlength; + unsigned int i; + + /* Zero-out rx packet memory */ + memset(pdata->loopback_rx_pkt, 0, MIN_PACKET_SIZE); + + /* Write tx packet to 118 */ + txcmd_a = (((unsigned int)pdata->loopback_tx_pkt) + & 0x03) << 16; + txcmd_a |= TX_CMD_A_FIRST_SEG_ | TX_CMD_A_LAST_SEG_; + txcmd_a |= MIN_PACKET_SIZE; + + txcmd_b = MIN_PACKET_SIZE << 16 | MIN_PACKET_SIZE; + + smsc911x_reg_write(txcmd_a, pdata, TX_DATA_FIFO); + smsc911x_reg_write(txcmd_b, pdata, TX_DATA_FIFO); + + bufp = ((u32) pdata->loopback_tx_pkt) & 0xFFFFFFFC; + wrsz = MIN_PACKET_SIZE + 3; + wrsz += (((u32) pdata->loopback_tx_pkt) & 0x3); + wrsz >>= 2; + + smsc911x_tx_writefifo(pdata, (unsigned int *)bufp, wrsz); + + /* Wait till transmit is done */ + i = 60; + do { + udelay(5); + status = smsc911x_tx_get_txstatus(pdata); + } while ((i--) && (!status)); + + if (!status) { + SMSC_WARNING("Failed to transmit during loopback test"); + continue; + } + if (status & TX_STS_ES_) { + SMSC_WARNING("Transmit encountered errors during " + "loopback test"); + continue; + } + + /* Wait till receive is done */ + i = 60; + do { + udelay(5); + status = smsc911x_rx_get_rxstatus(pdata); + } while ((i--) && (!status)); + + if (!status) { + SMSC_WARNING("Failed to receive during loopback test"); + continue; + } + if (status & RX_STS_ES_) { + SMSC_WARNING("Receive encountered errors during " + "loopback test"); + continue; + } + + pktlength = ((status & 0x3FFF0000UL) >> 16); + bufp = (u32)pdata->loopback_rx_pkt; + rdsz = pktlength + 3; + rdsz += ((u32)pdata->loopback_rx_pkt) & 0x3; + rdsz >>= 2; + + smsc911x_rx_readfifo(pdata, (unsigned int *)bufp, rdsz); + + if (pktlength != (MIN_PACKET_SIZE + 4)) { + SMSC_WARNING("Unexpected packet size during " + "loop back test, size=%d, " + "will retry", pktlength); + } else { + unsigned int j; + int mismatch = 0; + for (j = 0; j < MIN_PACKET_SIZE; j++) { + if (pdata->loopback_tx_pkt[j] + != pdata->loopback_rx_pkt[j]) { + mismatch = 1; + break; + } + } + if (!mismatch) { + SMSC_TRACE("Successfully verified " + "loopback packet"); + return 1; + } else { + SMSC_WARNING("Data miss match during " + "loop back test, will retry."); + } + } + } + + return 0; +} + +static int smsc911x_phy_loopbacktest(struct smsc911x_data *pdata) +{ + int result = 0; + unsigned int i; + unsigned int val; + unsigned long flags; + + /* Initialise tx packet */ + for (i = 0; i < 6; i++) { + /* Use broadcast destination address */ + pdata->loopback_tx_pkt[i] = (char)0xFF; + } + + for (i = 6; i < 12; i++) { + /* Use incrementing source address */ + pdata->loopback_tx_pkt[i] = (char)i; + } + + /* Set length type field */ + pdata->loopback_tx_pkt[12] = 0x00; + pdata->loopback_tx_pkt[13] = 0x00; + for (i = 14; i < MIN_PACKET_SIZE; i++) { + pdata->loopback_tx_pkt[i] = (char)i; + } + + val = smsc911x_reg_read(pdata, HW_CFG); + val &= HW_CFG_TX_FIF_SZ_; + val |= HW_CFG_SF_; + smsc911x_reg_write(val, pdata, HW_CFG); + + smsc911x_reg_write(TX_CFG_TX_ON_, pdata, TX_CFG); + smsc911x_reg_write((((unsigned int)pdata->loopback_rx_pkt) + & 0x03) << 8, pdata, RX_CFG); + + for (i = 0; i < 10; i++) { + /* Set PHY to 10/FD, no ANEG, and loopback mode */ + spin_lock_irqsave(&pdata->phy_lock, flags); + smsc911x_phy_write(pdata, MII_BMCR, 0x4100); + + /* Enable MAC tx/rx, FD */ + smsc911x_mac_write(pdata, MAC_CR, MAC_CR_FDPX_ + | MAC_CR_TXEN_ | MAC_CR_RXEN_); + spin_unlock_irqrestore(&pdata->phy_lock, flags); + + if (smsc911x_phy_check_loopbackpkt(pdata)) { + result = 1; + break; + } + pdata->resetcount++; + + /* Disable MAC rx */ + spin_lock_irqsave(&pdata->phy_lock, flags); + smsc911x_mac_write(pdata, MAC_CR, 0); + spin_unlock_irqrestore(&pdata->phy_lock, flags); + + smsc911x_phy_reset(pdata); + } + + /* Disable MAC */ + spin_lock_irqsave(&pdata->phy_lock, flags); + smsc911x_mac_write(pdata, MAC_CR, 0); + + /* Cancel PHY loopback mode */ + smsc911x_phy_write(pdata, MII_BMCR, 0); + spin_unlock_irqrestore(&pdata->phy_lock, flags); + + smsc911x_reg_write(0, pdata, TX_CFG); + smsc911x_reg_write(0, pdata, RX_CFG); + + return result; +} +#endif /* USE_PHY_WORK_AROUND */ + + +inline static void smsc911x_phy_dump_regs(struct smsc911x_data *pdata) +{ + printk("BCR = 0x%04x\n", smsc911x_phy_read(pdata, MII_BMCR)); + printk("BSR = 0x%04x\n", smsc911x_phy_read(pdata, MII_BMSR)); + printk("ID1 = 0x%04x\n", smsc911x_phy_read(pdata, MII_PHYSID1)); + printk("ID2 = 0x%04x\n", smsc911x_phy_read(pdata, MII_PHYSID2)); + printk("ADVER = 0x%04x\n", smsc911x_phy_read(pdata, MII_ADVERTISE)); + printk("LPA = 0x%04x\n", smsc911x_phy_read(pdata, MII_LPA)); + printk("EXP = 0x%04x\n", smsc911x_phy_read(pdata, MII_EXPANSION)); +} + +/* assumes phy_lock is held */ +static void smsc911x_phy_update_flowcontrol(struct smsc911x_data *pdata) +{ + unsigned int temp; + + if (pdata->mii.full_duplex) { + unsigned int phy_adv; + unsigned int phy_lpa; + phy_adv = smsc911x_phy_read(pdata, MII_ADVERTISE); + phy_lpa = smsc911x_phy_read(pdata, MII_LPA); + if (phy_adv & phy_lpa & LPA_PAUSE_CAP) { + /* Both ends support symmetric pause, enable + * PAUSE receive and transmit */ + smsc911x_mac_write(pdata, FLOW, 0xFFFF0002); + temp = smsc911x_reg_read(pdata, AFC_CFG); + temp |= 0xF; + smsc911x_reg_write(temp, pdata, AFC_CFG); + } else if (((phy_adv & ADVERTISE_PAUSE_ALL) == + ADVERTISE_PAUSE_ALL) && + ((phy_lpa & LPA_PAUSE_ALL) == LPA_PAUSE_ASYM)) { + /* We support symmetric and asym pause, the + * other end only supports asym, Enable PAUSE + * receive, disable PAUSE transmit */ + smsc911x_mac_write(pdata, FLOW, 0xFFFF0002); + temp = smsc911x_reg_read(pdata, AFC_CFG); + temp &= ~0xF; + smsc911x_reg_write(temp, pdata, AFC_CFG); + } else { + /* Disable PAUSE receive and transmit */ + smsc911x_mac_write(pdata, FLOW, 0); + temp = smsc911x_reg_read(pdata, AFC_CFG); + temp &= ~0xF; + smsc911x_reg_write(temp, pdata, AFC_CFG); + } + } else { + smsc911x_mac_write(pdata, FLOW, 0); + temp = smsc911x_reg_read(pdata, AFC_CFG); + temp |= 0xF; + smsc911x_reg_write(temp, pdata, AFC_CFG); + } +} + +static void smsc911x_phy_update_duplex(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned long flags; + unsigned int mac_cr; + + spin_lock_irqsave(&pdata->phy_lock, flags); + + mac_cr = smsc911x_mac_read(pdata, MAC_CR); + if (pdata->mii.full_duplex) { + SMSC_TRACE("configuring for full duplex mode"); + mac_cr |= MAC_CR_FDPX_; + } else { + SMSC_TRACE("configuring for half duplex mode"); + mac_cr &= ~MAC_CR_FDPX_; + } + smsc911x_mac_write(pdata, MAC_CR, mac_cr); + smsc911x_phy_update_flowcontrol(pdata); + + spin_unlock_irqrestore(&pdata->phy_lock, flags); +} + + +/* Update link mode if any thing has changed */ +static void smsc911x_phy_update_linkmode(struct net_device *dev, int init) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + + if (mii_check_media(&pdata->mii, netif_msg_link(pdata), init)) + smsc911x_phy_update_duplex(dev); + /* mii_check_media() exists if the media is forced... */ + if (pdata->mii.force_media) { + int cur_link = mii_link_ok(&pdata->mii); + int prev_link = netif_carrier_ok(dev); + + if (!prev_link && cur_link) { + printk(KERN_INFO "%s: link up\n", dev->name); + netif_carrier_on(dev); + } else if (prev_link && !cur_link) { + printk(KERN_INFO "%s: link down\n", dev->name); + netif_carrier_off(dev); + } + } + +#ifdef USE_LED1_WORK_AROUND + if (netif_carrier_ok(dev)) { + if ((pdata->gpio_orig_setting & GPIO_CFG_LED1_EN_) && + (!pdata->using_extphy)) { + /* Restore orginal GPIO configuration */ + pdata->gpio_setting = pdata->gpio_orig_setting; + smsc911x_reg_write(pdata->gpio_setting, pdata, + GPIO_CFG); + } + } else { + /* Check global setting that LED1 + * usage is 10/100 indicator */ + pdata->gpio_setting = smsc911x_reg_read(pdata, GPIO_CFG); + if ((pdata->gpio_setting & GPIO_CFG_LED1_EN_) + && (!pdata->using_extphy)) { + /* Force 10/100 LED off, after saving + * orginal GPIO configuration */ + pdata->gpio_orig_setting = pdata->gpio_setting; + + pdata->gpio_setting &= ~GPIO_CFG_LED1_EN_; + pdata->gpio_setting |= (GPIO_CFG_GPIOBUF0_ + | GPIO_CFG_GPIODIR0_ + | GPIO_CFG_GPIOD0_); + smsc911x_reg_write(pdata->gpio_setting, pdata, + GPIO_CFG); + } + } +#endif /* USE_LED1_WORK_AROUND */ +} + +/* Entry point for the link poller */ +static void smsc911x_phy_checklink(unsigned long ptr) +{ + struct net_device *dev = (struct net_device *)ptr; + struct smsc911x_data *pdata = netdev_priv(dev); + + smsc911x_phy_update_linkmode(dev, 0); + + if (!(pdata->stop_link_poll)) { + pdata->link_poll_timer.expires = jiffies + 2 * HZ; + add_timer(&pdata->link_poll_timer); + } else { + pdata->stop_link_poll = 0; + } +} + +static void smsc911x_phy_set_automdx(struct smsc911x_data *pdata) +{ + u16 ctrlstatus; + + ctrlstatus = smsc911x_phy_read(pdata, 27); + ctrlstatus &= 0x1fff; + ctrlstatus |= (0x6 << 13); + smsc911x_phy_write(pdata, 27, ctrlstatus); +} + +/* Initialises the PHY layer. Called at initialisation by open() so + * interrupts are enabled */ +static int smsc911x_phy_initialise(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned int phyid1 = 0; + unsigned int phyid2 = 0; + unsigned int temp; + + printk_debug("Calling phy_initialise()\n"); + + pdata->using_extphy = 0; + + switch (pdata->idrev & 0xFFFF0000) { + case 0x01170000: + case 0x01150000: + /* External PHY supported, try to autodetect */ + if (smsc911x_phy_initialise_external(pdata) < 0) { + SMSC_TRACE("External PHY is not detected, using " + "internal PHY instead"); + pdata->mii.phy_id = 1; + } + break; + default: + SMSC_TRACE("External PHY is not supported, using internal PHY " + "instead"); + pdata->mii.phy_id = 1; + break; + } + + spin_lock_irq(&pdata->phy_lock); + phyid1 = smsc911x_phy_read(pdata, MII_PHYSID1); + phyid2 = smsc911x_phy_read(pdata, MII_PHYSID2); + spin_unlock_irq(&pdata->phy_lock); + + if ((phyid1 == 0xFFFF) && (phyid2 == 0xFFFF)) { + SMSC_WARNING("Internal PHY not detected!"); + return 0; + } + + /* Reset the phy */ + if (!smsc911x_phy_reset(pdata)) { + SMSC_WARNING("PHY reset failed to complete."); + return 0; + } +#ifdef USE_PHY_WORK_AROUND + if (!smsc911x_phy_loopbacktest(pdata)) { + SMSC_WARNING("Failed Loop Back Test"); + return 0; + } else { + SMSC_TRACE("Passed Loop Back Test"); + } +#endif /* USE_PHY_WORK_AROUND */ + + + smsc911x_phy_set_automdx(pdata); + + /* Advertise all speeds and pause capabilities */ + spin_lock_irq(&pdata->phy_lock); + temp = smsc911x_phy_read(pdata, MII_ADVERTISE); + temp |= (ADVERTISE_ALL | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM); + smsc911x_phy_write(pdata, MII_ADVERTISE, temp); + pdata->mii.advertising = temp; + + if (!pdata->using_extphy) { + /* using internal phy, enable PHY interrupts */ + smsc911x_phy_read(pdata, MII_INTSTS); + smsc911x_phy_write(pdata, MII_INTMSK, PHY_INTMSK_DEFAULT_); + } + + /* begin to establish link */ + smsc911x_phy_write(pdata, MII_BMCR, BMCR_ANENABLE | BMCR_ANRESTART); + spin_unlock_irq(&pdata->phy_lock); + + smsc911x_phy_update_linkmode(dev, 1); + + pdata->stop_link_poll = 0; + setup_timer(&pdata->link_poll_timer, smsc911x_phy_checklink, + (unsigned long)dev); + pdata->link_poll_timer.expires = jiffies + 2 * HZ; + add_timer(&pdata->link_poll_timer); + + printk_debug("PHY initialised succesfully\n"); + return 1; +} + +/* Gets the number of tx statuses in the fifo */ +static unsigned int smsc911x_tx_get_txstatcount(struct smsc911x_data *pdata) +{ + unsigned int result = (smsc911x_reg_read(pdata, TX_FIFO_INF) + & TX_FIFO_INF_TSUSED_) >> 16; + return result; +} + +/* Reads tx statuses and increments counters where necessary */ +static void smsc911x_tx_update_txcounters(struct smsc911x_data *pdata) +{ + struct net_device *netdev = pdata->netdev; + unsigned int tx_stat; + + while ((tx_stat = smsc911x_tx_get_txstatus(pdata)) != 0) { + if (unlikely(tx_stat & 0x80000000)) { + /* In this driver the packet tag is used as the packet + * length. Since a packet length can never reach the + * size of 0x8000, this bit is reserved. It is worth + * noting that the "reserved bit" in the warning above + * does not reference a hardware defined reserved bit + * but rather a driver defined one. + */ + SMSC_WARNING("Packet tag reserved bit is high"); + } else { + if (unlikely(tx_stat & 0x00008000)) { + printk_debug("TX status error: 0x%08x (MAC 0x%08x)\n", + tx_stat, smsc911x_mac_read(pdata, MAC_CR)); + netdev->stats.tx_errors++; + } else { + netdev->stats.tx_packets++; + netdev->stats.tx_bytes += (tx_stat >> 16); + } + if (unlikely(tx_stat & 0x00000100)) { + netdev->stats.collisions += 16; + netdev->stats.tx_aborted_errors += 1; + } else { + netdev->stats.collisions += + ((tx_stat >> 3) & 0xF); + } + if (unlikely(tx_stat & 0x00000800)) { + netdev->stats.tx_carrier_errors += 1; + } + if (unlikely(tx_stat & 0x00000200)) { + netdev->stats.collisions++; + netdev->stats.tx_aborted_errors++; + } + } + } +} + +/* Increments the Rx error counters */ +static void +smsc911x_rx_counterrors(struct smsc911x_data *pdata, unsigned int rxstat) +{ + struct net_device *netdev = pdata->netdev; + int crc_err = 0; + + if (unlikely(rxstat & 0x00008000)) { + netdev->stats.rx_errors++; + if (unlikely(rxstat & 0x00000002)) { + netdev->stats.rx_crc_errors++; + crc_err = 1; + } + } + if (likely(!crc_err)) { + if (unlikely((rxstat & 0x00001020) == 0x00001020)) { + /* Frame type indicates length, + * and length error is set */ + netdev->stats.rx_length_errors++; + } + if (rxstat & RX_STS_MCAST_) + netdev->stats.multicast++; + } +} + +/* Quickly dumps bad packets */ +static void +smsc911x_rx_fastforward(struct smsc911x_data *pdata, unsigned int pktbytes) +{ + unsigned int pktwords = (pktbytes + NET_IP_ALIGN + 3) >> 2; + + if (likely(pktwords >= 4)) { + unsigned int timeout = 500; + unsigned int val; + smsc911x_reg_write(RX_DP_CTRL_RX_FFWD_, pdata, RX_DP_CTRL); + do { + udelay(1); + val = smsc911x_reg_read(pdata, RX_DP_CTRL); + } while (timeout-- && (val & RX_DP_CTRL_RX_FFWD_)); + + if (unlikely(timeout == 0)) + SMSC_WARNING("Timed out waiting for RX FFWD " + "to finish, RX_DP_CTRL: 0x%08X", val); + } else { + unsigned int temp; + while (pktwords--) + temp = smsc911x_reg_read(pdata, RX_DATA_FIFO); + } +} + +/* NAPI poll function */ +static int smsc911x_poll(struct napi_struct *napi, int budget) +{ + struct smsc911x_data *pdata = container_of(napi, struct smsc911x_data, napi); + struct net_device *dev = pdata->netdev; + int npackets = 0; + + while (npackets < budget) { + unsigned int pktlength; + unsigned int pktwords; + unsigned int rxstat = smsc911x_rx_get_rxstatus(pdata); + + /* break out of while loop if there are no more packets waiting */ + if (!rxstat) { + printk_debug("Stopping the RX poll\n"); + break; + } + + pktlength = ((rxstat & 0x3FFF0000) >> 16); + pktwords = (pktlength + NET_IP_ALIGN + 3) >> 2; + printk_debug("Going to read %i words (pktlen %i)\n", + pktwords, pktlength); + + smsc911x_rx_counterrors(pdata, rxstat); + + if (likely((rxstat & RX_STS_ES_) == 0)) { + struct sk_buff *skb; + skb = dev_alloc_skb(pktlength + NET_IP_ALIGN); + if (likely(skb)) { + skb->data = skb->head; + skb->tail = skb->head; + /* Align IP on 16B boundary */ + skb_reserve(skb, NET_IP_ALIGN); + skb_put(skb, pktlength - 4); + smsc911x_rx_readfifo(pdata, + (unsigned int *)skb->head, + pktwords); + skb->dev = dev; + skb->protocol = eth_type_trans(skb, dev); + skb->ip_summed = CHECKSUM_NONE; + netif_receive_skb(skb); + + /* Update counters */ + dev->stats.rx_packets++; + dev->stats.rx_bytes += (pktlength - 4); + dev->last_rx = jiffies; + npackets++; + continue; + } else { + SMSC_WARNING("Unable to allocate sk_buff " + "for rx packet, in PIO path"); + dev->stats.rx_dropped++; + } + } + /* At this point, the packet is to be read out + * of the fifo and discarded */ + smsc911x_rx_fastforward(pdata, pktlength); + } + + dev->stats.rx_dropped += smsc911x_reg_read(pdata, RX_DROP); + smsc911x_reg_write(INT_STS_RSFL_, pdata, INT_STS); + + if (npackets < budget) { + unsigned int temp; + /* We processed all packets available. Tell NAPI it can + * stop polling then re-enable rx interrupts */ + netif_rx_complete(dev, napi); + temp = smsc911x_reg_read(pdata, INT_EN); + temp |= INT_EN_RSFL_EN_; + smsc911x_reg_write(temp, pdata, INT_EN); + } + + /* Return total received packets */ + return npackets; +} + +/* Returns hash bit number for given MAC address + * Example: + * 01 00 5E 00 00 01 -> returns bit number 31 */ +static unsigned int smsc911x_hash(char addr[ETH_ALEN]) +{ + unsigned int crc; + unsigned int result; + + crc = ether_crc(ETH_ALEN, addr); + result = (crc >> 26) & 0x3f; + + return result; +} + +static void smsc911x_rx_multicast_update(struct smsc911x_data *pdata) +{ + /* Performs the multicast & mac_cr update. This is called when + * safe on the current hardware, and with the phy_lock held */ + unsigned int mac_cr = smsc911x_mac_read(pdata, MAC_CR); + mac_cr |= pdata->set_bits_mask; + mac_cr &= ~(pdata->clear_bits_mask); + smsc911x_mac_write(pdata, MAC_CR, mac_cr); + smsc911x_mac_write(pdata, HASHH, pdata->hashhi); + smsc911x_mac_write(pdata, HASHL, pdata->hashlo); +} + +static void smsc911x_rx_multicast_update_workaround(struct smsc911x_data *pdata) +{ + unsigned int mac_cr; + + /* This function is only called for older LAN911x devices + * (revA or revB), where MAC_CR, HASHH and HASHL should not + * be modified during Rx - newer devices immediately update the + * registers. + * + * This is called from interrupt context */ + + spin_lock(&pdata->phy_lock); + + /* Check Rx has stopped */ + if (smsc911x_mac_read(pdata, MAC_CR) & MAC_CR_RXEN_) + SMSC_WARNING("Rx not stopped\n"); + + /* Perform the update - safe to do now Rx has stopped */ + smsc911x_rx_multicast_update(pdata); + + /* Re-enable Rx */ + mac_cr = smsc911x_mac_read(pdata, MAC_CR); + mac_cr |= MAC_CR_RXEN_; + smsc911x_mac_write(pdata, MAC_CR, mac_cr); + + pdata->multicast_update_pending = 0; + + spin_unlock(&pdata->phy_lock); +} + +/* Sets the device MAC address to dev_addr, called with phy_lock held */ +static void +smsc911x_set_mac_address(struct smsc911x_data *pdata, u8 dev_addr[6]) +{ + u32 mac_high16 = (dev_addr[5] << 8) | dev_addr[4]; + u32 mac_low32 = (dev_addr[3] << 24) | (dev_addr[2] << 16) | + (dev_addr[1] << 8) | dev_addr[0]; + + smsc911x_mac_write(pdata, ADDRH, mac_high16); + smsc911x_mac_write(pdata, ADDRL, mac_low32); +} + +static int smsc911x_open(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned int timeout; + unsigned int temp; + unsigned int intcfg = 0; + struct sockaddr addr; + + /* Reset the LAN911x */ + smsc911x_reg_write(HW_CFG_SRST_, pdata, HW_CFG); + timeout = 10; + do { + udelay(10); + temp = smsc911x_reg_read(pdata, HW_CFG); + } while ((--timeout) && (temp & HW_CFG_SRST_)); + + if (unlikely(temp & HW_CFG_SRST_)) { + printk_err("Failed to complete reset"); + return -ENODEV; + } + + smsc911x_reg_write(0x00050000, pdata, HW_CFG); + smsc911x_reg_write(0x006E3740, pdata, AFC_CFG); + + /* Make sure EEPROM has finished loading before setting GPIO_CFG */ + timeout = 50; + while ((timeout--) && + (smsc911x_reg_read(pdata, E2P_CMD) & E2P_CMD_EPC_BUSY_)) { + udelay(10); + } + + if (unlikely(timeout == 0)) { + SMSC_WARNING("Timed out waiting for EEPROM " + "busy bit to clear"); + } +#if USE_DEBUG >= 1 + smsc911x_reg_write(0x00670700, pdata, GPIO_CFG); +#else + smsc911x_reg_write(0x70070000, pdata, GPIO_CFG); +#endif + + /* Initialise irqs, but leave all sources disabled */ + smsc911x_reg_write(0, pdata, INT_EN); + smsc911x_reg_write(0xFFFFFFFF, pdata, INT_STS); + + /* Set interrupt deassertion to 100uS */ + //intcfg = ((0x38 << 24) | INT_CFG_IRQ_EN_); + intcfg = ((0x00 << 24) | INT_CFG_IRQ_EN_); + // PPH modified intcfg = ((10 << 24) | INT_CFG_IRQ_EN_); + + if (pdata->irq_polarity) { + SMSC_TRACE("irq polarity: active high"); + intcfg |= INT_CFG_IRQ_POL_; + } else { + SMSC_TRACE("irq polarity: active low"); + } + + if (pdata->irq_type) { + SMSC_TRACE("irq type: push-pull"); + intcfg |= INT_CFG_IRQ_TYPE_; + } else { + SMSC_TRACE("irq type: open drain"); + } + + smsc911x_reg_write(intcfg, pdata, INT_CFG); + + printk_debug("Testing irq handler using IRQ %d\n", dev->irq); + pdata->software_irq_signal = 0; + smp_wmb(); + + temp = smsc911x_reg_read(pdata, INT_EN); + temp |= INT_EN_SW_INT_EN_; + smsc911x_reg_write(temp, pdata, INT_EN); + + timeout = 1000; + while (timeout--) { + smp_rmb(); + if (pdata->software_irq_signal) + break; + msleep(1); + } + + if (!pdata->software_irq_signal) { + printk(KERN_WARNING "%s: ISR failed signaling test (IRQ %d)\n", + dev->name, dev->irq); + return -ENODEV; + } + + printk_debug("IRQ handler passed test using IRQ %d\n", dev->irq); + netif_carrier_off(dev); + + if (!smsc911x_phy_initialise(dev)) { + printk_err("Failed to initialize the PHY"); + return -ENODEV; + } + + temp = smsc911x_reg_read(pdata, HW_CFG); + temp &= HW_CFG_TX_FIF_SZ_; + temp |= HW_CFG_SF_; + smsc911x_reg_write(temp, pdata, HW_CFG); + + temp = smsc911x_reg_read(pdata, FIFO_INT); + temp |= FIFO_INT_TX_AVAIL_LEVEL_; + temp &= ~(FIFO_INT_RX_STS_LEVEL_); + smsc911x_reg_write(temp, pdata, FIFO_INT); + + /* set RX Data offset to 2 bytes for alignment */ + smsc911x_reg_write((2 << 8), pdata, RX_CFG); + + /* enable the polling before enabling the interrupts */ + napi_enable(&pdata->napi); + + temp = smsc911x_reg_read(pdata, INT_EN); + temp |= (INT_EN_TDFA_EN_ | INT_EN_RSFL_EN_ | INT_EN_RDFL_EN_); + smsc911x_reg_write(temp, pdata, INT_EN); + + spin_lock_irq(&pdata->phy_lock); + + /* + * Reenable the full duplex mode, otherwise the TX engine will generate + * status errors (Luis Galdos) + */ + temp = smsc911x_mac_read(pdata, MAC_CR); + temp |= (MAC_CR_TXEN_ | MAC_CR_RXEN_ | MAC_CR_HBDIS_ | MAC_CR_FDPX_); + smsc911x_mac_write(pdata, MAC_CR, temp); + spin_unlock_irq(&pdata->phy_lock); + + smsc911x_reg_write(TX_CFG_TX_ON_, pdata, TX_CFG); + + /* Set the MAC once again */ + memcpy(addr.sa_data, dev->dev_addr, dev->addr_len); + if(smsc911x_set_mac(dev, &addr)) + printk_err("Couldn't set the MAC address.\n"); + + netif_start_queue(dev); + return 0; +} + +/* Entry point for stopping the interface */ +static int smsc911x_stop(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + + printk_info("Stopping the interface\n"); + + napi_disable(&pdata->napi); + + /* disable interrupts */ + smsc911x_reg_write(0, pdata, INT_EN); + + pdata->stop_link_poll = 1; + del_timer_sync(&pdata->link_poll_timer); + + netif_stop_queue(dev); + + /* At this point all Rx and Tx activity is stopped */ + dev->stats.rx_dropped += smsc911x_reg_read(pdata, RX_DROP); + smsc911x_tx_update_txcounters(pdata); + + /* Stop sending data after the last transmission */ + smsc911x_reg_write(TX_CFG_STOP_TX_, pdata, TX_CFG); + + SMSC_TRACE("Interface stopped"); + return 0; +} + +/* Entry point for transmitting a packet */ +static int smsc911x_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned int freespace; + unsigned int tx_cmd_a; + unsigned int tx_cmd_b; + unsigned int temp; + u32 wrsz; + u32 bufp; + + freespace = smsc911x_reg_read(pdata, TX_FIFO_INF) & TX_FIFO_INF_TDFREE_; + + if (unlikely(freespace < TX_FIFO_LOW_THRESHOLD)) + SMSC_WARNING("Tx data fifo low, space available: %d", + freespace); + + /* Word alignment adjustment */ + tx_cmd_a = ((((unsigned int)(skb->data)) & 0x03) << 16); + tx_cmd_a |= TX_CMD_A_FIRST_SEG_ | TX_CMD_A_LAST_SEG_; + tx_cmd_a |= (unsigned int)skb->len; + + tx_cmd_b = ((unsigned int)skb->len) << 16; + tx_cmd_b |= (unsigned int)skb->len; + + smsc911x_reg_write(tx_cmd_a, pdata, TX_DATA_FIFO); + smsc911x_reg_write(tx_cmd_b, pdata, TX_DATA_FIFO); + + bufp = ((u32)skb->data) & 0xFFFFFFFC; + wrsz = (u32)skb->len + 3; + wrsz += ((u32)skb->data) & 0x3; + wrsz >>= 2; + + smsc911x_tx_writefifo(pdata, (unsigned int *)bufp, wrsz); + freespace -= (skb->len + 32); + dev_kfree_skb(skb); + dev->trans_start = jiffies; + + if (unlikely(smsc911x_tx_get_txstatcount(pdata) >= 30)) + smsc911x_tx_update_txcounters(pdata); + + if (freespace < TX_FIFO_LOW_THRESHOLD) { + netif_stop_queue(dev); + temp = smsc911x_reg_read(pdata, FIFO_INT); + temp &= 0x00FFFFFF; + temp |= 0x32000000; + smsc911x_reg_write(temp, pdata, FIFO_INT); + } + + return NETDEV_TX_OK; +} + +/* Entry point for getting status counters */ +static struct net_device_stats *smsc911x_get_stats(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + smsc911x_tx_update_txcounters(pdata); + dev->stats.rx_dropped += smsc911x_reg_read(pdata, RX_DROP); + return &dev->stats; +} + +/* Entry point for setting addressing modes */ +static void smsc911x_set_multicast_list(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned long flags; + + if (dev->flags & IFF_PROMISC) { + /* Enabling promiscuous mode */ + pdata->set_bits_mask = MAC_CR_PRMS_; + pdata->clear_bits_mask = (MAC_CR_MCPAS_ | MAC_CR_HPFILT_); + pdata->hashhi = 0; + pdata->hashlo = 0; + } else if (dev->flags & IFF_ALLMULTI) { + /* Enabling all multicast mode */ + pdata->set_bits_mask = MAC_CR_MCPAS_; + pdata->clear_bits_mask = (MAC_CR_PRMS_ | MAC_CR_HPFILT_); + pdata->hashhi = 0; + pdata->hashlo = 0; + } else if (dev->mc_count > 0) { + /* Enabling specific multicast addresses */ + unsigned int hash_high = 0; + unsigned int hash_low = 0; + unsigned int count = 0; + struct dev_mc_list *mc_list = dev->mc_list; + + pdata->set_bits_mask = MAC_CR_HPFILT_; + pdata->clear_bits_mask = (MAC_CR_PRMS_ | MAC_CR_MCPAS_); + + while (mc_list) { + count++; + if ((mc_list->dmi_addrlen) == ETH_ALEN) { + unsigned int bitnum = + smsc911x_hash(mc_list->dmi_addr); + unsigned int mask = 0x01 << (bitnum & 0x1F); + if (bitnum & 0x20) + hash_high |= mask; + else + hash_low |= mask; + } else { + SMSC_WARNING("dmi_addrlen != 6"); + } + mc_list = mc_list->next; + } + if (count != (unsigned int)dev->mc_count) + SMSC_WARNING("mc_count != dev->mc_count"); + + pdata->hashhi = hash_high; + pdata->hashlo = hash_low; + } else { + /* Enabling local MAC address only */ + pdata->set_bits_mask = 0; + pdata->clear_bits_mask = + (MAC_CR_PRMS_ | MAC_CR_MCPAS_ | MAC_CR_HPFILT_); + pdata->hashhi = 0; + pdata->hashlo = 0; + } + + spin_lock_irqsave(&pdata->phy_lock, flags); + + if (pdata->generation <= 1) { + /* Older hardware revision - cannot change these flags while + * receiving data */ + if (!pdata->multicast_update_pending) { + unsigned int temp; + SMSC_TRACE("scheduling mcast update"); + pdata->multicast_update_pending = 1; + + /* Request the hardware to stop, then perform the + * update when we get an RX_STOP interrupt */ + smsc911x_reg_write(INT_STS_RXSTOP_INT_, pdata, INT_STS); + temp = smsc911x_reg_read(pdata, INT_EN); + temp |= INT_EN_RXSTOP_INT_EN_; + smsc911x_reg_write(temp, pdata, INT_EN); + + temp = smsc911x_mac_read(pdata, MAC_CR); + temp &= ~(MAC_CR_RXEN_); + smsc911x_mac_write(pdata, MAC_CR, temp); + } else { + /* There is another update pending, this should now + * use the newer values */ + } + } else { + /* Newer hardware revision - can write immediately */ + smsc911x_rx_multicast_update(pdata); + } + + spin_unlock_irqrestore(&pdata->phy_lock, flags); +} + +static irqreturn_t smsc911x_irqhandler(int irq, void *dev_id) +{ + struct net_device *dev = dev_id; + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned int intsts; + unsigned int inten; + unsigned int temp; + unsigned int intcfg; + int serviced = IRQ_NONE; + + intcfg = smsc911x_reg_read(pdata, INT_CFG); + intsts = smsc911x_reg_read(pdata, INT_STS); + inten = smsc911x_reg_read(pdata, INT_EN); + + printk_debug("New IRQ: intsts 0x%08x\n", intsts); + + if ((intcfg & (INT_CFG_IRQ_INT_ | INT_CFG_IRQ_EN_)) != (INT_CFG_IRQ_INT_ | + INT_CFG_IRQ_EN_)) + return serviced; + + + if (unlikely(intsts & inten & INT_STS_SW_INT_)) { + temp = smsc911x_reg_read(pdata, INT_EN); + temp &= (~INT_EN_SW_INT_EN_); + smsc911x_reg_write(temp, pdata, INT_EN); + smsc911x_reg_write(INT_STS_SW_INT_, pdata, INT_STS); + pdata->software_irq_signal = 1; + smp_wmb(); + serviced = IRQ_HANDLED; + } + + if (unlikely(intsts & inten & INT_STS_RXSTOP_INT_)) { + /* Called when there is a multicast update scheduled and + * it is now safe to complete the update */ + SMSC_TRACE("RX Stop interrupt"); + temp = smsc911x_reg_read(pdata, INT_EN); + temp &= (~INT_EN_RXSTOP_INT_EN_); + smsc911x_reg_write(temp, pdata, INT_EN); + smsc911x_reg_write(INT_STS_RXSTOP_INT_, pdata, INT_STS); + smsc911x_rx_multicast_update_workaround(pdata); + serviced = IRQ_HANDLED; + } + + if (intsts & inten & INT_STS_TDFA_) { + temp = smsc911x_reg_read(pdata, FIFO_INT); + temp |= FIFO_INT_TX_AVAIL_LEVEL_; + smsc911x_reg_write(temp, pdata, FIFO_INT); + smsc911x_reg_write(INT_STS_TDFA_, pdata, INT_STS); + netif_wake_queue(dev); + serviced = IRQ_HANDLED; + } + + if (unlikely(intsts & inten & INT_STS_RXE_)) { + smsc911x_reg_write(INT_STS_RXE_, pdata, INT_STS); + serviced = IRQ_HANDLED; + } + + if (likely(intsts & inten & INT_STS_RSFL_)) { + if (likely(netif_rx_schedule_prep(dev, &pdata->napi))) { + /* Disable Rx interrupts and schedule NAPI poll */ + temp = smsc911x_reg_read(pdata, INT_EN); + temp &= (~INT_EN_RSFL_EN_); + smsc911x_reg_write(temp, pdata, INT_EN); + __netif_rx_schedule(dev, &pdata->napi); + } + + serviced = IRQ_HANDLED; + } + + if (unlikely(intsts & inten & INT_STS_PHY_INT_)) { + smsc911x_reg_write(INT_STS_PHY_INT_, pdata, INT_STS); + spin_lock(&pdata->phy_lock); + temp = smsc911x_phy_read(pdata, MII_INTSTS); + spin_unlock(&pdata->phy_lock); + SMSC_TRACE("PHY interrupt, sts 0x%04X", (u16)temp); + smsc911x_phy_update_linkmode(dev, 0); + serviced = IRQ_HANDLED; + } + return serviced; +} + +#ifdef CONFIG_NET_POLL_CONTROLLER +void smsc911x_poll_controller(struct net_device *dev) +{ + disable_irq(dev->irq); + smsc911x_irqhandler(0, dev); + enable_irq(dev->irq); +} +#endif /* CONFIG_NET_POLL_CONTROLLER */ + +/* Standard ioctls for mii-tool */ +static int smsc911x_do_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned int chg_in_duplex; + int ret; + + if (!netif_running(dev)) + return -EINVAL; + ret = generic_mii_ioctl(&pdata->mii, if_mii(ifr), cmd, &chg_in_duplex); + if ((ret == 0) && (chg_in_duplex != 0)) + smsc911x_phy_update_duplex(dev); + + return ret; +} + +static int +smsc911x_ethtool_getsettings(struct net_device *dev, struct ethtool_cmd *cmd) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + + cmd->maxtxpkt = 1; + cmd->maxrxpkt = 1; + return mii_ethtool_gset(&pdata->mii, cmd); +} + +static int +smsc911x_ethtool_setsettings(struct net_device *dev, struct ethtool_cmd *cmd) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + + return mii_ethtool_sset(&pdata->mii, cmd); +} + +static void smsc911x_ethtool_getdrvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + strncpy(info->driver, SMSC_CHIPNAME, sizeof(info->driver)); + strncpy(info->version, SMSC_DRV_VERSION, sizeof(info->version)); + strncpy(info->bus_info, dev->dev.bus_id, sizeof(info->bus_info)); +} + +static int smsc911x_ethtool_nwayreset(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + + return mii_nway_restart(&pdata->mii); +} + +static u32 smsc911x_ethtool_getmsglevel(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + return pdata->msg_enable; +} + +static void smsc911x_ethtool_setmsglevel(struct net_device *dev, u32 level) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + pdata->msg_enable = level; +} + +static int smsc911x_ethtool_getregslen(struct net_device *dev) +{ + return (((E2P_CMD - ID_REV) / 4 + 1) + (WUCSR - MAC_CR) + 1 + 32) * + sizeof(u32); +} + +static void +smsc911x_ethtool_getregs(struct net_device *dev, struct ethtool_regs *regs, + void *buf) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + unsigned long flags; + unsigned int i; + unsigned int j = 0; + u32 *data = buf; + + regs->version = pdata->idrev; + for (i = ID_REV; i <= E2P_CMD; i += (sizeof(u32))) + data[j++] = smsc911x_reg_read(pdata, i); + + spin_lock_irqsave(&pdata->phy_lock, flags); + for (i = MAC_CR; i <= WUCSR; i++) + data[j++] = smsc911x_mac_read(pdata, i); + for (i = 0; i <= 31; i++) + data[j++] = smsc911x_phy_read(pdata, i); + spin_unlock_irqrestore(&pdata->phy_lock, flags); +} + +static void smsc911x_eeprom_enable_access(struct smsc911x_data *pdata) +{ + unsigned int temp = smsc911x_reg_read(pdata, GPIO_CFG); + temp &= ~GPIO_CFG_EEPR_EN_; + smsc911x_reg_write(temp, pdata, GPIO_CFG); + msleep(1); +} + +static int smsc911x_eeprom_send_cmd(struct smsc911x_data *pdata, u32 op) +{ + int timeout = 100; + u32 e2cmd; + + SMSC_TRACE("op 0x%08x", op); + if (smsc911x_reg_read(pdata, E2P_CMD) & E2P_CMD_EPC_BUSY_) { + SMSC_WARNING("Busy at start"); + return -EBUSY; + } + + e2cmd = op | E2P_CMD_EPC_BUSY_; + smsc911x_reg_write(e2cmd, pdata, E2P_CMD); + + do { + msleep(1); + e2cmd = smsc911x_reg_read(pdata, E2P_CMD); + } while ((e2cmd & E2P_CMD_EPC_BUSY_) && (timeout--)); + + if (!timeout) { + SMSC_TRACE("TIMED OUT"); + return -EAGAIN; + } + + if (e2cmd & E2P_CMD_EPC_TIMEOUT_) { + SMSC_TRACE("Error occured during eeprom operation"); + return -EINVAL; + } + + return 0; +} + +static int smsc911x_eeprom_read_location(struct smsc911x_data *pdata, + u8 address, u8 *data) +{ + u32 op = E2P_CMD_EPC_CMD_READ_ | address; + int ret; + + SMSC_TRACE("address 0x%x", address); + ret = smsc911x_eeprom_send_cmd(pdata, op); + + if (!ret) + data[address] = smsc911x_reg_read(pdata, E2P_DATA); + + return ret; +} + +static int smsc911x_eeprom_write_location(struct smsc911x_data *pdata, + u8 address, u8 data) +{ + u32 op = E2P_CMD_EPC_CMD_ERASE_ | address; + int ret; + + SMSC_TRACE("address 0x%x, data 0x%x", address, data); + ret = smsc911x_eeprom_send_cmd(pdata, op); + + if (!ret) { + op = E2P_CMD_EPC_CMD_WRITE_ | address; + smsc911x_reg_write((u32)data, pdata, E2P_DATA); + ret = smsc911x_eeprom_send_cmd(pdata, op); + } + + return ret; +} + +static int smsc911x_ethtool_get_eeprom_len(struct net_device *dev) +{ + return SMSC911X_EEPROM_SIZE; +} + +static int smsc911x_ethtool_get_eeprom(struct net_device *dev, + struct ethtool_eeprom *eeprom, u8 *data) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + u8 eeprom_data[SMSC911X_EEPROM_SIZE]; + int len; + int i; + + smsc911x_eeprom_enable_access(pdata); + + len = min(eeprom->len, SMSC911X_EEPROM_SIZE); + for (i = 0; i < len; i++) { + int ret = smsc911x_eeprom_read_location(pdata, i, eeprom_data); + if (ret < 0) { + eeprom->len = 0; + return ret; + } + } + + memcpy(data, &eeprom_data[eeprom->offset], len); + eeprom->len = len; + return 0; +} + +static int smsc911x_ethtool_set_eeprom(struct net_device *dev, + struct ethtool_eeprom *eeprom, u8 *data) +{ + int ret; + struct smsc911x_data *pdata = netdev_priv(dev); + + smsc911x_eeprom_enable_access(pdata); + smsc911x_eeprom_send_cmd(pdata, E2P_CMD_EPC_CMD_EWEN_); + ret = smsc911x_eeprom_write_location(pdata, eeprom->offset, *data); + smsc911x_eeprom_send_cmd(pdata, E2P_CMD_EPC_CMD_EWDS_); + + /* Single byte write, according to man page */ + eeprom->len = 1; + + return ret; +} + +static int smsc911x_ethtool_set_wol(struct net_device *dev, + struct ethtool_wolinfo *wol) +{ + struct smsc911x_data *pdata; + + /* Check for unsupported options */ + if (wol->wolopts & (WAKE_MAGICSECURE | WAKE_UCAST | WAKE_MCAST + | WAKE_BCAST | WAKE_ARP)) + return -EINVAL; + + pdata = netdev_priv(dev); + + /* When disable the WOL options need to disable the PHY-interrupts too */ + if (!wol->wolopts) { + printk_pmdbg("[ WOL ] Disabling all sources\n"); + pdata->pmt_ctrl &= ~(PMT_CTRL_WOL_EN_ | PMT_CTRL_ED_EN_); + pdata->phy_intmsk &= ~PHY_INTMSK_ENERGYON_; + pdata->mac_wucsr = 0; + goto exit_set_wol; + } + + /* + * For the magic packet we MUST configure the MAC too, but we can't do it + * at this point, cause the controller stops working. + */ + if (wol->wolopts & WAKE_MAGIC) { + printk_pmdbg("WOL: Enabling magic frame\n"); + pdata->mac_wucsr |= WUCSR_MPEN_; + pdata->pmt_ctrl |= PMT_CTRL_WOL_EN_; + } + + /* For the PHY-wakeup we must use the energy detection */ + if (wol->wolopts & WAKE_PHY) { + printk_pmdbg("[ WOL ] Enabling PHY energy\n"); + pdata->phy_intmsk |= PHY_INTMSK_ENERGYON_; + pdata->pmt_ctrl |= PMT_CTRL_ED_EN_; + } + + exit_set_wol: + return 0; +} + +/* Function for getting the infos about the WOL */ +static void smsc911x_ethtool_get_wol(struct net_device *net_dev, + struct ethtool_wolinfo *wol) +{ + /* Only for magic and PHY power detection available up now */ + wol->supported = WAKE_MAGIC | WAKE_PHY; +} + +static struct ethtool_ops smsc911x_ethtool_ops = { + .get_settings = smsc911x_ethtool_getsettings, + .set_settings = smsc911x_ethtool_setsettings, + .get_link = ethtool_op_get_link, + .get_drvinfo = smsc911x_ethtool_getdrvinfo, + .nway_reset = smsc911x_ethtool_nwayreset, + .get_msglevel = smsc911x_ethtool_getmsglevel, + .set_msglevel = smsc911x_ethtool_setmsglevel, + .get_regs_len = smsc911x_ethtool_getregslen, + .get_regs = smsc911x_ethtool_getregs, + .get_eeprom_len = smsc911x_ethtool_get_eeprom_len, + .get_eeprom = smsc911x_ethtool_get_eeprom, + .set_eeprom = smsc911x_ethtool_set_eeprom, + .get_wol = smsc911x_ethtool_get_wol, + .set_wol = smsc911x_ethtool_set_wol, +}; + + +static int smsc911x_set_mac(struct net_device *dev, void *addr) +{ + unsigned int reg; + int retval; + unsigned long flags; + struct smsc911x_data *pdata; + unsigned int low, high; + struct sockaddr *paddr = addr; + + printk_debug("Set mac called\n"); + + pdata = netdev_priv(dev); + + spin_lock_irqsave(&pdata->phy_lock, flags); + + /* First check that the MAC is not busy */ + reg = smsc911x_reg_read(pdata, MAC_CSR_CMD); + if (unlikely(reg & MAC_CSR_CMD_CSR_BUSY_)) { + printk_err("smsc911x_mac_read failed, MAC busy at entry"); + retval = -EBUSY; + goto exit_unlock; + } + + /* Get the MAC address */ + high = 0; + memcpy(&low, &(paddr->sa_data[0]), 4); + memcpy(&high, &(paddr->sa_data[4]), 2); + printk_debug("Going to set the MAC %04X%08X\n", high, low); + + /* Now set the high address */ + smsc911x_reg_write(high, pdata, MAC_CSR_DATA); + smsc911x_reg_write(ADDRH | MAC_CSR_CMD_CSR_BUSY_, pdata, MAC_CSR_CMD); + reg = smsc911x_reg_read(pdata, BYTE_TEST); + if (!smsc911x_mac_notbusy(pdata)) { + retval = -EBUSY; + goto exit_unlock; + } + + /* First set the low address */ + smsc911x_reg_write(low, pdata, MAC_CSR_DATA); + smsc911x_reg_write(ADDRL | MAC_CSR_CMD_CSR_BUSY_, pdata, MAC_CSR_CMD); + reg = smsc911x_reg_read(pdata, BYTE_TEST); + if (!smsc911x_mac_notbusy(pdata)) { + retval = -EBUSY; + goto exit_unlock; + } + + /* And save the IP inside the driver structure */ + memcpy(dev->dev_addr, paddr->sa_data, dev->addr_len); + + printk_debug("MAC successful changed to %02X%08X\n", + smsc911x_mac_read(pdata, ADDRH), + smsc911x_mac_read(pdata, ADDRL)); + + retval = 0; + + exit_unlock: + spin_unlock_irqrestore(&pdata->phy_lock, flags); + + return retval; +} + +/* Initializing private device structures */ +static int smsc911x_init(struct net_device *dev) +{ + struct smsc911x_data *pdata = netdev_priv(dev); + + SMSC_TRACE("Driver Parameters:"); + SMSC_TRACE("LAN base: 0x%08lX", (unsigned long)pdata->ioaddr); + SMSC_TRACE("IRQ: %d", dev->irq); + SMSC_TRACE("PHY will be autodetected."); + + if (pdata->ioaddr == 0) { + SMSC_WARNING("pdata->ioaddr: 0x00000000"); + return -ENODEV; + } + + /* Default generation to zero (all workarounds apply) */ + pdata->generation = 0; + + pdata->idrev = smsc911x_reg_read(pdata, ID_REV); + if (((pdata->idrev >> 16) & 0xFFFF) == (pdata->idrev & 0xFFFF)) { + SMSC_WARNING("idrev top 16 bits equal to bottom 16 bits, " + "idrev: 0x%08X", pdata->idrev); + SMSC_TRACE("This may mean the chip is set for 32 bit while " + "the bus is reading as 16 bit"); + return -ENODEV; + } + switch (pdata->idrev & 0xFFFF0000) { + case 0x01180000: + switch (pdata->idrev & 0x0000FFFFUL) { + case 0UL: + SMSC_TRACE("LAN9118 Beacon identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 0; + break; + case 1UL: + SMSC_TRACE + ("LAN9118 Concord A0 identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 1; + break; + case 2UL: + SMSC_TRACE + ("LAN9118 Concord A1 identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 2; + break; + default: + SMSC_TRACE + ("LAN9118 Concord A1 identified (NEW), idrev: 0x%08X", + pdata->idrev); + pdata->generation = 2; + break; + } + break; + + case 0x01170000: + switch (pdata->idrev & 0x0000FFFFUL) { + case 0UL: + SMSC_TRACE("LAN9117 Beacon identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 0; + break; + case 1UL: + SMSC_TRACE + ("LAN9117 Concord A0 identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 1; + break; + case 2UL: + SMSC_TRACE + ("LAN9117 Concord A1 identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 2; + break; + default: + SMSC_TRACE + ("LAN9117 Concord A1 identified (NEW), idrev: 0x%08X", + pdata->idrev); + pdata->generation = 2; + break; + } + break; + + case 0x01160000: + switch (pdata->idrev & 0x0000FFFFUL) { + case 0UL: + SMSC_WARNING("LAN911x not identified, idrev: 0x%08X", + pdata->idrev); + return -ENODEV; + case 1UL: + SMSC_TRACE + ("LAN9116 Concord A0 identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 1; + break; + case 2UL: + SMSC_TRACE + ("LAN9116 Concord A1 identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 2; + break; + default: + SMSC_TRACE + ("LAN9116 Concord A1 identified (NEW), idrev: 0x%08X", + pdata->idrev); + pdata->generation = 2; + break; + } + break; + + case 0x01150000: + switch (pdata->idrev & 0x0000FFFFUL) { + case 0UL: + SMSC_WARNING("LAN911x not identified, idrev: 0x%08X", + pdata->idrev); + return -ENODEV; + case 1UL: + SMSC_TRACE + ("LAN9115 Concord A0 identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 1; + break; + case 2UL: + SMSC_TRACE + ("LAN9115 Concord A1 identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 2; + break; + default: + SMSC_TRACE + ("LAN9115 Concord A1 identified (NEW), idrev: 0x%08X", + pdata->idrev); + pdata->generation = 2; + break; + } + break; + + case 0x118A0000UL: + switch (pdata->idrev & 0x0000FFFFUL) { + case 0UL: + SMSC_TRACE + ("LAN9218 Boylston identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 3; + break; + default: + SMSC_TRACE + ("LAN9218 Boylston identified (NEW), idrev: 0x%08X", + pdata->idrev); + pdata->generation = 3; + break; + } + break; + + case 0x117A0000UL: + switch (pdata->idrev & 0x0000FFFFUL) { + case 0UL: + SMSC_TRACE + ("LAN9217 Boylston identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 3; + break; + default: + SMSC_TRACE + ("LAN9217 Boylston identified (NEW), idrev: 0x%08X", + pdata->idrev); + pdata->generation = 3; + break; + } + break; + + case 0x116A0000UL: + switch (pdata->idrev & 0x0000FFFFUL) { + case 0UL: + SMSC_TRACE + ("LAN9216 Boylston identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 3; + break; + default: + SMSC_TRACE + ("LAN9216 Boylston identified (NEW), idrev: 0x%08X", + pdata->idrev); + pdata->generation = 3; + break; + } + break; + + case 0x115A0000UL: + switch (pdata->idrev & 0x0000FFFFUL) { + case 0UL: + SMSC_TRACE + ("LAN9215 Boylston identified, idrev: 0x%08X", + pdata->idrev); + pdata->generation = 3; + break; + default: + SMSC_TRACE + ("LAN9215 Boylston identified (NEW), idrev: 0x%08X", + pdata->idrev); + pdata->generation = 3; + break; + } + break; + + case 0x92100000UL: + case 0x92110000UL: + case 0x92200000UL: + case 0x92210000UL: + /* LAN9210/LAN9211/LAN9220/LAN9221 */ + pdata->generation = 4; + break; + + default: + SMSC_WARNING("LAN911x not identified, idrev: 0x%08X", + pdata->idrev); + return -ENODEV; + } + + if (pdata->generation == 0) + SMSC_WARNING("This driver is not intended " + "for this chip revision"); + + ether_setup(dev); + dev->open = smsc911x_open; + dev->stop = smsc911x_stop; + dev->hard_start_xmit = smsc911x_hard_start_xmit; + dev->get_stats = smsc911x_get_stats; + dev->set_multicast_list = smsc911x_set_multicast_list; + dev->flags |= IFF_MULTICAST; + dev->do_ioctl = smsc911x_do_ioctl; + dev->set_mac_address = smsc911x_set_mac; + netif_napi_add(dev, &pdata->napi, smsc911x_poll, 64); + dev->ethtool_ops = &smsc911x_ethtool_ops; + +#ifdef CONFIG_NET_POLL_CONTROLLER + dev->poll_controller = smsc911x_poll_controller; +#endif /* CONFIG_NET_POLL_CONTROLLER */ + + pdata->mii.phy_id_mask = 0x1f; + pdata->mii.reg_num_mask = 0x1f; + pdata->mii.force_media = 0; + pdata->mii.full_duplex = 0; + pdata->mii.dev = dev; + pdata->mii.mdio_read = smsc911x_mdio_read; + pdata->mii.mdio_write = smsc911x_mdio_write; + + pdata->msg_enable = NETIF_MSG_LINK; + + return 0; +} + +static int smsc911x_drv_remove(struct platform_device *pdev) +{ + struct net_device *dev; + struct smsc911x_data *pdata; + struct resource *res; + + dev = platform_get_drvdata(pdev); + BUG_ON(!dev); + pdata = netdev_priv(dev); + BUG_ON(!pdata); + BUG_ON(!pdata->ioaddr); + + SMSC_TRACE("Stopping driver."); + platform_set_drvdata(pdev, NULL); + unregister_netdev(dev); + free_irq(dev->irq, dev); + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "smsc911x-memory"); + if (!res) + platform_get_resource(pdev, IORESOURCE_MEM, 0); + + release_mem_region(res->start, res->end - res->start); + + iounmap(pdata->ioaddr); + + free_netdev(dev); + + return 0; +} + +static int smsc911x_drv_probe(struct platform_device *pdev) +{ + struct net_device *dev; + struct smsc911x_data *pdata; + struct resource *res; + unsigned int intcfg = 0; + int res_size; + int retval; + + printk(KERN_INFO "%s: Driver version %s.\n", SMSC_CHIPNAME, + SMSC_DRV_VERSION); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "smsc911x-memory"); + if (!res) + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + printk(KERN_WARNING "%s: Could not allocate resource.\n", + SMSC_CHIPNAME); + retval = -ENODEV; + goto out_0; + } + res_size = res->end - res->start; + + if (!request_mem_region(res->start, res_size, SMSC_CHIPNAME)) { + retval = -EBUSY; + goto out_0; + } + + dev = alloc_etherdev(sizeof(struct smsc911x_data)); + if (!dev) { + printk(KERN_WARNING "%s: Could not allocate device.\n", + SMSC_CHIPNAME); + retval = -ENOMEM; + goto out_release_io_1; + } + + SET_NETDEV_DEV(dev, &pdev->dev); + + pdata = netdev_priv(dev); + pdata->netdev = dev; + + dev->irq = platform_get_irq(pdev, 0); + pdata->ioaddr = ioremap_nocache(res->start, res_size); + + /* copy config parameters across if present, otherwise pdata + * defaults to zeros */ + if (pdev->dev.platform_data) { + struct smc911x_platdata *config = pdev->dev.platform_data; + pdata->irq_polarity = config->irq_polarity; + pdata->irq_flags = config->irq_flags; + } + + if (pdata->ioaddr == NULL) { + SMSC_WARNING("Error smsc911x base address invalid"); + retval = -ENOMEM; + goto out_free_netdev_2; + } + + retval = smsc911x_init(dev); + if (retval < 0) + goto out_unmap_io_3; + + /* configure irq polarity and type before connecting isr */ + if (pdata->irq_polarity) + intcfg |= INT_CFG_IRQ_POL_; + + /* + * @XXX: The "irq_type" is not used at this moment, because we are using + * the same platform-data as the driver from the Vanilla-kernel. + * (Luis Galdos) + */ + if (pdata->irq_type) + intcfg |= INT_CFG_IRQ_TYPE_; + + smsc911x_reg_write(intcfg, pdata, INT_CFG); + + retval = request_irq(dev->irq, smsc911x_irqhandler, + pdata->irq_flags, + SMSC_CHIPNAME, dev); + if (retval) { + SMSC_WARNING("Unable to claim requested irq: %d", dev->irq); + goto out_unmap_io_3; + } + + platform_set_drvdata(pdev, dev); + + retval = register_netdev(dev); + if (retval) { + SMSC_WARNING("Error %i registering device", retval); + goto out_unset_drvdata_4; + } else { + SMSC_TRACE("Network interface: \"%s\"", dev->name); + } + + spin_lock_init(&pdata->phy_lock); + + spin_lock_irq(&pdata->phy_lock); + + /* Check if mac address has been specified when bringing interface up */ + if (is_valid_ether_addr(dev->dev_addr)) { + smsc911x_set_mac(dev, dev->dev_addr); + SMSC_TRACE("MAC Address is specified by configuration"); + } else { + /* Try reading mac address from device. if EEPROM is present + * it will already have been set */ + u32 mac_high16 = smsc911x_mac_read(pdata, ADDRH); + u32 mac_low32 = smsc911x_mac_read(pdata, ADDRL); + dev->dev_addr[0] = (u8)(mac_low32); + dev->dev_addr[1] = (u8)(mac_low32 >> 8); + dev->dev_addr[2] = (u8)(mac_low32 >> 16); + dev->dev_addr[3] = (u8)(mac_low32 >> 24); + dev->dev_addr[4] = (u8)(mac_high16); + dev->dev_addr[5] = (u8)(mac_high16 >> 8); + + if (is_valid_ether_addr(dev->dev_addr)) { + /* eeprom values are valid so use them */ + SMSC_TRACE("Mac Address is read from LAN911x EEPROM"); + } else { + /* eeprom values are invalid, generate random MAC */ + random_ether_addr(dev->dev_addr); + smsc911x_set_mac_address(pdata, dev->dev_addr); + SMSC_TRACE("MAC Address is set to random_ether_addr"); + } + } + + spin_unlock_irq(&pdata->phy_lock); + + printk_info("%s: MAC Address: %02x:%02x:%02x:%02x:%02x:%02x\n", + dev->name, dev->dev_addr[0], dev->dev_addr[1], dev->dev_addr[2], + dev->dev_addr[3], dev->dev_addr[4], dev->dev_addr[5]); + + /* Enable the wakeup over this device (Luis Galdos) */ + device_init_wakeup(&pdev->dev, 1); + device_set_wakeup_enable(&pdev->dev, 0); + return 0; + +out_unset_drvdata_4: + platform_set_drvdata(pdev, NULL); + free_irq(dev->irq, dev); +out_unmap_io_3: + iounmap(pdata->ioaddr); +out_free_netdev_2: + free_netdev(dev); +out_release_io_1: + release_mem_region(res->start, res->end - res->start); +out_0: + return retval; +} + +/* Enter in the suspend mode */ +#if defined(CONFIG_PM) + +/* + * For the mode D1 we MUST left the interrupts enabled + */ +static int smsc911x_drv_state_wakeup(struct smsc911x_data *pdata, int mode) +{ + int retval; + unsigned long regval; + + retval = 0; + + if (mode != 1 && mode != 2) + return -EINVAL; + + /* Clear already received WUs */ + regval = smsc911x_mac_read(pdata, WUCSR); + regval &= ~(WUCSR_MPR_ | WUCSR_WUFR_); + regval |= pdata->mac_wucsr; /* Magic packet enable 'WUCSR_MPEN_' */ + printk_pmdbg("[ SUSP ] WUCSR 0x%08lx\n", regval); + smsc911x_mac_write(pdata, WUCSR, regval); + + /* For the D2 we must enable the PHY interrupt for the energy detection */ + regval = smsc911x_reg_read(pdata, INT_EN); + regval |= (INT_EN_PME_INT_EN_ | INT_EN_PHY_INT_EN_); + printk_pmdbg("[ SUSP ] INT_EN 0x%08lx\n", regval); + smsc911x_reg_write(regval, pdata, INT_EN); + + if (mode /* @FIXME: Enabled only for D2 */) { + u16 phy_mode; + + phy_mode = smsc911x_phy_read(pdata, MII_INTMSK); + phy_mode |= PHY_INTMSK_ENERGYON_; + smsc911x_phy_write(pdata, MII_INTMSK, phy_mode); + } + + /* Clear the PM mode and clear the current wakeup status */ + regval = smsc911x_reg_read(pdata, PMT_CTRL); + regval &= ~PMT_CTRL_PM_MODE_; + regval |= PMT_CTRL_WUPS_; + printk_pmdbg("[ SUSP ] PMT_CTRL 0x%08lx\n", regval); + smsc911x_reg_write(regval, pdata, PMT_CTRL); + + /* Enable the PME at prior and the wake on LAN */ + regval = smsc911x_reg_read(pdata, PMT_CTRL); + regval |= pdata->pmt_ctrl; /* Enable the ENERGY detect or WOL interrupt */ + regval |= PMT_CTRL_PME_EN_; + + if (mode == 1) + regval |= PMT_CTRL_PM_MODE_D1_; + else + regval |= PMT_CTRL_PM_MODE_D2_; + + printk_pmdbg("[ SUSP ] PMT_CTRL 0x%08lx\n", regval); + smsc911x_reg_write(regval, pdata, PMT_CTRL); + + return retval; +} + +/* For the state D2 we must disable the host-interrupts */ +static int smsc911x_drv_state_d2(struct smsc911x_data *pdata) +{ + unsigned long regval; + + /* Disable the interrupts of the controller */ + regval = smsc911x_reg_read(pdata, INT_CFG); + regval &= ~INT_CFG_IRQ_EN_; + smsc911x_reg_write(regval, pdata, INT_CFG); + + /* Set the phy to the power down mode */ + regval = smsc911x_phy_read(pdata, MII_BMCR); + regval |= BMCR_PDOWN; + smsc911x_phy_write(pdata, MII_BMCR, regval); + + /* + * Enter into the power mode D2 (the controller doesn't + * support the mode D3) + */ + regval = smsc911x_reg_read(pdata, PMT_CTRL); + regval &= ~PMT_CTRL_PM_MODE_; + regval |= PMT_CTRL_PM_MODE_D2_; + smsc911x_reg_write(regval, pdata, PMT_CTRL); + + return 0; +} + +static int smsc911x_drv_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct net_device *ndev; + struct smsc911x_data *pdata; + int retval; + + ndev = platform_get_drvdata(pdev); + pdata = netdev_priv(ndev); + + if (!ndev) + return -ENODEV; + + /* @FIXME: Implement the other supported power modes of the smsc911x */ + if (state.event != PM_EVENT_SUSPEND) + return -ENOTSUPP; + + if (netif_running(ndev)) { + + /* The below code is coming from the WinCE guys */ + netif_device_detach(ndev); + + /* + * If configured as wakeup-source enter the mode D1 for packet + * detection using the standard IRQ-line + */ + if (device_may_wakeup(&pdev->dev)) { + + /* + * Sanity check for verifying that a wakeup-source was + * configured from the user space. If the energy-detect + * wakeup was enabled, then use the D2 for entering into the + * power mode + */ + if (!(pdata->pmt_ctrl & (PMT_CTRL_WOL_EN_ | PMT_CTRL_ED_EN_))) { + printk_err("[ SUSP ] No WOL source defined.\n"); + retval = -EINVAL; + goto err_attach; + } + + /* + * By the WOL (magic packet, etc.) we can ONLY use the D1, but + * for the energy detect over the PHY we can change into D2 + */ + if (pdata->pmt_ctrl & PMT_CTRL_WOL_EN_) { + printk_pmdbg("[ SUSP ] Preparing D1 with wakeup\n"); + smsc911x_drv_state_wakeup(pdata, 1); + } else { + /* @TEST: Use first only D1 for the wakups */ + printk_pmdbg("[ SUSP ] Preparing D2 with wakeup\n"); + smsc911x_drv_state_wakeup(pdata, 2); + } + + enable_irq_wake(ndev->irq); + + } else { + /* + * Enter into the power mode D2 (the controller doesn't + * support the mode D3) + */ + smsc911x_drv_state_d2(pdata); + } + } + + return 0; + +err_attach: + netif_device_attach(ndev); + return retval; +} + +static int smsc911x_drv_resume(struct platform_device *pdev) +{ + int retval; + struct net_device *ndev; + unsigned long pmt_ctrl; + + pmt_ctrl = 0; + ndev = platform_get_drvdata(pdev); + retval = 0; + if (ndev) { + struct smsc911x_data *pdata = netdev_priv(ndev); + + if (netif_running(ndev)) { + unsigned long timeout; + unsigned long regval, pmt_ctrl; + + /* Assert the byte test register for waking up */ + smsc911x_reg_write(0x0, pdata, BYTE_TEST); + + timeout = 100000; + do { + timeout--; + regval = smsc911x_reg_read(pdata, PMT_CTRL); + udelay(1); + } while (timeout && !(regval & PMT_CTRL_READY_)); + + if (!timeout) { + printk_err("Wakeup timeout by the controller\n"); + retval = -EBUSY; + goto exit_resume; + } + + /* + * Check if we received a PM interrupt + * Please take note that we are supporting ONLY the Wake On LAN + * interrupts, and not the energy-on + * (Luis Galdos) + */ + pmt_ctrl = smsc911x_reg_read(pdata, PMT_CTRL); + regval = smsc911x_reg_read(pdata, INT_STS); + printk_pmdbg("[ WAKE ] PMT_CTRL 0x%08lx\n", pmt_ctrl); + printk_pmdbg("[ WAKE ] INT_STS 0x%08lx\n", regval); + if (regval & (INT_STS_PME_INT_ | INT_STS_PHY_INT_)) { + + /* Disable the power management interrupts */ + regval = smsc911x_reg_read(pdata, PMT_CTRL); + pmt_ctrl = regval & (PMT_CTRL_WOL_EN_ | PMT_CTRL_ED_EN_); + regval &= ~(PMT_CTRL_WOL_EN_ | PMT_CTRL_PME_EN_ | + PMT_CTRL_ED_EN_); + smsc911x_reg_write(regval, pdata, PMT_CTRL); + + /* Disable the PM interrupts */ + regval = smsc911x_reg_read(pdata, INT_EN); + regval &= ~(INT_EN_PME_INT_EN_ | INT_EN_PHY_INT_EN_); + smsc911x_reg_write(regval, pdata, INT_EN); + + /* Disable the wakeup-events on the MAC */ + regval = smsc911x_mac_read(pdata, WUCSR); + regval &= ~(WUCSR_MPEN_); + smsc911x_mac_write(pdata, WUCSR, regval); + } + + /* @XXX: Clear only the interrupts that were generated */ + regval = (INT_STS_PME_INT_ | INT_STS_PHY_INT_); + smsc911x_reg_write(regval, pdata, INT_STS); + + /* Set the controller into the state D0 */ + regval = smsc911x_reg_read(pdata, PMT_CTRL); + regval &= ~PMT_CTRL_PM_MODE_; + regval |= PMT_CTRL_PM_MODE_D0_; + smsc911x_reg_write(regval, pdata, PMT_CTRL); + + /* Paranoic sanity checks */ + regval = smsc911x_reg_read(pdata, PMT_CTRL); + if (regval & PMT_CTRL_PM_MODE_) + printk_err("PM mode isn't disabled (0x%04lx)\n", regval); + + if (!(regval & PMT_CTRL_READY_)) { + retval = -EBUSY; + printk_err("Device is still NOT ready.\n"); + goto exit_resume; + } + + regval = smsc911x_phy_read(pdata, MII_BMCR); + regval &= ~BMCR_PDOWN; + smsc911x_phy_write(pdata, MII_BMCR, regval); + + /* Reenable the interrupts now */ + regval = smsc911x_reg_read(pdata, INT_CFG); + regval |= INT_CFG_IRQ_EN_; + smsc911x_reg_write(regval, pdata, INT_CFG); + + /* Reset the wakeup control and status register */ + smsc911x_mac_write(pdata, WUCSR, 0x00); + + netif_device_attach(ndev); + } + } + +exit_resume: + return retval; +} +#else +#define smsc911x_drv_suspend NULL +#define smsc911x_drv_resume NULL +#endif /* defined(CONFIG_PM) */ + +static struct platform_driver smsc911x_driver = { + .probe = smsc911x_drv_probe, + .remove = smsc911x_drv_remove, + .suspend = smsc911x_drv_suspend, + .resume = smsc911x_drv_resume, + .driver = { + .name = SMSC_CHIPNAME, + }, +}; + +/* Entry point for loading the module */ +static int __init smsc911x_init_module(void) +{ + printk(KERN_INFO "SMSC 911X: Starting the platform driver.\n"); + return platform_driver_register(&smsc911x_driver); +} + +/* entry point for unloading the module */ +static void __exit smsc911x_cleanup_module(void) +{ + platform_driver_unregister(&smsc911x_driver); +} + +module_init(smsc911x_init_module); +module_exit(smsc911x_cleanup_module); |