From patchwork Sun Nov 22 22:01:48 2009 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Albert Herranz X-Patchwork-Id: 39016 Return-Path: X-Original-To: patchwork-incoming@ozlabs.org Delivered-To: patchwork-incoming@ozlabs.org Received: from bilbo.ozlabs.org (localhost [127.0.0.1]) by ozlabs.org (Postfix) with ESMTP id C6BD31E18F for ; Mon, 23 Nov 2009 09:05:52 +1100 (EST) Received: from smtp106.mail.ukl.yahoo.com (smtp106.mail.ukl.yahoo.com [77.238.184.38]) by ozlabs.org (Postfix) with SMTP id 7C50A100813 for ; Mon, 23 Nov 2009 09:02:18 +1100 (EST) Received: (qmail 97202 invoked from network); 22 Nov 2009 22:02:16 -0000 DomainKey-Signature: a=rsa-sha1; q=dns; c=nofws; s=s1024; d=yahoo.es; h=Received:X-Yahoo-SMTP:X-YMail-OSG:X-Yahoo-Newman-Property:From:To:Cc:Subject:Date:Message-Id:X-Mailer:In-Reply-To:References; b=FZkgIIgrOAaW+jwt+vGB42H8zu3wTG/h7IjCLLykis8RKOBnHffNXp2UaLbI6tRhxSqVGRNTpkZibFmie18Dz/AtuFB9Tah0lbYSK4wPHswBQs/fYOSKtZ5DVbRG+o8l+BmtyM6xjReQKeNhUOzHk9iyrL65mPGr3xUcKVRCXm8= ; Received: from 59.Red-88-24-158.staticIP.rima-tde.net (albert_herranz@88.24.158.59 with login) by smtp106.mail.ukl.yahoo.com with SMTP; 22 Nov 2009 22:02:15 +0000 GMT X-Yahoo-SMTP: czee06uswBAtfIYshc.kP27UlfEXaxwWNSjJ X-YMail-OSG: KPEwS4gVM1lQPZB_puG1rFOMGADZEFHcAn.gtOt52LuzzBbjXqqQhsMDYRX_1N5qCjylCjk8dEM3uFU0KWga7uQRc0Bp4WeajxjqcUuNOGX1d0TbTWQ0WYmki5hVpXnls9FWDAbMjhijaQLu9WunEuYZrPYirPfMXijHvQ6kJ0OiSOiJsfNNXlChInUdt8YiIOeHOyu4lkE_eZ405lF5ZLDDRsCHU5mf4oZUpHkACQkJJdJPeu8PTJmc7UubibzCSVvqDBLOL4hDpdzwQq.8nLTXTuNw13spaRwoEy8huSU70wi2yS9mqOLoIrkyW33fMmKzIVa9Gnp9QeoXqOQ3vmA- X-Yahoo-Newman-Property: ymail-3 From: Albert Herranz To: linuxppc-dev@lists.ozlabs.org Subject: [RFC PATCH 17/19] powerpc: wii: bootmii starlet 'mini' firmware support Date: Sun, 22 Nov 2009 23:01:48 +0100 Message-Id: <1258927311-4340-18-git-send-email-albert_herranz@yahoo.es> X-Mailer: git-send-email 1.6.3.3 In-Reply-To: <1258927311-4340-17-git-send-email-albert_herranz@yahoo.es> References: <1258927311-4340-1-git-send-email-albert_herranz@yahoo.es> <1258927311-4340-2-git-send-email-albert_herranz@yahoo.es> <1258927311-4340-3-git-send-email-albert_herranz@yahoo.es> <1258927311-4340-4-git-send-email-albert_herranz@yahoo.es> <1258927311-4340-5-git-send-email-albert_herranz@yahoo.es> <1258927311-4340-6-git-send-email-albert_herranz@yahoo.es> <1258927311-4340-7-git-send-email-albert_herranz@yahoo.es> <1258927311-4340-8-git-send-email-albert_herranz@yahoo.es> <1258927311-4340-9-git-send-email-albert_herranz@yahoo.es> <1258927311-4340-10-git-send-email-albert_herranz@yahoo.es> <1258927311-4340-11-git-send-email-albert_herranz@yahoo.es> <1258927311-4340-12-git-send-email-albert_herranz@yahoo.es> <1258927311-4340-13-git-send-email-albert_herranz@yahoo.es> <1258927311-4340-14-git-send-email-albert_herranz@yahoo.es> <1258927311-4340-15-git-send-email-albert_herranz@yahoo.es> <1258927311-4340-16-git-send-email-albert_herranz@yahoo.es> <1258927311-4340-17-git-send-email-albert_herranz@yahoo.es> Cc: Albert Herranz X-BeenThere: linuxppc-dev@lists.ozlabs.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: Linux on PowerPC Developers Mail List List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: linuxppc-dev-bounces+patchwork-incoming=ozlabs.org@lists.ozlabs.org Errors-To: linuxppc-dev-bounces+patchwork-incoming=ozlabs.org@lists.ozlabs.org Add support for the BootMii 'mini' firmware replacement for the Starlet processor. 'mini' is an open source IOS replacement written from scratch by Team Twiizers. It grants full access to the hardware found on the Nintendo Wii video game console via a custom IPC mechanism. Signed-off-by: Albert Herranz --- arch/powerpc/include/asm/starlet-mini.h | 175 ++++ arch/powerpc/include/asm/starlet.h | 26 + arch/powerpc/platforms/embedded6xx/Kconfig | 14 +- arch/powerpc/platforms/embedded6xx/Makefile | 1 + arch/powerpc/platforms/embedded6xx/starlet-mipc.c | 1053 +++++++++++++++++++++ 5 files changed, 1268 insertions(+), 1 deletions(-) create mode 100644 arch/powerpc/include/asm/starlet-mini.h create mode 100644 arch/powerpc/include/asm/starlet.h create mode 100644 arch/powerpc/platforms/embedded6xx/starlet-mipc.c diff --git a/arch/powerpc/include/asm/starlet-mini.h b/arch/powerpc/include/asm/starlet-mini.h new file mode 100644 index 0000000..bfa2a2c --- /dev/null +++ b/arch/powerpc/include/asm/starlet-mini.h @@ -0,0 +1,175 @@ +/* + * arch/powerpc/include/asm/starlet-mini.h + * + * Definitions for the 'mini' firmware replacement for Starlet + * Copyright (C) 2009 The GameCube Linux Team + * Copyright (C) 2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#ifndef __STARLET_MINI_H +#define __STARLET_MINI_H + +#ifdef CONFIG_STARLET_MINI + +/* + * mini ipc call numbering scheme + */ + +#define _MIPC_FAST 0x01 +#define _MIPC_SLOW 0x00 + +#define _MIPC_DEV_SYS 0x00 +#define _MIPC_DEV_NAND 0x01 +#define _MIPC_DEV_SDHC 0x02 +#define _MIPC_DEV_KEYS 0x03 +#define _MIPC_DEV_AES 0x04 +#define _MIPC_DEV_BOOT2 0x05 +#define _MIPC_DEV_PPC 0x06 +#define _MIPC_DEV_SDMMC 0x07 + +#define _MIPC_SYS_PING 0x0000 +#define _MIPC_SYS_JUMP 0x0001 +#define _MIPC_SYS_GETVERS 0x0002 +#define _MIPC_SYS_GETGITS 0x0003 +#define _MIPC_SYS_WRITE32 0x0100 +#define _MIPC_SYS_WRITE16 0x0101 +#define _MIPC_SYS_WRITE8 0x0102 +#define _MIPC_SYS_READ32 0x0103 +#define _MIPC_SYS_READ16 0x0104 +#define _MIPC_SYS_READ8 0x0105 +#define _MIPC_SYS_SET32 0x0106 +#define _MIPC_SYS_SET16 0x0107 +#define _MIPC_SYS_SET8 0x0108 +#define _MIPC_SYS_CLEAR32 0x0109 +#define _MIPC_SYS_CLEAR16 0x010a +#define _MIPC_SYS_CLEAR8 0x010b +#define _MIPC_SYS_MASK32 0x010c +#define _MIPC_SYS_MASK16 0x010d +#define _MIPC_SYS_MASK8 0x010e + +#define _MIPC_NAND_RESET 0x0000 +#define _MIPC_NAND_GETID 0x0001 +#define _MIPC_NAND_READ 0x0002 +#define _MIPC_NAND_WRITE 0x0003 +#define _MIPC_NAND_ERASE 0x0004 +#define _MIPC_NAND_STATUS 0x0005 + +#define _MIPC_SDHC_DISCOVER 0x0000 +#define _MIPC_SDHC_EXIT 0x0001 + +#define _MIPC_SDMMC_ACK 0x0000 +#define _MIPC_SDMMC_READ 0x0001 +#define _MIPC_SDMMC_WRITE 0x0002 +#define _MIPC_SDMMC_STATE 0x0003 +#define _MIPC_SDMMC_SIZE 0x0004 + +#define _MIPC_KEYS_GETOTP 0x0000 +#define _MIPC_KEYS_GETEEP 0x0001 + +#define _MIPC_AES_RESET 0x0000 +#define _MIPC_AES_SETIV 0x0001 +#define _MIPC_AES_SETKEY 0x0002 +#define _MIPC_AES_DECRYPT 0x0003 + +#define _MIPC_BOOT2_RUN 0x0000 +#define _MIPC_BOOT2_TMD 0x0001 + +#define _MIPC_PPC_BOOT 0x0000 + + +/* + * + */ + +#define _MIPC_MODEBITS 8 +#define _MIPC_DEVBITS 8 +#define _MIPC_NRBITS 16 + +#define _MIPC_MODEMASK ((1 << _MIPC_MODEBITS)-1) +#define _MIPC_DEVMASK ((1 << _MIPC_DEVBITS)-1) +#define _MIPC_NRMASK ((1 << _MIPC_NRBITS)-1) + +#define _MIPC_MODESHIFT (_MIPC_DEVSHIFT + _MIPC_DEVBITS) +#define _MIPC_DEVSHIFT (_MIPC_NRSHIFT + _MIPC_NRBITS) +#define _MIPC_NRSHIFT 0 + +#define _MIPC(mode, dev, nr) \ + (((mode) << _MIPC_MODESHIFT) | \ + ((dev) << _MIPC_DEVSHIFT) | \ + ((nr) << _MIPC_NRSHIFT)) + +#define _MIPC_FAST_SYS(nr) _MIPC(_MIPC_FAST, _MIPC_DEV_SYS, nr) + +#define MIPC_SYS_PING _MIPC_FAST_SYS(_MIPC_SYS_PING) +#define MIPC_SYS_WRITE32 _MIPC_FAST_SYS(_MIPC_SYS_WRITE32) +#define MIPC_SYS_WRITE16 _MIPC_FAST_SYS(_MIPC_SYS_WRITE16) +#define MIPC_SYS_WRITE8 _MIPC_FAST_SYS(_MIPC_SYS_WRITE8) +#define MIPC_SYS_READ32 _MIPC_FAST_SYS(_MIPC_SYS_READ32) +#define MIPC_SYS_READ16 _MIPC_FAST_SYS(_MIPC_SYS_READ16) +#define MIPC_SYS_READ8 _MIPC_FAST_SYS(_MIPC_SYS_READ8) +#define MIPC_SYS_SET32 _MIPC_FAST_SYS(_MIPC_SYS_SET32) +#define MIPC_SYS_SET16 _MIPC_FAST_SYS(_MIPC_SYS_SET16) +#define MIPC_SYS_SET8 _MIPC_FAST_SYS(_MIPC_SYS_SET8) +#define MIPC_SYS_CLEAR32 _MIPC_FAST_SYS(_MIPC_SYS_CLEAR32) +#define MIPC_SYS_CLEAR16 _MIPC_FAST_SYS(_MIPC_SYS_CLEAR16) +#define MIPC_SYS_CLEAR8 _MIPC_FAST_SYS(_MIPC_SYS_CLEAR8) +#define MIPC_SYS_MASK32 _MIPC_FAST_SYS(_MIPC_SYS_MASK32) +#define MIPC_SYS_MASK16 _MIPC_FAST_SYS(_MIPC_SYS_MASK16) +#define MIPC_SYS_MASK8 _MIPC_FAST_SYS(_MIPC_SYS_MASK8) + +#define MIPC_REQ_MAX_ARGS 6 + +struct mipc_infohdr { + char magic[3]; + u8 version; + phys_addr_t mem2_boundary; + phys_addr_t ipc_in; + size_t ipc_in_size; + phys_addr_t ipc_out; + size_t ipc_out_size; +}; + +struct mipc_device; +struct mipc_req; + +extern int mipc_infohdr_get(struct mipc_infohdr **hdrp); +extern void mipc_infohdr_put(struct mipc_infohdr *hdr); + +extern u32 mipc_in_be32(const volatile u32 __iomem *addr); +extern u16 mipc_in_be16(const volatile u16 __iomem *addr); +extern u8 mipc_in_8(const volatile u8 __iomem *addr); + +extern void mipc_out_be32(const volatile u32 __iomem *addr, u32 val); +extern void mipc_out_be16(const volatile u16 __iomem *addr, u16 val); +extern void mipc_out_8(const volatile u8 __iomem *addr, u8 val); + +extern void mipc_clear_bit(int nr, volatile unsigned long *addr); +extern void mipc_set_bit(int nr, volatile unsigned long *addr); +extern void mipc_clrsetbits_be32(const volatile u32 __iomem *addr, + u32 clear, u32 set); + +extern void mipc_wmb(void); + +#else + +struct mipc_infohdr; + +static inline int mipc_infohdr_get(struct mipc_infohdr **hdrp) +{ + return -ENODEV; +} + +static inline void mipc_infohdr_put(struct mipc_infohdr *hdr) +{ +} + +#endif /* CONFIG_STARLET_MINI */ + +#endif /* __STARLET_MINI_H */ + diff --git a/arch/powerpc/include/asm/starlet.h b/arch/powerpc/include/asm/starlet.h new file mode 100644 index 0000000..15e4ac3 --- /dev/null +++ b/arch/powerpc/include/asm/starlet.h @@ -0,0 +1,26 @@ +/* + * arch/powerpc/include/asm/starlet.h + * + * Definitions for the Starlet co-processor + * Copyright (C) 2009 The GameCube Linux Team + * Copyright (C) 2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#ifndef __STARLET_H +#define __STARLET_H + + +enum starlet_ipc_flavour { + STARLET_IPC_IOS, + STARLET_IPC_MINI, +}; + +enum starlet_ipc_flavour starlet_get_ipc_flavour(void); + +#endif /* __STARLET_H */ diff --git a/arch/powerpc/platforms/embedded6xx/Kconfig b/arch/powerpc/platforms/embedded6xx/Kconfig index 490f89e..f207944 100644 --- a/arch/powerpc/platforms/embedded6xx/Kconfig +++ b/arch/powerpc/platforms/embedded6xx/Kconfig @@ -125,5 +125,17 @@ config GAMECUBE config HLWD_PIC bool depends on STARLET_MINI - default y + +config STARLET_MINI + bool "BootMii Starlet 'mini' firmware support" + depends on WII && EXPERIMENTAL + help + If you say yes to this option, support will be included for the + BootMii 'mini' firmware running on the Starlet processor of the Wii. + The 'mini' firmware enables full access from Linux to all the + hardware contained in the video game console. + + This option is harmless if the 'mini' firmware is not installed. + + If in doubt, say Y here. diff --git a/arch/powerpc/platforms/embedded6xx/Makefile b/arch/powerpc/platforms/embedded6xx/Makefile index c1dcc54..f41a144 100644 --- a/arch/powerpc/platforms/embedded6xx/Makefile +++ b/arch/powerpc/platforms/embedded6xx/Makefile @@ -11,3 +11,4 @@ obj-$(CONFIG_USBGECKO_UDBG) += usbgecko_udbg.o obj-$(CONFIG_FLIPPER_PIC) += flipper-pic.o obj-$(CONFIG_GAMECUBE) += gamecube.o gamecube_dev.o obj-$(CONFIG_HLWD_PIC) += hlwd-pic.o +obj-$(CONFIG_STARLET_MINI) += starlet-mipc.o diff --git a/arch/powerpc/platforms/embedded6xx/starlet-mipc.c b/arch/powerpc/platforms/embedded6xx/starlet-mipc.c new file mode 100644 index 0000000..a6456a6 --- /dev/null +++ b/arch/powerpc/platforms/embedded6xx/starlet-mipc.c @@ -0,0 +1,1053 @@ +/* + * arch/powerpc/platforms/embedded6xx/starlet-mipc.c + * + * IPC driver for the 'mini' firmware replacement for Starlet + * Copyright (C) 2009 The GameCube Linux Team + * Copyright (C) 2009 Albert Herranz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ +#define DRV_MODULE_NAME "starlet-mipc" +#define pr_fmt(fmt) DRV_MODULE_NAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include /* for mdelay() */ +#include /* for _PAGE_KERNEL_NC */ +#include /* for get_tbl() */ +#include + +#include "hlwd-pic.h" + + +#define DRV_DESCRIPTION "IPC driver for 'mini'" +#define DRV_AUTHOR "Albert Herranz" + +static char mipc_driver_version[] = "0.5i"; + +#define MIPC_OF_COMPATIBLE "twiizers,starlet-mini-ipc" + +#define HW_AHBPROT_OF_COMPATIBLE "nintendo,hollywood-ahbprot" +#define HW_GPIO_OF_COMPATIBLE "nintendo,hollywood-gpio" + + +/* + * Hardware registers + */ +#define MIPC_TXBUF 0x00 /* data from cpu to starlet */ + +#define MIPC_CSR 0x04 +#define MIPC_CSR_TXSTART (1<<0) /* start transmit */ +#define MIPC_CSR_TBEI (1<<1) /* tx buf empty int */ +#define MIPC_CSR_RBFI (1<<2) /* rx buf full int */ +#define MIPC_CSR_RXRDY (1<<3) /* receiver ready */ +#define MIPC_CSR_RBFIMASK (1<<4) /* rx buf full int mask */ +#define MIPC_CSR_TBEIMASK (1<<5) /* tx buf empty int mask */ + +#define MIPC_RXBUF 0x08 /* data from starlet to cpu */ + + +#define MIPC_MIN_VER 1 +#define MIPC_MAX_VER 1 + +#define MIPC_INITIAL_TAG 1 + +#define MIPC_SYS_IO_TIMEOUT (250*1000) /* usecs */ +#define MIPC_DEV_TIMEOUT (10*1000*1000) /* usecs */ + + +/* + * Firmware request. + * + */ +struct mipc_req { + union { + struct { + u8 flags; + u8 device; + u16 req; + }; + u32 code; + }; + u32 tag; + u32 args[MIPC_REQ_MAX_ARGS]; +} __attribute__ ((packed)); + +/* + * + */ +struct mipc_device { + void __iomem *io_base; + int irq; + + struct device *dev; + + spinlock_t call_lock; /* serialize firmware calls */ + spinlock_t io_lock; /* serialize access to io registers */ + + struct mipc_infohdr *hdr; + + struct mipc_req *in_ring; + size_t in_ring_size; + volatile u16 intail_idx; + + struct mipc_req *out_ring; + size_t out_ring_size; + volatile u16 outhead_idx; + + u32 tag; +}; + +#define __spin_event_timeout(condition, timeout_usecs, result, __end_tbl) \ + for (__end_tbl = get_tbl() + tb_ticks_per_usec * timeout_usecs; \ + !(result = (condition)) && (int)(__end_tbl - get_tbl()) > 0;) + +/* + * Update control and status register. + */ +static inline void mipc_update_csr(void __iomem *io_base, u32 val) +{ + u32 csr; + + csr = in_be32(io_base + MIPC_CSR); + /* preserve interrupt masks */ + csr &= MIPC_CSR_RBFIMASK | MIPC_CSR_TBEIMASK; + csr |= val; + out_be32(io_base + MIPC_CSR, csr); +} + +static u16 mipc_peek_outtail(void __iomem *io_base) +{ + return in_be32(io_base + MIPC_RXBUF) & 0xffff; +} + +static u16 mipc_peek_inhead(void __iomem *io_base) +{ + return in_be32(io_base + MIPC_RXBUF) >> 16; +} + +static u16 mipc_peek_first_intail(void __iomem *io_base) +{ + return in_be32(io_base + MIPC_TXBUF) & 0xffff; +} + +static u16 mipc_peek_first_outhead(void __iomem *io_base) +{ + return in_be32(io_base + MIPC_TXBUF) >> 16; +} + +static void mipc_poke_intail(struct mipc_device *ipc_dev, u16 val) +{ + void __iomem *io_base = ipc_dev->io_base; + unsigned long flags; + + spin_lock_irqsave(&ipc_dev->io_lock, flags); + out_be32(io_base + MIPC_TXBUF, + (in_be32(io_base + MIPC_TXBUF) & 0xffff0000) | val); + spin_unlock_irqrestore(&ipc_dev->io_lock, flags); +} + +static void mipc_poke_outhead(struct mipc_device *ipc_dev, u16 val) +{ + void __iomem *io_base = ipc_dev->io_base; + unsigned long flags; + + spin_lock_irqsave(&ipc_dev->io_lock, flags); + out_be32(io_base + MIPC_TXBUF, + (in_be32(io_base + MIPC_TXBUF) & 0x0000ffff) | val<<16); + spin_unlock_irqrestore(&ipc_dev->io_lock, flags); +} + + + +static u16 mipc_get_next_intail(struct mipc_device *ipc_dev) +{ + return (ipc_dev->intail_idx + 1) & (ipc_dev->in_ring_size - 1); +} + +static u16 mipc_get_next_outhead(struct mipc_device *ipc_dev) +{ + return (ipc_dev->outhead_idx + 1) & (ipc_dev->out_ring_size - 1); +} + +static void mipc_print_req(struct mipc_req *req) +{ + int i; + + pr_info("req %pP = {\n", req); + pr_cont("code = %08X, tag = %08X\n", req->code, req->tag); + for (i = 0; i < MIPC_REQ_MAX_ARGS; i++) + pr_cont("arg[%d] = %08X\n", i, req->args[i]); + pr_cont("}\n"); +} + +#ifdef DEBUG_RINGS +static void mipc_dump_ring(struct mipc_req *req, size_t count) +{ + int i; + + for (i = 0; i < count; i++) + pr_devel("%d: %X (%08X)\n", i, req[i].tag, req[i].code); +} +#endif + +static void mipc_print_status(struct mipc_device *ipc_dev) +{ + size_t in_size, out_size; + + in_size = ipc_dev->in_ring_size * sizeof(*ipc_dev->in_ring); + out_size = ipc_dev->out_ring_size * sizeof(*ipc_dev->out_ring); + + pr_info("ppc: intail_idx=%u, outhead_idx=%u\n", + ipc_dev->intail_idx, ipc_dev->outhead_idx); + pr_cont("arm: inhead_idx=%u, outtail_idx=%u\n", + mipc_peek_inhead(ipc_dev->io_base), + mipc_peek_outtail(ipc_dev->io_base)); + pr_cont("in_ring=%uK@%p, out_ring=%uK@%p\n", + in_size / 1024, ipc_dev->in_ring, + out_size / 1024, ipc_dev->out_ring); +} + +static int mipc_send_req(struct mipc_device *ipc_dev, unsigned long timeout, + struct mipc_req *req) +{ + void __iomem *io_base = ipc_dev->io_base; + struct mipc_req *firm_req; + unsigned long ctx; + int result; + int error = 0; + + if (mipc_peek_inhead(io_base) == mipc_get_next_intail(ipc_dev)) { + pr_err("%s queue full\n", "ppc->arm ipc"); + __spin_event_timeout(mipc_peek_inhead(io_base) != + mipc_get_next_intail(ipc_dev), + timeout, result, ctx) { + /* busy wait */ + cpu_relax(); + } + if (!result) { + pr_err("%s queue drain timed out\n", "ppc->arm ipc"); + error = -EIO; + goto out; + } + } + + firm_req = ipc_dev->in_ring + ipc_dev->intail_idx; + *firm_req = *req; + ipc_dev->intail_idx = mipc_get_next_intail(ipc_dev); + mipc_poke_intail(ipc_dev, ipc_dev->intail_idx); + mipc_update_csr(ipc_dev->io_base, MIPC_CSR_TXSTART); +out: + if (error) + pr_devel("exit %d\n", error); + return error; +} + +static int __mipc_recv_req(struct mipc_device *ipc_dev, unsigned long timeout, + struct mipc_req *req) +{ + void __iomem *io_base = ipc_dev->io_base; + struct mipc_req *firm_req; + unsigned long ctx; + int result; + int error = 0; + + __spin_event_timeout(mipc_peek_outtail(io_base) != ipc_dev->outhead_idx, + timeout, result, ctx) { + /* busy wait */ + cpu_relax(); + } + if (mipc_peek_outtail(io_base) == ipc_dev->outhead_idx) { + error = -EIO; + goto out; + } + firm_req = ipc_dev->out_ring + ipc_dev->outhead_idx; + *req = *firm_req; + ipc_dev->outhead_idx = mipc_get_next_outhead(ipc_dev); + mipc_poke_outhead(ipc_dev, ipc_dev->outhead_idx); +out: + return error; +} + +static int mipc_recv_req(struct mipc_device *ipc_dev, unsigned long timeout, + struct mipc_req *req) +{ + int error; + + error = __mipc_recv_req(ipc_dev, timeout, req); + if (error) + pr_devel("arm->ppc ipc request timed out (%d)\n", error); + return error; +} + +static int mipc_recv_tagged(struct mipc_device *ipc_dev, + unsigned long timeout, + u32 code, u32 tag, + struct mipc_req *req) +{ + unsigned long ctx; + int result; + int error; + + error = mipc_recv_req(ipc_dev, timeout, req); + if (error) + goto out; + + __spin_event_timeout(req->code == code && req->tag == tag, + timeout, result, ctx) { + pr_devel("expected: code=%08X, tag=%08X\n", code, tag); + mipc_print_req(req); + pr_devel("+++ status\n"); + mipc_print_status(ipc_dev); +#ifdef DEBUG_RINGS + pr_devel("+++ in_ring\n"); + mipc_dump_ring(ipc_dev->in_ring, ipc_dev->in_ring_size); + pr_devel("+++ out_ring\n"); + mipc_dump_ring(ipc_dev->out_ring, ipc_dev->out_ring_size); +#endif + + error = mipc_recv_req(ipc_dev, timeout, req); + if (error) + goto out; + } + if (!result) { + pr_err("%s: recv timed out\n", __func__); + error = -EIO; + goto out; + } else + error = 0; + +out: + if (error) + pr_devel("exit %d\n", error); + return error; +} + +static void __mipc_fill_req(struct mipc_req *req, u32 code) +{ + memset(req, 0, sizeof(*req)); + req->code = code; +} + +static int mipc_sendrecv_call(struct mipc_device *ipc_dev, + unsigned long timeout, + struct mipc_req *req, struct mipc_req *resp) +{ + unsigned long flags; + int error; + + spin_lock_irqsave(&ipc_dev->call_lock, flags); + req->tag = ipc_dev->tag++; + error = mipc_send_req(ipc_dev, timeout, req); + if (error) + goto out; + error = mipc_recv_tagged(ipc_dev, timeout, req->code, req->tag, resp); +out: + spin_unlock_irqrestore(&ipc_dev->call_lock, flags); + + return error; +} + +static int mipc_sendrecv1_call(struct mipc_device *ipc_dev, + unsigned long timeout, + struct mipc_req *resp, u32 code, u32 arg) +{ + struct mipc_req req; + + __mipc_fill_req(&req, code); + req.args[0] = arg; + return mipc_sendrecv_call(ipc_dev, timeout, &req, resp); +} + + +static int mipc_send_call(struct mipc_device *ipc_dev, unsigned long timeout, + struct mipc_req *req) +{ + unsigned long flags; + int error; + + spin_lock_irqsave(&ipc_dev->call_lock, flags); + req->tag = ipc_dev->tag++; + error = mipc_send_req(ipc_dev, timeout, req); + spin_unlock_irqrestore(&ipc_dev->call_lock, flags); + + return error; +} + +static int mipc_send2_call(struct mipc_device *ipc_dev, unsigned long timeout, + u32 code, u32 arg1, u32 arg2) +{ + struct mipc_req req; + + __mipc_fill_req(&req, code); + req.args[0] = arg1; + req.args[1] = arg2; + return mipc_send_call(ipc_dev, timeout, &req); +} + +static int mipc_send3_call(struct mipc_device *ipc_dev, unsigned long timeout, + u32 code, u32 arg1, u32 arg2, u32 arg3) +{ + struct mipc_req req; + + __mipc_fill_req(&req, code); + req.args[0] = arg1; + req.args[1] = arg2; + req.args[2] = arg3; + return mipc_send_call(ipc_dev, timeout, &req); +} + +static int mipc_flush_send(struct mipc_device *ipc_dev, unsigned long timeout) +{ + void __iomem *io_base = ipc_dev->io_base; + unsigned long ctx; + int result; + int error = 0; + + __spin_event_timeout(mipc_peek_inhead(io_base) == ipc_dev->intail_idx, + timeout, result, ctx) { + /* busy wait */ + cpu_relax(); + } + if (!result) { + pr_err("%s: flush timed out\n", __func__); + error = -EIO; + goto out; + } +out: + if (error) + pr_devel("exit %d\n", error); + return error; +} + +static void mipc_flush_recv(struct mipc_device *ipc_dev, + unsigned long timeout) +{ + struct mipc_req req; + int error; + + do { + error = __mipc_recv_req(ipc_dev, timeout, &req); + } while (!error); +} + + + +static struct mipc_device *mipc_device_instance; + +struct mipc_device *mipc_get_device(void) +{ + if (!mipc_device_instance) + pr_err("uninitialized device instance!\n"); + return mipc_device_instance; +} + +static int mipc_ping(struct mipc_device *ipc_dev, unsigned long timeout) +{ + struct mipc_req resp; + int error; + + error = mipc_sendrecv1_call(ipc_dev, timeout, &resp, MIPC_SYS_PING, 0); + if (error) + pr_devel("exit %d\n", error); + return error; +} + +#define __declare_ipc_send2_accessor(_name, _suffix, _size, _call) \ +void mipc_##_name##_suffix(_size a, void __iomem *addr) \ +{ \ + struct mipc_device *ipc_dev = mipc_get_device(); \ + int error; \ + \ + error = mipc_send2_call(ipc_dev, MIPC_SYS_IO_TIMEOUT, _call, \ + (u32)addr, a); \ + if (!error) \ + return; \ + \ + pr_devel(__stringify(_name, _suffix) "(%p,%x)\n", addr, a); \ + BUG(); \ +} + +#define __declare_ipc_send3_accessor(_name, _suffix, _size, _call) \ +void mipc_##_name##_suffix(_size a, _size b, void __iomem *addr) \ +{ \ + struct mipc_device *ipc_dev = mipc_get_device(); \ + int error; \ + \ + error = mipc_send3_call(ipc_dev, MIPC_SYS_IO_TIMEOUT, _call, \ + (u32)addr, a, b); \ + if (!error) \ + return; \ + \ + pr_devel(__stringify(_name, _suffix) "(%p,%x,%x)\n", addr, a, b);\ + BUG(); \ +} + +#define __declare_ipc_sendrecv1_accessor(_name, _suffix, _size, _call) \ +_size mipc_##_name##_suffix(void __iomem *addr) \ +{ \ + struct mipc_device *ipc_dev = mipc_get_device(); \ + struct mipc_req resp; \ + int error; \ + \ + error = mipc_sendrecv1_call(ipc_dev, MIPC_SYS_IO_TIMEOUT, \ + &resp, _call, (u32)addr); \ + if (!error) \ + return resp.args[0]; \ + \ + pr_devel(__stringify(_name, _suffix) "(%p)\n", addr); \ + BUG(); \ + return 0; \ +} + +__declare_ipc_sendrecv1_accessor(read, l, unsigned int, MIPC_SYS_READ32) +__declare_ipc_sendrecv1_accessor(read, w, unsigned short, MIPC_SYS_READ16) +__declare_ipc_sendrecv1_accessor(read, b, unsigned char, MIPC_SYS_READ8) + +__declare_ipc_send2_accessor(write, l, unsigned int, MIPC_SYS_WRITE32) +__declare_ipc_send2_accessor(write, w, unsigned short, MIPC_SYS_WRITE16) +__declare_ipc_send2_accessor(write, b, unsigned char, MIPC_SYS_WRITE8) + +__declare_ipc_send2_accessor(setbit, l, unsigned int, MIPC_SYS_SET32) +__declare_ipc_send2_accessor(clearbit, l, unsigned int, MIPC_SYS_CLEAR32) +__declare_ipc_send3_accessor(clrsetbits, l, unsigned int, MIPC_SYS_MASK32) + +void mipc_wmb(void) +{ + struct mipc_device *ipc_dev = mipc_get_device(); + int error; + + error = mipc_ping(ipc_dev, MIPC_SYS_IO_TIMEOUT); + if (!error) + return; + + pr_devel(__stringify(_name, _suffix) "()\n"); + BUG(); +} + + +#define BITOP_MASK(nr) (1UL << ((nr) % BITS_PER_LONG)) +#define BITOP_WORD(nr) ((nr) / BITS_PER_LONG) + +void mipc_clear_bit(int nr, volatile unsigned long *addr) +{ + unsigned long mask = BITOP_MASK(nr); + unsigned long *p = ((unsigned long *)addr) + BITOP_WORD(nr); + + mipc_clearbitl(mask, p); +} + +void mipc_set_bit(int nr, volatile unsigned long *addr) +{ + unsigned long mask = BITOP_MASK(nr); + unsigned long *p = ((unsigned long *)addr) + BITOP_WORD(nr); + + mipc_setbitl(mask, p); +} + +void mipc_clrsetbits_be32(const volatile u32 __iomem *addr, u32 clear, u32 set) +{ + mipc_clrsetbitsl(clear, set, (void __iomem *)addr); +} + +u32 mipc_in_be32(const volatile u32 __iomem *addr) +{ + return mipc_readl((void __iomem *)addr); +} + +void mipc_out_be32(const volatile u32 __iomem *addr, u32 val) +{ + mipc_writel(val, (void __iomem *)addr); +} + +u16 mipc_in_be16(const volatile u16 __iomem *addr) +{ + return mipc_readw((void __iomem *)addr); +} + +void mipc_out_be16(const volatile u16 __iomem *addr, u16 val) +{ + mipc_writew(val, (void __iomem *)addr); +} + +u8 mipc_in_8(const volatile u8 __iomem *addr) +{ + return mipc_readb((void __iomem *)addr); +} + +void mipc_out_8(const volatile u8 __iomem *addr, u8 val) +{ + mipc_writeb(val, (void __iomem *)addr); +} + + +static int mipc_check_address(phys_addr_t pa) +{ + if (pa < 0x10000000 || pa > 0x14000000) + return -EINVAL; + return 0; +} + +int mipc_infohdr_get(struct mipc_infohdr **hdrp) +{ + struct device_node *np; + struct resource res; + struct mipc_infohdr *hdr; + char magic[4]; + phys_addr_t __iomem *p; + int error = -ENODEV; + + np = of_find_compatible_node(NULL, NULL, MIPC_OF_COMPATIBLE); + if (!np) { + pr_err("unable to find compatible node %s\n", + MIPC_OF_COMPATIBLE); + goto out; + } + + error = of_address_to_resource(np, 1, &res); + if (error) { + pr_err("mini ipc header ptr unavailable\n"); + goto out_put; + } + + /* grab mini information header location */ + p = ioremap(res.start, res.end - res.start + 1); + if (!p) { + pr_err("unable to ioremap mini ipc header ptr\n"); + error = -ENOMEM; + goto out_put; + } + /* check that the header pointer points to MEM2 */ + if (mipc_check_address(*p)) { + pr_devel("wrong mini ipc header address %pP\n", (void *)*p); + goto out_unmap_p; + } + + hdr = (struct mipc_infohdr *)ioremap_prot(*p, sizeof(*hdr), + PAGE_KERNEL); + if (!hdr) { + pr_err("unable to ioremap mini ipc header\n"); + error = -ENOMEM; + goto out_unmap_p; + } + __dma_sync(hdr, sizeof(*hdr), DMA_FROM_DEVICE); + + memcpy(magic, hdr->magic, 3); + magic[3] = 0; + if (memcmp(magic, "IPC", 3)) { + pr_devel("wrong magic \"%s\"\n", magic); + goto out_unmap_hdr; + } + if (hdr->version < MIPC_MIN_VER && hdr->version > MIPC_MAX_VER) { + pr_err("unsupported mini ipc version %d" + " (min %d, max %d)\n", hdr->version, + MIPC_MIN_VER, MIPC_MAX_VER); + goto out_unmap_hdr; + } + if (mipc_check_address(hdr->mem2_boundary)) { + pr_err("invalid mem2_boundary %pP\n", + (void *)hdr->mem2_boundary); + error = -EINVAL; + goto out_unmap_hdr; + } + if (mipc_check_address(hdr->ipc_in)) { + pr_err("invalid ipc_in %pP\n", (void *)hdr->ipc_in); + error = -EINVAL; + goto out_unmap_hdr; + } + if (mipc_check_address(hdr->ipc_out)) { + pr_err("invalid ipc_out %pP\n", (void *)hdr->ipc_out); + error = -EINVAL; + goto out_unmap_hdr; + } + + *hdrp = hdr; + error = 0; + goto out_unmap_p; + +out_unmap_hdr: + iounmap(hdr); +out_unmap_p: + iounmap(p); +out_put: + of_node_put(np); +out: + return error; +} + +void mipc_infohdr_put(struct mipc_infohdr *hdr) +{ + iounmap(hdr); +} + +static void mipc_print_infohdr(struct mipc_infohdr *hdr) +{ + pr_info("magic=%c%c%c, version=%d, mem2_boundary=%pP\n", + hdr->magic[0], hdr->magic[1], hdr->magic[2], + hdr->version, + (void *)hdr->mem2_boundary); + pr_cont("ipc_in[%u] @ %pP, ipc_out[%u] @ %pP\n", + hdr->ipc_in_size, (void *)hdr->ipc_in, + hdr->ipc_out_size, (void *)hdr->ipc_out); +} + +static void __iomem *mipc_get_hw_reg(char *compatible) +{ + struct device_node *np; + struct resource res; + void __iomem *hw_reg = NULL; + int error; + + np = of_find_compatible_node(NULL, NULL, compatible); + if (!np) { + pr_err("no compatible node found for %s\n", compatible); + goto out; + } + error = of_address_to_resource(np, 0, &res); + if (error) { + pr_err("no valid reg found for %s\n", np->name); + goto out_put; + } + hw_reg = (void __iomem *)res.start; + +out_put: + of_node_put(np); +out: + return hw_reg; +} + +static int mipc_do_simple_tests; + +#ifndef MODULE +static int __init mipc_simple_tests_setup(char *str) +{ + if (*str) + return 0; + mipc_do_simple_tests = 1; + return 1; +} +__setup("mipc_simple_tests", mipc_simple_tests_setup); +#endif + +static unsigned long tbl_to_ns(unsigned long tbl) +{ + return (tbl * 1000) / tb_ticks_per_usec; +} + +static void mipc_simple_tests(struct mipc_device *ipc_dev) +{ + void __iomem *io_base = ipc_dev->io_base; + void *gpio; + unsigned long t0; + unsigned long t_read, t_write; + unsigned long t_mipc_read, t_mipc_write, t_mipc_ping; + u32 val; + int i; + + gpio = mipc_get_hw_reg(HW_GPIO_OF_COMPATIBLE); + if (!gpio) + return; + + for (i = 0; i < 64000; i++) { + t0 = get_tbl(); + in_be32(io_base + MIPC_CSR); + t_read = get_tbl() - t0; + + t0 = get_tbl(); + out_be32(io_base + MIPC_CSR, 0); + t_write = get_tbl() - t0; + + t0 = get_tbl(); + val = mipc_readl(gpio); + t_mipc_read = get_tbl() - t0; + + t0 = get_tbl(); + /* turn off front LED */ + mipc_writel(val & ~0x20, gpio); + t_mipc_write = get_tbl() - t0; + + t0 = get_tbl(); + mipc_ping(ipc_dev, MIPC_SYS_IO_TIMEOUT); + t_mipc_ping = get_tbl() - t0; + } + + pr_info("io timings in timebase ticks" + " (1 usec = %lu ticks)\n", tb_ticks_per_usec); + pr_cont("mmio: read=%lu (%lu ns), write=%lu (%lu ns)\n", + t_read, tbl_to_ns(t_read), t_write, tbl_to_ns(t_write)); + pr_cont("mipc: read=%lu (%lu ns), write=%lu (%lu ns)\n", + t_mipc_read, tbl_to_ns(t_mipc_read), + t_mipc_write, tbl_to_ns(t_mipc_write)); + pr_cont("mipc: ping=%lu (%lu ns)\n", + t_mipc_ping, tbl_to_ns(t_mipc_ping)); +} + +static void mipc_shutdown_mini_devs(struct mipc_device *ipc_dev) +{ + struct mipc_req resp; + int error; + + error = mipc_sendrecv1_call(ipc_dev, MIPC_DEV_TIMEOUT, &resp, + _MIPC(_MIPC_SLOW, _MIPC_DEV_SDHC, + _MIPC_SDHC_EXIT), 0); + if (error) + pr_err("unable to shutdown mini SDHC subsystem\n"); +} + +static void mipc_starlet_fixups(struct mipc_device *ipc_dev) +{ + void __iomem *gpio; + + /* + * Try to turn off the front led and sensor bar. + * (not strictly starlet-only stuff but anyway...) + */ + gpio = mipc_get_hw_reg(HW_GPIO_OF_COMPATIBLE); + if (gpio) + mipc_clearbitl(0x120, gpio); + + /* tell 'mini' to relinquish control of hardware */ + mipc_shutdown_mini_devs(ipc_dev); +} + +static void mipc_init_ahbprot(struct mipc_device *ipc_dev) +{ + void __iomem *hw_ahbprot; + u32 initial_ahbprot, ahbprot = 0; + + hw_ahbprot = mipc_get_hw_reg(HW_AHBPROT_OF_COMPATIBLE); + if (!hw_ahbprot) + goto done; + + initial_ahbprot = mipc_readl(hw_ahbprot); + if (initial_ahbprot != 0xffffffff) { + pr_debug("AHBPROT=%08X (before)\n", initial_ahbprot); + /* enable full access from the PowerPC side */ + mipc_writel(0xffffffff, hw_ahbprot); + } + + ahbprot = mipc_readl(hw_ahbprot); + if (initial_ahbprot != ahbprot) + pr_debug("AHBPROT=%08X (after)\n", ahbprot); +done: + if (ahbprot != 0xffffffff) + pr_err("failed to set AHBPROT\n"); +} + +static int mipc_init(struct mipc_device *ipc_dev, struct resource *mem, int irq) +{ + struct mipc_infohdr *hdr; + void __iomem *io_base; + size_t io_size, in_size, out_size; + int error; + + error = mipc_infohdr_get(&hdr); + if (error) { + pr_err("unable to find mini ipc instance\n"); + goto out; + } + + spin_lock_init(&ipc_dev->call_lock); + spin_lock_init(&ipc_dev->io_lock); + + io_size = mem[0].end - mem[0].start + 1; + io_base = ipc_dev->io_base = ioremap(mem[0].start, io_size); + ipc_dev->irq = irq; + + ipc_dev->hdr = hdr; + + mipc_print_infohdr(hdr); + + in_size = hdr->ipc_in_size * sizeof(*ipc_dev->in_ring); + ipc_dev->in_ring = ioremap(hdr->ipc_in, in_size); + ipc_dev->in_ring_size = hdr->ipc_in_size; + ipc_dev->intail_idx = mipc_peek_first_intail(io_base); + + out_size = hdr->ipc_out_size * sizeof(*ipc_dev->out_ring); + ipc_dev->out_ring = ioremap(hdr->ipc_out, out_size); + ipc_dev->out_ring_size = hdr->ipc_out_size; + ipc_dev->outhead_idx = mipc_peek_first_outhead(io_base); + + ipc_dev->tag = MIPC_INITIAL_TAG; + mipc_device_instance = ipc_dev; + + mipc_print_status(ipc_dev); + + mipc_flush_send(ipc_dev, 5*1000); + mipc_flush_recv(ipc_dev, 5*1000); + error = mipc_ping(ipc_dev, 1*1000*1000); + if (error) + goto out; + + pr_info("ping OK\n"); + if (mipc_do_simple_tests) + mipc_simple_tests(ipc_dev); + + mipc_init_ahbprot(ipc_dev); + mipc_starlet_fixups(ipc_dev); + +out: + return error; +} + +static void mipc_exit(struct mipc_device *ipc_dev) +{ + if (ipc_dev->in_ring) + iounmap(ipc_dev->in_ring); + if (ipc_dev->out_ring) + iounmap(ipc_dev->out_ring); + mipc_infohdr_put(ipc_dev->hdr); +} + + +/* + * Driver model helper routines. + * + */ + +static int mipc_do_probe(struct device *dev, struct resource *mem, int irq) +{ + struct mipc_device *ipc_dev; + int error; + + ipc_dev = kzalloc(sizeof(*ipc_dev), GFP_KERNEL); + if (!ipc_dev) { + pr_err("failed to allocate ipc_dev\n"); + error = -ENOMEM; + goto out; + } + dev_set_drvdata(dev, ipc_dev); + ipc_dev->dev = dev; + + error = mipc_init(ipc_dev, mem, irq); + if (error) { + dev_set_drvdata(dev, NULL); + kfree(ipc_dev); + goto out; + } + + pr_info("ready\n"); + +#ifdef CONFIG_HLWD_PIC + /* + * Setup the Hollywood interrupt controller as soon as + * we detect that we are running under the mini firmware. + */ + hlwd_pic_probe(); +#endif + +out: + return error; +} + +static int mipc_do_remove(struct device *dev) +{ + struct mipc_device *ipc_dev = dev_get_drvdata(dev); + int error = 0; + + if (!ipc_dev) { + error = -ENODEV; + goto out; + } + + mipc_exit(ipc_dev); + dev_set_drvdata(dev, NULL); + kfree(ipc_dev); +out: + return error; +} + +static int mipc_do_shutdown(struct device *dev) +{ + struct mipc_device *ipc_dev = dev_get_drvdata(dev); + int error = 0; + + if (!ipc_dev) { + error = -ENODEV; + goto out; + } +out: + return error; +} + +/* + * OF platform driver hooks. + * + */ + +static int mipc_of_probe(struct of_device *odev, + const struct of_device_id *dev_id) +{ + struct resource mem[2]; + int error; + + error = of_address_to_resource(odev->node, 0, &mem[0]); + if (error) { + pr_err("no io memory range found (%d)\n", error); + goto out; + } + + error = mipc_do_probe(&odev->dev, mem, + irq_of_parse_and_map(odev->node, 0)); +out: + return error; +} + +static int mipc_of_remove(struct of_device *odev) +{ + return mipc_do_remove(&odev->dev); +} + +static int mipc_of_shutdown(struct of_device *odev) +{ + return mipc_do_shutdown(&odev->dev); +} + +static struct of_device_id mipc_of_match[] = { + { .compatible = MIPC_OF_COMPATIBLE }, + { }, +}; + +MODULE_DEVICE_TABLE(of, mipc_of_match); + +static struct of_platform_driver mipc_of_driver = { + .owner = THIS_MODULE, + .name = DRV_MODULE_NAME, + .match_table = mipc_of_match, + .probe = mipc_of_probe, + .remove = mipc_of_remove, + .shutdown = mipc_of_shutdown, +}; + +/* + * Kernel module interface hooks. + * + */ + +static int __init mipc_init_module(void) +{ + pr_info("%s - version %s\n", DRV_DESCRIPTION, mipc_driver_version); + + return of_register_platform_driver(&mipc_of_driver); +} + +static void __exit mipc_exit_module(void) +{ + of_unregister_platform_driver(&mipc_of_driver); +} + +module_init(mipc_init_module); +module_exit(mipc_exit_module); + +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_AUTHOR(DRV_AUTHOR); +MODULE_LICENSE("GPL"); +