From patchwork Fri Jan 31 13:34:35 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Linus Walleij X-Patchwork-Id: 23947 Return-Path: X-Original-To: linaro@patches.linaro.org Delivered-To: linaro@patches.linaro.org Received: from mail-ie0-f199.google.com (mail-ie0-f199.google.com [209.85.223.199]) by ip-10-151-82-157.ec2.internal (Postfix) with ESMTPS id 9BCD0202FA for ; Fri, 31 Jan 2014 13:34:52 +0000 (UTC) Received: by mail-ie0-f199.google.com with SMTP id x13sf15810651ief.10 for ; Fri, 31 Jan 2014 05:34:51 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:mime-version:delivered-to:from:to:cc:subject :date:message-id:x-original-sender:x-original-authentication-results :precedence:mailing-list:list-id:list-post:list-help:list-archive :list-unsubscribe; bh=b9MZ0eWw5p5CUoEmqQB+NWxsZKEcCBMv6VOKi+01Nf8=; b=BeBNJmUYxG/PGrwAEiZ0ISvizlJAaqMt2yAlkYYLkLsmvJqfYIYGca1Qb6Wojt8N+E u1K1tyqA+aV6iHPQiQlKDGhKwMTNHFg4vnNx3zFZiRvvv24gqyGXICBxHTalkrDxpw37 bzOHOmpBMGDh1maTYrCEATmwzIIgzliT/gN2i1M4OKb7W94H7tBkEw3A5LTQWAkVSxhu 5BSC+u+WSANqvBED8urjiUXgi9LDgPhOuGKpWDl+aXNz26OiOIesdDvsL0wOnkVe4VTt rQyFHnOQhLhpQLc0C0r9oZSoSJ9ndIFqC9aHd72Madz72YunMmtJSk2cBXMua0Kvu0y1 pC7w== X-Gm-Message-State: ALoCoQlU6+NoNfWGGjxGo54fZgKzYtE57k63b0U3b4ldyrawxYFwtWu4qE/d2HgnPt45ZTPcMOLU X-Received: by 10.182.111.227 with SMTP id il3mr7817531obb.41.1391175291470; Fri, 31 Jan 2014 05:34:51 -0800 (PST) MIME-Version: 1.0 X-BeenThere: patchwork-forward@linaro.org Received: by 10.140.101.134 with SMTP id u6ls961614qge.79.gmail; Fri, 31 Jan 2014 05:34:51 -0800 (PST) X-Received: by 10.58.106.134 with SMTP id gu6mr28943veb.44.1391175291378; Fri, 31 Jan 2014 05:34:51 -0800 (PST) Received: from mail-ve0-f179.google.com (mail-ve0-f179.google.com [209.85.128.179]) by mx.google.com with ESMTPS id sq4si3498737vdc.132.2014.01.31.05.34.51 for (version=TLSv1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); Fri, 31 Jan 2014 05:34:51 -0800 (PST) Received-SPF: neutral (google.com: 209.85.128.179 is neither permitted nor denied by best guess record for domain of patch+caf_=patchwork-forward=linaro.org@linaro.org) client-ip=209.85.128.179; Received: by mail-ve0-f179.google.com with SMTP id jx11so3159896veb.10 for ; Fri, 31 Jan 2014 05:34:51 -0800 (PST) X-Received: by 10.221.26.10 with SMTP id rk10mr16747582vcb.0.1391175291208; Fri, 31 Jan 2014 05:34:51 -0800 (PST) X-Forwarded-To: patchwork-forward@linaro.org X-Forwarded-For: patch@linaro.org patchwork-forward@linaro.org Delivered-To: patches@linaro.org Received: by 10.220.174.196 with SMTP id u4csp96199vcz; Fri, 31 Jan 2014 05:34:50 -0800 (PST) X-Received: by 10.14.172.133 with SMTP id t5mr2890695eel.99.1391175289755; Fri, 31 Jan 2014 05:34:49 -0800 (PST) Received: from mail-ee0-f54.google.com (mail-ee0-f54.google.com [74.125.83.54]) by mx.google.com with ESMTPS id w2si18006293eeg.112.2014.01.31.05.34.49 for (version=TLSv1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); Fri, 31 Jan 2014 05:34:49 -0800 (PST) Received-SPF: neutral (google.com: 74.125.83.54 is neither permitted nor denied by best guess record for domain of linus.walleij@linaro.org) client-ip=74.125.83.54; Received: by mail-ee0-f54.google.com with SMTP id e53so2294200eek.27 for ; Fri, 31 Jan 2014 05:34:49 -0800 (PST) X-Received: by 10.14.246.68 with SMTP id p44mr4719037eer.72.1391175289146; Fri, 31 Jan 2014 05:34:49 -0800 (PST) Received: from localhost.localdomain ([85.235.11.236]) by mx.google.com with ESMTPSA id 8sm31069223eef.1.2014.01.31.05.34.47 for (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 31 Jan 2014 05:34:48 -0800 (PST) From: Linus Walleij To: Samuel Ortiz , Lee Jones , linux-kernel@vger.kernel.org, linux-serial@vger.kernel.org, Greg Kroah-Hartman Cc: Linus Walleij , Russell King , Dmitry Eremin-Solenikov , Alessandro Gardich , Dmitry Artamonow Subject: [PATCH] RFC: MFD: driver for Atmel Microcontroller on iPaq h3xxx Date: Fri, 31 Jan 2014 14:34:35 +0100 Message-Id: <1391175275-27347-1-git-send-email-linus.walleij@linaro.org> X-Mailer: git-send-email 1.8.5.3 X-Removed-Original-Auth: Dkim didn't pass. X-Original-Sender: linus.walleij@linaro.org X-Original-Authentication-Results: mx.google.com; spf=neutral (google.com: 209.85.128.179 is neither permitted nor denied by best guess record for domain of patch+caf_=patchwork-forward=linaro.org@linaro.org) smtp.mail=patch+caf_=patchwork-forward=linaro.org@linaro.org Precedence: list Mailing-list: list patchwork-forward@linaro.org; contact patchwork-forward+owners@linaro.org List-ID: X-Google-Group-Id: 836684582541 List-Post: , List-Help: , List-Archive: List-Unsubscribe: , This adds a driver for the Atmel Microcontroller found on the iPAQ h3xxx series. This device handles some keys, the touchscreen, and the battery monitoring. This is a port of a driver from handhelds.org 2.6.21 kernel, written by Alessandro Gardich based on Andrew Christians original HAL-driver. It has been heavily cleaned and converted to mfd-core by Dmitry Artamonow and rewritten again for the v3.x series kernels by Linus Walleij, bringing back some of the functionality lost from Andrew's original driver. Cc: Russell King Cc: Dmitry Eremin-Solenikov Signed-off-by: Alessandro Gardich Signed-off-by: Dmitry Artamonow Signed-off-by: Linus Walleij --- Hi Greg, Sam, Lee: This is a peculiar driver I found out-of-tree. Basically the problem with it is this: the UART port is used only for kernel-internal purposes, and exposing it to userspace or making it subject of a line discipline is irrelevant. Compare to SPI or I2C buses: those have custom devices connected to them, they appear as kernel-internal things. i2c_register_board_info() or spi_register_board_info() can be used to hook a device directly to these buses with no userspace interaction. For UART devices these are implicitly assumed to be TTYs or modems and thus basically userspace business. But this thing isn't: this MFD device spawns a backlight, LED, power (battery) etc, and nothing userspace-related at all. They actually have to come up *before* userspace is up, trying to attach a line discipline from userspace would just be shoehorning. The solution in this current driver is to cut out the problem by accessing the serial port registers directly to send/recieve messages to the device, rather than reusing the code in tty/serial/sa1100.c in this case. Any hints what to do about creatures like this? Part of me wants to create a mechanism for kernel-internal devices using UARTs, part of me wants to do like this patch does just to avoid the hassle. So I'm ambivalent about this whole thing. --- drivers/mfd/Kconfig | 10 + drivers/mfd/Makefile | 1 + drivers/mfd/ipaq-micro.c | 488 +++++++++++++++++++++++++++++++++++++++++ include/linux/mfd/ipaq-micro.h | 149 +++++++++++++ 4 files changed, 648 insertions(+) create mode 100644 drivers/mfd/ipaq-micro.c create mode 100644 include/linux/mfd/ipaq-micro.h diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 49bb445d846a..255807afc582 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -242,6 +242,16 @@ config MFD_INTEL_MSIC Passage) chip. This chip embeds audio, battery, GPIO, etc. devices used in Intel Medfield platforms. +config MFD_IPAQ_MICRO + bool "Atmel Micro ASIC (iPAQ h3100/h3600/h3700) Support" + depends on SA1100_H3100 || SA1100_H3600 + select MFD_CORE + help + Select this to get support for the Microcontroller found in + the Compaq iPAQ handheld computers. This is an Atmel + AT90LS8535 microcontroller flashed with a special iPAQ + firmware using the custom protocol implemented in this driver. + config MFD_JANZ_CMODIO tristate "Janz CMOD-IO PCI MODULbus Carrier Board" select MFD_CORE diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 5aea5ef0a62f..cf8081fcb3ac 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -166,3 +166,4 @@ obj-$(CONFIG_MFD_RETU) += retu-mfd.o obj-$(CONFIG_MFD_AS3711) += as3711.o obj-$(CONFIG_MFD_AS3722) += as3722.o obj-$(CONFIG_MFD_STW481X) += stw481x.o +obj-$(CONFIG_MFD_IPAQ_MICRO) += ipaq-micro.o diff --git a/drivers/mfd/ipaq-micro.c b/drivers/mfd/ipaq-micro.c new file mode 100644 index 000000000000..8934caaeb700 --- /dev/null +++ b/drivers/mfd/ipaq-micro.c @@ -0,0 +1,488 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Compaq iPAQ h3xxx Atmel microcontroller companion support + * + * This is an Atmel AT90LS8535 with a special flashed-in firmware that + * implements the special protocol used by this driver. + * + * based on previous kernel 2.4 version by Andrew Christian + * Author : Alessandro Gardich + * Author : Dmitry Artamonow + * Author : Linus Walleij + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static void ipaq_micro_trigger_tx(struct ipaq_micro *micro) +{ + struct ipaq_micro_txdev *tx = µ->tx; + struct ipaq_micro_msg *msg = micro->msg; + int i, j; + u8 checksum; + u32 val; + + j = 0; + tx->buf[j++] = (u8) CHAR_SOF; + + checksum = ((msg->id & 0x0f) << 4) | (msg->tx_len & 0x0f); + tx->buf[j++] = checksum; + + for (i = 0; i < msg->tx_len; i++) { + tx->buf[j++] = msg->tx_data[i]; + checksum += msg->tx_data[i]; + } + + tx->buf[j++] = checksum; + tx->len = j; + tx->index = 0; + print_hex_dump(KERN_DEBUG, "data: ", DUMP_PREFIX_OFFSET, 16, 1, + tx->buf, tx->len, true); + + + val = readl(micro->base + UTCR3); + val |= UTCR3_TIE; /* enable interrupt */ + writel(val, micro->base + UTCR3); +} + + +int ipaq_micro_tx_msg(struct ipaq_micro *micro, struct ipaq_micro_msg *msg) +{ + unsigned long flags; + + dev_dbg(micro->dev, "TX msg: %02x, %d bytes\n", msg->id, msg->tx_len); + + spin_lock_irqsave(µ->lock, flags); + if (micro->msg != NULL) { + list_add_tail(&msg->node, µ->queue); + spin_unlock_irqrestore(µ->lock, flags); + return 0; + } + micro->msg = msg; + ipaq_micro_trigger_tx(micro); + spin_unlock_irqrestore(µ->lock, flags); + return 0; +} +EXPORT_SYMBOL(ipaq_micro_tx_msg); + +static void micro_rx_msg(struct ipaq_micro *micro, u8 id, int len, u8 *data) +{ + int i; + + dev_dbg(micro->dev, "RX msg: %02x, %d bytes\n", id, len); + + spin_lock(µ->lock); + switch (id) { + case MSG_VERSION: + case MSG_EEPROM_READ: + case MSG_EEPROM_WRITE: + case MSG_BACKLIGHT: + case MSG_NOTIFY_LED: + case MSG_THERMAL_SENSOR: + case MSG_BATTERY: + /* Handle synchronous messages */ + if (micro->msg && micro->msg->id == id) { + struct ipaq_micro_msg *msg = micro->msg; + + memcpy(msg->rx_data, data, len); + msg->rx_len = len; + complete(µ->msg->ack); + if (!list_empty(µ->queue)) { + micro->msg = list_entry(micro->queue.next, + struct ipaq_micro_msg, + node); + list_del_init(µ->msg->node); + ipaq_micro_trigger_tx(micro); + } else + micro->msg = NULL; + dev_dbg(micro->dev, "OK RX message 0x%02x\n", id); + } else { + dev_err(micro->dev, + "out of band RX message 0x%02x\n", id); + if(!micro->msg) + dev_info(micro->dev, "no message queued\n"); + else + dev_info(micro->dev, "expected message %02x\n", + micro->msg->id); + } + break; + case MSG_KEYBOARD: + if (micro->key != NULL) + micro->key(micro->key_data, len, data); + else + dev_dbg(micro->dev, "ipaq micro : key message ignored, " + "no handle \n"); + break; + case MSG_TOUCHSCREEN: + if (micro->ts != NULL) + micro->ts(micro->ts_data, len, data); + else + dev_dbg(micro->dev, "ipaq micro : touchscreen message " + "ignored, no handle \n"); + break; + default: + dev_err(micro->dev, + "ipaq micro : unknown msg %d [%d] ", id, len); + for (i = 0; i < len; ++i) + pr_cont("0x%02x ", data[i]); + pr_cont("\n"); + } + spin_unlock(µ->lock); +} + +static void micro_process_char(struct ipaq_micro *micro, u8 ch) +{ + struct ipaq_micro_rxdev *rx = µ->rx; + + switch (rx->state) { + case STATE_SOF: /* Looking for SOF */ + if (ch == CHAR_SOF) + rx->state = STATE_ID; /* Next byte is the id and len */ + break; + case STATE_ID: /* Looking for id and len byte */ + rx->id = (ch & 0xf0) >> 4 ; + rx->len = (ch & 0x0f); + rx->index = 0; + rx->chksum = ch; + rx->state = (rx->len > 0) ? STATE_DATA : STATE_CHKSUM; + break; + case STATE_DATA: /* Looking for 'len' data bytes */ + rx->chksum += ch; + rx->buf[rx->index] = ch; + if (++rx->index == rx->len) + rx->state = STATE_CHKSUM; + break; + case STATE_CHKSUM: /* Looking for the checksum */ + if (ch == rx->chksum) + micro_rx_msg(micro, rx->id, rx->len, rx->buf); + rx->state = STATE_SOF; + break; + } +} + +static void micro_rx_chars(struct ipaq_micro *micro) +{ + u32 status, ch; + + while ((status = readl(micro->base + UTSR1)) & UTSR1_RNE) { + ch = readl(micro->base + UTDR); + if (status & UTSR1_PRE) + dev_err(micro->dev, "ipaq micro_rx : parity error\n"); + else if (status & UTSR1_FRE) + dev_err(micro->dev, "ipaq micro_rx : framing error\n"); + else if (status & UTSR1_ROR) + dev_err(micro->dev, "ipaq micro_rx : overrun error\n"); + micro_process_char(micro, ch); + } +} + +static void ipaq_micro_get_version(struct ipaq_micro *micro) +{ + struct ipaq_micro_msg msg = { + .id = MSG_VERSION, + }; + + ipaq_micro_tx_msg_sync(micro, &msg); + if (msg.rx_len == 4) { + memcpy(micro->version, msg.rx_data, 4); + micro->version[4] = '\0'; + } else if (msg.rx_len == 9) { + memcpy(micro->version, msg.rx_data, 4); + micro->version[4] = '\0'; + /* Bytes 4-7 are "pack", byte 8 is "boot type" */ + } else { + dev_err(micro->dev, + "illegal version message %d bytes\n", msg.rx_len); + } +} + +static void ipaq_micro_eeprom_read(struct ipaq_micro *micro, + u8 address, u8 len, u8 *data) +{ + struct ipaq_micro_msg msg = { + .id = MSG_EEPROM_READ, + }; + u8 i; + + for (i = 0; i < len; i++) { + msg.tx_data[0] = address + i; + msg.tx_data[1] = 1; + msg.tx_len = 2; + ipaq_micro_tx_msg_sync(micro, &msg); + memcpy(data + (i * 2), msg.rx_data, 2); + } +} + +static char *ipaq_micro_str(u8 *wchar, u8 len) +{ + char retstr[256]; + u8 i; + + for (i = 0; i < len / 2; i++) + retstr[i] = wchar[i*2]; + return kstrdup(retstr, GFP_KERNEL); +} + +static u16 ipaq_micro_to_u16(u8 *data) +{ + return data[1] << 8 | data[0]; +} + +static void ipaq_micro_eeprom_dump(struct ipaq_micro *micro) +{ + u8 dump[256]; + char *str; + + ipaq_micro_eeprom_read(micro, 0, 128, dump); + str = ipaq_micro_str(dump, 10); + if (str) { + dev_info(micro->dev, "HM version %s\n", str); + kfree(str); + } + str = ipaq_micro_str(dump+10, 40); + if (str) { + dev_info(micro->dev, "serial number: %s\n", str); + /* Feed the random pool with this */ + add_device_randomness(str, strlen(str)); + kfree(str); + } + str = ipaq_micro_str(dump+50, 20); + if (str) { + dev_info(micro->dev, "module ID: %s\n", str); + kfree(str); + } + str = ipaq_micro_str(dump+70, 10); + if (str) { + dev_info(micro->dev, "product revision: %s\n", str); + kfree(str); + } + dev_info(micro->dev, "product ID: %u\n", ipaq_micro_to_u16(dump+80)); + dev_info(micro->dev, "frame rate: %u fps\n", + ipaq_micro_to_u16(dump+82)); + dev_info(micro->dev, "page mode: %u\n", ipaq_micro_to_u16(dump+84)); + dev_info(micro->dev, "country ID: %u\n", ipaq_micro_to_u16(dump+86)); + dev_info(micro->dev, "color display: %s\n", + ipaq_micro_to_u16(dump+88) ? "yes" : "no"); + dev_info(micro->dev, "ROM size: %u MiB\n", ipaq_micro_to_u16(dump+90)); + dev_info(micro->dev, "RAM size: %u KiB\n", ipaq_micro_to_u16(dump+92)); + dev_info(micro->dev, "screen: %u x %u\n", + ipaq_micro_to_u16(dump+94), ipaq_micro_to_u16(dump+96)); + print_hex_dump(KERN_DEBUG, "eeprom: ", DUMP_PREFIX_OFFSET, 16, 1, + dump, 256, true); + +} + +static void micro_tx_chars(struct ipaq_micro *micro) +{ + struct ipaq_micro_txdev *tx = µ->tx; + u32 val; + + while ((tx->index < tx->len) && + (readl(micro->base + UTSR1) & UTSR1_TNF)) { + writel(tx->buf[tx->index], micro->base + UTDR); + tx->index++; + } + + /* Stop interrupts */ + val = readl(micro->base + UTCR3); + val &= ~UTCR3_TIE; + writel(val, micro->base + UTCR3); +} + +static void micro_reset_comm(struct ipaq_micro *micro) +{ + struct ipaq_micro_rxdev *rx = µ->rx; + u32 val; + if (micro->msg) + complete(µ->msg->ack); + + /* Initialize Serial channel protocol frame */ + rx->state = STATE_SOF; /* Reset the state machine */ + + /* Set up interrupts */ + writel(0x01, micro->sdlc + 0x0); /* Select UART mode */ + + /* Clean up CR3 */ + writel(0x0, micro->base + UTCR3); + /* Format: 8N1 */ + writel(UTCR0_8BitData | UTCR0_1StpBit, micro->base + UTCR0); + /* Baud rate: 115200 */ + writel(0x0, micro->base + UTCR1); + writel(0x1, micro->base + UTCR2); + + /* Clear SR0 */ + writel(0xff, micro->base + UTSR0); + /* Enable RX int */ + writel(UTCR3_TXE | UTCR3_RXE | UTCR3_RIE, micro->base + UTCR3); + val = readl(micro->base + UTCR3); + val &= ~UTCR3_TIE; /* Disable TX int */ + writel(val, micro->base + UTCR3); +} + +static irqreturn_t micro_serial_isr(int irq, void *dev_id) +{ + struct ipaq_micro *micro = dev_id; + struct ipaq_micro_txdev *tx = µ->tx; + u32 status; + + status = readl(micro->base + UTSR0); + do { + if (status & (UTSR0_RID | UTSR0_RFS)) { + if (status & UTSR0_RID) + /* Clear the Receiver IDLE bit */ + writel(UTSR0_RID, micro->base + UTSR0); + micro_rx_chars(micro); + } + + /* Clear break bits */ + if (status & (UTSR0_RBB | UTSR0_REB)) + writel(status & (UTSR0_RBB | UTSR0_REB), + micro->base + UTSR0); + + if (status & UTSR0_TFS) + micro_tx_chars(micro); + + status = readl(micro->base + UTSR0); + + } while (((tx->index < tx->len) && (status & UTSR0_TFS)) || + (status & (UTSR0_RFS | UTSR0_RID))); + + return IRQ_HANDLED; +} + +static struct mfd_cell micro_cells[] = { + { + .name = "ipaq-micro-backlight", + }, + { + .name = "ipaq-micro-battery", + }, + { + .name = "ipaq-micro-keys", + }, + { + .name = "ipaq-micro-ts", + }, + { + .name = "ipaq-micro-leds", + }, +}; + +static int micro_suspend(struct device *dev) +{ + return 0; +} + +static int micro_resume(struct device *dev) +{ + struct ipaq_micro *micro = dev_get_drvdata(dev); + + micro_reset_comm(micro); + mdelay(10); + + return 0; +} + +static int micro_probe(struct platform_device *pdev) +{ + struct ipaq_micro *micro; + struct resource *res; + int ret; + int irq; + + micro = devm_kzalloc(&pdev->dev, sizeof(*micro), GFP_KERNEL); + if (!micro) + return -ENOMEM; + micro->dev = &pdev->dev; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; + micro->base = devm_request_and_ioremap(&pdev->dev, res); + if (!micro->base) + return -ENOMEM; + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res) + return -EINVAL; + micro->sdlc = devm_request_and_ioremap(&pdev->dev, res); + if (!micro->sdlc) + return -ENOMEM; + micro_reset_comm(micro); + irq = platform_get_irq(pdev, 0); + if (!irq) + return -EINVAL; + ret = devm_request_irq(&pdev->dev, irq, micro_serial_isr, + IRQF_SHARED, "ipaq-micro", + micro); + if (ret) { + dev_err(&pdev->dev, "%s: unable to grab serial port IRQ\n", + __func__); + return ret; + } else + dev_info(&pdev->dev, "grabbed serial port IRQ\n"); + + + spin_lock_init(µ->lock); + INIT_LIST_HEAD(µ->queue); + platform_set_drvdata(pdev, micro); + + ret = mfd_add_devices(&pdev->dev, pdev->id, micro_cells, + ARRAY_SIZE(micro_cells), NULL, 0, NULL); + if (ret) { + dev_err(&pdev->dev, "error adding MFD cells"); + return ret; + } + /* Check version */ + ipaq_micro_get_version(micro); + dev_info(&pdev->dev, "Atmel micro ASIC version %s\n", micro->version); + ipaq_micro_eeprom_dump(micro); + + return 0; +} + +static int micro_remove(struct platform_device *pdev) +{ + struct ipaq_micro *micro = platform_get_drvdata(pdev); + u32 val; + + mfd_remove_devices(&pdev->dev); + val = readl(micro->base + UTCR3); + val &= ~(UTCR3_RXE | UTCR3_RIE); /* disable receive interrupt */ + val &= ~(UTCR3_TXE | UTCR3_TIE); /* disable transmit interrupt */ + writel(val, micro->base + UTCR3); + return 0; +} + +static const struct dev_pm_ops micro_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(micro_suspend, micro_resume) +}; + +static struct platform_driver micro_device_driver = { + .driver = { + .name = "ipaq-h3xxx-micro", + .pm = µ_dev_pm_ops, + }, + .probe = micro_probe, + .remove = micro_remove, + /* .shutdown = micro_suspend, // FIXME */ +}; +module_platform_driver(micro_device_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("driver for iPAQ Atmel micro core and backlight"); diff --git a/include/linux/mfd/ipaq-micro.h b/include/linux/mfd/ipaq-micro.h new file mode 100644 index 000000000000..9466e97ff236 --- /dev/null +++ b/include/linux/mfd/ipaq-micro.h @@ -0,0 +1,149 @@ +/* + * Header file for the compaq Micro MFD + */ + +#ifndef _MFD_IPAQ_MICRO_H_ +#define _MFD_IPAQ_MICRO_H_ + +#include +#include +#include +struct device; + +#define TX_BUF_SIZE 32 +#define RX_BUF_SIZE 16 +#define CHAR_SOF 0x02 + +/* + * These are the different messages that can be sent to the microcontroller + * to control various aspects. + */ +#define MSG_VERSION 0x0 +#define MSG_KEYBOARD 0x2 +#define MSG_TOUCHSCREEN 0x3 +#define MSG_EEPROM_READ 0x4 +#define MSG_EEPROM_WRITE 0x5 +#define MSG_THERMAL_SENSOR 0x6 +#define MSG_NOTIFY_LED 0x8 +#define MSG_BATTERY 0x9 +#define MSG_SPI_READ 0xb +#define MSG_SPI_WRITE 0xc +#define MSG_BACKLIGHT 0xd /* H3600 only */ +#define MSG_CODEC_CTRL 0xe /* H3100 only */ +#define MSG_DISPLAY_CTRL 0xf /* H3100 only */ + +/* state of receiver parser */ +enum rx_state { + STATE_SOF = 0, /* Next byte should be start of frame */ + STATE_ID, /* Next byte is ID & message length */ + STATE_DATA, /* Next byte is a data byte */ + STATE_CHKSUM /* Next byte should be checksum */ +}; + +/** + * struct ipaq_micro_txdev - TX state + * @len: length of message in TX buffer + * @index: current index into TX buffer + * @buf: TX buffer + */ +struct ipaq_micro_txdev { + u8 len; + u8 index; + u8 buf[TX_BUF_SIZE]; +}; + +/** + * struct ipaq_micro_rxdev - RX state + * @state: context of RX state machine + * @chksum: calculated checksum + * @id: message ID from packet + * @len: RX buffer length + * @index: RX buffer index + * @buf: RX buffer + */ +struct ipaq_micro_rxdev { + enum rx_state state; + unsigned char chksum; + u8 id; + unsigned int len; + unsigned int index; + u8 buf[RX_BUF_SIZE]; +}; + +/** + * struct ipaq_micro_msg - message to the iPAQ microcontroller + * @id: 4-bit ID of the message + * @tx_len: length of TX data + * @tx_data: TX data to send + * @rx_len: length of receieved RX data + * @rx_data: RX data to recieve + * @ack: a completion that will be completed when RX is complete + * @node: list node if message gets queued + */ +struct ipaq_micro_msg { + u8 id; + u8 tx_len; + u8 tx_data[TX_BUF_SIZE]; + u8 rx_len; + u8 rx_data[RX_BUF_SIZE]; + struct completion ack; + struct list_head node; +}; + +/** + * struct ipaq_micro - iPAQ microcontroller state + * @dev: corresponding platform device + * @base: virtual memory base for underlying serial device + * @sdlc: virtual memory base for Synchronous Data Link Controller + * @version: version string + * @tx: TX state + * @rx: RX state + * @lock: lock for this state container + * @msg: current message + * @queue: message queue + * @key: callback for asynchronous key events + * @key_data: data to pass along with key events + * @ts: callback for asynchronous touchscreen events + * @ts_data: data to pass along with key events + */ +struct ipaq_micro { + struct device *dev; + void __iomem *base; + void __iomem *sdlc; + char version[5]; + struct ipaq_micro_txdev tx; /* transmit ISR state */ + struct ipaq_micro_rxdev rx; /* receive ISR state */ + spinlock_t lock; + struct ipaq_micro_msg *msg; + struct list_head queue; + void (*key) (void *data, int len, unsigned char *rxdata); + void *key_data; + void (*ts) (void *data, int len, unsigned char *rxdata); + void *ts_data; +}; + +extern int +ipaq_micro_tx_msg(struct ipaq_micro *micro, struct ipaq_micro_msg *msg); + +static inline int +ipaq_micro_tx_msg_sync(struct ipaq_micro *micro, + struct ipaq_micro_msg *msg) +{ + int ret; + + init_completion(&msg->ack); + ret = ipaq_micro_tx_msg(micro, msg); + wait_for_completion(&msg->ack); + + return ret; +} + +static inline int +ipaq_micro_tx_msg_async(struct ipaq_micro *micro, + struct ipaq_micro_msg *msg) +{ + init_completion(&msg->ack); + return ipaq_micro_tx_msg(micro, msg); +} + +#endif /* _MFD_IPAQ_MICRO_H_ */