/* * Copyright (C) 2007-2010 Motorola, Inc. * * 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. * * 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307, USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct cpcap_driver_info { struct list_head list; struct platform_device *pdev; }; static long ioctl(struct file *file, unsigned int cmd, unsigned long arg); static int cpcap_probe(struct spi_device *spi); static int cpcap_remove(struct spi_device *spi); const static struct file_operations cpcap_fops = { .owner = THIS_MODULE, .unlocked_ioctl = ioctl, }; static struct miscdevice cpcap_dev = { .minor = MISC_DYNAMIC_MINOR, .name = CPCAP_DEV_NAME, .fops = &cpcap_fops, }; static struct spi_driver cpcap_driver = { .driver = { .name = "cpcap", .bus = &spi_bus_type, .owner = THIS_MODULE, }, .probe = cpcap_probe, .remove = cpcap_remove, }; static struct platform_device cpcap_adc_device = { .name = "cpcap_adc", .id = -1, .dev.platform_data = NULL, }; static struct platform_device cpcap_key_device = { .name = "cpcap_key", .id = -1, .dev.platform_data = NULL, }; static struct platform_device cpcap_batt_device = { .name = "cpcap_battery", .id = -1, .dev.platform_data = NULL, }; static struct platform_device cpcap_uc_device = { .name = "cpcap_uc", .id = -1, .dev.platform_data = NULL, }; static struct platform_device cpcap_rtc_device = { .name = "cpcap_rtc", .id = -1, .dev.platform_data = NULL, }; /* List of required CPCAP devices that will ALWAYS be present. * * DO NOT ADD NEW DEVICES TO THIS LIST! You must use cpcap_driver_register() * for any new drivers for non-core functionality of CPCAP. */ static struct platform_device *cpcap_devices[] = { &cpcap_uc_device, &cpcap_adc_device, &cpcap_key_device, &cpcap_batt_device, &cpcap_rtc_device, }; static struct cpcap_device *misc_cpcap; static LIST_HEAD(cpcap_device_list); static DEFINE_MUTEX(cpcap_driver_lock); static int cpcap_reboot(struct notifier_block *this, unsigned long code, void *cmd) { int ret = -1; int result = NOTIFY_DONE; char *mode = cmd; unsigned short value; unsigned short counter = 0; /* Disable the USB transceiver */ ret = cpcap_regacc_write(misc_cpcap, CPCAP_REG_USBC2, 0, CPCAP_BIT_USBXCVREN); if (ret) { dev_err(&(misc_cpcap->spi->dev), "Disable Transciever failure.\n"); result = NOTIFY_BAD; } if (code == SYS_RESTART) { if (mode != NULL && !strncmp("outofcharge", mode, 12)) { /* Set the outofcharge bit in the cpcap */ ret = cpcap_regacc_write(misc_cpcap, CPCAP_REG_VAL1, CPCAP_BIT_OUT_CHARGE_ONLY, CPCAP_BIT_OUT_CHARGE_ONLY); if (ret) { dev_err(&(misc_cpcap->spi->dev), "outofcharge cpcap set failure.\n"); result = NOTIFY_BAD; } /* Set the soft reset bit in the cpcap */ cpcap_regacc_write(misc_cpcap, CPCAP_REG_VAL1, CPCAP_BIT_SOFT_RESET, CPCAP_BIT_SOFT_RESET); if (ret) { dev_err(&(misc_cpcap->spi->dev), "reset cpcap set failure.\n"); result = NOTIFY_BAD; } } /* Check if we are starting recovery mode */ if (mode != NULL && !strncmp("recovery", mode, 9)) { /* Set the fota (recovery mode) bit in the cpcap */ ret = cpcap_regacc_write(misc_cpcap, CPCAP_REG_VAL1, CPCAP_BIT_FOTA_MODE, CPCAP_BIT_FOTA_MODE); if (ret) { dev_err(&(misc_cpcap->spi->dev), "Recovery cpcap set failure.\n"); result = NOTIFY_BAD; } } else { /* Set the fota (recovery mode) bit in the cpcap */ ret = cpcap_regacc_write(misc_cpcap, CPCAP_REG_VAL1, 0, CPCAP_BIT_FOTA_MODE); if (ret) { dev_err(&(misc_cpcap->spi->dev), "Recovery cpcap clear failure.\n"); result = NOTIFY_BAD; } } /* Check if we are going into fast boot mode */ if (mode != NULL && !strncmp("bootloader", mode, 11)) { /* Set the bootmode bit in the cpcap */ ret = cpcap_regacc_write(misc_cpcap, CPCAP_REG_VAL1, CPCAP_BIT_BOOT_MODE, CPCAP_BIT_BOOT_MODE); if (ret) { dev_err(&(misc_cpcap->spi->dev), "Boot mode cpcap set failure.\n"); result = NOTIFY_BAD; } } cpcap_regacc_write(misc_cpcap, CPCAP_REG_MI2, 0, 0xFFFF); } else { ret = cpcap_regacc_write(misc_cpcap, CPCAP_REG_VAL1, 0, CPCAP_BIT_OUT_CHARGE_ONLY); if (ret) { dev_err(&(misc_cpcap->spi->dev), "outofcharge cpcap set failure.\n"); result = NOTIFY_BAD; } /* Clear the soft reset bit in the cpcap */ ret = cpcap_regacc_write(misc_cpcap, CPCAP_REG_VAL1, 0, CPCAP_BIT_SOFT_RESET); if (ret) { dev_err(&(misc_cpcap->spi->dev), "SW Reset cpcap set failure.\n"); result = NOTIFY_BAD; } /* Clear the fota (recovery mode) bit in the cpcap */ ret = cpcap_regacc_write(misc_cpcap, CPCAP_REG_VAL1, 0, CPCAP_BIT_FOTA_MODE); if (ret) { dev_err(&(misc_cpcap->spi->dev), "Recovery cpcap clear failure.\n"); result = NOTIFY_BAD; } } /* Always clear the kpanic bit */ ret = cpcap_regacc_write(misc_cpcap, CPCAP_REG_VAL1, 0, CPCAP_BIT_AP_KERNEL_PANIC); if (ret) { dev_err(&(misc_cpcap->spi->dev), "Clear kernel panic bit failure.\n"); result = NOTIFY_BAD; } /* Always clear the power cut bit on SW Shutdown*/ ret = cpcap_regacc_write(misc_cpcap, CPCAP_REG_PC1, 0, CPCAP_BIT_PC1_PCEN); if (ret) { dev_err(&(misc_cpcap->spi->dev), "Clear Power Cut bit failure.\n"); result = NOTIFY_BAD; } cpcap_regacc_write(misc_cpcap, CPCAP_REG_CRM, 0x0300, 0x3FFF); (void)cpcap_regacc_read(misc_cpcap, CPCAP_REG_INTS2, &value); if (!(value & CPCAP_BIT_VBUSVLD_S)) { while ((value & CPCAP_BIT_SESSVLD_S) && (counter < 100)) { mdelay(10); counter++; (void)cpcap_regacc_read(misc_cpcap, CPCAP_REG_INTS2, &value); } } /* Clear the charger and charge path settings to avoid a false turn on * event in caused by CPCAP. After clearing these settings, 100ms is * needed to before SYSRSTRTB is pulled low to avoid the false turn on * event. */ cpcap_regacc_write(misc_cpcap, CPCAP_REG_CRM, 0, 0x3FFF); mdelay(100); return result; } static struct notifier_block cpcap_reboot_notifier = { .notifier_call = cpcap_reboot, }; static int __init cpcap_init(void) { return spi_register_driver(&cpcap_driver); } static void cpcap_vendor_read(struct cpcap_device *cpcap) { unsigned short value; (void)cpcap_regacc_read(cpcap, CPCAP_REG_VERSC1, &value); cpcap->vendor = (enum cpcap_vendor)((value >> 6) & 0x0007); cpcap->revision = (enum cpcap_revision)(((value >> 3) & 0x0007) | ((value << 3) & 0x0038)); } int cpcap_device_unregister(struct platform_device *pdev) { struct cpcap_driver_info *info; struct cpcap_driver_info *tmp; int found; found = 0; mutex_lock(&cpcap_driver_lock); list_for_each_entry_safe(info, tmp, &cpcap_device_list, list) { if (info->pdev == pdev) { list_del(&info->list); /* * misc_cpcap != NULL suggests pdev * already registered */ if (misc_cpcap) { printk(KERN_INFO "CPCAP: unregister %s\n", pdev->name); platform_device_unregister(pdev); } info->pdev = NULL; kfree(info); found = 1; } } mutex_unlock(&cpcap_driver_lock); BUG_ON(!found); return 0; } int cpcap_device_register(struct platform_device *pdev) { int retval; struct cpcap_driver_info *info; retval = 0; info = kzalloc(sizeof(*info), GFP_KERNEL); if (!info) { printk(KERN_ERR "Cannot save device %s\n", pdev->name); return -ENOMEM; } mutex_lock(&cpcap_driver_lock); info->pdev = pdev; list_add_tail(&info->list, &cpcap_device_list); /* If misc_cpcap is valid, the CPCAP driver has already been probed. * Therefore, call platform_device_register() to probe the device. */ if (misc_cpcap) { dev_info(&(misc_cpcap->spi->dev), "Probing CPCAP device %s\n", pdev->name); /* * platform_data is non-empty indicates * CPCAP client devices need to pass their own data * In that case we put cpcap data in driver_data */ if (pdev->dev.platform_data != NULL) platform_set_drvdata(pdev, misc_cpcap); else pdev->dev.platform_data = misc_cpcap; retval = platform_device_register(pdev); } else printk(KERN_INFO "CPCAP: delaying %s probe\n", pdev->name); mutex_unlock(&cpcap_driver_lock); return retval; } static int cpcap_probe(struct spi_device *spi) { int i, retval = -EINVAL; struct cpcap_device *cpcap; struct cpcap_platform_data *data; unsigned int irq; struct cpcap_driver_info *info; cpcap = kzalloc(sizeof(*cpcap), GFP_KERNEL); if (cpcap == NULL) return -ENOMEM; cpcap->spi = spi; data = cpcap_get_plat_data(cpcap); if (data == NULL) { dev_info(&(spi->dev), "No platform data found for CPCAP\n"); goto free_mem; } /* fixup struct spi_device for backward compatibility */ spi->controller_data = data; spi_set_drvdata(spi, cpcap); /* setup IRQ GPIO (previously done in board file) */ retval = gpio_request(data->irq_gpio, "cpcap-irq"); if (retval) goto free_mem; retval = gpio_direction_input(data->irq_gpio); if (retval) goto free_gpio; irq = gpio_to_irq(data->irq_gpio); irq_set_irq_type(irq, IRQ_TYPE_EDGE_RISING); spi->irq = irq; retval = cpcap_regacc_init(cpcap); if (retval < 0) goto free_gpio; retval = cpcap_irq_init(cpcap); if (retval < 0) goto free_cpcap_irq; cpcap_vendor_read(cpcap); for (i = 0; i < ARRAY_SIZE(cpcap_devices); i++) cpcap_devices[i]->dev.platform_data = cpcap; retval = misc_register(&cpcap_dev); if (retval < 0) goto free_cpcap_irq; /* loop twice becuase cpcap_regulator_probe may refer to other devices * in this list to handle dependencies between regulators. Create them * all and then add them */ for (i = 0; i < CPCAP_NUM_REGULATORS; i++) { struct platform_device *pdev; pdev = platform_device_alloc("cpcap-regltr", i); if (!pdev) { dev_err(&(spi->dev), "Cannot create regulator\n"); continue; } pdev->dev.parent = &(spi->dev); pdev->dev.platform_data = &data->regulator_init[i]; platform_set_drvdata(pdev, cpcap); cpcap->regulator_pdev[i] = pdev; } for (i = 0; i < CPCAP_NUM_REGULATORS; i++) { /* vusb has to be added after sw5 so skip it for now, * it will be added from probe of sw5 */ if (i == CPCAP_VUSB) continue; platform_device_add(cpcap->regulator_pdev[i]); } platform_add_devices(cpcap_devices, ARRAY_SIZE(cpcap_devices)); mutex_lock(&cpcap_driver_lock); misc_cpcap = cpcap; /* kept for misc device */ list_for_each_entry(info, &cpcap_device_list, list) { int ret = 0; dev_info(&(spi->dev), "Probing CPCAP device %s\n", info->pdev->name); if (info->pdev->dev.platform_data != NULL) platform_set_drvdata(info->pdev, cpcap); else info->pdev->dev.platform_data = cpcap; ret = platform_device_register(info->pdev); } mutex_unlock(&cpcap_driver_lock); register_reboot_notifier(&cpcap_reboot_notifier); return 0; free_cpcap_irq: cpcap_irq_shutdown(cpcap); free_gpio: gpio_free(data->irq_gpio); free_mem: kfree(cpcap); return retval; } static int cpcap_remove(struct spi_device *spi) { struct cpcap_device *cpcap = spi_get_drvdata(spi); struct cpcap_driver_info *info; int i; unregister_reboot_notifier(&cpcap_reboot_notifier); mutex_lock(&cpcap_driver_lock); list_for_each_entry(info, &cpcap_device_list, list) { dev_info(&(spi->dev), "Removing CPCAP device %s\n", info->pdev->name); platform_device_unregister(info->pdev); } misc_cpcap = NULL; mutex_unlock(&cpcap_driver_lock); for (i = ARRAY_SIZE(cpcap_devices); i > 0; i--) platform_device_unregister(cpcap_devices[i-1]); for (i = 0; i < CPCAP_NUM_REGULATORS; i++) platform_device_unregister(cpcap->regulator_pdev[i]); misc_deregister(&cpcap_dev); cpcap_irq_shutdown(cpcap); kfree(cpcap); return 0; } static int test_ioctl(unsigned int cmd, unsigned long arg) { int retval = -EINVAL; struct cpcap_regacc read_data; struct cpcap_regacc write_data; switch (cmd) { case CPCAP_IOCTL_TEST_READ_REG: if (copy_from_user((void *)&read_data, (void *)arg, sizeof(read_data))) return -EFAULT; retval = cpcap_regacc_read(misc_cpcap, read_data.reg, &read_data.value); if (retval < 0) return retval; if (copy_to_user((void *)arg, (void *)&read_data, sizeof(read_data))) return -EFAULT; return 0; break; case CPCAP_IOCTL_TEST_WRITE_REG: if (copy_from_user((void *) &write_data, (void *) arg, sizeof(write_data))) return -EFAULT; retval = cpcap_regacc_write(misc_cpcap, write_data.reg, write_data.value, write_data.mask); break; default: retval = -ENOTTY; break; } return retval; } static int adc_ioctl(unsigned int cmd, unsigned long arg) { int retval = -EINVAL; struct cpcap_adc_phase phase; switch (cmd) { case CPCAP_IOCTL_ADC_PHASE: if (copy_from_user((void *) &phase, (void *) arg, sizeof(phase))) return -EFAULT; cpcap_adc_phase(misc_cpcap, &phase); retval = 0; break; default: retval = -ENOTTY; break; } return retval; } #if defined(CONFIG_LEDS_FLASH_RESET) int cpcap_direct_misc_write(unsigned short reg, unsigned short value,\ unsigned short mask) { int retval = -EINVAL; retval = cpcap_regacc_write(misc_cpcap, reg, value, mask); return retval; } #endif static long ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int retval = -ENOTTY; unsigned int cmd_num; cmd_num = _IOC_NR(cmd); if ((cmd_num > CPCAP_IOCTL_NUM_TEST__START) && (cmd_num < CPCAP_IOCTL_NUM_TEST__END)) { retval = test_ioctl(cmd, arg); } if ((cmd_num > CPCAP_IOCTL_NUM_ADC__START) && (cmd_num < CPCAP_IOCTL_NUM_ADC__END)) { retval = adc_ioctl(cmd, arg); } return retval; } static void cpcap_shutdown(void) { spi_unregister_driver(&cpcap_driver); } int cpcap_disable_offmode_wakeups(bool disable) { int retval = 0; return retval; } subsys_initcall(cpcap_init); module_exit(cpcap_shutdown); MODULE_ALIAS("platform:cpcap"); MODULE_DESCRIPTION("CPCAP driver"); MODULE_AUTHOR("Motorola"); MODULE_LICENSE("GPL");