/* * Cisco WAP4410N board support * * Copyright (C) 2014 Caleb James DeLisle * Copyright (C) 2015 Ryan A Young * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 as published * by the Free Software Foundation, or (at your option) any later version. */ #include #include #include #include #include #include #include "dev-eth.h" #include "dev-gpio-buttons.h" #include "dev-leds-gpio.h" #include "dev-usb.h" #include "dev-wmac.h" #include "machtypes.h" /* -------------- begin flash device -------------- */ #define FLASH_BASE 0xbf000000 /* where the actual art data is within the art partition. */ #define ART_DATA_OFFSET 0x1000 /* If changed, make sure to change image/Makefile too! */ #define KERN_SIZE 0x190000 /* Flash layout: u-boot/include/upgrade.h in cisco's GPL dump */ #define FLASH_SIZE (0x800000) #define BOOT_SIZE (0x40000) #define NVRAM_SIZE (0x10000) #define ART_SIZE (0x10000) /* * Note that this is different from upgrade.h, in which ENV_SIZE is 0x10000. * This is because the sercomm header is located in the upper end of the root * section instead of the env section, and it must not be overwritten by rootfs. */ #define ENV_SIZE (0x20000) #define NODE_INFO_OFFSET (BOOT_SIZE - 0x90) struct wap4410n_node_info { /** Serial number written on back of device. */ char serial_no[16]; /** Internal to Sercomm (?), France = { domain: 0x80, country: 0x00 } */ uint8_t domain; uint8_t country; /** written on the board, eg: 13 */ uint8_t hw_ver; uint8_t zero0[5]; /** ASCII numeric digits */ char wps_pin[8]; uint8_t zero1[16]; uint8_t mac_addr[6]; uint8_t zero2[3]; /** 31734572436f4d6d -> "1sErCoMm" does not seem to be checked. */ char magic_1sErCoMm[8]; /** 00010000 (offset 0x41, completely unaligned) */ uint8_t unknown0[4]; /** Used by upslug2 protocol */ uint8_t hardware_id[32]; /** 0000240800008000000000030000200400000008 */ uint8_t unknown1[20]; /** 734572436f4d6d -> "sErCoMm" */ uint8_t magic_sErCoMm[7]; uint8_t zero3[16]; }; #define UPGRADE_INFO_OFFSET 0x7dfff0 struct wap4410n_upgrade_info { /** Stock WAP4410: 2408 */ uint16_t product_id; /** Always 8000 */ uint16_t protocol; /** 2007 -> v2.0.7.x */ uint16_t fw_version; /** 90f7 same value as NSLU2 */ uint16_t unknown0; /** bootloader checks this and will "soft brick" if it's not correct. */ uint8_t eRcOmM[6]; uint8_t pad[2]; }; /* * An instruction in the bootloader which checks that 0x7dfff8 == "eRcOmM" and * bricks if it's not. * If this instruction is overwritten with a zero, you get 64k of extra space. * 2406 0006 1440 000a 8fbc 0020 <-- unpatched * 2406 0006 0000 0000 8fbc 0020 <-- patched */ #define SERCOMM_CHECK_LAST_INSN 0x24060006 #define SERCOMM_CHECK_INSN_OFFSET 0x19e08 #define SERCOMM_CHECK_NEXT_INSN 0x8fbc0020 /* Little bit of arithmatic on the flash layout. */ #define NVRAM_OFFSET BOOT_SIZE #define KERN_OFFSET (NVRAM_OFFSET + NVRAM_SIZE) #define ROOT_OFFSET (KERN_OFFSET + KERN_SIZE) #define ART_OFFSET (FLASH_SIZE - ART_SIZE) #define ENV_OFFSET (ART_OFFSET - ENV_SIZE) /* rootfs is whatever is left. */ #define ROOT_SIZE (ENV_OFFSET - ROOT_OFFSET) #define PART(b, s, n, f) { .name = n, .offset = b, .size = s, .mask_flags = f } static struct mtd_partition wap4410n_flash_partitions[] = { PART(0x00000000, BOOT_SIZE, "u-boot", MTD_WRITEABLE), PART(NVRAM_OFFSET, NVRAM_SIZE, "u-boot-env", MTD_WRITEABLE), PART(KERN_OFFSET, KERN_SIZE, "kernel", 0), PART(ROOT_OFFSET, ROOT_SIZE, "rootfs", 0), PART(ENV_OFFSET, ENV_SIZE, "sercomm", MTD_WRITEABLE), PART(ART_OFFSET, ART_SIZE, "art", MTD_WRITEABLE), /* Pseudo-partition over whole upgradable space, used by sysupgrade. */ PART(KERN_OFFSET, KERN_SIZE + ROOT_SIZE, "firmware", 0) }; #undef PART static struct physmap_flash_data wap4410n_flash_data = { .width = 2, .parts = wap4410n_flash_partitions, .nr_parts = ARRAY_SIZE(wap4410n_flash_partitions), }; static struct resource wap4410n_flash_resources[] = { [0] = { .start = FLASH_BASE, .end = FLASH_BASE + FLASH_SIZE - 1, .flags = IORESOURCE_MEM, }, }; static struct platform_device wap4410n_flash_device = { .name = "physmap-flash", .id = -1, .resource = wap4410n_flash_resources, .num_resources = ARRAY_SIZE(wap4410n_flash_resources), .dev = { .platform_data = &wap4410n_flash_data } }; static void __init wap4410n_flash_reg(void) { uint32_t *insn = (uint32_t *) (FLASH_BASE + SERCOMM_CHECK_INSN_OFFSET); int i; if (SERCOMM_CHECK_LAST_INSN != insn[-1] || SERCOMM_CHECK_NEXT_INSN != insn[1]) { printk(KERN_INFO "Unrecognized bootloader, costs 64k storage"); } else if (insn[0]) { printk(KERN_INFO "eRcOmM check at %p in uboot, costs 64k storage", (void *)insn); } else { printk(KERN_INFO "eRcOmM check at %p patched, gain 64k storage", (void *)insn); wap4410n_flash_partitions[3].size += wap4410n_flash_partitions[4].size; wap4410n_flash_data.nr_parts--; for (i = 4; i < wap4410n_flash_data.nr_parts; i++) { memcpy(&wap4410n_flash_partitions[i], &wap4410n_flash_partitions[i + 1], sizeof(struct mtd_partition)); } } platform_device_register(&wap4410n_flash_device); } /* -------------- end flash device -------------- */ /* -------------------- GPIO -------------------- */ #define LED_WIRELESS 0 #define LED_POWER 1 #define KEYS_POLL_INTERVAL 20 /* msecs */ #define KEYS_DEBOUNE_INTERVAL (3 * KEYS_POLL_INTERVAL) /* 2 lights are gpio, other 2 are hardwired. */ static struct gpio_led wap4410n_leds_gpio[] __initdata = { { .name = "wrt4410n:green:power", .gpio = LED_POWER, .active_low = 1, }, { .name = "wrt4410n:green:wireless", .gpio = LED_WIRELESS, .active_low = 1, }, }; static struct gpio_keys_button wap4410n_gpio_keys[] __initdata = { { .desc = "reset", .type = EV_KEY, .code = KEY_RESTART, .debounce_interval = KEYS_DEBOUNE_INTERVAL, .gpio = 21, .active_low = 1, } }; static void __init wap4410n_gpio_reg(void) { ath79_register_gpio_keys_polled( -1, KEYS_POLL_INTERVAL, ARRAY_SIZE(wap4410n_gpio_keys), wap4410n_gpio_keys ); ath79_register_leds_gpio( -1, ARRAY_SIZE(wap4410n_leds_gpio), wap4410n_leds_gpio ); } /* -------------------- end GPIO -------------------- */ /** Never called, just for build time verification. */ static void wap4410n_build_verify(void) { BUILD_BUG_ON((KERN_SIZE / 0x10000 * 0x10000) != KERN_SIZE); BUILD_BUG_ON(sizeof(struct wap4410n_upgrade_info) != 16); BUILD_BUG_ON(sizeof(struct wap4410n_node_info) != 0x90); } static void __init wap4410n_setup(void) { struct wap4410n_node_info *ni = (struct wap4410n_node_info *) (FLASH_BASE + NODE_INFO_OFFSET); uint8_t *art = (uint8_t *) (FLASH_BASE + FLASH_SIZE - ART_SIZE + ART_DATA_OFFSET); ath79_init_mac(ath79_eth0_data.mac_addr, ni->mac_addr, 0); ath79_eth0_data.phy_if_mode = PHY_INTERFACE_MODE_RGMII; /* TODO: SPEED_1000 causes a silent failure, testing needed. */ ath79_eth0_data.speed = SPEED_100; ath79_eth0_data.duplex = DUPLEX_FULL; ath79_register_eth(0); ath79_register_usb(); wap4410n_flash_reg(); ath79_register_wmac(art, ni->mac_addr); wap4410n_gpio_reg(); /* silence compiler warning */ if (0) wap4410n_build_verify(); } MIPS_MACHINE( ATH79_MACH_WAP4410N, "WAP4410N", "Linksys WAP4410N", wap4410n_setup );