| /* |
| * Copyright (c) 2015, The Linux Foundation. All rights reserved. |
| * Copyright 2014 Google Inc. |
| * |
| * See file CREDITS for list of people who contributed to this |
| * project. |
| * |
| * 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. |
| */ |
| |
| #include <libpayload.h> |
| #include <sysinfo.h> |
| #include <stdio.h> |
| #include <stdint.h> |
| |
| #include "base/init_funcs.h" |
| #include "boot/fit.h" |
| #include "drivers/bus/i2c/ipq806x.h" |
| #include "drivers/bus/i2c/ipq806x_gsbi.h" |
| #include "drivers/bus/spi/ipq806x.h" |
| #include "drivers/bus/usb/usb.h" |
| #include "drivers/gpio/gpio.h" |
| #include "drivers/gpio/ipq806x.h" |
| #include "drivers/gpio/sysinfo.h" |
| #include "drivers/power/ipq806x.h" |
| #include "drivers/sound/ipq806x.h" |
| #include "drivers/sound/route.h" |
| #include "drivers/storage/ipq806x_mmc.h" |
| #include "drivers/storage/mtd/mtd.h" |
| #include "drivers/storage/mtd/nand/ipq_nand.h" |
| #include "drivers/storage/mtd/stream.h" |
| #include "drivers/storage/spi_gpt.h" |
| #include "drivers/tpm/slb9635_i2c.h" |
| #include "drivers/tpm/tpm.h" |
| #include "drivers/video/ww_ring.h" |
| #include "vboot/stages.h" |
| #include "vboot/util/flag.h" |
| |
| #include "board.h" |
| |
| #define GPIO_SDCC_FUNC_VAL 2 |
| #define GPIO_I2S_FUNC_VAL 1 |
| |
| #define MSM_SDC1_BASE 0x12400000 |
| |
| #define EBI2ND_BASE 0x1ac00000 |
| |
| /* Structure describing properties of various Storm based boards. */ |
| struct board_descriptor { |
| const char *compat_string; // Match the device tree in FIT image. |
| int calibration_needed; // Some boards need to populate WiFi |
| // calibration data. |
| int use_nand; // true if NAND, false if eMMC |
| }; |
| |
| static struct board_descriptor bdescriptor; |
| |
| static void fill_board_descriptor(void) |
| { |
| int storm_board = 0; |
| bdescriptor.use_nand = 0; |
| |
| switch(lib_sysinfo.board_id) { |
| case BOARD_ID_WHIRLWIND_SP3: |
| bdescriptor.compat_string = "google,whirlwind-sp3"; |
| bdescriptor.calibration_needed = 1; |
| break; |
| |
| case BOARD_ID_WHIRLWIND_SP5: |
| bdescriptor.compat_string = "google,whirlwind-sp5"; |
| bdescriptor.calibration_needed = 1; |
| break; |
| |
| case BOARD_ID_PROTO_0_2_NAND: |
| bdescriptor.use_nand = 1; |
| storm_board = 1; |
| break; |
| |
| case BOARD_ID_PROTO_0: |
| case BOARD_ID_PROTO_0_2: |
| storm_board = 1; |
| break; |
| |
| default: |
| printf("Unknown board id %d; assuming like Storm Proto 0.2\n", |
| lib_sysinfo.board_id); |
| storm_board = 1; |
| break; |
| } |
| |
| if (storm_board) { |
| bdescriptor.compat_string = "google,storm-proto0"; |
| bdescriptor.calibration_needed = 0; |
| } |
| } |
| |
| static const DtPathMap mac_maps[] = { |
| { 0, "/soc/ethernet@37000000/local-mac-address" }, |
| { 0, "/soc/ethernet@37400000/local-mac-address" }, |
| { 1, "/chosen/bluetooth/local-mac-address" }, |
| {} |
| }; |
| |
| static const DtPathMap calibration_maps[] = { |
| {1, "/soc/pci@1b500000/pcie@0/ath10k@0,0/qcom,ath10k-calibration-data", |
| "wifi_calibration0"}, |
| {1, "/soc/pci@1b700000/pcie@0/ath10k@0,0/qcom,ath10k-calibration-data", |
| "wifi_calibration1"}, |
| {1, "/soc/pci@1b900000/pcie@0/ath10k@0,0/qcom,ath10k-calibration-data", |
| "wifi_calibration2"}, |
| {} |
| }; |
| |
| static int fix_device_tree(DeviceTreeFixup *fixup, DeviceTree *tree) |
| { |
| int rv; |
| |
| rv = dt_set_mac_addresses(tree, mac_maps); |
| |
| if (bdescriptor.calibration_needed) |
| rv |= dt_set_wifi_calibration(tree, calibration_maps); |
| |
| return rv; |
| } |
| |
| static DeviceTreeFixup ipq_enet_fixup = { |
| .fixup = fix_device_tree |
| }; |
| |
| /* DAC GPIO assignment. */ |
| enum storm_dac_gpio { |
| DAC_SDMODE = 25, |
| }; |
| |
| /* I2S bus GPIO assignments. */ |
| enum storm_i2s_gpio { |
| I2S_SYNC = 27, |
| I2S_CLK = 28, |
| I2S_DOUT = 32, |
| }; |
| |
| /* MMC bus GPIO assignments. */ |
| enum storm_emmc_gpio { |
| SDC1_DATA7 = 38, |
| SDC1_DATA6 = 39, |
| SDC1_DATA3 = 40, |
| SDC1_DATA2 = 41, |
| SDC1_CLK = 42, |
| SDC1_DATA1 = 43, |
| SDC1_DATA0 = 44, |
| SDC1_CMD = 45, |
| SDC1_DATA5 = 46, |
| SDC1_DATA4 = 47, |
| }; |
| |
| /* Storm GPIO access wrapper. */ |
| typedef struct |
| { |
| GpioOps gpio_ops; /* Depthcharge GPIO API wrapper. */ |
| gpio_t desc; /* GPIO description. */ |
| } StormGpio; |
| |
| static int get_gpio(struct GpioOps *me) |
| { |
| StormGpio *gpio = container_of(me, StormGpio, gpio_ops); |
| return gpio_get_in_value(gpio->desc); |
| } |
| |
| static int set_gpio(struct GpioOps *me, unsigned value) |
| { |
| StormGpio *gpio = container_of(me, StormGpio, gpio_ops); |
| gpio_set_out_value(gpio->desc, value); |
| return 0; |
| } |
| |
| static GpioOps *new_storm_dac_gpio_output() |
| { |
| StormGpio *gpio = xzalloc(sizeof(*gpio)); |
| gpio->gpio_ops.set = set_gpio; |
| gpio->desc = (gpio_t)DAC_SDMODE; |
| return &gpio->gpio_ops; |
| } |
| |
| static GpioOps *new_storm_gpio_input_from_coreboot(uint32_t port) |
| { |
| StormGpio *gpio = xzalloc(sizeof(*gpio)); |
| gpio->gpio_ops.get = get_gpio; |
| gpio->desc = (gpio_t)port; |
| return &gpio->gpio_ops; |
| } |
| |
| void board_mmc_gpio_config(void) |
| { |
| unsigned i; |
| unsigned char gpio_config_arr[] = { |
| SDC1_DATA7, SDC1_DATA6, SDC1_DATA3, |
| SDC1_DATA2, SDC1_DATA1, SDC1_DATA0, |
| SDC1_CMD, SDC1_DATA5, SDC1_DATA4}; |
| |
| gpio_tlmm_config_set(SDC1_CLK, GPIO_SDCC_FUNC_VAL, |
| GPIO_PULL_DOWN, GPIO_16MA, 1); |
| |
| for (i = 0; i < ARRAY_SIZE(gpio_config_arr); i++) { |
| gpio_tlmm_config_set(gpio_config_arr[i], |
| GPIO_SDCC_FUNC_VAL, GPIO_PULL_UP, GPIO_10MA, 1); |
| } |
| } |
| |
| void board_i2s_gpio_config(void) |
| { |
| unsigned i; |
| unsigned char gpio_config_arr[] = {I2S_SYNC, I2S_CLK, I2S_DOUT}; |
| |
| for (i = 0; i < ARRAY_SIZE(gpio_config_arr); i++) { |
| gpio_tlmm_config_set(gpio_config_arr[i], GPIO_I2S_FUNC_VAL, |
| GPIO_NO_PULL, GPIO_16MA, 1); |
| } |
| } |
| |
| void board_dac_gpio_config(void) |
| { |
| gpio_tlmm_config_set(DAC_SDMODE, FUNC_SEL_GPIO, GPIO_NO_PULL, |
| GPIO_16MA, 1); |
| } |
| |
| static uint8_t kb_buffer[4]; |
| static int kb_in, kb_out; |
| |
| static int storm_havekey(void) |
| { |
| /* |
| * We want to react to the button press only, i.e. we need to |
| * catch the "unpressed -> pressed" transition. |
| */ |
| static uint32_t prev = 1; |
| uint32_t rv = flag_fetch(FLAG_PHYS_PRESENCE); |
| |
| if (prev == rv) |
| return kb_in != kb_out; |
| |
| prev = rv; |
| if (!rv) |
| return kb_in != kb_out; |
| |
| if (((kb_in + 1) % sizeof(kb_buffer)) == kb_out) { |
| printf("%s: keyboard buffer overflow!\n", __func__); |
| return 0; |
| } |
| |
| /* Dev switch was pressed, what's the meaning of it? */ |
| if (vboot_in_recovery()) { |
| /* This must mean ^D, the user wants to switch to dev mode. */ |
| kb_buffer[kb_in++] = 0x4; |
| kb_in %= sizeof(kb_buffer); |
| |
| if (((kb_in + 1) % sizeof(kb_buffer)) != kb_out) |
| kb_buffer[kb_in++] = 0xd; |
| else |
| /* |
| * Should never happen, but worse come to worse the |
| * user will lose the CR and will have to reboot in |
| * recovery mode again to enter dev mode. |
| */ |
| printf("%s: keyboard buffer overflow!\n", __func__); |
| } else { |
| /* This must mean ^U, the user wants to boot from USB. */ |
| kb_buffer[kb_in++] = 0x15; |
| } |
| |
| kb_in %= sizeof(kb_buffer); |
| |
| return 1; |
| } |
| |
| static int storm_getchar(void) |
| { |
| int storm_char; |
| |
| while (!storm_havekey()) |
| ; |
| |
| storm_char = kb_buffer[kb_out++]; |
| |
| kb_out %= sizeof(kb_buffer); |
| |
| return storm_char; |
| } |
| |
| static struct console_input_driver storm_input_driver = |
| { |
| NULL, |
| &storm_havekey, |
| &storm_getchar, |
| CONSOLE_INPUT_TYPE_GPIO |
| }; |
| |
| static int board_setup(void) |
| { |
| sysinfo_install_flags(new_storm_gpio_input_from_coreboot); |
| |
| fill_board_descriptor(); |
| |
| fit_add_compat(bdescriptor.compat_string); |
| |
| console_add_input_driver(&storm_input_driver); |
| |
| power_set_ops(new_ipq806x_power_ops()); |
| |
| SpiController *spi = new_spi(0, 0); |
| flash_set_ops(&new_spi_flash(&spi->ops)->ops); |
| |
| UsbHostController *usb_host1 = new_usb_hc(XHCI, 0x11000000); |
| |
| list_insert_after(&usb_host1->list_node, &usb_host_controllers); |
| |
| if (bdescriptor.use_nand) { |
| MtdDevCtrlr *mtd = new_ipq_nand((void *)EBI2ND_BASE); |
| SpiGptCtrlr *virtual_dev = new_spi_gpt("RW_GPT", |
| new_mtd_stream(mtd), |
| "/soc/nand@0x1ac00000"); |
| list_insert_after(&virtual_dev->block_ctrlr.list_node, |
| &fixed_block_dev_controllers); |
| } else { |
| QcomMmcHost *mmc = new_qcom_mmc_host(1, MSM_SDC1_BASE, 8); |
| if (!mmc) |
| return -1; |
| |
| list_insert_after(&mmc->mmc.ctrlr.list_node, |
| &fixed_block_dev_controllers); |
| } |
| |
| Ipq806xI2c *i2c = new_ipq806x_i2c(GSBI_ID_1); |
| tpm_set_ops(&new_slb9635_i2c(&i2c->ops, 0x20)->base.ops); |
| |
| if (lib_sysinfo.board_id >= BOARD_ID_WHIRLWIND_SP5) { |
| |
| DisplayOps *ww_ring_ops = new_ww_ring_display |
| (&new_ipq806x_i2c (GSBI_ID_7)->ops, 0x32); |
| |
| display_set_ops(ww_ring_ops); |
| |
| /* |
| * Explicit initialization is required because the ring could |
| * be in an arbitrary state before the system is restarted, |
| * and board reset would not affect the state of the ring |
| * attached over i2c. |
| */ |
| display_init(); |
| } |
| |
| Ipq806xSound *sound = new_ipq806x_sound(new_storm_dac_gpio_output(), |
| 48000, 2, 16, 1000); |
| SoundRoute *sound_route = new_sound_route(&sound->ops); |
| sound_set_ops(&sound_route->ops); |
| |
| list_insert_after(&ipq_enet_fixup.list_node, &device_tree_fixups); |
| |
| return 0; |
| } |
| |
| int get_mach_id(void) |
| { |
| int i; |
| struct cb_mainboard *mainboard = |
| phys_to_virt(lib_sysinfo.cb_mainboard); |
| const char *part_number = (const char *)mainboard->strings + |
| mainboard->part_number_idx; |
| |
| struct PartDescriptor { |
| const char *part_name; |
| int mach_id; |
| } parts[] = { |
| {"Storm", 4936}, |
| {"AP148", CONFIG_MACHID}, |
| }; |
| |
| for (i = 0; i < ARRAY_SIZE(parts); i++) { |
| if (!strncmp(parts[i].part_name, |
| part_number, strlen(parts[i].part_name))) { |
| return parts[i].mach_id; |
| } |
| } |
| |
| return -1; |
| } |
| |
| int board_wan_port_number(void) |
| { |
| if ((lib_sysinfo.board_id == BOARD_ID_PROTO_0) || |
| (lib_sysinfo.board_id == BOARD_ID_PROTO_0_2)) |
| return 4; /* Storm variants */ |
| |
| return 1; /* Whirlwind variants, let it be the default. */ |
| } |
| |
| INIT_FUNC(board_setup); |