285 lines
6.8 KiB
C
285 lines
6.8 KiB
C
/* Copyright 2013-2014 IBM Corp.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
* implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
|
|
#include <skiboot.h>
|
|
#include <device.h>
|
|
#include <lpc.h>
|
|
#include <console.h>
|
|
#include <opal.h>
|
|
#include <interrupts.h>
|
|
#include <libflash/libflash.h>
|
|
#include <libflash/libffs.h>
|
|
#include <libflash/blocklevel.h>
|
|
#include <sfc-ctrl.h>
|
|
#include <ec/config.h>
|
|
#include <ec/gpio.h>
|
|
|
|
/*
|
|
* EC GPIO mapping
|
|
*/
|
|
#define RHESUS_RST_UCD90160_N EC_GPIO_PORT_J, 3
|
|
#define RHESUS_FM_PWR_CYCLE_N EC_GPIO_PORT_K, 2
|
|
#define RHESUS_EN_PWR_ON_SEQ EC_GPIO_PORT_R, 1
|
|
#define RHESUS_BOARD_REVISION0 EC_GPIO_PORT_F, 3
|
|
#define RHESUS_BOARD_REVISION1 EC_GPIO_PORT_F, 2
|
|
#define RHESUS_BOARD_REVISION2 EC_GPIO_PORT_E, 5
|
|
#define RHESUS_BOARD_REVISION3 EC_GPIO_PORT_E, 4
|
|
#define RHESUS_BOARD_REVISION4 EC_GPIO_PORT_E, 1
|
|
|
|
|
|
/*
|
|
* IO accessors for the EC driver
|
|
*/
|
|
void ec_outb(uint16_t addr, uint8_t data)
|
|
{
|
|
lpc_outb(data, addr);
|
|
}
|
|
|
|
uint8_t ec_inb(uint16_t addr)
|
|
{
|
|
return lpc_inb(addr);
|
|
}
|
|
|
|
static int rhesus_board_revision(void)
|
|
{
|
|
int revision = 0, ret = 0, i = 0;
|
|
|
|
static const struct {
|
|
EcGpioPort port;
|
|
uint8_t pin;
|
|
} revision_gpios[] = {
|
|
{ RHESUS_BOARD_REVISION0 },
|
|
{ RHESUS_BOARD_REVISION1 },
|
|
{ RHESUS_BOARD_REVISION2 },
|
|
{ RHESUS_BOARD_REVISION3 },
|
|
{ RHESUS_BOARD_REVISION4 },
|
|
};
|
|
for (i = 0; i < sizeof(revision_gpios) / sizeof(revision_gpios[0]); ++i)
|
|
{
|
|
ret = ec_gpio_read(revision_gpios[i].port, revision_gpios[i].pin);
|
|
if (ret < 0)
|
|
return ret;
|
|
revision <<= 1; revision |= ret;
|
|
}
|
|
|
|
return revision;
|
|
}
|
|
|
|
static int64_t rhesus_reboot(void)
|
|
{
|
|
// TODO(rlippert): This should use EC_SYS_RST_N, but there is nothing to
|
|
// deassert that at the moment.
|
|
int ret = 0;
|
|
ret = ec_gpio_set(RHESUS_FM_PWR_CYCLE_N, 0);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
ret = ec_gpio_setup(RHESUS_FM_PWR_CYCLE_N,
|
|
EC_GPIO_OUTPUT,
|
|
EC_GPIO_PULLUP_DISABLE);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int64_t rhesus_power_down(uint64_t request __unused)
|
|
{
|
|
int ret = 0;
|
|
ret = ec_gpio_set(RHESUS_EN_PWR_ON_SEQ, 0);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
ret = ec_gpio_setup(RHESUS_EN_PWR_ON_SEQ,
|
|
EC_GPIO_OUTPUT,
|
|
EC_GPIO_PULLUP_DISABLE);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rhesus_pnor_init(void)
|
|
{
|
|
struct spi_flash_ctrl *pnor_ctrl;
|
|
struct blocklevel_device *bl = NULL;
|
|
int rc;
|
|
|
|
/* Open controller, flash and ffs */
|
|
rc = sfc_open(&pnor_ctrl);
|
|
if (rc) {
|
|
prerror("PLAT: Failed to open PNOR flash controller\n");
|
|
goto fail;
|
|
}
|
|
rc = flash_init(pnor_ctrl, &bl, NULL);
|
|
if (rc) {
|
|
prerror("PLAT: Failed to open init PNOR driver\n");
|
|
goto fail;
|
|
}
|
|
|
|
rc = flash_register(bl);
|
|
if (!rc)
|
|
return 0;
|
|
|
|
fail:
|
|
if (bl)
|
|
flash_exit(bl);
|
|
if (pnor_ctrl)
|
|
sfc_close(pnor_ctrl);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void rhesus_init(void)
|
|
{
|
|
/* Initialize PNOR/NVRAM */
|
|
rhesus_pnor_init();
|
|
|
|
/* Setup UART for direct use by Linux */
|
|
uart_set_console_policy(UART_CONSOLE_OS);
|
|
}
|
|
|
|
static void rhesus_dt_fixup_uart(struct dt_node *lpc, bool has_irq)
|
|
{
|
|
/*
|
|
* The official OF ISA/LPC binding is a bit odd, it prefixes
|
|
* the unit address for IO with "i". It uses 2 cells, the first
|
|
* one indicating IO vs. Memory space (along with bits to
|
|
* represent aliasing).
|
|
*
|
|
* We pickup that binding and add to it "2" as a indication
|
|
* of FW space.
|
|
*
|
|
* TODO: Probe the UART instead if the LPC bus allows for it
|
|
*/
|
|
struct dt_node *uart;
|
|
char namebuf[32];
|
|
#define UART_IO_BASE 0x3f8
|
|
#define UART_IO_COUNT 8
|
|
|
|
snprintf(namebuf, sizeof(namebuf), "serial@i%x", UART_IO_BASE);
|
|
uart = dt_new(lpc, namebuf);
|
|
|
|
dt_add_property_cells(uart, "reg",
|
|
1, /* IO space */
|
|
UART_IO_BASE, UART_IO_COUNT);
|
|
dt_add_property_strings(uart, "compatible",
|
|
"ns16550",
|
|
"pnpPNP,501");
|
|
dt_add_property_cells(uart, "clock-frequency", 1843200);
|
|
dt_add_property_cells(uart, "current-speed", 115200);
|
|
|
|
/*
|
|
* This is needed by Linux for some obscure reasons,
|
|
* we'll eventually need to sanitize it but in the meantime
|
|
* let's make sure it's there
|
|
*/
|
|
dt_add_property_strings(uart, "device_type", "serial");
|
|
|
|
/* Expose the external interrupt if supported
|
|
*/
|
|
if (has_irq) {
|
|
uint32_t chip_id = dt_get_chip_id(lpc);
|
|
uint32_t irq = get_psi_interrupt(chip_id) + P8_IRQ_PSI_EXTERNAL;
|
|
dt_add_property_cells(uart, "interrupts", irq, 1);
|
|
dt_add_property_cells(uart, "interrupt-parent",
|
|
get_ics_phandle());
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This adds the legacy RTC device to the device-tree
|
|
* for Linux to use
|
|
*/
|
|
static void rhesus_dt_fixup_rtc(struct dt_node *lpc)
|
|
{
|
|
struct dt_node *rtc;
|
|
|
|
/*
|
|
* Follows the structure expected by the kernel file
|
|
* arch/powerpc/sysdev/rtc_cmos_setup.c
|
|
*/
|
|
rtc = dt_new_addr(lpc, "rtc", EC_RTC_PORT_BASE);
|
|
assert(rtc);
|
|
dt_add_property_string(rtc, "compatible", "pnpPNP,b00");
|
|
dt_add_property_cells(rtc, "reg",
|
|
1, /* IO space */
|
|
EC_RTC_PORT_BASE,
|
|
/* 1 index/data pair per 128 bytes */
|
|
(EC_RTC_BLOCK_SIZE / 128) * 2);
|
|
}
|
|
|
|
static void rhesus_dt_fixup(bool has_uart_irq)
|
|
{
|
|
struct dt_node *n, *primary_lpc = NULL;
|
|
|
|
/* Find the primary LPC bus */
|
|
dt_for_each_compatible(dt_root, n, "ibm,power8-lpc") {
|
|
if (!primary_lpc || dt_has_node_property(n, "primary", NULL))
|
|
primary_lpc = n;
|
|
if (dt_has_node_property(n, "#address-cells", NULL))
|
|
break;
|
|
}
|
|
|
|
if (!primary_lpc)
|
|
return;
|
|
|
|
rhesus_dt_fixup_rtc(primary_lpc);
|
|
rhesus_dt_fixup_uart(primary_lpc, has_uart_irq);
|
|
}
|
|
|
|
static bool rhesus_probe(void)
|
|
{
|
|
const char *model;
|
|
int rev;
|
|
bool has_uart_irq = false;
|
|
|
|
if (!dt_node_is_compatible(dt_root, "ibm,powernv"))
|
|
return false;
|
|
|
|
model = dt_prop_get_def(dt_root, "model", NULL);
|
|
if (!model || !(strstr(model, "rhesus") || strstr(model, "RHESUS")))
|
|
return false;
|
|
|
|
/* Grab board version from EC */
|
|
rev = rhesus_board_revision();
|
|
if (rev >= 0) {
|
|
printf("Rhesus board rev %d\n", rev);
|
|
dt_add_property_cells(dt_root, "revision-id", rev);
|
|
} else
|
|
prerror("Rhesus board revision not found !\n");
|
|
|
|
/* Add missing bits of device-tree such as the UART */
|
|
rhesus_dt_fixup(has_uart_irq);
|
|
|
|
/* Setup UART and use it as console */
|
|
uart_init();
|
|
|
|
return true;
|
|
}
|
|
|
|
DECLARE_PLATFORM(rhesus) = {
|
|
.name = "Rhesus",
|
|
.probe = rhesus_probe,
|
|
.init = rhesus_init,
|
|
.cec_power_down = rhesus_power_down,
|
|
.cec_reboot = rhesus_reboot,
|
|
};
|