diff options
Diffstat (limited to 'drivers/misc/modem_if_u1/lte_modem_bootloader.c')
-rw-r--r-- | drivers/misc/modem_if_u1/lte_modem_bootloader.c | 313 |
1 files changed, 313 insertions, 0 deletions
diff --git a/drivers/misc/modem_if_u1/lte_modem_bootloader.c b/drivers/misc/modem_if_u1/lte_modem_bootloader.c new file mode 100644 index 0000000..f259aae --- /dev/null +++ b/drivers/misc/modem_if_u1/lte_modem_bootloader.c @@ -0,0 +1,313 @@ +/* Lte modem bootloader support for Samsung Tuna Board. + * + * Copyright (C) 2011 Google, Inc. + * Copyright (C) 2011 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 <linux/init.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/miscdevice.h> + +#include <linux/uaccess.h> +#include <linux/fs.h> +#include <linux/wait.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/wakelock.h> +#include <linux/delay.h> +#include <linux/spi/spi.h> + +#include <linux/platform_data/modem.h> +#include <linux/platform_data/lte_modem_bootloader.h> + +#define LEN_XMIT_DELEY 100 + +#ifdef AIRPLAIN_MODE_TEST +int lte_airplain_mode; +#endif + +enum xmit_bootloader_status { + XMIT_BOOT_READY = 0, + XMIT_LOADER_READY, +}; + +struct lte_modem_bootloader { + struct spi_device *spi_dev; + struct miscdevice dev; + + struct mutex lock; + + unsigned int gpio_lte2ap_status; + enum xmit_bootloader_status xmit_status; +}; +#define to_loader(misc) container_of(misc, struct lte_modem_bootloader, dev); + +static inline +int spi_xmit(struct lte_modem_bootloader *loader, + const unsigned char val) +{ + unsigned char buf[1]; + int ret; + struct spi_message msg; + + struct spi_transfer xfer = { + .len = 1, + .tx_buf = buf, + }; + + buf[0] = val; + + spi_message_init(&msg); + spi_message_add_tail(&xfer, &msg); + + ret = spi_sync(loader->spi_dev, &msg); + + if (ret < 0) + mif_err("error %d\n", ret); + + return ret; +} + +static +int bootloader_write(struct lte_modem_bootloader *loader, + const char *addr, const int len) +{ + int i; + int ret = 0; + unsigned char lenbuf[4]; + + if (loader->xmit_status == XMIT_LOADER_READY) { + memcpy(lenbuf, &len, ARRAY_SIZE(lenbuf)); + for (i = 0 ; i < ARRAY_SIZE(lenbuf) ; i++) { + ret = spi_xmit(loader, lenbuf[i]); + if (ret < 0) + return ret; + } + msleep(LEN_XMIT_DELEY); + } + + for (i = 0 ; i < len ; i++) { + ret = spi_xmit(loader, addr[i]); + if (ret < 0) + return ret; + } + + return 0; +} + + +static +int bootloader_open(struct inode *inode, struct file *flip) +{ + struct lte_modem_bootloader *loader = to_loader(flip->private_data); + flip->private_data = loader; + + return 0; +} + +static +long bootloader_ioctl(struct file *flip, + unsigned int cmd, unsigned long arg) +{ + int ret = 0; + int status; + struct lte_modem_bootloader_param param; + struct lte_modem_bootloader *loader = flip->private_data; + + mutex_lock(&loader->lock); + switch (cmd) { + case IOCTL_LTE_MODEM_XMIT_BOOT: + + ret = copy_from_user(¶m, (const void __user *)arg, + sizeof(param)); + if (ret) { + mif_err("can not copy userdata\n"); + ret = -EFAULT; + goto exit_err; + } + + dev_info(&loader->spi_dev->dev, + "IOCTL_LTE_MODEM_XMIT_BOOT - bin size: %d\n", + param.len); + + ret = bootloader_write(loader, param.buf, param.len); + if (ret < 0) + mif_err("failed to xmit boot bin\n"); + else { + if (loader->xmit_status == XMIT_BOOT_READY) + loader->xmit_status = XMIT_LOADER_READY; + else + loader->xmit_status = XMIT_BOOT_READY; + } + + break; + case IOCTL_LTE_MODEM_LTE2AP_STATUS: + status = gpio_get_value(loader->gpio_lte2ap_status); + mif_debug("LTE2AP status :%d\n", status); + ret = copy_to_user((unsigned int *)arg, &status, + sizeof(status)); + + break; +#ifdef AIRPLAIN_MODE_TEST + case IOCTL_LTE_MODEM_AIRPLAIN_ON: + lte_airplain_mode = 1; + mif_info("IOCTL_LTE_MODEM LPM_ON\n"); + break; + case IOCTL_LTE_MODEM_AIRPLAIN_OFF: + mif_info("IOCTL_LTE_MODEM LPM_OFF\n"); + lte_airplain_mode = 0; + break; +#endif + default: + mif_err("ioctl cmd error\n"); + ret = -ENOIOCTLCMD; + + break; + } + mutex_unlock(&loader->lock); + +exit_err: + return ret; +} + +static const struct file_operations lte_modem_bootloader_fops = { + .owner = THIS_MODULE, + .open = bootloader_open, + .unlocked_ioctl = bootloader_ioctl, +}; + +static +int bootloader_gpio_setup(struct lte_modem_bootloader *loader) +{ + if (!loader->gpio_lte2ap_status) + return -EINVAL; + + gpio_request(loader->gpio_lte2ap_status, "GPIO_LTE2AP_STATUS"); + gpio_direction_input(loader->gpio_lte2ap_status); + + return 0; +} + +static +int __devinit lte_modem_bootloader_probe(struct spi_device *spi) +{ + int ret; + + struct lte_modem_bootloader *loader; + struct lte_modem_bootloader_platform_data *pdata; + + loader = kzalloc(sizeof(*loader), GFP_KERNEL); + if (!loader) { + mif_err("failed to allocate for lte_modem_bootloader\n"); + ret = -ENOMEM; + goto err_alloc; + } + mutex_init(&loader->lock); + + spi->bits_per_word = 8; + + if (spi_setup(spi)) { + mif_err("failed to setup spi for lte_modem_bootloader\n"); + ret = -EINVAL; + goto err_setup; + } + + loader->spi_dev = spi; + + if (!spi->dev.platform_data) { + mif_err("failed to get platform data for lte_modem_bootloader\n"); + ret = -EINVAL; + goto err_setup; + } + pdata = (struct lte_modem_bootloader_platform_data *) + spi->dev.platform_data; + loader->gpio_lte2ap_status = pdata->gpio_lte2ap_status; + + ret = bootloader_gpio_setup(loader); + if (ret) { + mif_err("failed to set gpio for lte_modem_boot_loader\n"); + goto err_setup; + } + + loader->gpio_lte2ap_status = pdata->gpio_lte2ap_status; + loader->xmit_status = XMIT_BOOT_READY; + + spi_set_drvdata(spi, loader); + + loader->dev.minor = MISC_DYNAMIC_MINOR; + loader->dev.name = "lte_spi"; + loader->dev.fops = <e_modem_bootloader_fops; + ret = misc_register(&loader->dev); + if (ret) { + mif_err("failed to register misc dev for lte_modem_bootloader\n"); + goto err_setup; + } + mif_info("lte_modem_bootloader successfully probed\n"); +#ifdef AIRPLAIN_MODE_TEST + lte_airplain_mode = 0; +#endif + return 0; + +err_setup: + mutex_destroy(&loader->lock); + kfree(loader); + +err_alloc: + + return ret; +} + +static +int __devexit lte_modem_bootloader_remove(struct spi_device *spi) +{ + struct lte_modem_bootloader *loader = spi_get_drvdata(spi); + + misc_deregister(&loader->dev); + mutex_destroy(&loader->lock); + kfree(loader); + + return 0; +} + +static +struct spi_driver lte_modem_bootloader_driver = { + .driver = { + .name = "lte_modem_spi", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = lte_modem_bootloader_probe, + .remove = __devexit_p(lte_modem_bootloader_remove), +}; + +static +int __init lte_modem_bootloader_init(void) +{ + return spi_register_driver(<e_modem_bootloader_driver); +} + +static +void __exit lte_modem_bootloader_exit(void) +{ + spi_unregister_driver(<e_modem_bootloader_driver); +} + +module_init(lte_modem_bootloader_init); +module_exit(lte_modem_bootloader_exit); + +MODULE_DESCRIPTION("LTE Modem Bootloader driver"); +MODULE_LICENSE("GPL"); |