summaryrefslogtreecommitdiff
path: root/drivers/mfd/cpcap-core.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mfd/cpcap-core.c')
-rw-r--r--drivers/mfd/cpcap-core.c577
1 files changed, 577 insertions, 0 deletions
diff --git a/drivers/mfd/cpcap-core.c b/drivers/mfd/cpcap-core.c
new file mode 100644
index 00000000000..b248c9d0d4a
--- /dev/null
+++ b/drivers/mfd/cpcap-core.c
@@ -0,0 +1,577 @@
+/*
+ * 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 <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/machine.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/cpcap.h>
+#include <linux/spi/cpcap-regbits.h>
+#include <linux/uaccess.h>
+#include <linux/reboot.h>
+#include <linux/notifier.h>
+#include <linux/delay.h>
+
+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 retval = -EINVAL;
+ struct cpcap_device *cpcap;
+ struct cpcap_platform_data *data;
+ int i;
+ struct cpcap_driver_info *info;
+
+ cpcap = kzalloc(sizeof(*cpcap), GFP_KERNEL);
+ if (cpcap == NULL)
+ return -ENOMEM;
+
+ cpcap->spi = spi;
+ data = spi->controller_data;
+ spi_set_drvdata(spi, cpcap);
+
+ retval = cpcap_regacc_init(cpcap);
+ if (retval < 0)
+ goto free_mem;
+ 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_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");