linux/drivers/mfd/ipaq-micro.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Compaq iPAQ h3xxx Atmel microcontroller companion support
   4 *
   5 * This is an Atmel AT90LS8535 with a special flashed-in firmware that
   6 * implements the special protocol used by this driver.
   7 *
   8 * based on previous kernel 2.4 version by Andrew Christian
   9 * Author : Alessandro Gardich <gremlin@gremlin.it>
  10 * Author : Dmitry Artamonow <mad_soft@inbox.ru>
  11 * Author : Linus Walleij <linus.walleij@linaro.org>
  12 */
  13
  14#include <linux/module.h>
  15#include <linux/init.h>
  16#include <linux/interrupt.h>
  17#include <linux/pm.h>
  18#include <linux/delay.h>
  19#include <linux/device.h>
  20#include <linux/platform_device.h>
  21#include <linux/io.h>
  22#include <linux/mfd/core.h>
  23#include <linux/mfd/ipaq-micro.h>
  24#include <linux/string.h>
  25#include <linux/random.h>
  26#include <linux/slab.h>
  27#include <linux/list.h>
  28
  29#include <mach/hardware.h>
  30
  31static void ipaq_micro_trigger_tx(struct ipaq_micro *micro)
  32{
  33        struct ipaq_micro_txdev *tx = &micro->tx;
  34        struct ipaq_micro_msg *msg = micro->msg;
  35        int i, bp;
  36        u8 checksum;
  37        u32 val;
  38
  39        bp = 0;
  40        tx->buf[bp++] = CHAR_SOF;
  41
  42        checksum = ((msg->id & 0x0f) << 4) | (msg->tx_len & 0x0f);
  43        tx->buf[bp++] = checksum;
  44
  45        for (i = 0; i < msg->tx_len; i++) {
  46                tx->buf[bp++] = msg->tx_data[i];
  47                checksum += msg->tx_data[i];
  48        }
  49
  50        tx->buf[bp++] = checksum;
  51        tx->len = bp;
  52        tx->index = 0;
  53
  54        /* Enable interrupt */
  55        val = readl(micro->base + UTCR3);
  56        val |= UTCR3_TIE;
  57        writel(val, micro->base + UTCR3);
  58}
  59
  60int ipaq_micro_tx_msg(struct ipaq_micro *micro, struct ipaq_micro_msg *msg)
  61{
  62        unsigned long flags;
  63
  64        dev_dbg(micro->dev, "TX msg: %02x, %d bytes\n", msg->id, msg->tx_len);
  65
  66        spin_lock_irqsave(&micro->lock, flags);
  67        if (micro->msg) {
  68                list_add_tail(&msg->node, &micro->queue);
  69                spin_unlock_irqrestore(&micro->lock, flags);
  70                return 0;
  71        }
  72        micro->msg = msg;
  73        ipaq_micro_trigger_tx(micro);
  74        spin_unlock_irqrestore(&micro->lock, flags);
  75        return 0;
  76}
  77EXPORT_SYMBOL(ipaq_micro_tx_msg);
  78
  79static void micro_rx_msg(struct ipaq_micro *micro, u8 id, int len, u8 *data)
  80{
  81        int i;
  82
  83        dev_dbg(micro->dev, "RX msg: %02x, %d bytes\n", id, len);
  84
  85        spin_lock(&micro->lock);
  86        switch (id) {
  87        case MSG_VERSION:
  88        case MSG_EEPROM_READ:
  89        case MSG_EEPROM_WRITE:
  90        case MSG_BACKLIGHT:
  91        case MSG_NOTIFY_LED:
  92        case MSG_THERMAL_SENSOR:
  93        case MSG_BATTERY:
  94                /* Handle synchronous messages */
  95                if (micro->msg && micro->msg->id == id) {
  96                        struct ipaq_micro_msg *msg = micro->msg;
  97
  98                        memcpy(msg->rx_data, data, len);
  99                        msg->rx_len = len;
 100                        complete(&micro->msg->ack);
 101                        if (!list_empty(&micro->queue)) {
 102                                micro->msg = list_entry(micro->queue.next,
 103                                                        struct ipaq_micro_msg,
 104                                                        node);
 105                                list_del_init(&micro->msg->node);
 106                                ipaq_micro_trigger_tx(micro);
 107                        } else
 108                                micro->msg = NULL;
 109                        dev_dbg(micro->dev, "OK RX message 0x%02x\n", id);
 110                } else {
 111                        dev_err(micro->dev,
 112                                "out of band RX message 0x%02x\n", id);
 113                        if (!micro->msg)
 114                                dev_info(micro->dev, "no message queued\n");
 115                        else
 116                                dev_info(micro->dev, "expected message %02x\n",
 117                                         micro->msg->id);
 118                }
 119                break;
 120        case MSG_KEYBOARD:
 121                if (micro->key)
 122                        micro->key(micro->key_data, len, data);
 123                else
 124                        dev_dbg(micro->dev, "key message ignored, no handle\n");
 125                break;
 126        case MSG_TOUCHSCREEN:
 127                if (micro->ts)
 128                        micro->ts(micro->ts_data, len, data);
 129                else
 130                        dev_dbg(micro->dev, "touchscreen message ignored, no handle\n");
 131                break;
 132        default:
 133                dev_err(micro->dev,
 134                        "unknown msg %d [%d] ", id, len);
 135                for (i = 0; i < len; ++i)
 136                        pr_cont("0x%02x ", data[i]);
 137                pr_cont("\n");
 138        }
 139        spin_unlock(&micro->lock);
 140}
 141
 142static void micro_process_char(struct ipaq_micro *micro, u8 ch)
 143{
 144        struct ipaq_micro_rxdev *rx = &micro->rx;
 145
 146        switch (rx->state) {
 147        case STATE_SOF: /* Looking for SOF */
 148                if (ch == CHAR_SOF)
 149                        rx->state = STATE_ID; /* Next byte is the id and len */
 150                break;
 151        case STATE_ID: /* Looking for id and len byte */
 152                rx->id = (ch & 0xf0) >> 4;
 153                rx->len = (ch & 0x0f);
 154                rx->index = 0;
 155                rx->chksum = ch;
 156                rx->state = (rx->len > 0) ? STATE_DATA : STATE_CHKSUM;
 157                break;
 158        case STATE_DATA: /* Looking for 'len' data bytes */
 159                rx->chksum += ch;
 160                rx->buf[rx->index] = ch;
 161                if (++rx->index == rx->len)
 162                        rx->state = STATE_CHKSUM;
 163                break;
 164        case STATE_CHKSUM: /* Looking for the checksum */
 165                if (ch == rx->chksum)
 166                        micro_rx_msg(micro, rx->id, rx->len, rx->buf);
 167                rx->state = STATE_SOF;
 168                break;
 169        }
 170}
 171
 172static void micro_rx_chars(struct ipaq_micro *micro)
 173{
 174        u32 status, ch;
 175
 176        while ((status = readl(micro->base + UTSR1)) & UTSR1_RNE) {
 177                ch = readl(micro->base + UTDR);
 178                if (status & UTSR1_PRE)
 179                        dev_err(micro->dev, "rx: parity error\n");
 180                else if (status & UTSR1_FRE)
 181                        dev_err(micro->dev, "rx: framing error\n");
 182                else if (status & UTSR1_ROR)
 183                        dev_err(micro->dev, "rx: overrun error\n");
 184                micro_process_char(micro, ch);
 185        }
 186}
 187
 188static void ipaq_micro_get_version(struct ipaq_micro *micro)
 189{
 190        struct ipaq_micro_msg msg = {
 191                .id = MSG_VERSION,
 192        };
 193
 194        ipaq_micro_tx_msg_sync(micro, &msg);
 195        if (msg.rx_len == 4) {
 196                memcpy(micro->version, msg.rx_data, 4);
 197                micro->version[4] = '\0';
 198        } else if (msg.rx_len == 9) {
 199                memcpy(micro->version, msg.rx_data, 4);
 200                micro->version[4] = '\0';
 201                /* Bytes 4-7 are "pack", byte 8 is "boot type" */
 202        } else {
 203                dev_err(micro->dev,
 204                        "illegal version message %d bytes\n", msg.rx_len);
 205        }
 206}
 207
 208static void ipaq_micro_eeprom_read(struct ipaq_micro *micro,
 209                                   u8 address, u8 len, u8 *data)
 210{
 211        struct ipaq_micro_msg msg = {
 212                .id = MSG_EEPROM_READ,
 213        };
 214        u8 i;
 215
 216        for (i = 0; i < len; i++) {
 217                msg.tx_data[0] = address + i;
 218                msg.tx_data[1] = 1;
 219                msg.tx_len = 2;
 220                ipaq_micro_tx_msg_sync(micro, &msg);
 221                memcpy(data + (i * 2), msg.rx_data, 2);
 222        }
 223}
 224
 225static char *ipaq_micro_str(u8 *wchar, u8 len)
 226{
 227        char retstr[256];
 228        u8 i;
 229
 230        for (i = 0; i < len / 2; i++)
 231                retstr[i] = wchar[i * 2];
 232        return kstrdup(retstr, GFP_KERNEL);
 233}
 234
 235static u16 ipaq_micro_to_u16(u8 *data)
 236{
 237        return data[1] << 8 | data[0];
 238}
 239
 240static void __init ipaq_micro_eeprom_dump(struct ipaq_micro *micro)
 241{
 242        u8 dump[256];
 243        char *str;
 244
 245        ipaq_micro_eeprom_read(micro, 0, 128, dump);
 246        str = ipaq_micro_str(dump, 10);
 247        if (str) {
 248                dev_info(micro->dev, "HW version %s\n", str);
 249                kfree(str);
 250        }
 251        str = ipaq_micro_str(dump+10, 40);
 252        if (str) {
 253                dev_info(micro->dev, "serial number: %s\n", str);
 254                /* Feed the random pool with this */
 255                add_device_randomness(str, strlen(str));
 256                kfree(str);
 257        }
 258        str = ipaq_micro_str(dump+50, 20);
 259        if (str) {
 260                dev_info(micro->dev, "module ID: %s\n", str);
 261                kfree(str);
 262        }
 263        str = ipaq_micro_str(dump+70, 10);
 264        if (str) {
 265                dev_info(micro->dev, "product revision: %s\n", str);
 266                kfree(str);
 267        }
 268        dev_info(micro->dev, "product ID: %u\n", ipaq_micro_to_u16(dump+80));
 269        dev_info(micro->dev, "frame rate: %u fps\n",
 270                 ipaq_micro_to_u16(dump+82));
 271        dev_info(micro->dev, "page mode: %u\n", ipaq_micro_to_u16(dump+84));
 272        dev_info(micro->dev, "country ID: %u\n", ipaq_micro_to_u16(dump+86));
 273        dev_info(micro->dev, "color display: %s\n",
 274                 ipaq_micro_to_u16(dump+88) ? "yes" : "no");
 275        dev_info(micro->dev, "ROM size: %u MiB\n", ipaq_micro_to_u16(dump+90));
 276        dev_info(micro->dev, "RAM size: %u KiB\n", ipaq_micro_to_u16(dump+92));
 277        dev_info(micro->dev, "screen: %u x %u\n",
 278                 ipaq_micro_to_u16(dump+94), ipaq_micro_to_u16(dump+96));
 279}
 280
 281static void micro_tx_chars(struct ipaq_micro *micro)
 282{
 283        struct ipaq_micro_txdev *tx = &micro->tx;
 284        u32 val;
 285
 286        while ((tx->index < tx->len) &&
 287               (readl(micro->base + UTSR1) & UTSR1_TNF)) {
 288                writel(tx->buf[tx->index], micro->base + UTDR);
 289                tx->index++;
 290        }
 291
 292        /* Stop interrupts */
 293        val = readl(micro->base + UTCR3);
 294        val &= ~UTCR3_TIE;
 295        writel(val, micro->base + UTCR3);
 296}
 297
 298static void micro_reset_comm(struct ipaq_micro *micro)
 299{
 300        struct ipaq_micro_rxdev *rx = &micro->rx;
 301        u32 val;
 302
 303        if (micro->msg)
 304                complete(&micro->msg->ack);
 305
 306        /* Initialize Serial channel protocol frame */
 307        rx->state = STATE_SOF;  /* Reset the state machine */
 308
 309        /* Set up interrupts */
 310        writel(0x01, micro->sdlc + 0x0); /* Select UART mode */
 311
 312        /* Clean up CR3 */
 313        writel(0x0, micro->base + UTCR3);
 314
 315        /* Format: 8N1 */
 316        writel(UTCR0_8BitData | UTCR0_1StpBit, micro->base + UTCR0);
 317
 318        /* Baud rate: 115200 */
 319        writel(0x0, micro->base + UTCR1);
 320        writel(0x1, micro->base + UTCR2);
 321
 322        /* Clear SR0 */
 323        writel(0xff, micro->base + UTSR0);
 324
 325        /* Enable RX int, disable TX int */
 326        writel(UTCR3_TXE | UTCR3_RXE | UTCR3_RIE, micro->base + UTCR3);
 327        val = readl(micro->base + UTCR3);
 328        val &= ~UTCR3_TIE;
 329        writel(val, micro->base + UTCR3);
 330}
 331
 332static irqreturn_t micro_serial_isr(int irq, void *dev_id)
 333{
 334        struct ipaq_micro *micro = dev_id;
 335        struct ipaq_micro_txdev *tx = &micro->tx;
 336        u32 status;
 337
 338        status = readl(micro->base + UTSR0);
 339        do {
 340                if (status & (UTSR0_RID | UTSR0_RFS)) {
 341                        if (status & UTSR0_RID)
 342                                /* Clear the Receiver IDLE bit */
 343                                writel(UTSR0_RID, micro->base + UTSR0);
 344                        micro_rx_chars(micro);
 345                }
 346
 347                /* Clear break bits */
 348                if (status & (UTSR0_RBB | UTSR0_REB))
 349                        writel(status & (UTSR0_RBB | UTSR0_REB),
 350                               micro->base + UTSR0);
 351
 352                if (status & UTSR0_TFS)
 353                        micro_tx_chars(micro);
 354
 355                status = readl(micro->base + UTSR0);
 356
 357        } while (((tx->index < tx->len) && (status & UTSR0_TFS)) ||
 358                 (status & (UTSR0_RFS | UTSR0_RID)));
 359
 360        return IRQ_HANDLED;
 361}
 362
 363static const struct mfd_cell micro_cells[] = {
 364        { .name = "ipaq-micro-backlight", },
 365        { .name = "ipaq-micro-battery", },
 366        { .name = "ipaq-micro-keys", },
 367        { .name = "ipaq-micro-ts", },
 368        { .name = "ipaq-micro-leds", },
 369};
 370
 371static int __maybe_unused micro_resume(struct device *dev)
 372{
 373        struct ipaq_micro *micro = dev_get_drvdata(dev);
 374
 375        micro_reset_comm(micro);
 376        mdelay(10);
 377
 378        return 0;
 379}
 380
 381static int __init micro_probe(struct platform_device *pdev)
 382{
 383        struct ipaq_micro *micro;
 384        struct resource *res;
 385        int ret;
 386        int irq;
 387
 388        micro = devm_kzalloc(&pdev->dev, sizeof(*micro), GFP_KERNEL);
 389        if (!micro)
 390                return -ENOMEM;
 391
 392        micro->dev = &pdev->dev;
 393
 394        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 395        micro->base = devm_ioremap_resource(&pdev->dev, res);
 396        if (IS_ERR(micro->base))
 397                return PTR_ERR(micro->base);
 398
 399        micro->sdlc = devm_platform_ioremap_resource(pdev, 1);
 400        if (IS_ERR(micro->sdlc))
 401                return PTR_ERR(micro->sdlc);
 402
 403        micro_reset_comm(micro);
 404
 405        irq = platform_get_irq(pdev, 0);
 406        if (!irq)
 407                return -EINVAL;
 408        ret = devm_request_irq(&pdev->dev, irq, micro_serial_isr,
 409                               IRQF_SHARED, "ipaq-micro",
 410                               micro);
 411        if (ret) {
 412                dev_err(&pdev->dev, "unable to grab serial port IRQ\n");
 413                return ret;
 414        } else
 415                dev_info(&pdev->dev, "grabbed serial port IRQ\n");
 416
 417        spin_lock_init(&micro->lock);
 418        INIT_LIST_HEAD(&micro->queue);
 419        platform_set_drvdata(pdev, micro);
 420
 421        ret = mfd_add_devices(&pdev->dev, pdev->id, micro_cells,
 422                              ARRAY_SIZE(micro_cells), NULL, 0, NULL);
 423        if (ret) {
 424                dev_err(&pdev->dev, "error adding MFD cells");
 425                return ret;
 426        }
 427
 428        /* Check version */
 429        ipaq_micro_get_version(micro);
 430        dev_info(&pdev->dev, "Atmel micro ASIC version %s\n", micro->version);
 431        ipaq_micro_eeprom_dump(micro);
 432
 433        return 0;
 434}
 435
 436static const struct dev_pm_ops micro_dev_pm_ops = {
 437        SET_SYSTEM_SLEEP_PM_OPS(NULL, micro_resume)
 438};
 439
 440static struct platform_driver micro_device_driver = {
 441        .driver   = {
 442                .name   = "ipaq-h3xxx-micro",
 443                .pm     = &micro_dev_pm_ops,
 444                .suppress_bind_attrs = true,
 445        },
 446};
 447builtin_platform_driver_probe(micro_device_driver, micro_probe);
 448