diff options
-rw-r--r-- | drivers/power/Kconfig | 6 | ||||
-rw-r--r-- | drivers/power/pmic/Kconfig | 11 | ||||
-rw-r--r-- | drivers/power/pmic/Makefile | 1 | ||||
-rw-r--r-- | drivers/power/pmic/pmic-uclass.c | 158 | ||||
-rw-r--r-- | include/dm/uclass-id.h | 3 | ||||
-rw-r--r-- | include/power/pmic.h | 189 |
6 files changed, 368 insertions, 0 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index f8f0239..d03626e 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -1,3 +1,7 @@ +menu "Power" + +source "drivers/power/pmic/Kconfig" + config AXP221_POWER boolean "axp221 / axp223 pmic support" depends on MACH_SUN6I || MACH_SUN8I @@ -73,3 +77,5 @@ config AXP221_ELDO3_VOLT disable eldo3. On some A31(s) tablets it might be used to supply 1.2V for the SSD2828 chip (converter of parallel LCD interface into MIPI DSI). + +endmenu diff --git a/drivers/power/pmic/Kconfig b/drivers/power/pmic/Kconfig new file mode 100644 index 0000000..d06d632 --- /dev/null +++ b/drivers/power/pmic/Kconfig @@ -0,0 +1,11 @@ +config DM_PMIC + bool "Enable Driver Model for PMIC drivers (UCLASS_PMIC)" + depends on DM + ---help--- + This config enables the driver-model PMIC support. + UCLASS_PMIC - designed to provide an I/O interface for PMIC devices. + For the multi-function PMIC devices, this can be used as parent I/O + device for each IC's interface. Then, each children uses its parent + for read/write. For detailed description, please refer to the files: + - 'drivers/power/pmic/pmic-uclass.c' + - 'include/power/pmic.h' diff --git a/drivers/power/pmic/Makefile b/drivers/power/pmic/Makefile index 985cfdb..594f620 100644 --- a/drivers/power/pmic/Makefile +++ b/drivers/power/pmic/Makefile @@ -5,6 +5,7 @@ # SPDX-License-Identifier: GPL-2.0+ # +obj-$(CONFIG_DM_PMIC) += pmic-uclass.o obj-$(CONFIG_POWER_LTC3676) += pmic_ltc3676.o obj-$(CONFIG_POWER_MAX8998) += pmic_max8998.o obj-$(CONFIG_POWER_MAX8997) += pmic_max8997.o diff --git a/drivers/power/pmic/pmic-uclass.c b/drivers/power/pmic/pmic-uclass.c new file mode 100644 index 0000000..d82d3da --- /dev/null +++ b/drivers/power/pmic/pmic-uclass.c @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2014-2015 Samsung Electronics + * Przemyslaw Marczak <p.marczak@samsung.com> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <fdtdec.h> +#include <errno.h> +#include <dm.h> +#include <dm/lists.h> +#include <dm/device-internal.h> +#include <dm/uclass-internal.h> +#include <power/pmic.h> +#include <linux/ctype.h> + +DECLARE_GLOBAL_DATA_PTR; + +static ulong str_get_num(const char *ptr, const char *maxptr) +{ + if (!ptr || !maxptr) + return 0; + + while (!isdigit(*ptr) && ptr++ < maxptr); + + return simple_strtoul(ptr, NULL, 0); +} + +int pmic_bind_childs(struct udevice *pmic, int offset, + const struct pmic_child_info *child_info) +{ + const struct pmic_child_info *info; + const void *blob = gd->fdt_blob; + struct driver *drv; + struct udevice *child; + const char *node_name; + int node_name_len; + int bind_count = 0; + int node; + int prefix_len; + int ret; + + debug("%s for '%s' at node offset: %d\n", __func__, pmic->name, + pmic->of_offset); + + for (node = fdt_first_subnode(blob, offset); + node > 0; + node = fdt_next_subnode(blob, node)) { + node_name = fdt_get_name(blob, node, &node_name_len); + + debug("* Found child node: '%s' at offset:%d\n", node_name, + node); + + child = NULL; + info = child_info; + while (info->prefix) { + prefix_len = strlen(info->prefix); + if (strncasecmp(info->prefix, node_name, prefix_len) || + !info->driver) { + info++; + continue; + } + + debug(" - compatible prefix: '%s'\n", info->prefix); + + drv = lists_driver_lookup_name(info->driver); + if (!drv) { + debug(" - driver: '%s' not found!\n", + info->driver); + continue; + } + + debug(" - found child driver: '%s'\n", drv->name); + + ret = device_bind(pmic, drv, node_name, NULL, + node, &child); + if (ret) { + debug(" - child binding error: %d\n", ret); + continue; + } + + debug(" - bound child device: '%s'\n", child->name); + + child->driver_data = str_get_num(node_name + + prefix_len, + node_name + + node_name_len); + + debug(" - set 'child->driver_data': %lu\n", + child->driver_data); + break; + } + + if (child) + bind_count++; + else + debug(" - compatible prefix not found\n"); + } + + debug("Bound: %d childs for PMIC: '%s'\n", bind_count, pmic->name); + return bind_count; +} + +int pmic_get(const char *name, struct udevice **devp) +{ + return uclass_get_device_by_name(UCLASS_PMIC, name, devp); +} + +int pmic_reg_count(struct udevice *dev) +{ + const struct dm_pmic_ops *ops = dev_get_driver_ops(dev); + if (!ops) + return -ENOSYS; + + return ops->reg_count; +} + +int pmic_read(struct udevice *dev, uint reg, uint8_t *buffer, int len) +{ + const struct dm_pmic_ops *ops = dev_get_driver_ops(dev); + int ret; + + if (!buffer) + return -EFAULT; + + if (!ops || !ops->read) + return -ENOSYS; + + ret = ops->read(dev, reg, buffer, len); + if (ret) + return ret; + + return 0; +} + +int pmic_write(struct udevice *dev, uint reg, const uint8_t *buffer, int len) +{ + const struct dm_pmic_ops *ops = dev_get_driver_ops(dev); + int ret; + + if (!buffer) + return -EFAULT; + + if (!ops || !ops->write) + return -ENOSYS; + + ret = ops->write(dev, reg, buffer, len); + if (ret) + return ret; + + return 0; +} + +UCLASS_DRIVER(pmic) = { + .id = UCLASS_PMIC, + .name = "pmic", +}; diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h index 095bcb2..e7adf53 100644 --- a/include/dm/uclass-id.h +++ b/include/dm/uclass-id.h @@ -49,6 +49,9 @@ enum uclass_id { UCLASS_USB_DEV_GENERIC, /* USB generic device */ UCLASS_USB_HUB, /* USB hub */ + /* Power Management */ + UCLASS_PMIC, /* PMIC I/O device */ + UCLASS_COUNT, UCLASS_INVALID = -1, }; diff --git a/include/power/pmic.h b/include/power/pmic.h index afbc5aa..f7ae781 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,189 @@ struct pmic { struct pmic *parent; struct list_head list; }; +#endif /* CONFIG_POWER */ + +#ifdef CONFIG_DM_PMIC +/** + * U-Boot PMIC Framework + * ===================== + * + * UCLASS_PMIC - The is designed to provide an I/O interface for PMIC devices. + * + * For the multi-function PMIC devices, this can be used as parent I/O device + * for each IC's interface. Then, each children uses its parent for read/write. + * + * The driver model tree could look 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_RTC (in the future) + * + * We can find two PMIC cases in boards design: + * - single I/O interface + * - multiple I/O interfaces + * We bind single PMIC device for each interface, to provide an I/O as a parent, + * of proper child devices. Each child usually implements a different function, + * controlled by the same interface. + * + * The binding should be done automatically. If 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 device nodes, for multi-interface PMIC. + * + * Note: + * Each PMIC interface driver should use a different compatible string. + * + * If each pmic child device driver need access the PMIC-specific registers, + * it need know only the register address and the access can be 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 the example below. It should be done by call + * the 'pmic_bind_childs()' - please refer to the description of this function + * in this header file. This function, should be called in the driver's '.bind' + * method. + * + * For the example driver, please refer the MAX77686 driver: + * - 'drivers/power/pmic/max77686.c' + */ + +/** + * 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. + * + * @reg_count: devices register count + * @read: read 'len' bytes at "reg" and store it into the 'buffer' + * @write: write 'len' bytes from the 'buffer' to the register at 'reg' address + */ +struct dm_pmic_ops { + int reg_count; + int (*read)(struct udevice *dev, uint reg, uint8_t *buffer, int len); + int (*write)(struct udevice *dev, uint reg, const uint8_t *buffer, + int len); +}; + +/* 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, +}; + +/** + * struct pmic_child_info - basic device's child info for bind child nodes with + * the driver by the node name prefix and driver name. This is a helper struct + * for function: pmic_bind_childs(). + * + * @prefix - child node name prefix (or its name if is unique or single) + * @driver - driver name for the sub-node with prefix + */ +struct pmic_child_info { + const char *prefix; + const char *driver; +}; + +/* drivers/power/pmic-uclass.c */ + +/** + * pmic_bind_childs() - bind drivers for given parent pmic, using child info + * found in 'child_info' array. + * + * @pmic - pmic device - the parent of found child's + * @child_info - N-childs info array + * @return a positive number of childs, or 0 if no child found (error) + * + * Note: For N-childs the child_info array should have N+1 entries and the last + * entry prefix should be NULL - the same as for drivers compatible. + * + * For example, a single prefix info (N=1): + * static const struct pmic_child_info bind_info[] = { + * { .prefix = "ldo", .driver = "ldo_driver" }, + * { }, + * }; + * + * This function is useful for regulator sub-nodes: + * my_regulator@0xa { + * reg = <0xa>; + * (pmic - bind automatically by compatible) + * compatible = "my_pmic"; + * ... + * (pmic's childs - bind by pmic_bind_childs()) + * (nodes prefix: "ldo", driver: "my_regulator_ldo") + * ldo1 { ... }; + * ldo2 { ... }; + * + * (nodes prefix: "buck", driver: "my_regulator_buck") + * buck1 { ... }; + * buck2 { ... }; + * }; + */ +int pmic_bind_childs(struct udevice *pmic, int offset, + const struct pmic_child_info *child_info); + +/** + * pmic_get: get the pmic device using its name + * + * @name - device name + * @devp - returned pointer to the pmic device + * @return 0 on success or negative value of errno. + * + * The returned devp device can be used with pmic_read/write calls + */ +int pmic_get(const char *name, struct udevice **devp); + +/** + * pmic_reg_count: get the pmic register count + * + * The required pmic device can be obtained by 'pmic_get()' + * + * @dev - pointer to the UCLASS_PMIC device + * @return register count value on success or negative value of errno. + */ +int pmic_reg_count(struct udevice *dev); + +/** + * pmic_read/write: read/write to the UCLASS_PMIC device + * + * The required pmic device can be obtained by 'pmic_get()' + * + * @pmic - pointer to the UCLASS_PMIC device + * @reg - device register offset + * @buffer - pointer to read/write buffer + * @len - byte count for read/write + * @return 0 on success or negative value of errno. + */ +int pmic_read(struct udevice *dev, uint reg, uint8_t *buffer, int len); +int pmic_write(struct udevice *dev, uint reg, const uint8_t *buffer, int len); +#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 +276,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) |