blob: 2f50e3a2f56df55f9eb1017a3dbcbfcffa8376da [file] [log] [blame]
/*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
#include <config.h>
#include <libpayload.h>
#include <sysinfo.h>
#include <stdio.h>
#include "base/init_funcs.h"
#include "boot/fit.h"
#include "boot/ramoops.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/tpm/slb9635_i2c.h"
#include "drivers/tpm/tpm.h"
#include "drivers/video/ww_ring.h"
#include "vboot/callbacks/nvstorage_flash.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
/* 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.
};
static struct board_descriptor bdescriptor;
static void fill_board_descriptor(void)
{
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_ARKHAM_PROTO:
bdescriptor.compat_string = "google,arkham";
bdescriptor.calibration_needed = 1;
break;
default:
bdescriptor.compat_string = "google,storm-proto0";
bdescriptor.calibration_needed = 0;
break;
}
}
/*
* MAC address fixup. There might be more addresses in lib_sysinfo than
* required. Just two need to be set, at the particular paths in the device
* tree listed in the array below.
*/
static int set_mac_addresses(DeviceTree *tree)
{
/*
* Map MAC addresses found in the coreboot table into the device tree
* locations.
* Some locations need to be forced to create, some locations are
* skipped if not present in the existing device tree.
*/
static const struct mac_addr_map {
char *dt_path;
int force_create;
} maps[] = {
{ "soc/ethernet@37000000", 0 },
{ "soc/ethernet@37400000", 0 },
{ "chosen/bluetooth", 1 }
};
int i;
for (i = 0; i < ARRAY_SIZE(maps); i++) {
DeviceTreeNode *gmac_node;
const struct mac_addr_map *map;
if (i >= lib_sysinfo.num_macs)
break;
map = maps + i;
gmac_node = dt_find_node_by_path(tree->root, map->dt_path,
NULL, NULL,
map->force_create);
if (!gmac_node) {
printf("Failed to %s %s in the device tree\n",
map->force_create ? "create" : "find",
map->dt_path);
continue;
}
dt_add_bin_prop(gmac_node, "local-mac-address",
lib_sysinfo.macs[i].mac_addr,
sizeof(lib_sysinfo.macs[i].mac_addr));
}
return 0;
}
static int fix_device_tree(DeviceTreeFixup *fixup, DeviceTree *tree)
{
int rv;
rv = set_mac_addresses(tree);
if (bdescriptor.calibration_needed)
rv |= set_wifi_calibration(tree);
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. */
struct cb_gpio *cbgpio; /* GPIO description. */
} StormGpio;
static int get_gpio(struct GpioOps *me)
{
unsigned value;
StormGpio *gpio = container_of(me, StormGpio, gpio_ops);
value = gpio_get_in_value(gpio->cbgpio->port);
if (gpio->cbgpio->polarity == CB_GPIO_ACTIVE_LOW)
value = !value;
return value;
}
static int set_gpio(struct GpioOps *me, unsigned value)
{
StormGpio *gpio = container_of(me, StormGpio, gpio_ops);
gpio_set_out_value(gpio->cbgpio->port, value);
return 0;
}
static GpioOps *new_storm_dac_gpio_output()
{
StormGpio *gpio = xzalloc(sizeof(*gpio) + sizeof(struct cb_gpio));
gpio->gpio_ops.set = set_gpio;
gpio->cbgpio = (void *)(gpio + 1);
gpio->cbgpio->port = (gpio_t)DAC_SDMODE;
return &gpio->gpio_ops;
}
static StormGpio phys_presence_flag = {
.gpio_ops = { .get = get_gpio }
};
static void install_phys_presence_flag(void)
{
phys_presence_flag.cbgpio = sysinfo_lookup_gpio("developer");
if (!phys_presence_flag.cbgpio) {
printf("%s failed retrieving phys presence GPIO\n", __func__);
return;
}
flag_install(FLAG_PHYS_PRESENCE, &phys_presence_flag.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 void set_ramoops_buffer(void)
{
uint64_t base, total_size, record_size;
/*
* Hardcoded record and total sizes could be defined through Kconfig.
*
* The 'total_size' bytes of memory, aligned at 'record_size' boundary
* is found at the top of available memory as defined in the coreboot
* table and assigned to the ramoops cache.
*
* This is fairly brittle, as other parts of depthcharge or libpayload
* could be using this memory for something. But this is no wose than
* hardcoding this area to any particular address.
*
* A proper solution would be to have coreboot assign this memory and
* explixitly describe this in the coreboot memory table.
*/
record_size = 0x20000;
total_size = 0x100000;
base = 0;
/* Let's allocate it as high as possible in the available memory */
for (int i = 0; i < lib_sysinfo.n_memranges; i++) {
uint64_t new_base, size;
struct memrange *range = lib_sysinfo.memrange + i;
size = range->size;
if ((range->type != CB_MEM_RAM) ||
(size < (total_size + record_size)))
continue;
/* Record size aligned area is guaranteed to fit. */
new_base = ALIGN_DOWN(range->base + size - total_size,
record_size);
if (new_base > base)
base = new_base;
}
if (base)
ramoops_buffer(base, total_size, record_size);
}
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
};
static int board_setup(void)
{
sysinfo_install_flags();
fill_board_descriptor();
fit_set_compat(bdescriptor.compat_string);
install_phys_presence_flag();
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);
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,
lib_sysinfo.board_id >= BOARD_ID_ARKHAM_PROTO);
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);
flash_nvram_init();
list_insert_after(&ipq_enet_fixup.list_node, &device_tree_fixups);
set_ramoops_buffer();
return 0;
}
int get_mach_id(void)
{
int i;
struct cb_mainboard *mainboard = lib_sysinfo.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);