209 строки
4.9 KiB
C
209 строки
4.9 KiB
C
/*
|
|
* Copyright (C) 2009 ST-Ericsson
|
|
*
|
|
* Author: Srinidhi KASAGAR <srinidhi.kasagar@stericsson.com>
|
|
*
|
|
* 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.
|
|
*
|
|
* AB4500 is a companion power management chip used with U8500.
|
|
* On this platform, this is interfaced with SSP0 controller
|
|
* which is a ARM primecell pl022.
|
|
*
|
|
* At the moment the module just exports read/write features.
|
|
* Interrupt management to be added - TODO.
|
|
*/
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/spi/spi.h>
|
|
#include <linux/mfd/ab4500.h>
|
|
|
|
/* just required if probe fails, we need to
|
|
* unregister the device
|
|
*/
|
|
static struct spi_driver ab4500_driver;
|
|
|
|
/*
|
|
* This funtion writes to any AB4500 registers using
|
|
* SPI protocol & before it writes it packs the data
|
|
* in the below 24 bit frame format
|
|
*
|
|
* *|------------------------------------|
|
|
* *| 23|22...18|17.......10|9|8|7......0|
|
|
* *| r/w bank adr data |
|
|
* * ------------------------------------
|
|
*
|
|
* This function shouldn't be called from interrupt
|
|
* context
|
|
*/
|
|
int ab4500_write(struct ab4500 *ab4500, unsigned char block,
|
|
unsigned long addr, unsigned char data)
|
|
{
|
|
struct spi_transfer xfer;
|
|
struct spi_message msg;
|
|
int err;
|
|
unsigned long spi_data =
|
|
block << 18 | addr << 10 | data;
|
|
|
|
mutex_lock(&ab4500->lock);
|
|
ab4500->tx_buf[0] = spi_data;
|
|
ab4500->rx_buf[0] = 0;
|
|
|
|
xfer.tx_buf = ab4500->tx_buf;
|
|
xfer.rx_buf = NULL;
|
|
xfer.len = sizeof(unsigned long);
|
|
|
|
spi_message_init(&msg);
|
|
spi_message_add_tail(&xfer, &msg);
|
|
|
|
err = spi_sync(ab4500->spi, &msg);
|
|
mutex_unlock(&ab4500->lock);
|
|
|
|
return err;
|
|
}
|
|
EXPORT_SYMBOL(ab4500_write);
|
|
|
|
int ab4500_read(struct ab4500 *ab4500, unsigned char block,
|
|
unsigned long addr)
|
|
{
|
|
struct spi_transfer xfer;
|
|
struct spi_message msg;
|
|
unsigned long spi_data =
|
|
1 << 23 | block << 18 | addr << 10;
|
|
|
|
mutex_lock(&ab4500->lock);
|
|
ab4500->tx_buf[0] = spi_data;
|
|
ab4500->rx_buf[0] = 0;
|
|
|
|
xfer.tx_buf = ab4500->tx_buf;
|
|
xfer.rx_buf = ab4500->rx_buf;
|
|
xfer.len = sizeof(unsigned long);
|
|
|
|
spi_message_init(&msg);
|
|
spi_message_add_tail(&xfer, &msg);
|
|
|
|
spi_sync(ab4500->spi, &msg);
|
|
mutex_unlock(&ab4500->lock);
|
|
|
|
return ab4500->rx_buf[0];
|
|
}
|
|
EXPORT_SYMBOL(ab4500_read);
|
|
|
|
/* ref: ab3100 core */
|
|
#define AB4500_DEVICE(devname, devid) \
|
|
static struct platform_device ab4500_##devname##_device = { \
|
|
.name = devid, \
|
|
.id = -1, \
|
|
}
|
|
|
|
/* list of childern devices of ab4500 - all are
|
|
* not populated here - TODO
|
|
*/
|
|
AB4500_DEVICE(charger, "ab4500-charger");
|
|
AB4500_DEVICE(audio, "ab4500-audio");
|
|
AB4500_DEVICE(usb, "ab4500-usb");
|
|
AB4500_DEVICE(tvout, "ab4500-tvout");
|
|
AB4500_DEVICE(sim, "ab4500-sim");
|
|
AB4500_DEVICE(gpadc, "ab4500-gpadc");
|
|
AB4500_DEVICE(clkmgt, "ab4500-clkmgt");
|
|
AB4500_DEVICE(misc, "ab4500-misc");
|
|
|
|
static struct platform_device *ab4500_platform_devs[] = {
|
|
&ab4500_charger_device,
|
|
&ab4500_audio_device,
|
|
&ab4500_usb_device,
|
|
&ab4500_tvout_device,
|
|
&ab4500_sim_device,
|
|
&ab4500_gpadc_device,
|
|
&ab4500_clkmgt_device,
|
|
&ab4500_misc_device,
|
|
};
|
|
|
|
static int __init ab4500_probe(struct spi_device *spi)
|
|
{
|
|
struct ab4500 *ab4500;
|
|
unsigned char revision;
|
|
int err = 0;
|
|
int i;
|
|
|
|
ab4500 = kzalloc(sizeof *ab4500, GFP_KERNEL);
|
|
if (!ab4500) {
|
|
dev_err(&spi->dev, "could not allocate AB4500\n");
|
|
err = -ENOMEM;
|
|
goto not_detect;
|
|
}
|
|
|
|
ab4500->spi = spi;
|
|
spi_set_drvdata(spi, ab4500);
|
|
|
|
mutex_init(&ab4500->lock);
|
|
|
|
/* read the revision register */
|
|
revision = ab4500_read(ab4500, AB4500_MISC, AB4500_REV_REG);
|
|
|
|
/* revision id 0x0 is for early drop, 0x10 is for cut1.0 */
|
|
if (revision == 0x0 || revision == 0x10)
|
|
dev_info(&spi->dev, "Detected chip: %s, revision = %x\n",
|
|
ab4500_driver.driver.name, revision);
|
|
else {
|
|
dev_err(&spi->dev, "unknown chip: 0x%x\n", revision);
|
|
goto not_detect;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ab4500_platform_devs); i++) {
|
|
ab4500_platform_devs[i]->dev.parent =
|
|
&spi->dev;
|
|
platform_set_drvdata(ab4500_platform_devs[i], ab4500);
|
|
}
|
|
|
|
/* register the ab4500 platform devices */
|
|
platform_add_devices(ab4500_platform_devs,
|
|
ARRAY_SIZE(ab4500_platform_devs));
|
|
|
|
return err;
|
|
|
|
not_detect:
|
|
spi_unregister_driver(&ab4500_driver);
|
|
kfree(ab4500);
|
|
return err;
|
|
}
|
|
|
|
static int __devexit ab4500_remove(struct spi_device *spi)
|
|
{
|
|
struct ab4500 *ab4500 =
|
|
spi_get_drvdata(spi);
|
|
|
|
kfree(ab4500);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct spi_driver ab4500_driver = {
|
|
.driver = {
|
|
.name = "ab4500",
|
|
.owner = THIS_MODULE,
|
|
},
|
|
.probe = ab4500_probe,
|
|
.remove = __devexit_p(ab4500_remove)
|
|
};
|
|
|
|
static int __devinit ab4500_init(void)
|
|
{
|
|
return spi_register_driver(&ab4500_driver);
|
|
}
|
|
|
|
static void __exit ab4500_exit(void)
|
|
{
|
|
spi_unregister_driver(&ab4500_driver);
|
|
}
|
|
|
|
subsys_initcall(ab4500_init);
|
|
module_exit(ab4500_exit);
|
|
|
|
MODULE_AUTHOR("Srinidhi KASAGAR <srinidhi.kasagar@stericsson.com");
|
|
MODULE_DESCRIPTION("AB4500 core driver");
|
|
MODULE_LICENSE("GPL");
|