src/sys/dev/ic/iosf.c
2023-07-10 00:10:46 +00:00

336 lines
6.9 KiB
C

/* $OpenBSD: iosf.c,v 1.1 2023/04/23 00:20:26 dlg Exp $ */
/*
* Copyright (c) 2023 David Gwynne <dlg@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/mutex.h>
#include <sys/rwlock.h>
#include <machine/bus.h>
#include <dev/ic/iosfvar.h>
#define IOSF_MBI_MASK_HI 0xffffff00
#define IOSF_MBI_MASK_LO 0x000000ff
#define IOSF_MBI_ENABLE 0x000000f0
#define IOSF_MBI_MCR_OP_SHIFT 24
#define IOSF_MBI_MCR_PORT_SHIFT 16
#define IOSF_MBI_MCR_OFFSET_SHIFT 8
/* IOSF sideband read/write opcodes */
#define IOSF_MBI_OP_MMIO_READ 0x00
#define IOSF_MBI_OP_MMIO_WRITE 0x01
#define IOSF_MBI_OP_CFG_READ 0x04
#define IOSF_MBI_OP_CFG_WRITE 0x05
#define IOSF_MBI_OP_CR_READ 0x06
#define IOSF_MBI_OP_CR_WRITE 0x07
#define IOSF_MBI_OP_REG_READ 0x10
#define IOSF_MBI_OP_REG_WRITE 0x11
#define IOSF_MBI_OP_ESRAM_READ 0x12
#define IOSF_MBI_OP_ESRAM_WRITE 0x13
/* Baytrail */
#define IOSF_BT_MBI_UNIT_AUNIT 0x00
#define IOSF_BT_MBI_UNIT_SMC 0x01
#define IOSF_BT_MBI_UNIT_CPU 0x02
#define IOSF_BT_MBI_UNIT_BUNIT 0x03
#define IOSF_BT_MBI_UNIT_PMC 0x04
#define IOSF_BT_MBI_UNIT_GFX 0x06
#define IOSF_BT_MBI_UNIT_SMI 0x0C
#define IOSF_BT_MBI_UNIT_CCK 0x14
#define IOSF_BT_MBI_UNIT_USB 0x43
#define IOSF_BT_MBI_UNIT_SATA 0xA3
#define IOSF_BT_MBI_UNIT_PCIE 0xA6
/* semaphore bits */
#define IOSF_PUNIT_SEM_BIT (1 << 0)
#define IOSF_PUNIT_SEM_ACQUIRE (1 << 1)
struct cfdriver iosf_cd = {
NULL, "iosf", DV_DULL
};
/*
* serialise register ops
*/
static struct mutex iosf_mbi_mtx = MUTEX_INITIALIZER(IPL_HIGH);
/*
* rwlock for kernel to coordinate access to the mbi with
*/
static struct rwlock iosf_lock = RWLOCK_INITIALIZER("iosf");
/*
* drivers provide an iosf_mbi that acts as a backend for the code below.
*/
static struct iosf_mbi *iosf_mbi;
void
iosf_mbi_attach(struct iosf_mbi *mbi)
{
/*
* assume this is serialised by autoconf being run sequentially
* during boot.
*/
if (iosf_mbi == NULL || iosf_mbi->mbi_prio < mbi->mbi_prio)
iosf_mbi = mbi;
}
static inline uint32_t
iosf_mbi_mcr(uint8_t op, uint8_t port, uint32_t offset)
{
uint32_t rv = IOSF_MBI_ENABLE;
rv |= op << IOSF_MBI_MCR_OP_SHIFT;
rv |= port << IOSF_MBI_MCR_PORT_SHIFT;
rv |= (offset & IOSF_MBI_MASK_LO) << IOSF_MBI_MCR_OFFSET_SHIFT;
return (rv);
}
static inline uint32_t
iosf_mbi_mcrx(uint32_t offset)
{
return (offset & IOSF_MBI_MASK_HI);
}
/*
* serialised mbi mdr operations
*/
static uint32_t
iosf_mbi_mdr_read(struct iosf_mbi *mbi, uint8_t port, uint8_t op,
uint32_t offset)
{
uint32_t mcr, mcrx, mdr;
mcr = iosf_mbi_mcr(op, port, offset);
mcrx = iosf_mbi_mcrx(offset);
mtx_enter(&iosf_mbi_mtx);
mdr = (*mbi->mbi_mdr_rd)(mbi, mcr, mcrx);
mtx_leave(&iosf_mbi_mtx);
return (mdr);
}
static void
iosf_mbi_mdr_write(struct iosf_mbi *mbi, uint8_t port, uint8_t op,
uint32_t offset, uint32_t mdr)
{
uint32_t mcr, mcrx;
mcr = iosf_mbi_mcr(op, port, offset);
mcrx = iosf_mbi_mcrx(offset);
mtx_enter(&iosf_mbi_mtx);
(*mbi->mbi_mdr_wr)(mbi, mcr, mcrx, mdr);
mtx_leave(&iosf_mbi_mtx);
}
static void
iosf_mbi_mdr_modify(struct iosf_mbi *mbi, uint8_t port, uint8_t op,
uint32_t offset, uint32_t bits, uint32_t mask)
{
uint32_t mcr, mcrx, mdr;
mcr = iosf_mbi_mcr(op, port, offset);
mcrx = iosf_mbi_mcrx(offset);
mtx_enter(&iosf_mbi_mtx);
mdr = (*mbi->mbi_mdr_rd)(mbi, mcr, mcrx);
CLR(mdr, mask);
SET(mdr, bits & mask);
(*mbi->mbi_mdr_wr)(mbi, mcr, mcrx, mdr);
mtx_leave(&iosf_mbi_mtx);
}
/*
* linux compat api
*/
int
iosf_mbi_read(uint8_t port, uint8_t opcode, uint32_t offset, uint32_t *mdrp)
{
struct iosf_mbi *mbi;
mbi = iosf_mbi;
if (mbi == NULL)
return (ENODEV);
/* check port != BT_MBI_UNIT_GFX? */
*mdrp = iosf_mbi_mdr_read(mbi, port, opcode, offset);
return (0);
}
int
iosf_mbi_write(uint8_t port, uint8_t opcode, uint32_t offset, uint32_t mdr)
{
struct iosf_mbi *mbi;
mbi = iosf_mbi;
if (mbi == NULL)
return (ENODEV);
/* check port != BT_MBI_UNIT_GFX? */
iosf_mbi_mdr_write(mbi, port, opcode, offset, mdr);
return (0);
}
int
iosf_mbi_modify(uint8_t port, uint8_t opcode, uint32_t offset,
uint32_t bits, uint32_t mask)
{
struct iosf_mbi *mbi;
mbi = iosf_mbi;
if (mbi == NULL)
return (ENODEV);
/* check port != BT_MBI_UNIT_GFX? */
iosf_mbi_mdr_modify(mbi, port, opcode, offset, bits, mask);
return (0);
}
int
iosf_mbi_available(void)
{
return (iosf_mbi != NULL);
}
static uint32_t
iosf_mbi_sem_get(struct iosf_mbi *mbi)
{
uint32_t sem;
sem = iosf_mbi_mdr_read(mbi,
IOSF_BT_MBI_UNIT_PMC, IOSF_MBI_OP_REG_READ, mbi->mbi_semaddr);
return (ISSET(sem, IOSF_PUNIT_SEM_BIT));
}
static void
iosf_mbi_sem_reset(struct iosf_mbi *mbi)
{
iosf_mbi_mdr_modify(mbi,
IOSF_BT_MBI_UNIT_PMC, IOSF_MBI_OP_REG_READ, mbi->mbi_semaddr,
0, IOSF_PUNIT_SEM_BIT);
}
void
iosf_mbi_punit_acquire(void)
{
rw_enter_write(&iosf_lock);
}
void
iosf_mbi_punit_release(void)
{
rw_exit_write(&iosf_lock);
}
void iosf_mbi_assert_punit_acquired(void)
{
int s;
if (splassert_ctl == 0)
return;
s = rw_status(&iosf_lock);
if (s != RW_WRITE)
splassert_fail(RW_WRITE, s, __func__);
}
static void
iosf_sem_wait(uint64_t usec, int waitok)
{
if (waitok)
tsleep_nsec(&nowake, PRIBIO, "iosfsem", USEC_TO_NSEC(usec));
else
delay(usec);
}
#include <dev/i2c/i2cvar.h>
int
iosf_i2c_acquire(int flags)
{
struct iosf_mbi *mbi;
int waitok = !cold && !ISSET(flags, I2C_F_POLL);
unsigned int i;
mbi = iosf_mbi;
if (mbi == NULL)
return (0);
if (waitok)
rw_enter_write(&iosf_lock);
else if (iosf_lock.rwl_owner != 0)
panic("%s", __func__);
/* XXX disable C6 and C7 states */
iosf_mbi_mdr_write(mbi, IOSF_BT_MBI_UNIT_PMC, IOSF_MBI_OP_REG_WRITE,
mbi->mbi_semaddr, IOSF_PUNIT_SEM_ACQUIRE);
for (i = 0; i < 50; i++) {
if (iosf_mbi_sem_get(mbi)) {
/* success! */
return (0);
}
iosf_sem_wait(10000, waitok);
}
iosf_mbi_sem_reset(mbi);
if (waitok)
rw_exit_write(&iosf_lock);
else if (iosf_lock.rwl_owner != 0)
panic("%s", __func__);
return (EWOULDBLOCK);
}
void
iosf_i2c_release(int flags)
{
struct iosf_mbi *mbi;
int waitok = !cold && !ISSET(flags, I2C_F_POLL);
mbi = iosf_mbi;
if (mbi == NULL)
return;
iosf_mbi_sem_reset(mbi);
if (waitok)
rw_exit_write(&iosf_lock);
else if (iosf_lock.rwl_owner != 0)
panic("%s", __func__);
}