/*
- * Microcontroller Message Bus
+ * Microcontroller Message Bus
+ * Linux kernel driver
*
- * Copyright (c) 2009 Michael Buesch <mb@bu3sch.de>
+ * Copyright (c) 2009 Michael Buesch <mb@bu3sch.de>
*
- * Licensed under the GNU/GPL. See COPYING for details.
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*/
#include "ucmb.h"
#include <linux/spi/spi.h>
#include <linux/spi/spi_gpio.h>
#include <linux/spi/spi_bitbang.h>
+#include <linux/gpio.h>
#include <linux/gfp.h>
#include <linux/delay.h>
#include <linux/crc16.h>
+#include <asm/uaccess.h>
+
#define PFX "ucmb: "
-#define DEBUG
+#undef DEBUG
MODULE_LICENSE("GPL");
struct ucmb {
- unsigned int msg_delay_ms;
+ struct mutex mutex;
+
+ bool is_open;
+
+ unsigned int msg_delay_usec;
+ unsigned int gpio_reset;
+ bool reset_activelow;
/* Misc character device driver */
struct miscdevice mdev;
struct platform_device spi_gpio_pdev;
};
+#define UCMB_MAX_MSG_DELAY (10 * 1000 * 1000) /* 10 seconds */
+
+
struct ucmb_message_hdr {
__le16 magic; /* UCMB_MAGIC */
- __le16 len; /* Payload length (excluding header) */
+ __le16 len; /* Payload length (excluding header and footer) */
} __attribute__((packed));
struct ucmb_message_footer {
} __attribute__((packed));
#define UCMB_MAGIC 0x1337
-#define UCMB_MAX_MSG_LEN 0x200
enum ucmb_status_code {
UCMB_STAT_OK = 0,
static int ucmb_spi_busnum_count = 1337;
-
-
-static struct ucmb_platform_data ucmb_list[] = {
- { //FIXME don't define it here.
- .name = "ucmb",
- .gpio_cs = 3,
- .gpio_sck = 0,
- .gpio_miso = 1,
- .gpio_mosi = 2,
- .mode = SPI_MODE_0,
- .max_speed_hz = 128000, /* Hz */
- .msg_delay_ms = 1, /* mS */
- },
-};
+static int ucmb_pdev_id_count;
static int __devinit ucmb_spi_probe(struct spi_device *sdev)
.remove = __devexit_p(ucmb_spi_remove),
};
+static void ucmb_toggle_reset_line(struct ucmb *ucmb, bool active)
+{
+ if (ucmb->reset_activelow)
+ active = !active;
+ gpio_set_value(ucmb->gpio_reset, active);
+}
+
+static int ucmb_reset_microcontroller(struct ucmb *ucmb)
+{
+ if (ucmb->gpio_reset == UCMB_NO_RESET)
+ return -ENODEV;
+
+ ucmb_toggle_reset_line(ucmb, 1);
+ msleep(50);
+ ucmb_toggle_reset_line(ucmb, 0);
+ msleep(50);
+
+ return 0;
+}
+
static int ucmb_status_code_to_errno(enum ucmb_status_code code)
{
switch (code) {
return container_of(filp->f_op, struct ucmb, mdev_fops);
}
+static int ucmb_open(struct inode *inode, struct file *filp)
+{
+ struct ucmb *ucmb = filp_to_ucmb(filp);
+ int err = 0;
+
+ mutex_lock(&ucmb->mutex);
+
+ if (ucmb->is_open) {
+ err = -EBUSY;
+ goto out_unlock;
+ }
+ ucmb->is_open = 1;
+ ucmb->msg_delay_usec = 0;
+
+out_unlock:
+ mutex_unlock(&ucmb->mutex);
+
+ return err;
+}
+
+static int ucmb_release(struct inode *inode, struct file *filp)
+{
+ struct ucmb *ucmb = filp_to_ucmb(filp);
+
+ mutex_lock(&ucmb->mutex);
+ WARN_ON(!ucmb->is_open);
+ ucmb->is_open = 0;
+ mutex_unlock(&ucmb->mutex);
+
+ return 0;
+}
+
+static int ucmb_ioctl(struct inode *inode, struct file *filp,
+ unsigned int cmd, unsigned long arg)
+{
+ struct ucmb *ucmb = filp_to_ucmb(filp);
+ int ret = 0;
+
+ mutex_lock(&ucmb->mutex);
+ switch (cmd) {
+ case UCMB_IOCTL_RESETUC:
+ ret = ucmb_reset_microcontroller(ucmb);
+ break;
+ case UCMB_IOCTL_GMSGDELAY:
+ if (put_user(ucmb->msg_delay_usec, (unsigned int __user *)arg)) {
+ ret = -EFAULT;
+ break;
+ }
+ break;
+ case UCMB_IOCTL_SMSGDELAY: {
+ unsigned int msg_delay_usec;
+
+ if (get_user(msg_delay_usec, (unsigned int __user *)arg)) {
+ ret = -EFAULT;
+ break;
+ }
+ if (msg_delay_usec > UCMB_MAX_MSG_DELAY) {
+ ret = -E2BIG;
+ break;
+ }
+ ucmb->msg_delay_usec = msg_delay_usec;
+ break;
+ }
+ default:
+ ret = -EINVAL;
+ }
+ mutex_unlock(&ucmb->mutex);
+
+ return ret;
+}
+
static ssize_t ucmb_read(struct file *filp, char __user *user_buf,
size_t size, loff_t *offp)
{
struct ucmb_status status = { .magic = cpu_to_le16(UCMB_MAGIC), };
u16 crc = 0xFFFF;
+ mutex_lock(&ucmb->mutex);
+
size = min_t(size_t, size, PAGE_SIZE);
err = -ENOMEM;
if (err)
goto out_free;
- crc = crc16(crc, &hdr, sizeof(hdr));
+ crc = crc16(crc, (u8 *)&hdr, sizeof(hdr));
crc = crc16(crc, buf, size);
crc ^= 0xFFFF;
if (crc != le16_to_cpu(footer.crc)) {
out_free:
free_page((unsigned long)buf);
out:
+ mutex_unlock(&ucmb->mutex);
+
return err ? err : size;
}
struct spi_transfer spi_data_xfer;
struct spi_message spi_msg;
+ mutex_lock(&ucmb->mutex);
+
err = -ENOMEM;
buf = (char *)__get_free_page(GFP_KERNEL);
if (!buf)
goto out;
size = min_t(size_t, PAGE_SIZE, size);
- size = min_t(size_t, UCMB_MAX_MSG_LEN, size);
err = -EFAULT;
if (copy_from_user(buf, user_buf, size))
goto out_free;
hdr.len = cpu_to_le16(size);
- footer.crc = crc16(footer.crc, &hdr, sizeof(hdr));
+ footer.crc = crc16(footer.crc, (u8 *)&hdr, sizeof(hdr));
footer.crc = crc16(footer.crc, buf, size);
footer.crc ^= 0xFFFF;
if (err)
goto out_free;
- /* The microcontroller deserves some time to process the message. */
- if (ucmb->msg_delay_ms)
- msleep(ucmb->msg_delay_ms);
+ if (ucmb->msg_delay_usec) {
+ /* The microcontroller deserves some time to process the message. */
+ if (ucmb->msg_delay_usec >= 1000000) {
+ ssleep(ucmb->msg_delay_usec / 1000000);
+ msleep(DIV_ROUND_UP(ucmb->msg_delay_usec % 1000000, 1000));
+ } else if (ucmb->msg_delay_usec >= 1000) {
+ msleep(DIV_ROUND_UP(ucmb->msg_delay_usec, 1000));
+ } else
+ udelay(ucmb->msg_delay_usec);
+ }
/* Get the status code. */
err = spi_read(ucmb->sdev, (u8 *)&status, sizeof(status));
out_free:
free_page((unsigned long)buf);
out:
+ mutex_unlock(&ucmb->mutex);
+
return err ? err : size;
}
ucmb = kzalloc(sizeof(struct ucmb), GFP_KERNEL);
if (!ucmb)
return -ENOMEM;
- ucmb->msg_delay_ms = pdata->msg_delay_ms;
+ mutex_init(&ucmb->mutex);
+ ucmb->gpio_reset = pdata->gpio_reset;
+ ucmb->reset_activelow = pdata->reset_activelow;
/* Create the SPI GPIO bus master. */
goto err_free_spi_device;
}
+ /* Initialize the RESET line. */
+
+ if (pdata->gpio_reset != UCMB_NO_RESET) {
+ err = gpio_request(pdata->gpio_reset, pdata->name);
+ if (err) {
+ printk(KERN_ERR PFX
+ "Failed to request RESET GPIO line\n");
+ goto err_unreg_spi_device;
+ }
+ err = gpio_direction_output(pdata->gpio_reset,
+ pdata->reset_activelow);
+ if (err) {
+ printk(KERN_ERR PFX
+ "Failed to set RESET GPIO direction\n");
+ goto err_free_reset_gpio;
+ }
+ ucmb_reset_microcontroller(ucmb);
+ }
+
/* Create the Misc char device. */
ucmb->mdev.minor = MISC_DYNAMIC_MINOR;
ucmb->mdev.name = pdata->name;
ucmb->mdev.parent = &pdev->dev;
+ ucmb->mdev_fops.open = ucmb_open;
+ ucmb->mdev_fops.release = ucmb_release;
ucmb->mdev_fops.read = ucmb_read;
ucmb->mdev_fops.write = ucmb_write;
+ ucmb->mdev_fops.ioctl = ucmb_ioctl;
ucmb->mdev.fops = &ucmb->mdev_fops;
err = misc_register(&ucmb->mdev);
if (err) {
printk(KERN_ERR PFX "Failed to register miscdev %s\n",
ucmb->mdev.name);
- goto err_unreg_spi_device;
+ goto err_free_reset_gpio;
}
platform_set_drvdata(pdev, ucmb);
return 0;
+err_free_reset_gpio:
+ if (pdata->gpio_reset != UCMB_NO_RESET)
+ gpio_free(pdata->gpio_reset);
err_unreg_spi_device:
spi_unregister_device(ucmb->sdev);
err_free_spi_device:
printk(KERN_ERR PFX "Failed to unregister miscdev %s\n",
ucmb->mdev.name);
}
+ if (ucmb->gpio_reset != UCMB_NO_RESET)
+ gpio_free(ucmb->gpio_reset);
spi_unregister_device(ucmb->sdev);
spi_dev_put(ucmb->sdev);
platform_device_unregister(&ucmb->spi_gpio_pdev);
.owner = THIS_MODULE,
},
.probe = ucmb_probe,
- .remove = __devexit_p(ucmb_probe),
+ .remove = __devexit_p(ucmb_remove),
};
-static int ucmb_modinit(void)
+int ucmb_device_register(struct ucmb_platform_data *pdata)
{
- struct ucmb_platform_data *pdata;
struct platform_device *pdev;
- int err, i;
-
- printk(KERN_INFO "Microcontroller message bus driver\n");
+ int err;
- err = platform_driver_register(&ucmb_driver);
+ pdev = platform_device_alloc("ucmb", ucmb_pdev_id_count++);
+ if (!pdev) {
+ printk(KERN_ERR PFX "Failed to allocate platform device.\n");
+ return -ENOMEM;
+ }
+ err = platform_device_add_data(pdev, pdata, sizeof(*pdata));
if (err) {
- printk(KERN_ERR PFX "Failed to register platform driver\n");
+ printk(KERN_ERR PFX "Failed to add platform data.\n");
+ platform_device_put(pdev);
return err;
}
- err = spi_register_driver(&ucmb_spi_driver);
+ err = platform_device_add(pdev);
if (err) {
- printk(KERN_ERR PFX "Failed to register SPI driver\n");
- platform_driver_unregister(&ucmb_driver);
+ printk(KERN_ERR PFX "Failed to register platform device.\n");
+ platform_device_put(pdev);
return err;
}
+ pdata->pdev = pdev;
- for (i = 0; i < ARRAY_SIZE(ucmb_list); i++) {
- pdata = &ucmb_list[i];
+ return 0;
+}
+EXPORT_SYMBOL(ucmb_device_register);
- pdev = platform_device_alloc("ucmb", i);
- if (!pdev) {
- printk(KERN_ERR PFX "Failed to allocate platform device.\n");
- break;
- }
- err = platform_device_add_data(pdev, pdata, sizeof(*pdata));
- if (err) {
- printk(KERN_ERR PFX "Failed to add platform data.\n");
- platform_device_put(pdev);
- break;
- }
- err = platform_device_add(pdev);
- if (err) {
- printk(KERN_ERR PFX "Failed to register platform device.\n");
- platform_device_put(pdev);
- break;
- }
- pdata->pdev = pdev;
+void ucmb_device_unregister(struct ucmb_platform_data *pdata)
+{
+ if (!pdata->pdev)
+ return;
+ platform_device_unregister(pdata->pdev);
+ platform_device_put(pdata->pdev);
+ pdata->pdev = NULL;
+}
+EXPORT_SYMBOL(ucmb_device_unregister);
+
+static int ucmb_modinit(void)
+{
+ int err;
+
+ printk(KERN_INFO "Microcontroller message bus driver\n");
+
+ err = spi_register_driver(&ucmb_spi_driver);
+ if (err) {
+ printk(KERN_ERR PFX "Failed to register SPI driver\n");
+ return err;
+ }
+ err = platform_driver_register(&ucmb_driver);
+ if (err) {
+ printk(KERN_ERR PFX "Failed to register platform driver\n");
+ spi_unregister_driver(&ucmb_spi_driver);
+ return err;
}
return 0;
}
-module_init(ucmb_modinit);
+subsys_initcall(ucmb_modinit);
static void ucmb_modexit(void)
{
- struct ucmb_platform_data *pdata;
- int i;
-
- for (i = 0; i < ARRAY_SIZE(ucmb_list); i++) {
- pdata = &ucmb_list[i];
-
- if (pdata->pdev) {
- platform_device_unregister(pdata->pdev);
- platform_device_put(pdata->pdev);
- }
- }
- spi_unregister_driver(&ucmb_spi_driver);
platform_driver_unregister(&ucmb_driver);
+ spi_unregister_driver(&ucmb_spi_driver);
}
module_exit(ucmb_modexit);