From abc84551c04783c2d59fdd4aa474d5a82ed30cf5 Mon Sep 17 00:00:00 2001 From: John Crispin Date: Sat, 12 Dec 2015 06:42:29 +0000 Subject: ar71xx: add support for Cisco's MR18 This patch adds support for Cisco's MR18. Detailed instructions for the flashing the device can be found in the OpenWrt forum thread: Signed-off-by: Chris R Blake SVN-Revision: 47878 --- .../ar71xx/files/arch/mips/ath79/Kconfig.openwrt | 11 + target/linux/ar71xx/files/arch/mips/ath79/Makefile | 1 + .../linux/ar71xx/files/arch/mips/ath79/mach-mr18.c | 297 +++++++++++++++++++++ .../linux/ar71xx/files/arch/mips/ath79/machtypes.h | 1 + 4 files changed, 310 insertions(+) create mode 100644 target/linux/ar71xx/files/arch/mips/ath79/mach-mr18.c (limited to 'target/linux/ar71xx/files/arch/mips') diff --git a/target/linux/ar71xx/files/arch/mips/ath79/Kconfig.openwrt b/target/linux/ar71xx/files/arch/mips/ath79/Kconfig.openwrt index 43ffa70978..2b62d89d59 100644 --- a/target/linux/ar71xx/files/arch/mips/ath79/Kconfig.openwrt +++ b/target/linux/ar71xx/files/arch/mips/ath79/Kconfig.openwrt @@ -853,6 +853,17 @@ config ATH79_MACH_MR16 select ATH79_DEV_M25P80 select ATH79_DEV_WMAC +config ATH79_MACH_MR18 + bool "Meraki MR18 board support" + select SOC_QCA955X + select ATH79_DEV_AP9X_PCI if PCI + select ATH79_DEV_ETH + select ATH79_DEV_GPIO_BUTTONS + select ATH79_DEV_LEDS_GPIO + select ATH79_DEV_NFC + select ATH79_DEV_WMAC + select LEDS_NU801 + config ATH79_MACH_MR600 bool "OpenMesh MR600 board support" select SOC_AR934X diff --git a/target/linux/ar71xx/files/arch/mips/ath79/Makefile b/target/linux/ar71xx/files/arch/mips/ath79/Makefile index b001876b34..f9c5b8fb9e 100644 --- a/target/linux/ar71xx/files/arch/mips/ath79/Makefile +++ b/target/linux/ar71xx/files/arch/mips/ath79/Makefile @@ -98,6 +98,7 @@ obj-$(CONFIG_ATH79_MACH_HORNET_UB) += mach-hornet-ub.o obj-$(CONFIG_ATH79_MACH_MC_MAC1200R) += mach-mc-mac1200r.o obj-$(CONFIG_ATH79_MACH_MR12) += mach-mr12.o obj-$(CONFIG_ATH79_MACH_MR16) += mach-mr16.o +obj-$(CONFIG_ATH79_MACH_MR18) += mach-mr18.o obj-$(CONFIG_ATH79_MACH_MR1750) += mach-mr1750.o obj-$(CONFIG_ATH79_MACH_MR600) += mach-mr600.o obj-$(CONFIG_ATH79_MACH_MR900) += mach-mr900.o diff --git a/target/linux/ar71xx/files/arch/mips/ath79/mach-mr18.c b/target/linux/ar71xx/files/arch/mips/ath79/mach-mr18.c new file mode 100644 index 0000000000..a24cb3fce6 --- /dev/null +++ b/target/linux/ar71xx/files/arch/mips/ath79/mach-mr18.c @@ -0,0 +1,297 @@ +/* + * Cisco Meraki MR18 board support + * + * Copyright (C) 2015 Chris Blake + * Copyright (C) 2015 Christian Lamparter + * Copyright (C) 2015 Thomas Hebb + * + * Based on Cisco Meraki GPL Release r23-20150601 MR18 Device Config + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "common.h" +#include "dev-eth.h" +#include "pci.h" +#include "dev-gpio-buttons.h" +#include "dev-leds-gpio.h" +#include "dev-nfc.h" +#include "dev-wmac.h" +#include "machtypes.h" + +#define MR18_GPIO_LED_POWER_WHITE 18 +#define MR18_GPIO_LED_POWER_ORANGE 21 + +#define MR18_GPIO_BTN_RESET 17 +#define MR18_KEYS_POLL_INTERVAL 20 /* msecs */ +#define MR18_KEYS_DEBOUNCE_INTERVAL (3 * MR18_KEYS_POLL_INTERVAL) + +#define MR18_WAN_PHYADDR 3 + +/* used for eth calibration */ +#define MR18_OTP_BASE (AR71XX_APB_BASE + 0x130000) +#define MR18_OTP_SIZE (0x2000) /* just a guess */ +#define MR18_OTP_MEM_0_REG (0x0000) +#define MR18_OTP_INTF2_REG (0x1008) +#define MR18_OTP_STATUS0_REG (0x1018) +#define MR18_OTP_STATUS0_EFUSE_VALID BIT(2) + +#define MR18_OTP_STATUS1_REG (0x101c) +#define MR18_OTP_LDO_CTRL_REG (0x1024) +#define MR18_OTP_LDO_STATUS_REG (0x102c) +#define MR18_OTP_LDO_STATUS_POWER_ON BIT(0) + +static struct gpio_led MR18_leds_gpio[] __initdata = { + { + .name = "mr18:white:power", + .gpio = MR18_GPIO_LED_POWER_WHITE, + .active_low = 1, + }, { + .name = "mr18:orange:power", + .gpio = MR18_GPIO_LED_POWER_ORANGE, + .active_low = 0, + }, +}; + +static struct gpio_keys_button MR18_gpio_keys[] __initdata = { + { + .desc = "reset", + .type = EV_KEY, + .code = KEY_RESTART, + .debounce_interval = MR18_KEYS_DEBOUNCE_INTERVAL, + .gpio = MR18_GPIO_BTN_RESET, + .active_low = 1, + }, +}; + +static struct led_nu801_template tricolor_led_template = { + .device_name = "mr18", + .name = "tricolor", + .num_leds = 1, + .cki = 11, + .sdi = 12, + .lei = -1, + .ndelay = 500, + .init_brightness = { + LED_OFF, + LED_OFF, + LED_OFF, + }, + .default_trigger = "none", + .led_colors = { "red", "green", "blue" }, +}; + +static struct led_nu801_platform_data tricolor_led_data = { + .num_controllers = 1, + .template = &tricolor_led_template, +}; + +static struct platform_device tricolor_leds = { + .name = "leds-nu801", + .id = -1, + .dev.platform_data = &tricolor_led_data, +}; + +static int mr18_extract_sgmii_res_cal(void) +{ + void __iomem *base; + unsigned int reversed_sgmii_value; + + unsigned int otp_value, otp_per_val, rbias_per, read_data; + unsigned int rbias_pos_or_neg; + unsigned int sgmii_res_cal_value; + int res_cal_val; + + base = ioremap_nocache(MR18_OTP_BASE, MR18_OTP_SIZE); + if (!base) + return -EIO; + + __raw_writel(0x7d, base + MR18_OTP_INTF2_REG); + __raw_writel(0x00, base + MR18_OTP_LDO_CTRL_REG); + + while (__raw_readl(base + MR18_OTP_LDO_STATUS_REG) & + MR18_OTP_LDO_STATUS_POWER_ON); + + __raw_readl(base + MR18_OTP_MEM_0_REG + 4); + + while (!(__raw_readl(base + MR18_OTP_STATUS0_REG) & + MR18_OTP_STATUS0_EFUSE_VALID)); + + read_data = __raw_readl(base + MR18_OTP_STATUS1_REG); + + iounmap(base); + + if (!(read_data & 0x1fff)) + return -ENODEV; + + if (read_data & 0x00001000) + otp_value = (read_data & 0xfc0) >> 6; + else + otp_value = read_data & 0x3f; + + if (otp_value > 31) { + otp_per_val = 63 - otp_value; + rbias_pos_or_neg = 1; + } else { + otp_per_val = otp_value; + rbias_pos_or_neg = 0; + } + + rbias_per = otp_per_val * 15; + + if (rbias_pos_or_neg == 1) + res_cal_val = (rbias_per + 34) / 21; + else if (rbias_per > 34) + res_cal_val = -((rbias_per - 34) / 21); + else + res_cal_val = (34 - rbias_per) / 21; + + sgmii_res_cal_value = (8 + res_cal_val) & 0xf; + + reversed_sgmii_value = (sgmii_res_cal_value & 8) >> 3; + reversed_sgmii_value |= (sgmii_res_cal_value & 4) >> 1; + reversed_sgmii_value |= (sgmii_res_cal_value & 2) << 1; + reversed_sgmii_value |= (sgmii_res_cal_value & 1) << 3; + printk(KERN_INFO "SGMII cal value = 0x%x\n", reversed_sgmii_value); + return reversed_sgmii_value; +} + +#define QCA955X_PLL_ETH_SGMII_SERDES_REG 0x004c +#define QCA955X_PLL_ETH_SGMII_SERDES_LOCK_DETECT BIT(2) +#define QCA955X_PLL_ETH_SGMII_SERDES_PLL_REFCLK BIT(1) +#define QCA955X_PLL_ETH_SGMII_SERDES_EN_PLL BIT(0) + +#define QCA955X_GMAC_REG_SGMII_SERDES 0x0018 +#define QCA955X_SGMII_SERDES_RES_CALIBRATION BIT(23) +#define QCA955X_SGMII_SERDES_RES_CALIBRATION_MASK 0xf +#define QCA955X_SGMII_SERDES_RES_CALIBRATION_SHIFT 23 +#define QCA955X_SGMII_SERDES_LOCK_DETECT_STATUS BIT(15) + +static void mr18_setup_qca955x_eth_serdes_cal(unsigned int sgmii_value) +{ + void __iomem *ethbase, *pllbase; + u32 t; + + ethbase = ioremap_nocache(QCA955X_GMAC_BASE, QCA955X_GMAC_SIZE); + pllbase = ioremap_nocache(AR71XX_PLL_BASE, AR71XX_PLL_SIZE); + + /* To Check the locking of the SGMII PLL */ + t = __raw_readl(ethbase + QCA955X_GMAC_REG_SGMII_SERDES); + t &= ~(QCA955X_SGMII_SERDES_RES_CALIBRATION_MASK << + QCA955X_SGMII_SERDES_RES_CALIBRATION_SHIFT); + t |= (sgmii_value & QCA955X_SGMII_SERDES_RES_CALIBRATION_MASK) << + QCA955X_SGMII_SERDES_RES_CALIBRATION_SHIFT; + __raw_writel(t, ethbase + QCA955X_GMAC_REG_SGMII_SERDES); + + __raw_writel(QCA955X_PLL_ETH_SGMII_SERDES_LOCK_DETECT | + QCA955X_PLL_ETH_SGMII_SERDES_PLL_REFCLK | + QCA955X_PLL_ETH_SGMII_SERDES_EN_PLL, + pllbase + QCA955X_PLL_ETH_SGMII_SERDES_REG); + + ath79_device_reset_clear(QCA955X_RESET_SGMII_ANALOG); + ath79_device_reset_clear(QCA955X_RESET_SGMII); + + while (!(__raw_readl(ethbase + QCA955X_GMAC_REG_SGMII_SERDES) & + QCA955X_SGMII_SERDES_LOCK_DETECT_STATUS)); + + iounmap(ethbase); + iounmap(pllbase); +} + +static struct ath9k_platform_data pci_main_wifi_data = { + .led_pin = -1, +}; +static struct ath9k_platform_data pci_scan_wifi_data = { + .led_pin = -1, +}; + +static int mr18_dual_pci_plat_dev_init(struct pci_dev *dev) +{ + /* The PCIE devices are attached to different busses but they + * both share the same slot number. Checking the PCI_SLOT vals + * does not work. + */ + switch (dev->bus->number) { + case 0: + dev->dev.platform_data = &pci_main_wifi_data; + break; + case 1: + dev->dev.platform_data = &pci_scan_wifi_data; + break; + } + + return 0; +} + +static void __init mr18_setup(void) +{ + int res; + + /* NAND */ + ath79_nfc_set_ecc_mode(AR934X_NFC_ECC_SOFT_BCH); + ath79_register_nfc(); + + /* even though, the PHY is connected via RGMII, + * the SGMII/SERDES PLLs need to be calibrated and locked. + * Or else, the PHY won't be working for this platfrom. + * + * Figuring this out took such a long time, that we want to + * point this quirk out, before someone wants to remove it. + */ + res = mr18_extract_sgmii_res_cal(); + if (res >= 0) { + /* Setup SoC Eth Config */ + ath79_setup_qca955x_eth_cfg(QCA955X_ETH_CFG_RGMII_EN | + (3 << QCA955X_ETH_CFG_RXD_DELAY_SHIFT) | + (3 << QCA955X_ETH_CFG_RDV_DELAY_SHIFT)); + + /* MDIO Interface */ + ath79_register_mdio(0, 0x0); + + mr18_setup_qca955x_eth_serdes_cal(res); + + /* GMAC0 is connected to an Atheros AR8035-A */ + ath79_init_mac(ath79_eth0_data.mac_addr, NULL, 0); + ath79_eth0_data.mii_bus_dev = &ath79_mdio0_device.dev; + ath79_eth0_data.phy_if_mode = PHY_INTERFACE_MODE_RGMII; + ath79_eth0_data.phy_mask = BIT(MR18_WAN_PHYADDR); + ath79_eth0_pll_data.pll_1000 = 0xa6000000; + ath79_eth0_pll_data.pll_100 = 0xa0000101; + ath79_eth0_pll_data.pll_10 = 0x80001313; + ath79_register_eth(0); + } else { + printk(KERN_ERR "failed to read EFUSE for ethernet cal\n"); + } + + /* LEDs and Buttons */ + platform_device_register(&tricolor_leds); + ath79_register_leds_gpio(-1, ARRAY_SIZE(MR18_leds_gpio), + MR18_leds_gpio); + ath79_register_gpio_keys_polled(-1, MR18_KEYS_POLL_INTERVAL, + ARRAY_SIZE(MR18_gpio_keys), + MR18_gpio_keys); + + /* Clear RTC reset (Needed by SoC WiFi) */ + ath79_device_reset_clear(QCA955X_RESET_RTC); + + /* WiFi */ + ath79_register_wmac_simple(); + + pci_main_wifi_data.eeprom_name = "pci_wmac0.eeprom"; + pci_scan_wifi_data.eeprom_name = "pci_wmac1.eeprom"; + ath79_pci_set_plat_dev_init(mr18_dual_pci_plat_dev_init); + ath79_register_pci(); +} +MIPS_MACHINE(ATH79_MACH_MR18, "MR18", "Meraki MR18", mr18_setup); diff --git a/target/linux/ar71xx/files/arch/mips/ath79/machtypes.h b/target/linux/ar71xx/files/arch/mips/ath79/machtypes.h index 550d927af9..2ea772d061 100644 --- a/target/linux/ar71xx/files/arch/mips/ath79/machtypes.h +++ b/target/linux/ar71xx/files/arch/mips/ath79/machtypes.h @@ -87,6 +87,7 @@ enum ath79_mach_type { ATH79_MACH_HORNET_UB, /* ALFA Networks Hornet-UB */ ATH79_MACH_MR12, /* Cisco Meraki MR12 */ ATH79_MACH_MR16, /* Cisco Meraki MR16 */ + ATH79_MACH_MR18, /* Cisco Meraki MR18 */ ATH79_MACH_MR1750, /* OpenMesh MR1750 */ ATH79_MACH_MR600V2, /* OpenMesh MR600v2 */ ATH79_MACH_MR600, /* OpenMesh MR600 */ -- cgit v1.2.3