mirror of https://github.com/xemu-project/xemu.git
375 lines
11 KiB
C
375 lines
11 KiB
C
/*
|
|
* QEMU SMBus Xbox System Management Controller
|
|
*
|
|
* Copyright (c) 2011 espes
|
|
* Copyright (c) 2020-2021 Matt Borgerson
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/option.h"
|
|
#include "hw/hw.h"
|
|
#include "hw/acpi/acpi.h"
|
|
#include "hw/i2c/i2c.h"
|
|
#include "hw/i2c/smbus_slave.h"
|
|
#include "qemu/config-file.h"
|
|
#include "qapi/error.h"
|
|
#include "sysemu/block-backend.h"
|
|
#include "sysemu/blockdev.h"
|
|
#include "sysemu/sysemu.h"
|
|
#include "smbus.h"
|
|
#include "sysemu/runstate.h"
|
|
#include "hw/qdev-properties.h"
|
|
#include "block/block_int-io.h"
|
|
|
|
#define TYPE_XBOX_SMC "smbus-xbox-smc"
|
|
#define XBOX_SMC(obj) OBJECT_CHECK(SMBusSMCDevice, (obj), TYPE_XBOX_SMC)
|
|
|
|
// #define DEBUG
|
|
#ifdef DEBUG
|
|
# define DPRINTF(format, ...) printf(format, ## __VA_ARGS__)
|
|
#else
|
|
# define DPRINTF(format, ...) do { } while (0)
|
|
#endif
|
|
|
|
/*
|
|
* Hardware is a PIC16LC
|
|
* http://www.xbox-linux.org/wiki/PIC
|
|
*/
|
|
|
|
#define SMC_REG_VER 0x01
|
|
#define SMC_REG_POWER 0x02
|
|
#define SMC_REG_POWER_RESET 0x01
|
|
#define SMC_REG_POWER_CYCLE 0x40
|
|
#define SMC_REG_POWER_SHUTDOWN 0x80
|
|
#define SMC_REG_TRAYSTATE 0x03
|
|
#define SMC_REG_TRAYSTATE_OPEN 0x10
|
|
#define SMC_REG_TRAYSTATE_NO_MEDIA_DETECTED 0x40
|
|
#define SMC_REG_TRAYSTATE_MEDIA_DETECTED 0x60
|
|
#define SMC_REG_AVPACK 0x04
|
|
#define SMC_REG_AVPACK_SCART 0x00
|
|
#define SMC_REG_AVPACK_HDTV 0x01
|
|
#define SMC_REG_AVPACK_VGA 0x02
|
|
#define SMC_REG_AVPACK_RFU 0x03
|
|
#define SMC_REG_AVPACK_SVIDEO 0x04
|
|
#define SMC_REG_AVPACK_COMPOSITE 0x06
|
|
#define SMC_REG_AVPACK_NONE 0x07
|
|
#define SMC_REG_FANMODE 0x05
|
|
#define SMC_REG_FANSPEED 0x06
|
|
#define SMC_REG_LEDMODE 0x07
|
|
#define SMC_REG_LEDSEQ 0x08
|
|
#define SMC_REG_CPUTEMP 0x09
|
|
#define SMC_REG_BOARDTEMP 0x0a
|
|
#define SMC_REG_TRAYEJECT 0x0c
|
|
#define SMC_REG_INTACK 0x0d
|
|
#define SMC_REG_ERROR_WRITE 0x0e
|
|
#define SMC_REG_ERROR_READ 0x0f
|
|
#define SMC_REG_INTSTATUS 0x11
|
|
#define SMC_REG_INTSTATUS_POWER 0x01
|
|
#define SMC_REG_INTSTATUS_TRAYCLOSED 0x02
|
|
#define SMC_REG_INTSTATUS_TRAYOPENING 0x04
|
|
#define SMC_REG_INTSTATUS_AVPACK_PLUG 0x08
|
|
#define SMC_REG_INTSTATUS_AVPACK_UNPLUG 0x10
|
|
#define SMC_REG_INTSTATUS_EJECT_BUTTON 0x20
|
|
#define SMC_REG_INTSTATUS_TRAYCLOSING 0x40
|
|
#define SMC_REG_RESETONEJECT 0x19
|
|
#define SMC_REG_INTEN 0x1a
|
|
#define SMC_REG_SCRATCH 0x1b
|
|
#define SMC_REG_SCRATCH_SHORT_ANIMATION 0x04
|
|
|
|
#define SMC_VERSION_LENGTH 3
|
|
|
|
typedef struct SMBusSMCDevice {
|
|
SMBusDevice smbusdev;
|
|
char *version_string;
|
|
int version_string_index;
|
|
uint8_t cmd;
|
|
uint8_t traystate_reg;
|
|
uint8_t avpack_reg;
|
|
uint8_t intstatus_reg;
|
|
uint8_t scratch_reg;
|
|
uint8_t error_reg;
|
|
} SMBusSMCDevice;
|
|
|
|
static void smc_quick_cmd(SMBusDevice *dev, uint8_t read)
|
|
{
|
|
DPRINTF("smc_quick_cmd: addr=0x%02x read=%d\n", dev->i2c.address, read);
|
|
}
|
|
|
|
static int smc_write_data(SMBusDevice *dev, uint8_t *buf, uint8_t len)
|
|
{
|
|
SMBusSMCDevice *smc = XBOX_SMC(dev);
|
|
|
|
smc->cmd = buf[0];
|
|
uint8_t cmd = smc->cmd;
|
|
buf++;
|
|
len--;
|
|
|
|
if (len < 1) return 0;
|
|
|
|
DPRINTF("smc_write_byte: addr=0x%02x cmd=0x%02x val=0x%02x\n",
|
|
dev->i2c.address, cmd, buf[0]);
|
|
|
|
switch (cmd) {
|
|
case SMC_REG_VER:
|
|
/* version string reset */
|
|
smc->version_string_index = buf[0];
|
|
break;
|
|
|
|
case SMC_REG_POWER:
|
|
if (buf[0] & (SMC_REG_POWER_RESET | SMC_REG_POWER_CYCLE)) {
|
|
qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
|
|
} else if (buf[0] & SMC_REG_POWER_SHUTDOWN) {
|
|
qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
|
|
}
|
|
break;
|
|
|
|
case SMC_REG_ERROR_WRITE:
|
|
smc->error_reg = buf[0];
|
|
break;
|
|
|
|
case SMC_REG_SCRATCH:
|
|
smc->scratch_reg = buf[0];
|
|
break;
|
|
|
|
/* challenge response
|
|
* (http://www.xbox-linux.org/wiki/PIC_Challenge_Handshake_Sequence) */
|
|
case 0x20:
|
|
break;
|
|
case 0x21:
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static uint8_t smc_receive_byte(SMBusDevice *dev)
|
|
{
|
|
SMBusSMCDevice *smc = XBOX_SMC(dev);
|
|
DPRINTF("smc_receive_byte: addr=0x%02x cmd=0x%02x\n",
|
|
dev->i2c.address, smc->cmd);
|
|
|
|
uint8_t cmd = smc->cmd++;
|
|
|
|
switch (cmd) {
|
|
case SMC_REG_VER:
|
|
return smc->version_string[
|
|
smc->version_string_index++ % SMC_VERSION_LENGTH];
|
|
|
|
case SMC_REG_TRAYSTATE:
|
|
return smc->traystate_reg;
|
|
|
|
case SMC_REG_SCRATCH:
|
|
return smc->scratch_reg;
|
|
|
|
case SMC_REG_AVPACK:
|
|
return smc->avpack_reg;
|
|
|
|
case SMC_REG_ERROR_READ:
|
|
return smc->error_reg;
|
|
|
|
case SMC_REG_INTSTATUS: {
|
|
uint8_t r = smc->intstatus_reg;
|
|
smc->intstatus_reg = 0; // FIXME: Confirm clear on read
|
|
return r;
|
|
}
|
|
|
|
/* challenge request:
|
|
* must be non-0 */
|
|
case 0x1c:
|
|
return 0x52;
|
|
case 0x1d:
|
|
return 0x72;
|
|
case 0x1e:
|
|
return 0xea;
|
|
case 0x1f:
|
|
return 0x46;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool xbox_smc_avpack_to_reg(const char *avpack, uint8_t *value)
|
|
{
|
|
uint8_t r;
|
|
|
|
if (!strcmp(avpack, "composite")) {
|
|
r = SMC_REG_AVPACK_COMPOSITE;
|
|
} else if (!strcmp(avpack, "scart")) {
|
|
r = SMC_REG_AVPACK_SCART;
|
|
} else if (!strcmp(avpack, "svideo")) {
|
|
r = SMC_REG_AVPACK_SVIDEO;
|
|
} else if (!strcmp(avpack, "vga")) {
|
|
r = SMC_REG_AVPACK_VGA;
|
|
} else if (!strcmp(avpack, "rfu")) {
|
|
r = SMC_REG_AVPACK_RFU;
|
|
} else if (!strcmp(avpack, "hdtv")) {
|
|
r = SMC_REG_AVPACK_HDTV;
|
|
} else if (!strcmp(avpack, "none")) {
|
|
r = SMC_REG_AVPACK_NONE;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
if (value) {
|
|
*value = r;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void xbox_smc_append_avpack_hint(Error **errp)
|
|
{
|
|
error_append_hint(errp, "Valid options are: composite, scart, svideo, vga, rfu, hdtv (default), none\n");
|
|
}
|
|
|
|
void xbox_smc_append_smc_version_hint(Error **errp)
|
|
{
|
|
error_append_hint(errp, "Valid versions must have 3 characters total\n");
|
|
}
|
|
|
|
static void smbus_smc_realize(DeviceState *dev, Error **errp)
|
|
{
|
|
SMBusSMCDevice *smc = XBOX_SMC(dev);
|
|
char *avpack;
|
|
char *smc_version;
|
|
|
|
smc->version_string = NULL;
|
|
smc->version_string_index = 0;
|
|
smc->traystate_reg = 0;
|
|
smc->avpack_reg = 0; /* Default value for Chihiro machine */
|
|
smc->intstatus_reg = 0;
|
|
smc->scratch_reg = 0;
|
|
smc->cmd = 0;
|
|
smc->error_reg = 0;
|
|
|
|
if (object_property_get_bool(qdev_get_machine(), "short-animation", NULL)) {
|
|
smc->scratch_reg = SMC_REG_SCRATCH_SHORT_ANIMATION;
|
|
}
|
|
|
|
avpack = object_property_get_str(qdev_get_machine(), "avpack", NULL);
|
|
if (avpack) {
|
|
if (!xbox_smc_avpack_to_reg(avpack, &smc->avpack_reg)) {
|
|
error_setg(errp, "Unsupported avpack option '%s'", avpack);
|
|
xbox_smc_append_avpack_hint(errp);
|
|
}
|
|
|
|
g_free(avpack);
|
|
}
|
|
|
|
smc_version = object_property_get_str(qdev_get_machine(), "smc-version", NULL);
|
|
if (smc_version) {
|
|
if (strlen(smc_version) != SMC_VERSION_LENGTH) {
|
|
error_setg(errp, "Unsupported SMC version string '%s'", smc_version);
|
|
xbox_smc_append_smc_version_hint(errp);
|
|
}
|
|
smc->version_string = g_strdup(smc_version);
|
|
g_free(smc_version);
|
|
}
|
|
|
|
xbox_smc_update_tray_state();
|
|
}
|
|
|
|
static void smbus_smc_class_initfn(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
SMBusDeviceClass *sc = SMBUS_DEVICE_CLASS(klass);
|
|
|
|
dc->realize = smbus_smc_realize;
|
|
sc->quick_cmd = smc_quick_cmd;
|
|
sc->receive_byte = smc_receive_byte;
|
|
sc->write_data = smc_write_data;
|
|
}
|
|
|
|
static TypeInfo smbus_smc_info = {
|
|
.name = TYPE_XBOX_SMC,
|
|
.parent = TYPE_SMBUS_DEVICE,
|
|
.instance_size = sizeof(SMBusSMCDevice),
|
|
.class_init = smbus_smc_class_initfn,
|
|
};
|
|
|
|
static void smbus_smc_register_devices(void)
|
|
{
|
|
type_register_static(&smbus_smc_info);
|
|
}
|
|
|
|
type_init(smbus_smc_register_devices)
|
|
|
|
void smbus_xbox_smc_init(I2CBus *smbus, int address)
|
|
{
|
|
DeviceState *dev;
|
|
dev = qdev_new(TYPE_XBOX_SMC);
|
|
qdev_prop_set_uint8(dev, "address", address);
|
|
qdev_realize_and_unref(dev, (BusState *)smbus, &error_fatal);
|
|
}
|
|
|
|
static void xbox_assert_extsmi(void)
|
|
{
|
|
Object *obj = object_resolve_path_type("", TYPE_ACPI_DEVICE_IF, NULL);
|
|
acpi_send_event(DEVICE(obj), ACPI_EXTSMI_STATUS);
|
|
}
|
|
|
|
void xbox_smc_power_button(void)
|
|
{
|
|
Object *obj = object_resolve_path_type("", TYPE_XBOX_SMC, NULL);
|
|
SMBusSMCDevice *smc = XBOX_SMC(obj);
|
|
smc->intstatus_reg |= SMC_REG_INTSTATUS_POWER;
|
|
xbox_assert_extsmi();
|
|
}
|
|
|
|
void xbox_smc_eject_button(void)
|
|
{
|
|
Object *obj = object_resolve_path_type("", TYPE_XBOX_SMC, NULL);
|
|
SMBusSMCDevice *smc = XBOX_SMC(obj);
|
|
smc->intstatus_reg |= SMC_REG_INTSTATUS_EJECT_BUTTON;
|
|
xbox_assert_extsmi();
|
|
}
|
|
|
|
// FIXME: Ideally this would be called on a tray state change callback (see
|
|
// tray_moved event), for now it's called explicitly from UI upon user
|
|
// interaction.
|
|
void xbox_smc_update_tray_state(void)
|
|
{
|
|
Object *obj = object_resolve_path_type("", TYPE_XBOX_SMC, NULL);
|
|
SMBusSMCDevice *smc = XBOX_SMC(obj);
|
|
|
|
const char *blk_name = "ide0-cd1";
|
|
BlockBackend *blk = blk_by_name(blk_name);
|
|
assert(blk != NULL);
|
|
|
|
if (blk_dev_is_tray_open(blk)) {
|
|
smc->traystate_reg = SMC_REG_TRAYSTATE_OPEN;
|
|
smc->intstatus_reg |= SMC_REG_INTSTATUS_TRAYOPENING;
|
|
} else {
|
|
BlockDriverState *bs = blk_bs(blk);
|
|
smc->traystate_reg = (bs && bs->drv)
|
|
? SMC_REG_TRAYSTATE_MEDIA_DETECTED
|
|
: SMC_REG_TRAYSTATE_NO_MEDIA_DETECTED;
|
|
smc->intstatus_reg |= SMC_REG_INTSTATUS_TRAYCLOSED;
|
|
}
|
|
|
|
xbox_assert_extsmi();
|
|
}
|