From: "Ying-Chun Liu (PaulLiu)" paul.liu@linaro.org
Add DA9052 mfd driver from Dialog. Modify Kconfig/Makefile for DA9052 mfd driver.
Signed-off-by: Zhou Jingyu Jingyu.Zhou@freescale.com Acked-by: Lily Zhang r58066@freescale.com Signed-off-by: Ying-Chun Liu (PaulLiu) paul.liu@linaro.org --- drivers/mfd/Kconfig | 37 +++ drivers/mfd/Makefile | 2 + drivers/mfd/da9052-core.c | 536 +++++++++++++++++++++++++++++++++++++++++++++ drivers/mfd/da9052-i2c.c | 378 ++++++++++++++++++++++++++++++++ drivers/mfd/da9052-spi.c | 402 +++++++++++++++++++++++++++++++++ 5 files changed, 1355 insertions(+), 0 deletions(-) create mode 100644 drivers/mfd/da9052-core.c create mode 100644 drivers/mfd/da9052-i2c.c create mode 100644 drivers/mfd/da9052-spi.c
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 0f09c05..a3d0568 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -483,6 +483,43 @@ config PCF50633_GPIO Say yes here if you want to include support GPIO for pins on the PCF50633 chip.
+config PMIC_DIALOG + bool "Support Dialog Semiconductor PMIC" + depends on I2C=y + depends on SPI=y + select MFD_CORE + help + Support for Dialog semiconductor PMIC chips. + Use the options provided to support the desired PMIC's. +choice + prompt "Chip Type" + depends on PMIC_DIALOG +config PMIC_DA9052 + bool "Support Dialog Semiconductor DA9052 PMIC" + help + Support for Dialog semiconductor DA9052 PMIC with inbuilt + SPI & I2C connectivities. + This driver provides common support for accessing the device, + additional drivers must be enabled in order to use the + functionality of the device. +config PMIC_DA9053AA + bool "Support Dialog Semiconductor DA9053 AA PMIC" + help + Support for Dialog semiconductor DA9053 AA PMIC with inbuilt + SPI & I2C connectivities. + This driver provides common support for accessing the device, + additional drivers must be enabled in order to use the + functionality of the device. +config PMIC_DA9053Bx + bool "Support Dialog Semiconductor DA9053 BA/BB PMIC" + help + Support for Dialog semiconductor DA9053 BA/BB PMIC with inbuilt + SPI & I2C connectivities. + This driver provides common support for accessing the device, + additional drivers must be enabled in order to use the + functionality of the device. +endchoice + config MFD_MC13783 tristate
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index efe3cc3..d30b69c 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -94,3 +94,5 @@ obj-$(CONFIG_MFD_OMAP_USB_HOST) += omap-usb-host.o obj-$(CONFIG_MFD_PM8921_CORE) += pm8921-core.o obj-$(CONFIG_MFD_PM8XXX_IRQ) += pm8xxx-irq.o obj-$(CONFIG_MFD_TPS65910) += tps65910.o tps65910-irq.o +da9052-objs := da9052-spi.o da9052-i2c.o da9052-core.o +obj-$(CONFIG_PMIC_DIALOG) += da9052.o diff --git a/drivers/mfd/da9052-core.c b/drivers/mfd/da9052-core.c new file mode 100644 index 0000000..002cf50 --- /dev/null +++ b/drivers/mfd/da9052-core.c @@ -0,0 +1,536 @@ +/* + * da9052-core.c -- Device access for Dialog DA9052 + * + * Copyright(c) 2009 Dialog Semiconductor Ltd. + * + * Author: Dialog Semiconductor Ltd dchen@diasemi.com + * + * 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. + * + */ + +#include <linux/device.h> +#include <linux/jiffies.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/irq.h> +#include <linux/list.h> +#include <linux/mfd/core.h> +#include <linux/spi/spi.h> +#include <linux/i2c.h> +#include <linux/semaphore.h> + +#include <linux/mfd/da9052/da9052.h> +#include <linux/mfd/da9052/adc.h> + +#define SUCCESS 0 +#define FAILURE 1 + +struct da9052_eh_nb eve_nb_array[EVE_CNT]; +static struct da9052_ssc_ops ssc_ops; +struct mutex manconv_lock; +static struct semaphore eve_nb_array_lock; + +void da9052_lock(struct da9052 *da9052) +{ + mutex_lock(&da9052->ssc_lock); +} +EXPORT_SYMBOL(da9052_lock); + +void da9052_unlock(struct da9052 *da9052) +{ + mutex_unlock(&da9052->ssc_lock); +} +EXPORT_SYMBOL(da9052_unlock); + +int da9052_ssc_write(struct da9052 *da9052, struct da9052_ssc_msg *sscmsg) +{ + int ret = 0; + + /* Reg address should be a valid address on PAGE0 or PAGE1 */ + if ((sscmsg->addr < DA9052_PAGE0_REG_START) || + (sscmsg->addr > DA9052_PAGE1_REG_END) || + ((sscmsg->addr > DA9052_PAGE0_REG_END) && + (sscmsg->addr < DA9052_PAGE1_REG_START))) + return INVALID_REGISTER; + + ret = ssc_ops.write(da9052, sscmsg); + + /* Update local cache if required */ + if (!ret) { + /* Check if this register is Non-volatile*/ + if (da9052->ssc_cache[sscmsg->addr].type != VOLATILE) { + /* Update value */ + da9052->ssc_cache[sscmsg->addr].val = sscmsg->data; + /* Make this cache entry valid */ + da9052->ssc_cache[sscmsg->addr].status = VALID; + } + } + + return ret; +} + +int da9052_ssc_read(struct da9052 *da9052, struct da9052_ssc_msg *sscmsg) +{ + int ret = 0; + + /* Reg addr should be a valid address on PAGE0 or PAGE1 */ + if ((sscmsg->addr < DA9052_PAGE0_REG_START) || + (sscmsg->addr > DA9052_PAGE1_REG_END) || + ((sscmsg->addr > DA9052_PAGE0_REG_END) && + (sscmsg->addr < DA9052_PAGE1_REG_START))) + return INVALID_REGISTER; + + /* + * Check if this is a Non-volatile register, if yes then return value - + * from cache instead of actual reading from hardware. Before reading - + * cache entry, make sure that the entry is valid + */ + /* The read request is for Non-volatile register */ + /* Check if we have valid cached value for this */ + if (da9052->ssc_cache[sscmsg->addr].status == VALID) { + /* We have valid cached value, copy this value */ + sscmsg->data = da9052->ssc_cache[sscmsg->addr].val; + + return 0; + } + + ret = ssc_ops.read(da9052, sscmsg); + + /* Update local cache if required */ + if (!ret) { + /* Check if this register is Non-volatile*/ + if (da9052->ssc_cache[sscmsg->addr].type != VOLATILE) { + /* Update value */ + da9052->ssc_cache[sscmsg->addr].val = sscmsg->data; + /* Make this cache entry valid */ + da9052->ssc_cache[sscmsg->addr].status = VALID; + } + } + + return ret; +} + +int da9052_ssc_write_many(struct da9052 *da9052, struct da9052_ssc_msg *sscmsg, + int msg_no) +{ + int ret = 0; + int cnt = 0; + + /* Check request size */ + if (msg_no > MAX_READ_WRITE_CNT) + return -EIO; + + ret = ssc_ops.write_many(da9052, sscmsg, msg_no); + /* Update local cache, if required */ + for (cnt = 0; cnt < msg_no; cnt++) { + /* Check if this register is Non-volatile*/ + if (da9052->ssc_cache[sscmsg[cnt].addr].type != VOLATILE) { + /* Update value */ + da9052->ssc_cache[sscmsg[cnt].addr].val = + sscmsg[cnt].data; + /* Make this cache entry valid */ + da9052->ssc_cache[sscmsg[cnt].addr].status = VALID; + } + } + return ret; +} + +int da9052_ssc_read_many(struct da9052 *da9052, struct da9052_ssc_msg *sscmsg, + int msg_no) +{ + int ret = 0; + int cnt = 0; + + /* Check request size */ + if (msg_no > MAX_READ_WRITE_CNT) + return -EIO; + + ret = ssc_ops.read_many(da9052, sscmsg, msg_no); + /* Update local cache, if required */ + for (cnt = 0; cnt < msg_no; cnt++) { + /* Check if this register is Non-volatile*/ + if (da9052->ssc_cache[sscmsg[cnt].addr].type + != VOLATILE) { + /* Update value */ + da9052->ssc_cache[sscmsg[cnt].addr].val = + sscmsg[cnt].data; + /* Make this cache entry valid */ + da9052->ssc_cache[sscmsg[cnt].addr].status = VALID; + } + } + return ret; +} + +static irqreturn_t da9052_eh_isr(int irq, void *dev_id) +{ + struct da9052 *da9052 = dev_id; + /* Schedule work to be done */ + schedule_work(&da9052->eh_isr_work); + /* Disable IRQ */ + disable_irq_nosync(da9052->irq); + return IRQ_HANDLED; +} + +int eh_register_nb(struct da9052 *da9052, struct da9052_eh_nb *nb) +{ + + if (nb == NULL) { + printk(KERN_INFO "EH REGISTER FUNCTION FAILED\n"); + return -EINVAL; + } + + if (nb->eve_type >= EVE_CNT) { + printk(KERN_INFO "Invalid DA9052 Event Type\n"); + return -EINVAL; + } + + /* Initialize list head inside notifier block */ + INIT_LIST_HEAD(&nb->nb_list); + + /* Acquire NB array lock */ + if (down_interruptible(&eve_nb_array_lock)) + return -EAGAIN; + + /* Add passed NB to corresponding EVENT list */ + list_add_tail(&nb->nb_list, &(eve_nb_array[nb->eve_type].nb_list)); + + /* Release NB array lock */ + up(&eve_nb_array_lock); + + return 0; +} + +int eh_unregister_nb(struct da9052 *da9052, struct da9052_eh_nb *nb) +{ + + if (nb == NULL) + return -EINVAL; + + /* Acquire nb array lock */ + if (down_interruptible(&eve_nb_array_lock)) + return -EAGAIN; + + /* Remove passed NB from list */ + list_del_init(&(nb->nb_list)); + + /* Release NB array lock */ + up(&eve_nb_array_lock); + + return 0; +} + +static int process_events(struct da9052 *da9052, int events_sts) +{ + + int cnt = 0; + int tmp_events_sts = 0; + unsigned char event = 0; + + struct list_head *ptr; + struct da9052_eh_nb *nb_ptr; + + /* Now we have retrieved all events, process them one by one */ + for (cnt = 0; cnt < EVE_CNT; cnt++) { + /* + * Starting with highest priority event, + * traverse through all event + */ + tmp_events_sts = events_sts; + + /* Find the event associated with higher priority */ + event = cnt; + + /* Check if interrupt is received for this event */ + if (!((tmp_events_sts >> cnt) & 0x1)) + /* Event bit is not set for this event */ + /* Move to next event */ + continue; + + if (event == PEN_DOWN_EVE) { + if (list_empty(&(eve_nb_array[event].nb_list))) + continue; + } + + /* Event bit is set, execute all registered call backs */ + if (down_interruptible(&eve_nb_array_lock)) { + printk(KERN_CRIT "Can't acquire eve_nb_array_lock\n"); + return -EIO; + } + + list_for_each(ptr, &(eve_nb_array[event].nb_list)) { + /* + * nb_ptr will point to the structure in which + * nb_list is embedded + */ + nb_ptr = list_entry(ptr, struct da9052_eh_nb, nb_list); + nb_ptr->call_back(nb_ptr, events_sts); + } + up(&eve_nb_array_lock); + } + return 0; +} + +void eh_workqueue_isr(struct work_struct *work) +{ + struct da9052 *da9052 = + container_of(work, struct da9052, eh_isr_work); + + struct da9052_ssc_msg eve_data[4]; + struct da9052_ssc_msg eve_mask_data[4]; + int events_sts, ret; + u32 mask; + unsigned char cnt = 0; + + /* nIRQ is asserted, read event registeres to know what happened */ + events_sts = 0; + mask = 0; + + /* Prepare ssc message to read all four event registers */ + for (cnt = 0; cnt < DA9052_EVE_REGISTERS; cnt++) { + eve_data[cnt].addr = (DA9052_EVENTA_REG + cnt); + eve_data[cnt].data = 0; + } + + /* Prepare ssc message to read all four event registers */ + for (cnt = 0; cnt < DA9052_EVE_REGISTERS; cnt++) { + eve_mask_data[cnt].addr = (DA9052_IRQMASKA_REG + cnt); + eve_mask_data[cnt].data = 0; + } + + /* Now read all event and mask registers */ + da9052_lock(da9052); + + ret = da9052_ssc_read_many(da9052, eve_data, DA9052_EVE_REGISTERS); + if (ret) { + enable_irq(da9052->irq); + da9052_unlock(da9052); + return; + } + + ret = da9052_ssc_read_many(da9052, eve_mask_data, DA9052_EVE_REGISTERS); + if (ret) { + enable_irq(da9052->irq); + da9052_unlock(da9052); + return; + } + /* Collect all events */ + for (cnt = 0; cnt < DA9052_EVE_REGISTERS; cnt++) + events_sts |= (eve_data[cnt].data << (DA9052_EVE_REGISTER_SIZE + * cnt)); + /* Collect all mask */ + for (cnt = 0; cnt < DA9052_EVE_REGISTERS; cnt++) + mask |= (eve_mask_data[cnt].data << (DA9052_EVE_REGISTER_SIZE + * cnt)); + events_sts &= ~mask; + da9052_unlock(da9052); + + /* Check if we really got any event */ + if (events_sts == 0) { + enable_irq(da9052->irq); + da9052_unlock(da9052); + return; + } + + /* Process all events occurred */ + process_events(da9052, events_sts); + + da9052_lock(da9052); + /* Now clear EVENT registers */ + for (cnt = 0; cnt < 4; cnt++) { + if (eve_data[cnt].data) { + ret = da9052_ssc_write(da9052, &eve_data[cnt]); + if (ret) { + enable_irq(da9052->irq); + da9052_unlock(da9052); + return; + } + } + } + da9052_unlock(da9052); + + /* + * This delay is necessary to avoid hardware fake interrupts + * from DA9052. + */ +#if defined CONFIG_PMIC_DA9052 || defined CONFIG_PMIC_DA9053AA + udelay(50); +#endif + /* Enable HOST interrupt */ + enable_irq(da9052->irq); +} + +static void da9052_eh_restore_irq(struct da9052 *da9052) +{ + /* Put your platform and board specific code here */ + free_irq(da9052->irq, NULL); +} + +static int da9052_add_subdevice_pdata(struct da9052 *da9052, + const char *name, void *pdata, size_t pdata_size) +{ + struct mfd_cell cell = { + .name = name, + .platform_data = pdata, + .pdata_size = pdata_size, + }; + return mfd_add_devices(da9052->dev, -1, &cell, 1, NULL, 0); +} + +static int da9052_add_subdevice(struct da9052 *da9052, const char *name) +{ + return da9052_add_subdevice_pdata(da9052, name, NULL, 0); +} + +static int add_da9052_devices(struct da9052 *da9052) +{ + s32 ret = 0; + struct da9052_platform_data *pdata = da9052->dev->platform_data; + struct da9052_leds_platform_data leds_data = { + .num_leds = pdata->led_data->num_leds, + .led = pdata->led_data->led, + }; + struct da9052_regulator_platform_data regulator_pdata = { + .regulators = pdata->regulators, + }; + + struct da9052_tsi_platform_data tsi_data = *(pdata->tsi_data); + + if (pdata && pdata->init) { + ret = pdata->init(da9052); + if (ret != 0) + return ret; + } else + pr_err("No platform initialisation supplied\n"); + + ret = da9052_add_subdevice(da9052, "da9052-rtc"); + if (ret) + return ret; + ret = da9052_add_subdevice(da9052, "da9052-onkey"); + if (ret) + return ret; + + ret = da9052_add_subdevice(da9052, "WLED-1"); + if (ret) + return ret; + + ret = da9052_add_subdevice(da9052, "WLED-2"); + if (ret) + return ret; + + ret = da9052_add_subdevice(da9052, "WLED-3"); + if (ret) + return ret; + + ret = da9052_add_subdevice(da9052, "da9052-adc"); + if (ret) + return ret; + + ret = da9052_add_subdevice(da9052, "da9052-wdt"); + if (ret) + return ret; + + ret = da9052_add_subdevice_pdata(da9052, "da9052-leds", + &leds_data, sizeof(leds_data)); + if (ret) + return ret; + + ret = da9052_add_subdevice_pdata(da9052, "da9052-regulator", + ®ulator_pdata, sizeof(regulator_pdata)); + if (ret) + return ret; + + ret = da9052_add_subdevice_pdata(da9052, "da9052-tsi", + &tsi_data, sizeof(tsi_data)); + if (ret) + return ret; + + ret = da9052_add_subdevice(da9052, "da9052-bat"); + if (ret) + return ret; + + return ret; +} + +int da9052_ssc_init(struct da9052 *da9052) +{ + int cnt; + struct da9052_platform_data *pdata; + struct da9052_ssc_msg ssc_msg; + + /* Initialize eve_nb_array */ + for (cnt = 0; cnt < EVE_CNT; cnt++) + INIT_LIST_HEAD(&(eve_nb_array[cnt].nb_list)); + + /* Initialize mutex required for ADC Manual read */ + mutex_init(&manconv_lock); + + /* Initialize NB array lock */ + sema_init(&eve_nb_array_lock, 1); + + /* Assign the read-write function pointers */ + da9052->read = da9052_ssc_read; + da9052->write = da9052_ssc_write; + da9052->read_many = da9052_ssc_read_many; + da9052->write_many = da9052_ssc_write_many; + + if (SPI == da9052->connecting_device && ssc_ops.write == NULL) { + /* Assign the read/write pointers to SPI/read/write */ + ssc_ops.write = da9052_spi_write; + ssc_ops.read = da9052_spi_read; + ssc_ops.write_many = da9052_spi_write_many; + ssc_ops.read_many = da9052_spi_read_many; + } else if (I2C == da9052->connecting_device + && ssc_ops.write == NULL) { + /* Assign the read/write pointers to SPI/read/write */ + ssc_ops.write = da9052_i2c_write; + ssc_ops.read = da9052_i2c_read; + ssc_ops.write_many = da9052_i2c_write_many; + ssc_ops.read_many = da9052_i2c_read_many; + } else + return -1; + /* Assign the EH notifier block register/de-register functions */ + da9052->register_event_notifier = eh_register_nb; + da9052->unregister_event_notifier = eh_unregister_nb; + + /* Initialize ssc lock */ + mutex_init(&da9052->ssc_lock); + + pdata = da9052->dev->platform_data; + add_da9052_devices(da9052); + + INIT_WORK(&da9052->eh_isr_work, eh_workqueue_isr); + ssc_msg.addr = DA9052_IRQMASKA_REG; + ssc_msg.data = 0xff; + da9052->write(da9052, &ssc_msg); + ssc_msg.addr = DA9052_IRQMASKC_REG; + ssc_msg.data = 0xff; + da9052->write(da9052, &ssc_msg); + if (request_irq(da9052->irq, da9052_eh_isr, IRQ_TYPE_LEVEL_LOW, + DA9052_EH_DEVICE_NAME, da9052)) + return -EIO; + enable_irq_wake(da9052->irq); + + return 0; +} + +void da9052_ssc_exit(struct da9052 *da9052) +{ + printk(KERN_INFO "DA9052: Unregistering SSC device.\n"); + mutex_destroy(&manconv_lock); + /* Restore IRQ line */ + da9052_eh_restore_irq(da9052); + free_irq(da9052->irq, NULL); + mutex_destroy(&da9052->ssc_lock); + mutex_destroy(&da9052->eve_nb_lock); + return; +} + +MODULE_AUTHOR("Dialog Semiconductor Ltd dchen@diasemi.com"); +MODULE_DESCRIPTION("DA9052 MFD Core"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DA9052_SSC_DEVICE_NAME); diff --git a/drivers/mfd/da9052-i2c.c b/drivers/mfd/da9052-i2c.c new file mode 100644 index 0000000..5fd966a --- /dev/null +++ b/drivers/mfd/da9052-i2c.c @@ -0,0 +1,378 @@ +/* + * Copyright(c) 2009 Dialog Semiconductor Ltd. + * + * 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. + * + * da9052-i2c.c: I2C SSC (Synchronous Serial Communication) driver for DA9052 + */ + +#include <linux/device.h> +#include <linux/mfd/core.h> +#include <linux/i2c.h> +#include <linux/mfd/da9052/da9052.h> +#include <linux/mfd/da9052/reg.h> + +static struct da9052 *da9052_i2c; + +#define I2C_CONNECTED 0 + +static int da9052_i2c_is_connected(void) +{ + + struct da9052_ssc_msg msg; + + /* printk("Entered da9052_i2c_is_connected.............\n"); */ + + msg.addr = DA9052_INTERFACE_REG; + + /* Test spi connectivity by performing read of the GPIO_0-1 register */ + if (0 != da9052_i2c_read(da9052_i2c, &msg)) { + printk(KERN_DEBUG "da9052_i2c_is_connected - "\ + "i2c read failed.............\n"); + return -1; + } else { + printk(KERN_DEBUG "da9052_i2c_is_connected - "\ + "i2c read success..............\n"); + return 0; + } + +} + +static int __devinit da9052_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter; + /* printk("\n\tEntered da9052_i2c_is_probe.............\n"); */ + + da9052_i2c = kzalloc(sizeof(struct da9052), GFP_KERNEL); + + if (!da9052_i2c) + return -ENOMEM; + + /* Get the bus driver handler */ + adapter = to_i2c_adapter(client->dev.parent); + + /* Check i2c bus driver supports byte data transfer */ + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_info(&client->dev,\ + "Error in %s:i2c_check_functionality\n", __func__); + return -ENODEV; + } + + /* Store handle to i2c client */ + da9052_i2c->i2c_client = client; + da9052_i2c->irq = client->irq; + + da9052_i2c->dev = &client->dev; + + /* Initialize i2c data structure here*/ + da9052_i2c->adapter = adapter; + + /* host i2c driver looks only first 7 bits for the slave address */ + da9052_i2c->slave_addr = DA9052_I2C_ADDR >> 1; + + /* Store the i2c client data */ + i2c_set_clientdata(client, da9052_i2c); + + /* Validate I2C connectivity */ + if (I2C_CONNECTED == da9052_i2c_is_connected()) { + /* I2C is connected */ + da9052_i2c->connecting_device = I2C; + if (0 != da9052_ssc_init(da9052_i2c)) + return -ENODEV; + } else { + return -ENODEV; + } + + /* printk("Exiting da9052_i2c_probe.....\n"); */ + + return 0; +} + +static int da9052_i2c_remove(struct i2c_client *client) +{ + + struct da9052 *da9052 = i2c_get_clientdata(client); + + mfd_remove_devices(da9052->dev); + kfree(da9052); + return 0; +} + +int da9052_i2c_write(struct da9052 *da9052, struct da9052_ssc_msg *msg) +{ + struct i2c_msg i2cmsg; + unsigned char buf[2] = {0}; + int ret = 0; + + /* Copy the ssc msg to local character buffer */ + buf[0] = msg->addr; + buf[1] = msg->data; + + /*Construct a i2c msg for a da9052 driver ssc message request */ + i2cmsg.addr = da9052->slave_addr; + i2cmsg.len = 2; + i2cmsg.buf = buf; + + /* To write the data on I2C set flag to zero */ + i2cmsg.flags = 0; + + /* Start the i2c transfer by calling host i2c driver function */ + ret = i2c_transfer(da9052->adapter, &i2cmsg, 1); + + if (ret < 0) { + dev_info(&da9052->i2c_client->dev,\ + "_%s:master_xfer Failed!!\n", __func__); + return ret; + } + + return 0; +} + +int da9052_i2c_read(struct da9052 *da9052, struct da9052_ssc_msg *msg) +{ + + /*Get the da9052_i2c client details*/ + unsigned char buf[2] = {0, 0}; + struct i2c_msg i2cmsg[2]; + int ret = 0; + + /* Copy SSC Msg to local character buffer */ + buf[0] = msg->addr; + + /*Construct a i2c msg for a da9052 driver ssc message request */ + i2cmsg[0].addr = da9052->slave_addr ; + i2cmsg[0].len = 1; + i2cmsg[0].buf = &buf[0]; + + /*To write the data on I2C set flag to zero */ + i2cmsg[0].flags = 0; + + /* Read the data from da9052*/ + /*Construct a i2c msg for a da9052 driver ssc message request */ + i2cmsg[1].addr = da9052->slave_addr ; + i2cmsg[1].len = 1; + i2cmsg[1].buf = &buf[1]; + + /*To read the data on I2C set flag to I2C_M_RD */ + i2cmsg[1].flags = I2C_M_RD; + + /* Start the i2c transfer by calling host i2c driver function */ + ret = i2c_transfer(da9052->adapter, i2cmsg, 2); + if (ret < 0) { + dev_info(&da9052->i2c_client->dev,\ + "2 - %s:master_xfer Failed!!\n", __func__); + return ret; + } + + msg->data = *i2cmsg[1].buf; + + return 0; +} + +int da9052_i2c_write_many(struct da9052 *da9052, + struct da9052_ssc_msg *sscmsg, int msg_no) +{ + + struct i2c_msg i2cmsg; + unsigned char data_buf[MAX_READ_WRITE_CNT+1]; + struct da9052_ssc_msg ctrlb_msg; + struct da9052_ssc_msg *msg_queue = sscmsg; + int ret = 0; + /* Flag to check if requested registers are contiguous */ + unsigned char cont_data = 1; + unsigned char cnt = 0; + + /* Check if requested registers are contiguous */ + for (cnt = 1; cnt < msg_no; cnt++) { + if ((msg_queue[cnt].addr - msg_queue[cnt-1].addr) != 1) { + /* Difference is not 1, i.e. non-contiguous registers */ + cont_data = 0; + break; + } + } + + if (cont_data == 0) { + /* Requested registers are non-contiguous */ + for (cnt = 0; cnt < msg_no; cnt++) { + ret = da9052->write(da9052, &msg_queue[cnt]); + if (ret != 0) + return ret; + } + return 0; + } + /* + * Requested registers are contiguous + * or PAGE WRITE sequence of I2C transactions is as below + * (slave_addr + reg_addr + data_1 + data_2 + ...) + * First read current WRITE MODE via CONTROL_B register of DA9052 + */ + ctrlb_msg.addr = DA9052_CONTROLB_REG; + ctrlb_msg.data = 0x0; + ret = da9052->read(da9052, &ctrlb_msg); + + if (ret != 0) + return ret; + + /* Check if PAGE WRITE mode is set */ + if (ctrlb_msg.data & DA9052_CONTROLB_WRITEMODE) { + /* REPEAT WRITE mode is configured */ + /* Now set DA9052 into PAGE WRITE mode */ + ctrlb_msg.data &= ~DA9052_CONTROLB_WRITEMODE; + ret = da9052->write(da9052, &ctrlb_msg); + + if (ret != 0) + return ret; + } + + /* Put first register address */ + data_buf[0] = msg_queue[0].addr; + + for (cnt = 0; cnt < msg_no; cnt++) + data_buf[cnt+1] = msg_queue[cnt].data; + + /* Construct a i2c msg for PAGE WRITE */ + i2cmsg.addr = da9052->slave_addr ; + /* First register address + all data*/ + i2cmsg.len = (msg_no + 1); + i2cmsg.buf = data_buf; + + /*To write the data on I2C set flag to zero */ + i2cmsg.flags = 0; + + /* Start the i2c transfer by calling host i2c driver function */ + ret = i2c_transfer(da9052->adapter, &i2cmsg, 1); + if (ret < 0) { + dev_info(&da9052->i2c_client->dev,\ + "1 - i2c_transfer function falied in [%s]!!!\n", __func__); + return ret; + } + + return 0; +} + +int da9052_i2c_read_many(struct da9052 *da9052, + struct da9052_ssc_msg *sscmsg, int msg_no) +{ + + struct i2c_msg i2cmsg; + unsigned char data_buf[MAX_READ_WRITE_CNT]; + struct da9052_ssc_msg *msg_queue = sscmsg; + int ret = 0; + /* Flag to check if requested registers are contiguous */ + unsigned char cont_data = 1; + unsigned char cnt = 0; + + /* Check if requested registers are contiguous */ + for (cnt = 1; cnt < msg_no; cnt++) { + if ((msg_queue[cnt].addr - msg_queue[cnt-1].addr) != 1) { + /* Difference is not 1, i.e. non-contiguous registers */ + cont_data = 0; + break; + } + } + + if (cont_data == 0) { + /* Requested registers are non-contiguous */ + for (cnt = 0; cnt < msg_no; cnt++) { + ret = da9052->read(da9052, &msg_queue[cnt]); + if (ret != 0) { + dev_info(&da9052->i2c_client->dev,\ + "Error in %s", __func__); + return ret; + } + } + return 0; + } + + /* + * We want to perform PAGE READ via I2C + * For PAGE READ sequence of I2C transactions is as below + * (slave_addr + reg_addr) + (slave_addr + data_1 + data_2 + ...) + */ + /* Copy address of first register */ + data_buf[0] = msg_queue[0].addr; + + /* Construct a i2c msg for first transaction of PAGE READ i.e. write */ + i2cmsg.addr = da9052->slave_addr ; + i2cmsg.len = 1; + i2cmsg.buf = data_buf; + + /*To write the data on I2C set flag to zero */ + i2cmsg.flags = 0; + + /* Start the i2c transfer by calling host i2c driver function */ + ret = i2c_transfer(da9052->adapter, &i2cmsg, 1); + if (ret < 0) { + dev_info(&da9052->i2c_client->dev,\ + "1 - i2c_transfer function falied in [%s]!!!\n", __func__); + return ret; + } + + /* Now Read the data from da9052 */ + /* Construct a i2c msg for second transaction of PAGE READ i.e. read */ + i2cmsg.addr = da9052->slave_addr ; + i2cmsg.len = msg_no; + i2cmsg.buf = data_buf; + + /*To read the data on I2C set flag to I2C_M_RD */ + i2cmsg.flags = I2C_M_RD; + + /* Start the i2c transfer by calling host i2c driver function */ + ret = i2c_transfer(da9052->adapter, + &i2cmsg, 1); + if (ret < 0) { + dev_info(&da9052->i2c_client->dev,\ + "2 - i2c_transfer function falied in [%s]!!!\n", __func__); + return ret; + } + + /* Gather READ data */ + for (cnt = 0; cnt < msg_no; cnt++) + sscmsg[cnt].data = data_buf[cnt]; + + return 0; +} + +static struct i2c_device_id da9052_ssc_id[] = { + { DA9052_SSC_I2C_DEVICE_NAME, 0}, + {} +}; + +static struct i2c_driver da9052_i2c_driver = { + .driver = { + .name = DA9052_SSC_I2C_DEVICE_NAME, + .owner = THIS_MODULE, + }, + .probe = da9052_i2c_probe, + .remove = da9052_i2c_remove, + .id_table = da9052_ssc_id, +}; + +static int __init da9052_i2c_init(void) +{ + int ret = 0; + /* printk("\n\nEntered da9052_i2c_init................\n\n"); */ + ret = i2c_add_driver(&da9052_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Unable to register %s\n", + DA9052_SSC_I2C_DEVICE_NAME); + return ret; + } + return 0; +} +subsys_initcall(da9052_i2c_init); + +static void __exit da9052_i2c_exit(void) +{ + i2c_del_driver(&da9052_i2c_driver); +} +module_exit(da9052_i2c_exit); + +MODULE_AUTHOR("Dialog Semiconductor Ltd dchen@diasemi.com"); +MODULE_DESCRIPTION("I2C driver for Dialog DA9052 PMIC"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DA9052_SSC_I2C_DEVICE_NAME); diff --git a/drivers/mfd/da9052-spi.c b/drivers/mfd/da9052-spi.c new file mode 100644 index 0000000..f25e188f --- /dev/null +++ b/drivers/mfd/da9052-spi.c @@ -0,0 +1,402 @@ +/* + * Copyright(c) 2009 Dialog Semiconductor Ltd. + * + * 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. + * + * da9052-spi.c: SPI SSC (Synchronous Serial Communication) driver for DA9052 + */ + +#include <linux/device.h> +#include <linux/mfd/core.h> +#include <linux/spi/spi.h> +#include <linux/mfd/da9052/da9052.h> +#include <linux/mfd/da9052/reg.h> + + +struct da9052 *da9052_spi; + +#define SPI_CONNECTED 0 + +static int da9052_spi_is_connected(void) +{ + + struct da9052_ssc_msg msg; + + /* printk("Entered da9052_spi_is_connected.............\n"); */ + + msg.addr = DA9052_INTERFACE_REG; + + /* Test spi connectivity by performing read of the GPIO_0-1 register + and then verify the read value*/ + if (0 != da9052_spi_read(da9052_spi, &msg)) { + printk(KERN_DEBUG "da9052_spi_is_connected - "\ + "spi read failed.............\n"); + return -1; + } else if (0x88 != msg.data) { + printk(KERN_DEBUG "da9052_spi_is_connected - " \ + "spi read failed. Msg data =%x ..............\n", + msg.data); + return -1; + } + + return 0; + +} + +static int da9052_spi_probe(struct spi_device *spi) +{ + /* printk("\n\tEntered da9052_spi_probe.....\n"); */ + + da9052_spi = kzalloc(sizeof(struct da9052), GFP_KERNEL); + + if (!da9052_spi) + return -ENOMEM; + + + spi->mode = SPI_MODE_0 | SPI_CPOL; + spi->bits_per_word = 8; + spi_setup(spi); + + da9052_spi->dev = &spi->dev; + + da9052_spi->spi_dev = spi; + + /* + * Allocate memory for RX/TX bufferes used in single register + * read/write + */ + da9052_spi->spi_rx_buf = kmalloc(2, GFP_KERNEL | GFP_DMA); + if (!da9052_spi->spi_rx_buf) + return -ENOMEM; + + da9052_spi->spi_tx_buf = kmalloc(2, GFP_KERNEL | GFP_DMA); + if (!da9052_spi->spi_tx_buf) + return -ENOMEM; + + da9052_spi->spi_active_page = PAGECON_0; + da9052_spi->rw_pol = 1; + + + dev_set_drvdata(&spi->dev, da9052_spi); + + + /* Validate SPI connectivity */ + if (SPI_CONNECTED == da9052_spi_is_connected()) { + /* SPI is connected */ + da9052_spi->connecting_device = SPI; + if (0 != da9052_ssc_init(da9052_spi)) + return -ENODEV; + } else { + return -ENODEV; + } + + /* printk("Exiting da9052_spi_probe.....\n"); */ + + return 0; +} + +static int da9052_spi_remove(struct spi_device *spi) +{ + struct da9052 *da9052 = dev_get_drvdata(&spi->dev); + + printk("Entered da9052_spi_remove()\n"); + if (SPI == da9052->connecting_device) + da9052_ssc_exit(da9052); + mfd_remove_devices(&spi->dev); + kfree(da9052->spi_rx_buf); + kfree(da9052->spi_tx_buf); + kfree(da9052); + return 0; +} + +static struct spi_driver da9052_spi_driver = { + .driver = { + .name = DA9052_SSC_SPI_DEVICE_NAME, + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = da9052_spi_probe, + .remove = __devexit_p(da9052_spi_remove), +}; + + +static int da9052_spi_set_page(struct da9052 *da9052, unsigned char page) +{ + + struct da9052_ssc_msg sscmsg; + struct spi_message message; + struct spi_transfer xfer; + int ret = 0; + + printk(KERN_DEBUG "Entered da9052_spi_set_page.....\n"); + if ((page != PAGECON_0) && ((page != PAGECON_128))) + return INVALID_PAGE; + + /* Current configuration is PAGE-0 and write request for PAGE-1 */ + /* set register address */ + sscmsg.addr = DA9052_PAGECON0_REG; + /* set value */ + sscmsg.data = page; + + /* Check value of R/W_POL bit of INTERFACE register */ + if (!da9052->rw_pol) { + /* We need to set 0th bit for write operation */ + sscmsg.addr = ((sscmsg.addr << 1) | RW_POL); + } else { + /* We need to reset 0th bit for write operation */ + sscmsg.addr = (sscmsg.addr << 1); + } + + /* SMDK-6410 host SPI driver specific stuff */ + + /* Build our spi message */ + printk(KERN_DEBUG "da9052_spi_set_page - "\ + "Calling spi_message_init.....\n"); + spi_message_init(&message); + memset(&xfer, 0, sizeof(xfer)); + + xfer.len = 2; + xfer.tx_buf = da9052->spi_tx_buf; + xfer.rx_buf = da9052->spi_rx_buf; + + da9052->spi_tx_buf[0] = sscmsg.addr; + da9052->spi_tx_buf[1] = sscmsg.data; + + printk(KERN_DEBUG "da9052_spi_set_page - "\ + "Calling spi_message_add_tail.....\n"); + spi_message_add_tail(&xfer, &message); + + /* Now, do the i/o */ + printk(KERN_DEBUG "da9052_spi_set_page - Calling spi_sync.....\n"); + ret = spi_sync(da9052->spi_dev, &message); + + if (ret == 0) { + /* Active Page set successfully */ + da9052->spi_active_page = page; + return 0; + } else { + /* Error in setting Active Page */ + return ret; + } + + return 0; +} + +int da9052_spi_write(struct da9052 *da9052, struct da9052_ssc_msg *msg) +{ + + struct spi_message message; + struct spi_transfer xfer; + int ret; + + /* + * We need a seperate copy of da9052_ssc_msg so that caller's + * copy remains intact + */ + struct da9052_ssc_msg sscmsg; + + /* Copy callers data in to our local copy */ + sscmsg.addr = msg->addr; + sscmsg.data = msg->data; + + if ((sscmsg.addr > PAGE_0_END) && + (da9052->spi_active_page == PAGECON_0)) { + /* + * Current configuration is PAGE-0 and write request + * for PAGE-1 + */ + da9052_spi_set_page(da9052, PAGECON_128); + /* Set register address accordindly */ + sscmsg.addr = (sscmsg.addr - PAGE_1_START); + } else if ((sscmsg.addr < PAGE_1_START) && + (da9052->spi_active_page == PAGECON_128)) { + /* + * Current configuration is PAGE-1 and write request + * for PAGE-0 + */ + da9052_spi_set_page(da9052, PAGECON_0); + } else if (sscmsg.addr > PAGE_0_END) { + /* + * Current configuration is PAGE-1 and write request + * for PAGE-1. Just need to adjust register address + */ + sscmsg.addr = (sscmsg.addr - PAGE_1_START); + } + + /* Check value of R/W_POL bit of INTERFACE register */ + if (!da9052->rw_pol) { + /* We need to set 0th bit for write operation */ + sscmsg.addr = ((sscmsg.addr << 1) | RW_POL); + } else { + /* We need to reset 0th bit for write operation */ + sscmsg.addr = (sscmsg.addr << 1); + } + + /* SMDK-6410 host SPI driver specific stuff */ + + /* Build our spi message */ + spi_message_init(&message); + memset(&xfer, 0, sizeof(xfer)); + + xfer.len = 2; + xfer.tx_buf = da9052->spi_tx_buf; + xfer.rx_buf = da9052->spi_rx_buf; + + da9052->spi_tx_buf[0] = sscmsg.addr; + da9052->spi_tx_buf[1] = sscmsg.data; + + spi_message_add_tail(&xfer, &message); + + /* Now, do the i/o */ + ret = spi_sync(da9052->spi_dev, &message); + + return ret; +} + +int da9052_spi_write_many(struct da9052 *da9052, struct da9052_ssc_msg *sscmsg, + int msg_no) +{ + int cnt, ret = 0; + + for (cnt = 0; cnt < msg_no; cnt++, sscmsg++) { + ret = da9052_ssc_write(da9052, sscmsg); + if (ret != 0) { + printk(KERN_DEBUG "Error in %s\n", __func__); + return -EIO; + } + } + + return 0; +} + +int da9052_spi_read(struct da9052 *da9052, struct da9052_ssc_msg *msg) +{ + + struct spi_message message; + struct spi_transfer xfer; + int ret; + + /* + * We need a seperate copy of da9052_ssc_msg so that + * caller's copy remains intact + */ + struct da9052_ssc_msg sscmsg; + + + /* Copy callers data in to our local copy */ + sscmsg.addr = msg->addr; + sscmsg.data = msg->data; + + if ((sscmsg.addr > PAGE_0_END) && + (da9052->spi_active_page == PAGECON_0)) { + /* + * Current configuration is PAGE-0 and + * read request for PAGE-1 + */ + printk(KERN_DEBUG "da9052_spi_read - if PAGECON_128.....\n"); + da9052_spi_set_page(da9052, PAGECON_128); + /* Set register address accordindly */ + sscmsg.addr = (sscmsg.addr - PAGE_1_START); + } else if ((sscmsg.addr < PAGE_1_START) && + (da9052->spi_active_page == PAGECON_128)) { + /* + * Current configuration is PAGE-1 and + * write request for PAGE-0 + */ + printk(KERN_DEBUG "da9052_spi_read - if PAGECON_0.....\n"); + da9052_spi_set_page(da9052, PAGECON_0); + } else if (sscmsg.addr > PAGE_0_END) { + /* + * Current configuration is PAGE-1 and write + * request for PAGE-1 + * Just need to adjust register address + */ + sscmsg.addr = (sscmsg.addr - PAGE_1_START); + } + + /* Check value of R/W_POL bit of INTERFACE register */ + if (da9052->rw_pol) { + /* We need to set 0th bit for read operation */ + sscmsg.addr = ((sscmsg.addr << 1) | RW_POL); + } else { + /* We need to reset 0th bit for write operation */ + sscmsg.addr = (sscmsg.addr << 1); + } + + /* SMDK-6410 host SPI driver specific stuff */ + + /* Build our spi message */ + spi_message_init(&message); + memset(&xfer, 0, sizeof(xfer)); + + xfer.len = 2; + xfer.tx_buf = da9052->spi_tx_buf; + xfer.rx_buf = da9052->spi_rx_buf; + + da9052->spi_tx_buf[0] = sscmsg.addr; + da9052->spi_tx_buf[1] = 0xff; + + da9052->spi_rx_buf[0] = 0; + da9052->spi_rx_buf[1] = 0; + + spi_message_add_tail(&xfer, &message); + + /* Now, do the i/o */ + ret = spi_sync(da9052->spi_dev, &message); + + if (ret == 0) { + /* Update read value in callers copy */ + msg->data = da9052->spi_rx_buf[1]; + return 0; + } else { + return ret; + } + + + return 0; +} + +int da9052_spi_read_many(struct da9052 *da9052, struct da9052_ssc_msg *sscmsg, + int msg_no) +{ + int cnt, ret = 0; + + for (cnt = 0; cnt < msg_no; cnt++, sscmsg++) { + ret = da9052_ssc_read(da9052, sscmsg); + if (ret != 0) { + printk(KERN_DEBUG "Error in %s\n", __func__); + return -EIO; + } + } + + return 0; +} + +static int __init da9052_spi_init(void) +{ + int ret = 0; + /*printk("Entered da9052_spi_init.....\n");*/ + ret = spi_register_driver(&da9052_spi_driver); + if (ret != 0) { + printk(KERN_ERR "Unable to register %s\n", + DA9052_SSC_SPI_DEVICE_NAME); + return ret; + } + return 0; +} +module_init(da9052_spi_init); + +static void __exit da9052_spi_exit(void) +{ + spi_unregister_driver(&da9052_spi_driver); +} + +module_exit(da9052_spi_exit); + +MODULE_AUTHOR("Dialog Semiconductor Ltd dchen@diasemi.com"); +MODULE_DESCRIPTION("SPI driver for Dialog DA9052 PMIC"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DA9052_SSC_SPI_DEVICE_NAME);