Re: [PATCH] RFC: MFD: driver for Atmel Microcontroller on iPaq h3xxx

From: Lee Jones
Date: Mon Feb 17 2014 - 04:22:16 EST


> 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 <linux@xxxxxxxxxxxxxxxx>
> Cc: Dmitry Eremin-Solenikov <dbaryshkov@xxxxxxxxx>
> Signed-off-by: Alessandro Gardich <gremlin@xxxxxxxxxx>
> Signed-off-by: Dmitry Artamonow <mad_soft@xxxxxxxx>
> Signed-off-by: Linus Walleij <linus.walleij@xxxxxxxxxx>
> ---

> 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

<snip>

> +++ 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.

Can you put this at the bottom of the header?

> + * 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 <gremlin@xxxxxxxxxx>
> + * Author : Dmitry Artamonow <mad_soft@xxxxxxxx>
> + * Author : Linus Walleij <linus.walleij@xxxxxxxxxx>
> + */
> +
> +#include <linux/module.h>

Does it matter that you're using:

module_platform_driver();

... yet you can't build this as a module?

<snip>

> +static void ipaq_micro_trigger_tx(struct ipaq_micro *micro)
> +{
> + struct ipaq_micro_txdev *tx = &micro->tx;
> + struct ipaq_micro_msg *msg = micro->msg;
> + int i, j;
> + u8 checksum;
> + u32 val;
> +
> + j = 0;

Tiny nit:
The naming convention of 'j' could be more indicative of its function.

> + tx->buf[j++] = (u8) CHAR_SOF;

Is this cast required?

> + 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);
> +
> +

Too many new lines.

> + val = readl(micro->base + UTCR3);
> + val |= UTCR3_TIE; /* enable interrupt */
> + writel(val, micro->base + UTCR3);

The comment should encompass all three lines of code.

> +}
> +
> +

Too many new lines.

> +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(&micro->lock, flags);
> + if (micro->msg != NULL) {

if (!micro->msg) is preferred.

> + list_add_tail(&msg->node, &micro->queue);
> + spin_unlock_irqrestore(&micro->lock, flags);
> + return 0;
> + }
> + micro->msg = msg;
> + ipaq_micro_trigger_tx(micro);
> + spin_unlock_irqrestore(&micro->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;
> +

<snip>

> + case MSG_KEYBOARD:
> + if (micro->key != NULL)

!micro->key

> + micro->key(micro->key_data, len, data);
> + else
> + dev_dbg(micro->dev, "ipaq micro : key message ignored, "
> + "no handle \n");

Log print spread over two lines. Doesn't Checkpatch complain about
this?

> + break;
> + case MSG_TOUCHSCREEN:
> + if (micro->ts != NULL)

!micro->ts ... etc etc

> + micro->ts(micro->ts_data, len, data);
> + else
> + dev_dbg(micro->dev, "ipaq micro : touchscreen message "
> + "ignored, no handle \n");

Print spill.

> +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];

It's common practice to space out the [i*2] for readability.

<snip>

> +static void micro_tx_chars(struct ipaq_micro *micro)
> +{
> + struct ipaq_micro_txdev *tx = &micro->tx;
> + u32 val;
> +
> + while ((tx->index < tx->len) &&
> + (readl(micro->base + UTSR1) & UTSR1_TNF)) {
> + writel(tx->buf[tx->index], micro->base + UTDR);

This is pretty messy. Better broken out? Your call.

<snip>

> + /* Stop interrupts */
> + val = readl(micro->base + UTCR3);
> + val &= ~UTCR3_TIE;
> + writel(val, micro->base + UTCR3);

Ah, so it's correct here. Please see my comment surrounding "start
interrupts" above.

> +}
> +
> +static void micro_reset_comm(struct ipaq_micro *micro)
> +{
> + struct ipaq_micro_rxdev *rx = &micro->rx;
> + u32 val;

New line here please.

> + if (micro->msg)
> + complete(&micro->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);

Any reason for squishing these together?

> + /* 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);
> +}

<snip>

> +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",
> + },
> +};

Is Device Tree ever going to be supported on the iPAC? If not, I think
it's okay for you to make these single line entries.

> +static int micro_suspend(struct device *dev)
> +{
> + return 0;
> +}

Doesn't the PM framework check for NULL .suspend?

> +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;

Can you space all of these out a bit?

> + 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__);

Any need to print the function name if you know the name of the
device?

> + return ret;
> + } else
> + dev_info(&pdev->dev, "grabbed serial port IRQ\n");
> +
> +

Too many new lines.

> + spin_lock_init(&micro->lock);
> + INIT_LIST_HEAD(&micro->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;
> + }

New line here.

> + /* 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;

Break these up please.

> +}
> +
> +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 = &micro_dev_pm_ops,
> + },
> + .probe = micro_probe,
> + .remove = micro_remove,
> + /* .shutdown = micro_suspend, // FIXME */
> +};
> +module_platform_driver(micro_device_driver);

It may seem silly for these piece of h/w, but don't we have a 'no new
device drivers without DT support rule'?

> +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 <linux/spinlock.h>
> +#include <linux/completion.h>
> +#include <linux/list.h>
> +struct device;

What's this for?

<snip>

> +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);
> +}

Why are these in here? Where else are they called from?

--
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org â Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/