blob: 68b6b2f7fb607b1dcd75fbafe34e2dc34796d8e2 [file] [log] [blame]
/* SPDX-License-Identifier: GPL-2.0-only */
#include <arch/io.h>
#include <console/console.h>
#include <device/device.h>
#include <device/pnp.h>
#include <option.h>
#include <pc80/keyboard.h>
#include <stdint.h>
#include "mec5035.h"
static const u16 MAILBOX_INDEX = 0x910;
static const u16 MAILBOX_DATA = MAILBOX_INDEX + 1;
static inline u8 __get_mailbox_register(u8 index)
{
outb(index + 0x10, MAILBOX_INDEX);
return inb(MAILBOX_DATA);
}
static inline void __set_mailbox_register(u8 index, u8 data)
{
outb(index + 0x10, MAILBOX_INDEX);
outb(data, MAILBOX_DATA);
}
static void wait_ec(void)
{
u8 busy;
do {
outb(0, MAILBOX_INDEX);
busy = inb(MAILBOX_DATA);
} while (busy);
}
static enum cb_err read_mailbox_regs(u8 *data, u8 start, u8 count)
{
if (start + count >= NUM_REGISTERS) {
printk(BIOS_ERR, "%s: Invalid start or count argument.\n", __func__);
return CB_ERR_ARG;
}
while (count--) {
*data = __get_mailbox_register(start);
data++;
start++;
}
return CB_SUCCESS;
}
static enum cb_err write_mailbox_regs(const u8 *data, u8 start, u8 count)
{
if (start + count >= NUM_REGISTERS) {
printk(BIOS_ERR, "%s: Invalid start or count argument.\n", __func__);
return CB_ERR_ARG;
}
while (count--) {
__set_mailbox_register(start, *data);
data++;
start++;
}
return CB_SUCCESS;
}
static void ec_command(u8 cmd)
{
outb(0, MAILBOX_INDEX);
outb(cmd, MAILBOX_DATA);
wait_ec();
}
u8 mec5035_mouse_touchpad(u8 setting)
{
u8 buf[15] = {0};
write_mailbox_regs(&setting, 2, 1);
ec_command(CMD_MOUSE_TP);
/* The vendor firmware reads 15 bytes starting at index 1, presumably
to get some sort of return code. Though I don't know for sure if
this is the case. Assume the first byte is the return code. */
read_mailbox_regs(buf, 1, 15);
return buf[0];
}
void mec5035_control_radio(enum ec_radio_dev dev, enum ec_radio_state state)
{
/* From LPC traces and userspace testing with other values,
the second byte has to be 2 for an unknown reason. */
u8 buf[RADIO_CTRL_NUM_ARGS] = {(u8)dev, 2, (u8)state};
write_mailbox_regs(buf, 2, RADIO_CTRL_NUM_ARGS);
ec_command(CMD_RADIO_CTRL);
}
void mec5035_early_init(void)
{
/* If this isn't sent the EC shuts down the system after about 15
seconds, flashing a pattern on the keyboard LEDs corresponding
to "processor failure" according to Dell service manuals. */
ec_command(CMD_CPU_OK);
}
static void mec5035_init(struct device *dev)
{
/* Unconditionally use this argument for now as this setting
is probably the most sensible default out of the 3 choices. */
mec5035_mouse_touchpad(TP_PS2_MOUSE);
pc_keyboard_init(NO_AUX_DEVICE);
mec5035_control_radio(RADIO_WLAN, get_uint_option("wlan", RADIO_ON));
mec5035_control_radio(RADIO_WWAN, get_uint_option("wwan", RADIO_ON));
mec5035_control_radio(RADIO_BT, get_uint_option("bluetooth", RADIO_ON));
}
static struct device_operations ops = {
.init = mec5035_init,
.read_resources = noop_read_resources,
.set_resources = noop_set_resources
};
static struct pnp_info pnp_dev_info[] = {
{ NULL, 0, 0, 0, }
};
static void mec5035_enable(struct device *dev)
{
pnp_enable_devices(dev, &ops, ARRAY_SIZE(pnp_dev_info), pnp_dev_info);
}
struct chip_operations ec_dell_mec5035_ops = {
.name = "MEC5035 EC",
.enable_dev = mec5035_enable,
};