summaryrefslogtreecommitdiff
path: root/src/ec/dell/mec5035/mec5035.c
blob: 68b6b2f7fb607b1dcd75fbafe34e2dc34796d8e2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/* 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,
};