[U-Boot] [PATCH v2 03/12] dm: pmic: add implementation of driver model pmic uclass

Przemyslaw Marczak
2015-03-03T16:24:34Z

This is an introduction to driver-model multi uclass PMIC support.
It starts with UCLASS_PMIC - a common PMIC devices uclass type
to provide device read/write operations only.

Beside two basic operations the pmic platform data is introduced,
which provides basic informations about the pmic device I/O interface
and is shared with all childs (and should also for childs new uclass
types in the future).

Usually PMIC devices provides various functionalities with single
or multiple I/O interfaces.
Using this new framework and new uclass types introduced in the future,
it can be handle like this:

_ root device
|
|_ BUS 0 device (e.g. I2C0) - UCLASS_I2C/SPI/...
| |_ PMIC device 1 (read/write ops) - UCLASS_PMIC
| |_ REGULATOR device (ldo/buck/... ops) - UCLASS_REGULATOR
| |_ CHARGER device (charger ops) - UCLASS_CHARGER (in the future)
| |_ MUIC device (microUSB con ops) - UCLASS_MUIC (in the future)
| |_ ...
|
|_ BUS 1 device (e.g. I2C1) - UCLASS_I2C/SPI/...
|_ PMIC device 2 (read/write ops) - UCLASS_PMIC
|_ RTC device (rtc ops) - UCLASS_MUIC (in the future)

For each PMIC device interface, new UCLASS_PMIC device is bind with proper
pmic driver, and it's child devices provides some specified operations.

All new definitions can be found in file:
- 'include/power/pmic.h'

Uclass file:
- pmic-uclass.c - provides a common code for UCLASS_PMIC device drivers

The old pmic framework is still kept and is independent.

Changes:
- new uclass-id: UCLASS_PMIC
- new config: CONFIG_DM_PMIC

New pmic api is documented in: doc/README.power-framework-dm

Signed-off-by: Przemyslaw Marczak <p.marczak@samsung.com>
---
Changes V2:
- pmic uclass: adjust uclass code to the mainline changes
- pmic uclass: remove pmic_i2c and pmic_spi
- pmic uclass: modify pmic_platdata
- pmic uclass: add pmic_if_* functions
- pmic uclass: remove pmic_init_dm()
- pmic uclass: cleanup
- pmic.h: define pmic ops structure (read/write operations)
- pmic.h: add comments to functions
---
drivers/power/Makefile | 1 +
drivers/power/pmic-uclass.c | 191 +++++++++++++++++++++++++++++++
include/dm/uclass-id.h | 3 +
include/power/pmic.h | 265 ++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 460 insertions(+)
create mode 100644 drivers/power/pmic-uclass.c

diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index 2145652..5c9a189 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -21,3 +21,4 @@ obj-$(CONFIG_DIALOG_POWER) += power_dialog.o
obj-$(CONFIG_POWER_FSL) += power_fsl.o
obj-$(CONFIG_POWER_I2C) += power_i2c.o
obj-$(CONFIG_POWER_SPI) += power_spi.o
+obj-$(CONFIG_DM_PMIC) += pmic-uclass.o
diff --git a/drivers/power/pmic-uclass.c b/drivers/power/pmic-uclass.c
new file mode 100644
index 0000000..309463e
--- /dev/null
+++ b/drivers/power/pmic-uclass.c
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2014-2015 Samsung Electronics
+ * Przemyslaw Marczak <p.marczak@samsung.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+#include <common.h>
+#include <linux/types.h>
+#include <fdtdec.h>
+#include <dm.h>
+#include <power/pmic.h>
+#include <dm/device-internal.h>
+#include <dm/uclass-internal.h>
+#include <dm/root.h>
+#include <dm/lists.h>
+#include <compiler.h>
+#include <errno.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+static char * const pmic_interfaces[] = {
+ "I2C",
+ "SPI",
+ "--",
+};
+
+const char *pmic_if_str(struct udevice *pmic)
+{
+ int if_types = ARRAY_SIZE(pmic_interfaces);
+ int if_type;
+
+ if_type = pmic_if_type(pmic);
+ if (if_type < 0 || if_type >= if_types)
+ return pmic_interfaces[if_types - 1];
+
+ return pmic_interfaces[if_type];
+}
+
+int pmic_if_type(struct udevice *pmic)
+{
+ struct pmic_platdata *pl = pmic->platdata;
+
+ return pl->if_type;
+}
+
+int pmic_if_bus_num(struct udevice *pmic)
+{
+ struct pmic_platdata *pl = pmic->platdata;
+
+ return pl->if_bus_num;
+}
+
+int pmic_if_addr_cs(struct udevice *pmic)
+{
+ struct pmic_platdata *pl = pmic->platdata;
+
+ return pl->if_addr_cs;
+}
+
+int pmic_if_max_offset(struct udevice *pmic)
+{
+ struct pmic_platdata *pl = pmic->platdata;
+
+ return pl->if_max_offset;
+}
+
+int pmic_read(struct udevice *pmic, unsigned reg, unsigned char *val)
+{
+ const struct dm_pmic_ops *ops;
+
+ ops = pmic_get_uclass_ops(pmic, UCLASS_PMIC);
+ if (!ops)
+ return -ENODEV;
+
+ if (!ops->read)
+ return -EPERM;
+
+ if (ops->read(pmic, reg, val))
+ return -EIO;
+
+ return 0;
+}
+
+int pmic_write(struct udevice *pmic, unsigned reg, unsigned char val)
+{
+ const struct dm_pmic_ops *ops;
+
+ ops = pmic_get_uclass_ops(pmic, UCLASS_PMIC);
+ if (!ops)
+ return -ENODEV;
+
+ if (!ops->write)
+ return -EPERM;
+
+ if (ops->write(pmic, reg, val))
+ return -EIO;
+
+ return 0;
+}
+
+int pmic_get(char *name, struct udevice **pmic)
+{
+ struct udevice *dev;
+ struct uclass *uc;
+ int ret;
+
+ ret = uclass_get(UCLASS_PMIC, &uc);
+ if (ret) {
+ error("PMIC uclass not initialized!");
+ return ret;
+ }
+
+ uclass_foreach_dev(dev, uc) {
+ if (!dev)
+ continue;
+
+ if (!strncmp(name, dev->name, strlen(name))) {
+ ret = device_probe(dev);
+ if (ret)
+ error("Dev: %s probe failed", dev->name);
+ *pmic = dev;
+ return ret;
+ }
+ }
+
+ *pmic = NULL;
+ return -EINVAL;
+}
+
+int pmic_i2c_get(int bus, int addr, struct udevice **pmic)
+{
+ struct udevice *dev;
+ int ret;
+
+ ret = i2c_get_chip_for_busnum(bus, addr, 1, &dev);
+ if (ret) {
+ error("Chip: %d on bus %d not found!", addr, bus);
+ *pmic = NULL;
+ return ret;
+ }
+
+ *pmic = dev;
+ return 0;
+}
+
+int pmic_spi_get(int bus, int cs, struct udevice **pmic)
+{
+ struct udevice *busp, *devp;
+ int ret;
+
+ ret = spi_find_bus_and_cs(bus, cs, &busp, &devp);
+ if (ret) {
+ error("Chip: %d on bus %d not found!", cs, bus);
+ *pmic = NULL;
+ return ret;
+ }
+
+ *pmic = devp;
+ return 0;
+}
+
+int pmic_io_dev(struct udevice *pmic, struct udevice **pmic_io)
+{
+ struct pmic_platdata *pl;
+ int ret;
+
+ pl = dev_get_platdata(pmic);
+ if (!pl) {
+ error("pmic: %s platdata not found!", pmic->name);
+ return -EINVAL;
+ }
+
+ switch (pl->if_type) {
+ case PMIC_I2C:
+ ret = pmic_i2c_get(pl->if_bus_num, pl->if_addr_cs, pmic_io);
+ break;
+ case PMIC_SPI:
+ ret = pmic_spi_get(pl->if_bus_num, pl->if_addr_cs, pmic_io);
+ break;
+ default:
+ error("Unsupported interface for: %s", pmic->name);
+ ret = -ENXIO;
+ }
+
+ return ret;
+}
+
+UCLASS_DRIVER(pmic) = {
+ .id = UCLASS_PMIC,
+ .name = "pmic",
+};
diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h
index 91bb90d..ff360ac 100644
--- a/include/dm/uclass-id.h
+++ b/include/dm/uclass-id.h
@@ -35,6 +35,9 @@ enum uclass_id {
UCLASS_I2C_EEPROM, /* I2C EEPROM device */
UCLASS_MOD_EXP, /* RSA Mod Exp device */

+ /* PMIC uclass and PMIC related uclasses */
+ UCLASS_PMIC,
+
UCLASS_COUNT,
UCLASS_INVALID = -1,
};
diff --git a/include/power/pmic.h b/include/power/pmic.h
index afbc5aa..94171ac 100644
--- a/include/power/pmic.h
+++ b/include/power/pmic.h
@@ -1,4 +1,7 @@
/*
+ * Copyright (C) 2014-2015 Samsung Electronics
+ * Przemyslaw Marczak <p.marczak@samsung.com>
+ *
* Copyright (C) 2011-2012 Samsung Electronics
* Lukasz Majewski <l.majewski@samsung.com>
*
@@ -9,10 +12,13 @@
#define __CORE_PMIC_H_

#include <linux/list.h>
+#include <spi.h>
#include <i2c.h>
#include <power/power_chrg.h>

enum { PMIC_I2C, PMIC_SPI, PMIC_NONE};
+
+#ifdef CONFIG_POWER
enum { I2C_PMIC, I2C_NUM, };
enum { PMIC_READ, PMIC_WRITE, };
enum { PMIC_SENSOR_BYTE_ORDER_LITTLE, PMIC_SENSOR_BYTE_ORDER_BIG, };
@@ -77,7 +83,265 @@ struct pmic {
struct pmic *parent;
struct list_head list;
};
+#endif /* CONFIG_POWER */
+
+#ifdef CONFIG_DM_PMIC
+/**
+ * Driver model pmic framework.
+ * The PMIC_UCLASS uclass is designed to provide a common I/O
+ * interface for pmic child devices of various uclass types.
+ *
+ * Usually PMIC devices provides more than one functionality,
+ * which means that we should implement uclass operations for
+ * each functionality - one driver per uclass.
+ *
+ * And usually PMIC devices provide those various functionalities
+ * by one or more interfaces. And this could looks like this:
+ *
+ *_ root device
+ * |_ BUS 0 device (e.g. I2C0) - UCLASS_I2C/SPI/...
+ * | |_ PMIC device (READ/WRITE ops) - UCLASS_PMIC
+ * | |_ REGULATOR device (ldo/buck/... ops) - UCLASS_REGULATOR
+ * | |_ CHARGER device (charger ops) - UCLASS_CHARGER (in the future)
+ * | |_ MUIC device (microUSB connector ops) - UCLASS_MUIC (in the future)
+ * | |_ ...
+ * |
+ * |_ BUS 1 device (e.g. I2C1) - UCLASS_I2C/SPI/...
+ * |_ PMIC device (READ/WRITE ops) - UCLASS_PMIC
+ * |_ RTC device (rtc ops) - UCLASS_MUIC (in the future)
+ *
+ * Two PMIC types are possible:
+ * - single I/O interface
+ * Then UCLASS_PMIC device should be a parent of all pmic devices, where each
+ * is usually different uclass type, but need to access the same interface
+ *
+ * - multiple I/O interfaces
+ * For each interface the UCLASS_PMIC device should be a parent of only those
+ * devices (different uclass types), which needs to access the specified
+ * interface.
+ *
+ * For each case binding should be done automatically. If only device tree
+ * nodes/subnodes are proper defined, then:
+ * |_ the ROOT driver will bind the device for I2C/SPI node:
+ * |_ the I2C/SPI driver should bind a device for pmic node:
+ * |_ the PMIC driver should bind devices for its childs:
+ * |_ regulator (child)
+ * |_ charger (child)
+ * |_ other (child)
+ *
+ * The same for other bus nodes, if pmic uses more then one interface.
+ *
+ * Note:
+ * Each PMIC interface driver should use different compatible string.
+ *
+ * If each pmic child device driver need access the pmic specified registers,
+ * it need to know only the register address and the access is done through
+ * the parent pmic driver. Like in the example:
+ *
+ *_ root driver
+ * |_ dev: bus I2C0 - UCLASS_I2C
+ * | |_ dev: my_pmic (read/write) (is parent) - UCLASS_PMIC
+ * | |_ dev: my_regulator (set value/etc..) (is child) - UCLASS_REGULATOR
+ *
+ *
+ * To ensure such device relationship, the pmic device driver should also bind
+ * all its child devices, like in this example:
+ *
+ * my_pmic.c - pmic driver:
+ *
+ * int my_pmic_bind(struct udevice *my_pmic)
+ * {
+ * // A list of other drivers for this pmic
+ * char *my_pmic_drv_list = {"my_regulator", "my_charger", "my_rtc"};
+ * struct udevice *my_pmic_child_dev[3];
+ * ...
+ * for (i=0; i < ARRAY_SIZE(my_pmic_drv_list); i++) {
+ * // Look for child device driver
+ * drv_my_reg = lists_driver_lookup_name(my_pmic_drv_list[i]);
+ *
+ * // If driver found, then bind the new device to it
+ * if (drv_my_reg)
+ * ret = device_bind(my_pmic, my_pmic_drv_list[i],
+ * my_pmic->name, my_pmic->platdata,
+ * my_pmic->of_offset,
+ * my_pmic_child_dev[i]);
+ * ...
+ * }
+ * ...
+ * }
+ *
+ * my_regulator.c - regulator driver (child of pmic I/O driver):
+ *
+ * int my_regulator_set_value(struct udevice *dev, int type, int num, int *val)
+ * {
+ * ...
+ * some_val = ...;
+ *
+ * // dev->parent == my_pmic
+ * pmic_write(dev->parent, some_reg, some_val);
+ * ...
+ * }
+ *
+ * In this example pmic driver is always a parent for other pmic devices,
+ * because the first argument of device_bind() call is the parent device,
+ * and here it is 'my_pmic'.
+ *
+ * For all childs the device->platdata is the same, and provide an info about
+ * the same interface - it's used for getting the devices by interface address
+ * and uclass types.
+ */
+
+/**
+ * struct pmic_platdata - PMIC chip interface info
+ *
+ * This is required for pmic command purposes and should be shared
+ * by pmic device and it's childs as the same platdata pointer.
+ *
+ * @if_type: one of: PMIC_I2C, PMIC_SPI, PMIC_NONE
+ * @if_bus_num: usually alias seq of i2c/spi bus device
+ * @if_addr_cd: i2c/spi chip addr or cs
+ * @if_max_offset: max register offset within the chip
+ */
+struct pmic_platdata {
+ int if_type;
+ int if_bus_num;
+ int if_addr_cs;
+ int if_max_offset;
+};
+
+/**
+ * struct dm_pmic_ops - PMIC device I/O interface
+ *
+ * Should be implemented by UCLASS_PMIC device drivers. The standard
+ * device operations provides the I/O interface for it's childs.
+ *
+ * @read: called for read "reg" value into "val" through "pmic" parent
+ * @write: called for write "val" into "reg" through the "pmic" parent
+ */
+struct dm_pmic_ops {
+ int (*read)(struct udevice *pmic, unsigned reg, unsigned char *val);
+ int (*write)(struct udevice *pmic, unsigned reg, unsigned char val);
+};
+
+/* enum pmic_op_type - used for various pmic devices operation calls,
+ * for reduce a number of lines with the same code for read/write or get/set.
+ *
+ * @PMIC_OP_GET - get operation
+ * @PMIC_OP_SET - set operation
+*/
+enum pmic_op_type {
+ PMIC_OP_GET,
+ PMIC_OP_SET,
+};
+
+/**
+ * pmic_get_uclass_ops - inline function for getting device uclass operations
+ *
+ * @dev - device to check
+ * @uclass_id - device uclass id
+ *
+ * Returns:
+ * void pointer to device operations or NULL pointer on error
+ */
+static __inline__ const void *pmic_get_uclass_ops(struct udevice *dev,
+ int uclass_id)
+{
+ const void *ops;
+
+ if (!dev)
+ return NULL;
+
+ if (dev->driver->id != uclass_id)
+ return NULL;
+
+ ops = dev->driver->ops;
+ if (!ops)
+ return NULL;
+
+ return ops;
+}
+
+/* drivers/power/pmic-uclass.c */
+
+/**
+ * pmic_..._get: looking for the pmic device using the specified arguments:
+ *
+ * NAME:
+ * @name - device name (useful for single specific names)
+ *
+ * or SPI/I2C interface:
+ * @bus - device I2C/SPI bus number
+ * @addr_cs - device specific address for I2C or chip select for SPI,
+ * on the given bus number
+ *
+ * Returns:
+ * @pmic - returned pointer to the pmic device
+ * Returns: 0 on success or negative value of errno.
+ *
+ * The returned pmic device can be used with:
+ * - pmic_read/write calls
+ * - pmic_if_* calls
+ */
+int pmic_get(char *name, struct udevice **pmic);
+int pmic_i2c_get(int bus, int addr, struct udevice **pmic);
+int pmic_spi_get(int bus, int cs, struct udevice **pmic);
+
+/**
+ * pmic_io_dev: returns given pmic/regulator, uclass pmic I/O udevice
+ *
+ * @pmic - pointer to pmic_io child device
+ *
+ * Returns:
+ * @pmic_io - pointer to the I/O chip device
+ *
+ * This is used for pmic command, and also can be used if
+ * needs raw read/write operations for uclass regulator device.
+ * Example of use:
+ * pmic_io_dev(regulator, &pmic_io);
+ * pmic_write(pmic_io, 0x0, 0x1);
+ *
+ * This base on pmic_platdata info, so for the given pmic i2c/spi chip,
+ * as '@pmic', it should return the same chip, if platdata is filled.
+ */
+int pmic_io_dev(struct udevice *pmic, struct udevice **pmic_io);
+
+/**
+ * pmic_if_* functions: returns one of given pmic interface attribute:
+ * - type - one of enum { PMIC_I2C, PMIC_SPI, PMIC_NONE}
+ * - bus number - usually i2c/spi bus seq number
+ * - addres for i2c or cs for spi - basaed on dm_i2c/spi_chip
+ * - max offset - device internal address range
+ * - string - "I2C"/"SPI"/"--"
+ *
+ * @pmic - pointer to the pmic device
+ *
+ * Returns: one of listed attribute on success, or a negative value of errno.
+ */
+int pmic_if_type(struct udevice *pmic);
+int pmic_if_bus_num(struct udevice *pmic);
+int pmic_if_addr_cs(struct udevice *pmic);
+int pmic_if_max_offset(struct udevice *pmic);
+const char *pmic_if_str(struct udevice *pmic);
+
+/**
+ * pmic_read/write: read/write to the UCLASS_PMIC device
+ *
+ * The required pmic device can be obtained by:
+ * - pmic_i2c_get()
+ * - pmic_spi_get()
+ * - pmic_io_dev() - for pmic command purposes
+ *
+ * @pmic - pointer to the UCLASS_PMIC device
+ * @reg - device register offset
+ * @val - value for write or its pointer to read
+ *
+ * Returns: offset value on success or negative value of errno.
+ */
+int pmic_read(struct udevice *pmic, unsigned reg, unsigned char *val);
+int pmic_write(struct udevice *pmic, unsigned reg, unsigned char val);
+#endif /* CONFIG_DM_PMIC */

+#ifdef CONFIG_POWER
int pmic_init(unsigned char bus);
int power_init_board(void);
int pmic_dialog_init(unsigned char bus);
@@ -88,6 +352,7 @@ int pmic_probe(struct pmic *p);
int pmic_reg_read(struct pmic *p, u32 reg, u32 *val);
int pmic_reg_write(struct pmic *p, u32 reg, u32 val);
int pmic_set_output(struct pmic *p, u32 reg, int ldo, int on);
+#endif

#define pmic_i2c_addr (p->hw.i2c.addr)
#define pmic_i2c_tx_num (p->hw.i2c.tx_num)
--
1.9.1