blob: ce9c33dc733c1613b7b80b6a724ff9c5b9065b1a [file] [log] [blame]
// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
/* Copyright 2013-2019 IBM Corp. */
#ifndef __LPC_H
#define __LPC_H
#include <opal.h>
#include <ccan/endian/endian.h>
/* Note about LPC interrupts
*
* LPC interrupts come in two categories:
*
* - External device LPC interrupts
* - Error interrupts generated by the LPC controller
*
* The former is implemented differently depending on whether
* you are using Murano/Venice or Naples.
*
* The former two chips don't have a pin to deserialize the LPC
* SerIRQ protocol, so the only source of LPC device interrupts
* is an external interrupt pin, which is usually connected to a
* CPLD which deserializes SerIRQ.
*
* So in that case, we get external interrupts from the PSI which
* are in effect the "OR" of all the active LPC interrupts.
*
* The error interrupt generated by the LPC controllers however
* are internally routed normally to the PSI bridge and muxed with
* the I2C interrupts.
*
* On Naples, there is a pin to deserialize SerIRQ, so the individual
* LPC device interrupts (up to 19) are represented in the same status
* and mask register as the LPC error interrupts. They are still all
* then turned into a single XIVE interrupts in the PSI however, muxed
* with the I2C.
*
* In order to more/less transparently handle this, we let individual
* "drivers" register for specific LPC interrupts. On Naples, the handlers
* will be called individually based on what has been demuxed by the
* controller. On Venice/Murano, all the handlers will be called on
* every external interrupt. The platform is responsible of calling
* lpc_all_interrupts() from the platform external interrupt handler.
*/
/* Routines for accessing the LPC bus on Power8 */
extern void lpc_init(void);
extern void lpc_init_interrupts(void);
extern void lpc_finalize_interrupts(void);
/* Check for a default bus */
extern bool lpc_present(void);
/* Return of LPC is currently usable. This can be false if the caller
* currently holds a lock that would make it unsafe, or the LPC bus
* is known to be in some error condition (TBI).
*/
extern bool lpc_ok(void);
/* Handle the interrupt from the LPC controller */
extern void lpc_interrupt(uint32_t chip_id);
/* On P9, we have a different route for SerIRQ */
extern void lpc_serirq(uint32_t chip_id, uint32_t index);
/* Call all external handlers */
extern void lpc_all_interrupts(uint32_t chip_id);
/* Register/deregister handler */
struct lpc_client {
/* Callback on LPC reset */
void (*reset)(uint32_t chip_id);
/* Callback on LPC interrupt */
void (*interrupt)(uint32_t chip_id, uint32_t irq_msk);
/* Bitmask of interrupts this client is interested in
* Note: beware of ordering, use LPC_IRQ() macro
*/
uint32_t interrupts;
#define LPC_IRQ(n) (0x80000000 >> (n))
};
extern void lpc_register_client(uint32_t chip_id, const struct lpc_client *clt,
uint32_t policy);
/* Return the policy for a given serirq */
extern unsigned int lpc_get_irq_policy(uint32_t chip_id, uint32_t psi_idx);
/* Default bus accessors that perform error logging */
extern int64_t lpc_write(enum OpalLPCAddressType addr_type, uint32_t addr,
uint32_t data, uint32_t sz);
extern int64_t lpc_read(enum OpalLPCAddressType addr_type, uint32_t addr,
uint32_t *data, uint32_t sz);
/*
* LPC bus accessors that return errors as required but do not log the failure.
* Useful if the caller wants to test the presence of a device on the LPC bus.
*/
extern int64_t lpc_probe_write(enum OpalLPCAddressType addr_type, uint32_t addr,
uint32_t data, uint32_t sz);
extern int64_t lpc_probe_read(enum OpalLPCAddressType addr_type, uint32_t addr,
uint32_t *data, uint32_t sz);
/*
* helpers for doing a bulk io to firmware space.
*/
extern int64_t lpc_fw_read(uint32_t addr, void *buf, uint32_t sz);
extern int64_t lpc_fw_write(uint32_t addr, const void *buf, uint32_t sz);
/* Mark LPC bus as used by console */
extern void lpc_used_by_console(void);
/*
* Simplified big endian FW accessors
*/
static inline int64_t lpc_fw_read32(uint32_t *val, uint32_t addr)
{
return lpc_read(OPAL_LPC_FW, addr, val, 4);
}
static inline int64_t lpc_fw_write32(uint32_t val, uint32_t addr)
{
return lpc_write(OPAL_LPC_FW, addr, val, 4);
}
/*
* Simplified Little Endian IO space accessors
*
* Note: We do *NOT* handle unaligned accesses
*/
static inline void lpc_outb(uint8_t data, uint32_t addr)
{
lpc_write(OPAL_LPC_IO, addr, data, 1);
}
static inline uint8_t lpc_inb(uint32_t addr)
{
uint32_t d32;
int64_t rc = lpc_read(OPAL_LPC_IO, addr, &d32, 1);
return (rc == OPAL_SUCCESS) ? d32 : 0xff;
}
static inline void lpc_outw(uint16_t data, uint32_t addr)
{
lpc_write(OPAL_LPC_IO, addr, cpu_to_le16(data), 2);
}
static inline uint16_t lpc_inw(uint32_t addr)
{
uint32_t d32;
int64_t rc = lpc_read(OPAL_LPC_IO, addr, &d32, 2);
return (rc == OPAL_SUCCESS) ? le16_to_cpu(d32) : 0xffff;
}
static inline void lpc_outl(uint32_t data, uint32_t addr)
{
lpc_write(OPAL_LPC_IO, addr, cpu_to_le32(data), 4);
}
static inline uint32_t lpc_inl(uint32_t addr)
{
uint32_t d32;
int64_t rc = lpc_read(OPAL_LPC_IO, addr, &d32, 4);
return (rc == OPAL_SUCCESS) ? le32_to_cpu(d32) : 0xffffffff;
}
#endif /* __LPC_H */