[RFC] xenon: add SMC support This adds lowlevel support for the "System Management Controller" on the Xenon southbridge, which controls power, the frontpanel leds, infrared remote, tilt switch, AVIP detection, RTC and DVD tray control. It requires a userspace daemon. Signed-off-by: Felix Domke --- drivers/char/Kconfig | 10 + drivers/char/Makefile | 1 drivers/char/xenon_smc.c | 276 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 287 insertions(+) Index: linux-2.6.21/drivers/char/Kconfig =================================================================== --- linux-2.6.21.orig/drivers/char/Kconfig 2007-05-01 14:12:29.000000000 +0200 +++ linux-2.6.21/drivers/char/Kconfig 2007-05-01 14:12:34.000000000 +0200 @@ -350,6 +350,16 @@ this case. If you have never heard about all this, it's safe to say N. +config XENON_SMC + bool "Xenon System Management Controller (SMC)" + depends on PPC_XENON + help + The System Management controller in the Xbox 360. + It requires a userspace daemon available at + http://... + and controls power, the frontpanel leds, infrared remote, + tilt switch, AVIP detection, RTC and DVD tray. + config STALLION tristate "Stallion EasyIO or EC8/32 support" depends on STALDRV && BROKEN_ON_SMP Index: linux-2.6.21/drivers/char/Makefile =================================================================== --- linux-2.6.21.orig/drivers/char/Makefile 2007-05-01 14:12:29.000000000 +0200 +++ linux-2.6.21/drivers/char/Makefile 2007-05-01 14:12:34.000000000 +0200 @@ -56,6 +56,7 @@ obj-$(CONFIG_HVCS) += hvcs.o obj-$(CONFIG_SGI_MBCS) += mbcs.o obj-$(CONFIG_BRIQ_PANEL) += briq_panel.o +obj-$(CONFIG_XENON_SMC) += xenon_smc.o obj-$(CONFIG_PRINTER) += lp.o obj-$(CONFIG_TIPAR) += tipar.o Index: linux-2.6.21/drivers/char/xenon_smc.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ linux-2.6.21/drivers/char/xenon_smc.c 2007-05-01 14:12:34.000000000 +0200 @@ -0,0 +1,276 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define DRV_NAME "xenon_smc" +#define DRV_VERSION "0.1" + +struct xenon_smc +{ + void __iomem *base; + unsigned long is_active; + wait_queue_head_t wait; +}; + +#define to_smc(pdev) dev_get_drvdata(&pdev->dev) + +static struct xenon_smc *first_smc; + +static int get_smc(struct xenon_smc *ctx, u8 *msg); +static void send_smc(struct xenon_smc *ctx, void *msg); +static irqreturn_t xenon_smc_irq(int irq, void *dev_id); +static ssize_t smc_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos); +static int smc_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg); +static ssize_t smc_read(struct file *file, char __user *data, + size_t len, loff_t *ppos); +static int smc_open(struct inode *inode, struct file *file); +static int smc_release(struct inode *inode, struct file *file); +static int xenon_smc_init_one (struct pci_dev *pdev, const struct pci_device_id *ent); +static void __devexit xenon_smc_remove(struct pci_dev *pdev); + +static const struct pci_device_id xenon_smc_pci_tbl[] = { + { PCI_VDEVICE(MICROSOFT, 0x580d), 0 }, + { } /* terminate list */ +}; + +static struct pci_driver xenon_smc_pci_driver = { + .name = DRV_NAME, + .id_table = xenon_smc_pci_tbl, + .probe = xenon_smc_init_one, + .remove = __devexit_p(xenon_smc_remove) +}; + +static const struct file_operations smc_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = smc_write, + .ioctl = smc_ioctl, + .open = smc_open, + .read = smc_read, + .release = smc_release, +}; + +static struct miscdevice smc_miscdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "smc", + .fops = &smc_fops, +}; + +MODULE_DESCRIPTION("Driver for Xenon Southbridge SMC"); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(pci, xenon_smc_pci_tbl); +MODULE_VERSION(DRV_VERSION); + +static int get_smc(struct xenon_smc *ctx, u8 *msg) +{ + if (readl(ctx->base + 0x94) & 4) + { + u32 *message = (u32*)msg; + writel(4, ctx->base + 0x94); + readsl(ctx->base + 0x90, message, 4); + writel(0, ctx->base + 0x94); + return 1; + } + return 0; +} + +static void send_smc(struct xenon_smc *ctx, void *msg) +{ + while (!(readl(ctx->base + 0x84) & 4)) + cpu_relax(); + + writel(4, ctx->base + 0x84); + writesl(ctx->base + 0x80, msg, 4); + writel(0, ctx->base + 0x84); +} + +static irqreturn_t xenon_smc_irq(int irq, void *dev_id) +{ + struct pci_dev *pdev = dev_id; + struct xenon_smc *ctx = to_smc(pdev); + + unsigned int irqs = readl(ctx->base + 0x50) & 0x10000000; + if (irqs) + wake_up(&ctx->wait); + + writel(irqs, ctx->base + 0x58); // ack irq + + return IRQ_HANDLED; +} + +static ssize_t smc_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ +// struct xenon_smc *ctx = file->f_dentry->d_inode->i_private; + struct xenon_smc *ctx = first_smc; + unsigned char msg[16]; + if (len != 16) + return -EINVAL; + + if (copy_from_user(msg, data, 16)) + return -EFAULT; + + send_smc(ctx, msg); + + return 16; +} + +static int smc_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return -ENODEV; +} + +static ssize_t smc_read(struct file *file, char __user *data, + size_t len, loff_t *ppos) +{ +// struct xenon_smc *ctx = file->f_dentry->d_inode->i_private; + struct xenon_smc *ctx = first_smc; + int ret; + u8 msg[0x10]; + + if (len != 16) + return -EINVAL; + + if (!(readl(ctx->base + 0x94) & 4)) + { + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + ret = wait_event_interruptible(ctx->wait, readl(ctx->base + 0x94) & 4); + if (ret < 0) + return ret; + } + + if (get_smc(ctx, msg) == 0) + return -EAGAIN; + + if (copy_to_user(data, msg, 0x10)) + return -EFAULT; + + return 0x10; +} + +static int smc_open(struct inode *inode, struct file *file) +{ +// struct xenon_smc *ctx = file->f_dentry->d_inode->i_private; + struct xenon_smc *ctx = first_smc; + + if (test_and_set_bit(0, &ctx->is_active)) + return -EBUSY; + + return nonseekable_open(inode, file); +} + +static int smc_release(struct inode *inode, struct file *file) +{ + struct xenon_smc *ctx = first_smc; + clear_bit(0, &ctx->is_active); + return 0; +} + +static void show_logo(struct xenon_smc *ctx) +{ + unsigned char msg[16] = {0x99, 0x01, 0x63, 0}; + send_smc(ctx, msg); +} + +static int xenon_smc_init_one (struct pci_dev *pdev, const struct pci_device_id *ent) +{ + static int printed_version; + int rc; + int pci_dev_busy = 0; + struct xenon_smc *ctx; + unsigned long mmio_start; + + if (!printed_version++) + dev_printk(KERN_INFO, &pdev->dev, "version " DRV_VERSION "\n"); + + rc = pci_enable_device(pdev); + if (rc) + return rc; + + ctx = kzalloc(sizeof(struct xenon_smc), GFP_KERNEL); + if (!ctx) + goto err_out_free; + + dev_set_drvdata(&pdev->dev, ctx); + first_smc = ctx; + + rc = pci_request_regions(pdev, DRV_NAME); + if (rc) { + pci_dev_busy = 1; + goto err_out; + } + + pci_intx(pdev, 1); + + printk(KERN_INFO "attached to xenon SMC\n"); + + rc = misc_register(&smc_miscdev); + if (rc) { + printk(KERN_ERR "xenonsmc: misc_register failed.\n"); + goto err_out_regions; + } + + mmio_start = pci_resource_start (pdev, 0); + ctx->base = ioremap(mmio_start, 0x100); + + init_waitqueue_head(&ctx->wait); + + if (request_irq(pdev->irq, xenon_smc_irq, IRQF_SHARED, "xenonsmc", pdev)) { + printk(KERN_ERR "xenonsmc: request_irq failed\n"); + goto err_out_ioremap; + } + + show_logo(ctx); + + return 0; + +err_out_ioremap: + iounmap(ctx->base); + +err_out_regions: + pci_release_regions(pdev); + +err_out_free: + kfree(ctx); + +err_out: + if (!pci_dev_busy) + pci_disable_device(pdev); + return rc; +} + +static void __devexit xenon_smc_remove(struct pci_dev *pdev) +{ + struct xenon_smc *ctx = to_smc(pdev); + + misc_deregister(&smc_miscdev); + iounmap(ctx->base); + + pci_release_regions(pdev); + pci_disable_device(pdev); +} + +static int __init xenon_smc_init(void) +{ + return pci_register_driver(&xenon_smc_pci_driver); +} + +static void __exit xenon_smc_exit(void) +{ + pci_unregister_driver(&xenon_smc_pci_driver); +} + +module_init(xenon_smc_init); +module_exit(xenon_smc_exit);