diff options
Diffstat (limited to 'drivers/base')
-rw-r--r-- | drivers/base/Kconfig | 8 | ||||
-rw-r--r-- | drivers/base/Makefile | 2 | ||||
-rw-r--r-- | drivers/base/bus.c | 10 | ||||
-rw-r--r-- | drivers/base/driver.c | 335 | ||||
-rw-r--r-- | drivers/base/featctrl.c | 159 | ||||
-rw-r--r-- | drivers/base/platform.c | 15 | ||||
-rw-r--r-- | drivers/base/power.c | 315 | ||||
-rw-r--r-- | drivers/base/regmap/Kconfig | 14 | ||||
-rw-r--r-- | drivers/base/regmap/Makefile | 5 | ||||
-rw-r--r-- | drivers/base/regmap/internal.h | 39 | ||||
-rw-r--r-- | drivers/base/regmap/regmap-fmt.c | 574 | ||||
-rw-r--r-- | drivers/base/regmap/regmap-i2c.c | 89 | ||||
-rw-r--r-- | drivers/base/regmap/regmap-mmio.c | 22 | ||||
-rw-r--r-- | drivers/base/regmap/regmap-multi.c | 104 | ||||
-rw-r--r-- | drivers/base/regmap/regmap-spi.c | 42 | ||||
-rw-r--r-- | drivers/base/regmap/regmap.c | 139 | ||||
-rw-r--r-- | drivers/base/resource.c | 32 | ||||
-rw-r--r-- | drivers/base/soc.c | 123 |
18 files changed, 1833 insertions, 194 deletions
diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig index 5bc70aa1e5..21a4793cfa 100644 --- a/drivers/base/Kconfig +++ b/drivers/base/Kconfig @@ -2,3 +2,11 @@ config PM_GENERIC_DOMAINS bool + +config FEATURE_CONTROLLER + bool "Feature controller support" if COMPILE_TEST || SANDBOX + +config SOC_BUS + bool + +source "drivers/base/regmap/Kconfig" diff --git a/drivers/base/Makefile b/drivers/base/Makefile index 59645c6f53..acc53763da 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -6,3 +6,5 @@ obj-y += resource.o obj-y += regmap/ obj-$(CONFIG_PM_GENERIC_DOMAINS) += power.o +obj-$(CONFIG_FEATURE_CONTROLLER) += featctrl.o +obj-$(CONFIG_SOC_BUS) += soc.o diff --git a/drivers/base/bus.c b/drivers/base/bus.c index 3c2bab937a..a5c9c930da 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -47,9 +47,9 @@ int bus_register(struct bus_type *bus) return 0; } -int device_match(struct device_d *dev, struct driver_d *drv) +int device_match(struct device *dev, struct driver *drv) { - if (IS_ENABLED(CONFIG_OFDEVICE) && dev->device_node && + if (IS_ENABLED(CONFIG_OFDEVICE) && dev->of_node && drv->of_compatible) return of_match(dev, drv); @@ -70,7 +70,7 @@ int device_match(struct device_d *dev, struct driver_d *drv) return -1; } -int device_match_of_modalias(struct device_d *dev, struct driver_d *drv) +int device_match_of_modalias(struct device *dev, struct driver *drv) { const struct platform_device_id *id = drv->id_table; const char *of_modalias = NULL, *p; @@ -80,10 +80,10 @@ int device_match_of_modalias(struct device_d *dev, struct driver_d *drv) if (!device_match(dev, drv)) return 0; - if (!id || !IS_ENABLED(CONFIG_OFDEVICE) || !dev->device_node) + if (!id || !IS_ENABLED(CONFIG_OFDEVICE) || !dev->of_node) return -1; - of_property_for_each_string(dev->device_node, "compatible", prop, compat) { + of_property_for_each_string(dev->of_node, "compatible", prop, compat) { p = strchr(compat, ','); of_modalias = p ? p + 1 : compat; diff --git a/drivers/base/driver.c b/drivers/base/driver.c index 303ca061ce..fbc5cbebe0 100644 --- a/drivers/base/driver.c +++ b/drivers/base/driver.c @@ -24,9 +24,11 @@ #include <fs.h> #include <of.h> #include <linux/list.h> +#include <linux/overflow.h> #include <linux/err.h> #include <complete.h> #include <pinctrl.h> +#include <featctrl.h> #include <linux/clk/clk-conf.h> #ifdef CONFIG_DEBUG_PROBES @@ -45,21 +47,45 @@ LIST_HEAD(active_device_list); EXPORT_SYMBOL(active_device_list); static LIST_HEAD(deferred); -struct device_d *get_device_by_name(const char *name) +static LIST_HEAD(device_alias_list); + +struct device *find_device(const char *str) +{ + struct device *dev; + struct device_node *np; + + dev = get_device_by_name(str); + if (dev) + return dev; + + np = of_find_node_by_path_or_alias(NULL, str); + if (np) + return of_find_device_by_node(np); + + return NULL; +} + +struct device *get_device_by_name(const char *name) { - struct device_d *dev; + struct device *dev; + struct device_alias *alias; for_each_device(dev) { if(!strcmp(dev_name(dev), name)) return dev; } + list_for_each_entry(alias, &device_alias_list, list) { + if(!strcmp(alias->name, name)) + return alias->dev; + } + return NULL; } -static struct device_d *get_device_by_name_id(const char *name, int id) +static struct device *get_device_by_name_id(const char *name, int id) { - struct device_d *dev; + struct device *dev; for_each_device(dev) { if(!strcmp(dev->name, name) && id == dev->id) @@ -80,53 +106,78 @@ int get_free_deviceid(const char *name_template) }; } -int device_probe(struct device_d *dev) +static void dev_report_permanent_probe_deferral(struct device *dev) +{ + if (dev->deferred_probe_reason) + dev_err(dev, "probe permanently deferred (%s)\n", + dev->deferred_probe_reason); + else + dev_err(dev, "probe permanently deferred\n"); +} + +int device_probe(struct device *dev) { static int depth = 0; int ret; + ret = of_feature_controller_check(dev->of_node); + if (ret < 0) + return ret; + if (ret == FEATCTRL_GATED) { + dev_dbg(dev, "feature gated, skipping probe\n"); + return -ENODEV; + } + depth++; pr_report_probe("%*sprobe-> %s\n", depth * 4, "", dev_name(dev)); pinctrl_select_state_default(dev); - of_clk_set_defaults(dev->device_node, false); + of_clk_set_defaults(dev->of_node, false); list_add(&dev->active, &active_device_list); - ret = dev->bus->probe(dev); - if (ret == 0) - goto out; + if (dev->bus->probe) + ret = dev->bus->probe(dev); + else if (dev->driver->probe) + ret = dev->driver->probe(dev); + else + ret = 0; - if (ret == -EPROBE_DEFER) { - list_del(&dev->active); - list_add(&dev->active, &deferred); + depth--; + switch (ret) { + case 0: + return 0; + case -EPROBE_DEFER: /* * -EPROBE_DEFER should never appear on a deep-probe machine so * inform the user immediately. */ - if (deep_probe_is_supported()) - dev_err(dev, "probe deferred\n"); - else - dev_dbg(dev, "probe deferred\n"); - goto out; - } + if (deep_probe_is_supported()) { + dev_report_permanent_probe_deferral(dev); + break; + } - list_del(&dev->active); - INIT_LIST_HEAD(&dev->active); + list_move(&dev->active, &deferred); + + dev_dbg(dev, "probe deferred\n"); + return -EPROBE_DEFER; + case -ENODEV: + case -ENXIO: + dev_dbg(dev, "probe failed: %pe\n", ERR_PTR(ret)); + break; + default: + dev_err(dev, "probe failed: %pe\n", ERR_PTR(ret)); + break; + } - if (ret == -ENODEV || ret == -ENXIO) - dev_dbg(dev, "probe failed: %s\n", strerror(-ret)); - else - dev_err(dev, "probe failed: %s\n", strerror(-ret)); + list_del_init(&dev->active); -out: - depth--; return ret; } -int device_detect(struct device_d *dev) +int device_detect(struct device *dev) { if (!dev->detect) return -ENOSYS; @@ -137,7 +188,7 @@ int device_detect_by_name(const char *__devname) { char *devname = xstrdup(__devname); char *str = devname; - struct device_d *dev; + struct device *dev; int ret = -ENODEV; while (1) { @@ -160,13 +211,13 @@ int device_detect_by_name(const char *__devname) void device_detect_all(void) { - struct device_d *dev; + struct device *dev; for_each_device(dev) device_detect(dev); } -static int match(struct driver_d *drv, struct device_d *dev) +static int match(struct driver *drv, struct device *dev) { int ret; @@ -175,7 +226,7 @@ static int match(struct driver_d *drv, struct device_d *dev) dev->driver = drv; - if (dev->bus->match(dev, drv)) + if (dev->bus->match && dev->bus->match(dev, drv)) goto err_out; ret = device_probe(dev); if (ret) @@ -187,9 +238,9 @@ err_out: return -1; } -int register_device(struct device_d *new_device) +int register_device(struct device *new_device) { - struct driver_d *drv; + struct driver *drv; if (new_device->id == DEVICE_ID_DYNAMIC) { new_device->id = get_free_deviceid(new_device->name); @@ -234,17 +285,24 @@ int register_device(struct device_d *new_device) } EXPORT_SYMBOL(register_device); -int unregister_device(struct device_d *old_dev) +int unregister_device(struct device *old_dev) { + struct device_alias *alias, *at; struct cdev *cdev, *ct; - struct device_d *child, *dt; + struct device *child, *dt; + struct device_node *np; dev_dbg(old_dev, "unregister\n"); dev_remove_parameters(old_dev); if (old_dev->driver) - old_dev->bus->remove(old_dev); + device_remove(old_dev); + + list_for_each_entry_safe(alias, at, &device_alias_list, list) { + if(alias->dev == old_dev) + list_del(&alias->list); + } list_for_each_entry_safe(child, dt, &old_dev->children, sibling) { dev_dbg(old_dev, "unregister child %s\n", dev_name(child)); @@ -252,9 +310,9 @@ int unregister_device(struct device_d *old_dev) } list_for_each_entry_safe(cdev, ct, &old_dev->cdevs, devices_list) { - if (cdev->master) { + if (cdev_is_partition(cdev)) { dev_dbg(old_dev, "unregister part %s\n", cdev->name); - devfs_del_partition(cdev->name); + cdevfs_del_partition(cdev); } } @@ -266,6 +324,10 @@ int unregister_device(struct device_d *old_dev) if (old_dev->parent) list_del(&old_dev->sibling); + np = dev_of_node(old_dev); + if (np && np->dev == old_dev) + np->dev = NULL; + return 0; } EXPORT_SYMBOL(unregister_device); @@ -277,12 +339,13 @@ EXPORT_SYMBOL(unregister_device); * This frees dynamically allocated resources allocated during device * lifetime, but not the device itself. */ -void free_device_res(struct device_d *dev) +void free_device_res(struct device *dev) { free(dev->name); dev->name = NULL; free(dev->unique_name); dev->unique_name = NULL; + free(dev->deferred_probe_reason); } EXPORT_SYMBOL(free_device_res); @@ -293,7 +356,7 @@ EXPORT_SYMBOL(free_device_res); * This frees dynamically allocated resources allocated during device * lifetime and finally the device itself. */ -void free_device(struct device_d *dev) +void free_device(struct device *dev) { free_device_res(dev); free(dev); @@ -309,8 +372,8 @@ EXPORT_SYMBOL(free_device); */ static int device_probe_deferred(void) { - struct device_d *dev, *tmp; - struct driver_d *drv; + struct device *dev, *tmp; + struct driver *drv; bool success; do { @@ -334,15 +397,15 @@ static int device_probe_deferred(void) } while (success); list_for_each_entry(dev, &deferred, active) - dev_err(dev, "probe permanently deferred\n"); + dev_report_permanent_probe_deferral(dev); return 0; } late_initcall(device_probe_deferred); -struct driver_d *get_driver_by_name(const char *name) +struct driver *get_driver_by_name(const char *name) { - struct driver_d *drv; + struct driver *drv; for_each_driver(drv) { if(!strcmp(name, drv->name)) @@ -352,9 +415,9 @@ struct driver_d *get_driver_by_name(const char *name) return NULL; } -int register_driver(struct driver_d *drv) +int register_driver(struct driver *drv) { - struct device_d *dev = NULL; + struct device *dev = NULL; if (!drv->name) return -EINVAL; @@ -373,7 +436,24 @@ int register_driver(struct driver_d *drv) } EXPORT_SYMBOL(register_driver); -struct resource *dev_get_resource(struct device_d *dev, unsigned long type, +void unregister_driver(struct driver *drv) +{ + struct device *dev; + + list_del(&drv->list); + list_del(&drv->bus_list); + + bus_for_each_device(drv->bus, dev) { + if (dev->driver == drv) { + device_remove(dev); + dev->driver = NULL; + list_del(&dev->active); + INIT_LIST_HEAD(&dev->active); + } + } +} + +struct resource *dev_get_resource(struct device *dev, unsigned long type, int num) { int i, n = 0; @@ -390,7 +470,7 @@ struct resource *dev_get_resource(struct device_d *dev, unsigned long type, return ERR_PTR(-ENOENT); } -void *dev_get_mem_region(struct device_d *dev, int num) +void *dev_get_mem_region(struct device *dev, int num) { struct resource *res; @@ -402,7 +482,7 @@ void *dev_get_mem_region(struct device_d *dev, int num) } EXPORT_SYMBOL(dev_get_mem_region); -struct resource *dev_get_resource_by_name(struct device_d *dev, +struct resource *dev_get_resource_by_name(struct device *dev, unsigned long type, const char *name) { @@ -421,7 +501,19 @@ struct resource *dev_get_resource_by_name(struct device_d *dev, return ERR_PTR(-ENOENT); } -struct resource *dev_request_mem_resource_by_name(struct device_d *dev, const char *name) +static struct resource *dev_request_iomem_resource(struct device *dev, + const struct resource *res) +{ + return request_iomem_region(dev_name(dev), res->start, res->end); +} + +int dev_request_resource(struct device *dev, const struct resource *res) +{ + return PTR_ERR_OR_ZERO(dev_request_iomem_resource(dev, res)); +} +EXPORT_SYMBOL(dev_request_resource); + +struct resource *dev_request_mem_resource_by_name(struct device *dev, const char *name) { struct resource *res; @@ -429,11 +521,12 @@ struct resource *dev_request_mem_resource_by_name(struct device_d *dev, const ch if (IS_ERR(res)) return ERR_CAST(res); - return request_iomem_region(dev_name(dev), res->start, res->end); + return dev_request_iomem_resource(dev, res); } EXPORT_SYMBOL(dev_request_mem_resource_by_name); -void __iomem *dev_request_mem_region_by_name(struct device_d *dev, const char *name) +void __iomem *dev_request_mem_region_by_name(struct device *dev, + const char *name) { struct resource *res; @@ -445,7 +538,26 @@ void __iomem *dev_request_mem_region_by_name(struct device_d *dev, const char *n } EXPORT_SYMBOL(dev_request_mem_region_by_name); -struct resource *dev_request_mem_resource(struct device_d *dev, int num) +void __iomem *dev_platform_get_and_ioremap_resource(struct device *dev, + int num, + struct resource **out_res) +{ + struct resource *res; + + res = dev_request_mem_resource(dev, num); + if (IS_ERR(res)) + return IOMEM_ERR_PTR(PTR_ERR(res)); + else if (WARN_ON(IS_ERR_VALUE(res->start))) + return IOMEM_ERR_PTR(-EINVAL); + + if (out_res) + *out_res = res; + + return IOMEM(res->start); +} +EXPORT_SYMBOL(dev_platform_get_and_ioremap_resource); + +struct resource *dev_request_mem_resource(struct device *dev, int num) { struct resource *res; @@ -453,22 +565,22 @@ struct resource *dev_request_mem_resource(struct device_d *dev, int num) if (IS_ERR(res)) return ERR_CAST(res); - return request_iomem_region(dev_name(dev), res->start, res->end); + return dev_request_iomem_resource(dev, res); } -void __iomem *dev_request_mem_region_err_null(struct device_d *dev, int num) +void __iomem *dev_request_mem_region_err_null(struct device *dev, int num) { struct resource *res; res = dev_request_mem_resource(dev, num); - if (IS_ERR(res)) + if (IS_ERR(res) || WARN_ON(!res->start)) return NULL; return IOMEM(res->start); } EXPORT_SYMBOL(dev_request_mem_region_err_null); -void __iomem *dev_request_mem_region(struct device_d *dev, int num) +void __iomem *dev_request_mem_region(struct device *dev, int num) { struct resource *res; @@ -505,9 +617,9 @@ int generic_memmap_ro(struct cdev *cdev, void **map, int flags) * * NOTE: This function expects dev->name to be free()-able, so extra * precautions needs to be taken when mixing its usage with manual - * assignement of device_d.name. + * assignement of device.name. */ -int dev_set_name(struct device_d *dev, const char *fmt, ...) +int dev_set_name(struct device *dev, const char *fmt, ...) { va_list vargs; int err; @@ -530,24 +642,63 @@ int dev_set_name(struct device_d *dev, const char *fmt, ...) } EXPORT_SYMBOL_GPL(dev_set_name); +/** + * dev_add_alias - add alias for device + * @dev: device + * @fmt: format string for the device's alias + */ +int dev_add_alias(struct device *dev, const char *fmt, ...) +{ + va_list va, va_copy; + unsigned int len; + struct device_alias *alias; + + va_start(va, fmt); + va_copy(va_copy, va); + len = vsnprintf(NULL, 0, fmt, va_copy); + va_end(va_copy); + + alias = malloc(struct_size(alias, name, len + 1)); + if (!alias) + return -ENOMEM; + + vsnprintf(alias->name, len + 1, fmt, va); + + va_end(va); + + alias->dev = dev; + list_add_tail(&alias->list, &device_alias_list); + + return 0; +} +EXPORT_SYMBOL_GPL(dev_add_alias); + +bool device_remove(struct device *dev) +{ + if (dev->bus && dev->bus->remove) + dev->bus->remove(dev); + else if (dev->driver->remove) + dev->driver->remove(dev); + else + return false; /* nothing to do */ + + return true; +} +EXPORT_SYMBOL_GPL(device_remove); + static void devices_shutdown(void) { - struct device_d *dev; - int depth = 0; + struct device *dev; list_for_each_entry(dev, &active_device_list, active) { - if (dev->bus->remove) { - depth++; - pr_report_probe("%*sremove-> %s\n", depth * 4, "", dev_name(dev)); - dev->bus->remove(dev); - dev->driver = NULL; - depth--; - } + if (device_remove(dev)) + pr_report_probe("%*sremove-> %s\n", 1 * 4, "", dev_name(dev)); + dev->driver = NULL; } } devshutdown_exitcall(devices_shutdown); -int dev_get_drvdata(struct device_d *dev, const void **data) +int dev_get_drvdata(struct device *dev, const void **data) { if (dev->of_id_entry) { *data = dev->of_id_entry->data; @@ -562,7 +713,7 @@ int dev_get_drvdata(struct device_d *dev, const void **data) return -ENODEV; } -const void *device_get_match_data(struct device_d *dev) +const void *device_get_match_data(struct device *dev) { if (dev->of_id_entry) return dev->of_id_entry->data; @@ -573,6 +724,25 @@ const void *device_get_match_data(struct device_d *dev) return NULL; } +static void device_set_deferred_probe_reason(struct device *dev, + const struct va_format *vaf) +{ + char *reason; + char *last_char; + + free(dev->deferred_probe_reason); + + reason = xasprintf("%pV", vaf); + + /* drop newline char at end of reason string */ + last_char = reason + strlen(reason) - 1; + + if (*last_char == '\n') + *last_char = '\0'; + + dev->deferred_probe_reason = reason; +} + /** * dev_err_probe - probe error check and log helper * @loglevel: log level configured in source file @@ -584,8 +754,12 @@ const void *device_get_match_data(struct device_d *dev) * This helper implements common pattern present in probe functions for error * checking: print debug or error message depending if the error value is * -EPROBE_DEFER and propagate error upwards. - * In case of -EPROBE_DEFER it sets also defer probe reason, which can be - * checked later by reading devices_deferred debugfs attribute. + * + * In case of -EPROBE_DEFER it sets the device's deferred_probe_reason attribute, + * but does not report an error. The error is recorded and displayed later, if + * (and only if) the probe is permanently deferred. For all other error codes, + * it just outputs the error along with the formatted message. + * * It replaces code sequence:: * * if (err != -EPROBE_DEFER) @@ -601,8 +775,8 @@ const void *device_get_match_data(struct device_d *dev) * Returns @err. * */ -int dev_err_probe(const struct device_d *dev, int err, const char *fmt, ...); -int dev_err_probe(const struct device_d *dev, int err, const char *fmt, ...) +int dev_err_probe(struct device *dev, int err, const char *fmt, ...); +int dev_err_probe(struct device *dev, int err, const char *fmt, ...) { struct va_format vaf; va_list args; @@ -611,6 +785,9 @@ int dev_err_probe(const struct device_d *dev, int err, const char *fmt, ...) vaf.fmt = fmt; vaf.va = &args; + if (err == -EPROBE_DEFER) + device_set_deferred_probe_reason(dev, &vaf); + dev_printf(err == -EPROBE_DEFER ? MSG_DEBUG : MSG_ERR, dev, "error %pe: %pV", ERR_PTR(err), &vaf); @@ -622,7 +799,7 @@ EXPORT_SYMBOL_GPL(dev_err_probe); /* * device_find_child - device iterator for locating a particular device. - * @parent: parent struct device_d + * @parent: parent struct device * @match: Callback function to check device * @data: Data to pass to match function * @@ -631,10 +808,10 @@ EXPORT_SYMBOL_GPL(dev_err_probe); * current device can be obtained, this function will return to the caller * and not iterate over any more devices. */ -struct device_d *device_find_child(struct device_d *parent, void *data, - int (*match)(struct device_d *dev, void *data)) +struct device *device_find_child(struct device *parent, void *data, + int (*match)(struct device *dev, void *data)) { - struct device_d *child; + struct device *child; if (!parent) return NULL; diff --git a/drivers/base/featctrl.c b/drivers/base/featctrl.c new file mode 100644 index 0000000000..153720e5ee --- /dev/null +++ b/drivers/base/featctrl.c @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2022 Ahmad Fatoum, Pengutronix + +#define pr_fmt(fmt) "featctrl: " fmt + +#include <common.h> +#include <driver.h> +#include <errno.h> +#include <of.h> + +#include <featctrl.h> + +/* List of registered feature controllers */ +static LIST_HEAD(of_feature_controllers); + +/** + * feature_controller_register() - Register a feature controller + * @feat: Pointer to feature controller + */ +int feature_controller_register(struct feature_controller *feat) +{ + struct device_node *np = dev_of_node(feat->dev); + + if (!np) + return -EINVAL; + + list_add(&feat->list, &of_feature_controllers); + dev_dbg(feat->dev, "Registering feature controller\n"); + return 0; +} +EXPORT_SYMBOL_GPL(feature_controller_register); + +/** + * featctrl_get_from_provider() - Look-up feature gate + * @spec: OF phandle args to use for look-up + * @gateid: ID of feature controller gate populated on successful lookup + * + * Looks for a feature controller under the node specified by @spec. + * + * Returns a valid pointer to struct feature_controller on success or ERR_PTR() + * on failure. + */ +static struct feature_controller *featctrl_get_from_provider(struct of_phandle_args *spec, + unsigned *gateid) +{ + struct feature_controller *featctrl; + int ret; + + ret = of_device_ensure_probed(spec->np); + if (ret < 0) + return ERR_PTR(ret); + + if (spec->args_count > 1) + return ERR_PTR(-EINVAL); + + /* Check if we have such a controller in our array */ + list_for_each_entry(featctrl, &of_feature_controllers, list) { + if (dev_of_node(featctrl->dev) == spec->np) { + *gateid = spec->args_count ? spec->args[0] : 0; + return featctrl; + } + } + + return ERR_PTR(-ENOENT); +} + +/** + * of_feature_controller_check - Check whether a feature controller gates the device + * @np: Device node to check + * + * Parse device's OF node to find a feature controller specifier. If such is + * found, checks it to determine whether device is gated. + * + * Returns FEATCTRL_GATED if a specified feature controller gates the device + * and FEATCTRL_OKAY if none do. On error a negative error code is returned. + */ +int of_feature_controller_check(struct device_node *np) +{ + struct of_phandle_args featctrl_args; + struct feature_controller *featctrl; + int ret, err = 0, i, ngates; + + ngates = of_count_phandle_with_args(np, "barebox,feature-gates", + "#feature-cells"); + if (ngates <= 0) + return FEATCTRL_OKAY; + + for (i = 0; i < ngates; i++) { + unsigned gateid = 0; + + ret = of_parse_phandle_with_args(np, "barebox,feature-gates", + "#feature-cells", i, &featctrl_args); + if (ret < 0) + return ret; + + featctrl = featctrl_get_from_provider(&featctrl_args, &gateid); + if (IS_ERR(featctrl)) { + ret = PTR_ERR(featctrl); + pr_debug("%s() failed to find feature controller: %pe\n", + __func__, ERR_PTR(ret)); + /* + * Assume that missing featctrls are unresolved + * dependency are report them as deferred + */ + return (ret == -ENOENT) ? -EPROBE_DEFER : ret; + } + + ret = featctrl->check(featctrl, gateid); + + dev_dbg(featctrl->dev, "checking %pOF: %d\n", np, ret); + + if (ret == FEATCTRL_OKAY) + return FEATCTRL_OKAY; + if (ret != FEATCTRL_GATED) + err = ret; + } + + return err ?: FEATCTRL_GATED; +} +EXPORT_SYMBOL_GPL(of_feature_controller_check); + +static int of_featctrl_fixup(struct device_node *root, void *context) +{ + struct device_node *srcnp, *dstnp; + int err = 0; + + for_each_node_with_property(srcnp, "barebox,feature-gates") { + int ret; + + ret = of_feature_controller_check(srcnp); + if (ret < 0) + err = ret; + if (ret != FEATCTRL_GATED) + continue; + + dstnp = of_get_node_by_reproducible_name(root, srcnp); + if (!dstnp) { + pr_debug("gated node %pOF not in fixup DT\n", srcnp); + continue; + } + + pr_debug("fixing up gating of node %pOF\n", dstnp); + /* Convention is deleting non-existing CPUs, not disable them. */ + if (of_property_match_string(srcnp, "device_type", "cpu") >= 0) + of_delete_node(dstnp); + else + of_device_disable(dstnp); + } + + return err; +} + +static __maybe_unused int of_featctrl_fixup_register(void) +{ + return of_register_fixup(of_featctrl_fixup, NULL); +} +#ifdef CONFIG_FEATURE_CONTROLLER_FIXUP +device_initcall(of_featctrl_fixup_register); +#endif diff --git a/drivers/base/platform.c b/drivers/base/platform.c index 2fb73917b4..ac7c473c8c 100644 --- a/drivers/base/platform.c +++ b/drivers/base/platform.c @@ -11,31 +11,25 @@ #include <of.h> #include <pm_domain.h> -static int platform_probe(struct device_d *dev) +static int platform_probe(struct device *dev) { int ret; ret = genpd_dev_pm_attach(dev); if (ret < 0) - return ret; + return dev_err_probe(dev, ret, "power domain attach failed\n"); return dev->driver->probe(dev); } -static void platform_remove(struct device_d *dev) -{ - if (dev->driver->remove) - dev->driver->remove(dev); -} - -int platform_driver_register(struct driver_d *drv) +int platform_driver_register(struct driver *drv) { drv->bus = &platform_bus; return register_driver(drv); } -int platform_device_register(struct device_d *new_device) +int platform_device_register(struct device *new_device) { new_device->bus = &platform_bus; @@ -46,7 +40,6 @@ struct bus_type platform_bus = { .name = "platform", .match = device_match, .probe = platform_probe, - .remove = platform_remove, }; static int platform_init(void) diff --git a/drivers/base/power.c b/drivers/base/power.c index 96cac1a091..f7629f554a 100644 --- a/drivers/base/power.c +++ b/drivers/base/power.c @@ -2,6 +2,7 @@ #include <common.h> #include <driver.h> #include <errno.h> +#include <linux/device.h> #include <of.h> #include <pm_domain.h> @@ -10,6 +11,14 @@ static LIST_HEAD(gpd_list); +static inline struct generic_pm_domain *dev_to_genpd(struct device *dev) +{ + if (IS_ERR_OR_NULL(dev->pm_domain)) + return ERR_PTR(-EINVAL); + + return dev->pm_domain; +} + /** * pm_genpd_init - Initialize a generic I/O PM domain object. * @genpd: PM domain object to initialize. @@ -31,6 +40,19 @@ int pm_genpd_init(struct generic_pm_domain *genpd, void *gov, bool is_off) } EXPORT_SYMBOL_GPL(pm_genpd_init); +int pm_genpd_remove(struct generic_pm_domain *genpd) +{ + if (IS_ERR_OR_NULL(genpd)) + return -EINVAL; + + list_del(&genpd->gpd_list_node); + + pr_debug("%s: removed %s\n", __func__, genpd->name); + + return 0; +} +EXPORT_SYMBOL_GPL(pm_genpd_remove); + /** * struct of_genpd_provider - PM domain provider registration structure * @link: Entry in global list of PM domain providers @@ -80,6 +102,37 @@ static struct generic_pm_domain *genpd_xlate_simple( } /** + * genpd_xlate_onecell() - Xlate function using a single index. + * @genpdspec: OF phandle args to map into a PM domain + * @data: xlate function private data - pointer to struct genpd_onecell_data + * + * This is a generic xlate function that can be used to model simple PM domain + * controllers that have one device tree node and provide multiple PM domains. + * A single cell is used as an index into an array of PM domains specified in + * the genpd_onecell_data struct when registering the provider. + */ +static struct generic_pm_domain *genpd_xlate_onecell( + struct of_phandle_args *genpdspec, + void *data) +{ + struct genpd_onecell_data *genpd_data = data; + unsigned int idx = genpdspec->args[0]; + + if (genpdspec->args_count != 1) + return ERR_PTR(-EINVAL); + + if (idx >= genpd_data->num_domains) { + pr_err("%s: invalid domain index %u\n", __func__, idx); + return ERR_PTR(-EINVAL); + } + + if (!genpd_data->domains[idx]) + return ERR_PTR(-ENOENT); + + return genpd_data->domains[idx]; +} + +/** * genpd_add_provider() - Register a PM domain provider for a node * @np: Device node pointer associated with the PM domain provider. * @xlate: Callback for decoding PM domain from phandle arguments. @@ -125,6 +178,69 @@ int of_genpd_add_provider_simple(struct device_node *np, EXPORT_SYMBOL_GPL(of_genpd_add_provider_simple); /** + * of_genpd_add_provider_onecell() - Register a onecell PM domain provider + * @np: Device node pointer associated with the PM domain provider. + * @data: Pointer to the data associated with the PM domain provider. + */ +int of_genpd_add_provider_onecell(struct device_node *np, + struct genpd_onecell_data *data) +{ + struct generic_pm_domain *genpd; + unsigned int i; + int ret = -EINVAL; + + if (!np || !data) + return -EINVAL; + + if (!data->xlate) + data->xlate = genpd_xlate_onecell; + + for (i = 0; i < data->num_domains; i++) { + genpd = data->domains[i]; + + if (!genpd) + continue; + if (!genpd_present(genpd)) + goto error; + } + + ret = genpd_add_provider(np, data->xlate, data); + if (ret < 0) + goto error; + + return 0; + +error: + while (i--) { + genpd = data->domains[i]; + + if (!genpd) + continue; + } + + return ret; +} +EXPORT_SYMBOL_GPL(of_genpd_add_provider_onecell); + +/** + * of_genpd_del_provider() - Remove a previously registered PM domain provider + * @np: Device node pointer associated with the PM domain provider + */ +void of_genpd_del_provider(struct device_node *np) +{ + struct of_genpd_provider *cp, *tmp; + + list_for_each_entry_safe(cp, tmp, &of_genpd_providers, link) { + if (cp->node == np) { + list_del(&cp->link); + kfree(cp); + break; + } + } +} +EXPORT_SYMBOL_GPL(of_genpd_del_provider); + +/** * genpd_get_from_provider() - Look-up PM domain * @genpdspec: OF phandle args to use for look-up * @@ -139,14 +255,32 @@ static struct generic_pm_domain *genpd_get_from_provider( struct of_phandle_args *genpdspec) { struct generic_pm_domain *genpd = ERR_PTR(-ENOENT); + struct device_node *node = genpdspec->np; struct of_genpd_provider *provider; + int ret; if (!genpdspec) return ERR_PTR(-EINVAL); + ret = of_device_ensure_probed(node); + if (ret) { + struct device_node *parent; + + /* + * If "barebox,allow-dummy" property is set for power domain + * provider, assume it's turned on. + */ + parent = of_get_parent(node); + if (of_get_property(node, "barebox,allow-dummy", NULL) || + of_get_property(parent, "barebox,allow-dummy", NULL)) + return NULL; + + return ERR_PTR(ret); + } + /* Check if we have such a provider in our array */ list_for_each_entry(provider, &of_genpd_providers, link) { - if (provider->node == genpdspec->np) + if (provider->node == node) genpd = provider->xlate(genpdspec, provider->data); if (!IS_ERR(genpd)) break; @@ -155,7 +289,7 @@ static struct generic_pm_domain *genpd_get_from_provider( return genpd; } -static int _genpd_power_on(struct generic_pm_domain *genpd, bool timed) +static int _genpd_power_on(struct generic_pm_domain *genpd) { if (!genpd->power_on) return 0; @@ -166,19 +300,18 @@ static int _genpd_power_on(struct generic_pm_domain *genpd, bool timed) /** * genpd_power_on - Restore power to a given PM domain and its masters. * @genpd: PM domain to power up. - * @depth: nesting count for lockdep. * * Restore power to @genpd and all of its masters so that it is possible to * resume a device belonging to it. */ -static int genpd_power_on(struct generic_pm_domain *genpd, unsigned int depth) +static int genpd_power_on(struct generic_pm_domain *genpd) { int ret; - if (genpd_status_on(genpd)) + if (!genpd || genpd_status_on(genpd)) return 0; - ret = _genpd_power_on(genpd, true); + ret = _genpd_power_on(genpd); if (ret) return ret; @@ -187,14 +320,56 @@ static int genpd_power_on(struct generic_pm_domain *genpd, unsigned int depth) return 0; } -static int __genpd_dev_pm_attach(struct device_d *dev, struct device_node *np, +int pm_runtime_resume_and_get_genpd(struct device *dev) +{ + struct generic_pm_domain *genpd; + + genpd = dev_to_genpd(dev); + if (IS_ERR(genpd)) + return PTR_ERR(genpd); + + return genpd_power_on(genpd); +} + +static void genpd_add_device(struct generic_pm_domain *genpd, struct device *dev) +{ + dev->pm_domain = genpd; +} + +static void genpd_remove_device(struct generic_pm_domain *genpd, + struct device *dev) +{ + dev->pm_domain = NULL; +} + +static bool have_genpd_providers; + +void genpd_activate(void) +{ + have_genpd_providers = true; +} + +static struct bus_type genpd_bus_type = { + .name = "genpd", +}; + +static int __init genpd_bus_init(void) +{ + return bus_register(&genpd_bus_type); +} +core_initcall(genpd_bus_init); + +static int __genpd_dev_pm_attach(struct device *dev, unsigned int index, bool power_on) { struct of_phandle_args pd_args; struct generic_pm_domain *pd; int ret; - ret = of_parse_phandle_with_args(np, "power-domains", + if (!have_genpd_providers) + return 0; + + ret = of_parse_phandle_with_args(dev->of_node, "power-domains", "#power-domain-cells", index, &pd_args); if (ret < 0) return ret; @@ -211,10 +386,15 @@ static int __genpd_dev_pm_attach(struct device_d *dev, struct device_node *np, return (ret == -ENOENT) ? -EPROBE_DEFER : ret; } - dev_dbg(dev, "adding to PM domain %s\n", pd->name); + dev_dbg(dev, "adding to PM domain %s\n", pd ? pd->name : "dummy"); - if (power_on) - ret = genpd_power_on(pd, 0); + genpd_add_device(pd, dev); + + if (power_on) { + ret = genpd_power_on(pd); + if (ret < 0) + genpd_remove_device(pd, dev); + } return ret ?: 1; } @@ -232,19 +412,124 @@ static int __genpd_dev_pm_attach(struct device_d *dev, struct device_node *np, * found or turned on, then return -EPROBE_DEFER to ensure that the device is * not probed and to re-try again later. */ -int genpd_dev_pm_attach(struct device_d *dev) +int genpd_dev_pm_attach(struct device *dev) { - if (!dev->device_node) + if (!dev->of_node) return 0; /* * Devices with multiple PM domains must be attached separately, as we * can only attach one PM domain per device. */ - if (of_count_phandle_with_args(dev->device_node, "power-domains", + if (of_count_phandle_with_args(dev->of_node, "power-domains", "#power-domain-cells") != 1) return 0; - return __genpd_dev_pm_attach(dev, dev->device_node, 0, true); + return __genpd_dev_pm_attach(dev, 0, true); } EXPORT_SYMBOL_GPL(genpd_dev_pm_attach); + +/** + * dev_pm_domain_attach_by_id - Associate a device with one of its PM domains. + * @dev: The device used to lookup the PM domain. + * @index: The index of the PM domain. + * + * As @dev may only be attached to a single PM domain, the backend PM domain + * provider creates a virtual device to attach instead. If attachment succeeds, + * the ->detach() callback in the struct dev_pm_domain are assigned by the + * corresponding backend attach function, as to deal with detaching of the + * created virtual device. + * + * This function should typically be invoked by a driver during the probe phase, + * in case its device requires power management through multiple PM domains. The + * driver may benefit from using the received device, to configure device-links + * towards its original device. Depending on the use-case and if needed, the + * links may be dynamically changed by the driver, which allows it to control + * the power to the PM domains independently from each other. + * + * Callers must ensure proper synchronization of this function with power + * management callbacks. + * + * Returns the virtual created device when successfully attached to its PM + * domain, NULL in case @dev don't need a PM domain, else an ERR_PTR(). + * Note that, to detach the returned virtual device, the driver shall call + * dev_pm_domain_detach() on it, typically during the remove phase. + */ +struct device *genpd_dev_pm_attach_by_id(struct device *dev, + unsigned int index) +{ + struct device *virt_dev; + int num_domains; + int ret; + + if (!dev->of_node) + return NULL; + + /* Verify that the index is within a valid range. */ + num_domains = of_count_phandle_with_args(dev->of_node, "power-domains", + "#power-domain-cells"); + if (index >= num_domains) + return NULL; + + /* Allocate and register device on the genpd bus. */ + virt_dev = kzalloc(sizeof(*virt_dev), GFP_KERNEL); + if (!virt_dev) + return ERR_PTR(-ENOMEM); + + dev_set_name(virt_dev, "genpd"); + virt_dev->bus = &genpd_bus_type; + virt_dev->parent = dev; + virt_dev->of_node = dev->of_node; + virt_dev->id = index; + + ret = device_register(virt_dev); + if (ret) { + kfree(dev); + return ERR_PTR(ret); + } + + /* Try to attach the device to the PM domain at the specified index. */ + ret = __genpd_dev_pm_attach(virt_dev, index, false); + if (ret < 1) { + device_unregister(virt_dev); + return ret ? ERR_PTR(ret) : NULL; + } + + return virt_dev; +} +EXPORT_SYMBOL_GPL(genpd_dev_pm_attach_by_id); + +/** + * genpd_dev_pm_attach_by_name - Associate a device with one of its PM domains. + * @dev: The device used to lookup the PM domain. + * @name: The name of the PM domain. + * + * Parse device's OF node to find a PM domain specifier using the + * power-domain-names DT property. For further description see + * genpd_dev_pm_attach_by_id(). + */ +struct device *genpd_dev_pm_attach_by_name(struct device *dev, const char *name) +{ + int index; + + if (!dev->of_node) + return NULL; + + index = of_property_match_string(dev->of_node, "power-domain-names", + name); + if (index < 0) + return NULL; + + return genpd_dev_pm_attach_by_id(dev, index); +} +EXPORT_SYMBOL_GPL(genpd_dev_pm_attach_by_name); + +void pm_genpd_print(void) +{ + struct generic_pm_domain *genpd; + + printf("%-20s %6s\n", "name", "active"); + list_for_each_entry(genpd, &gpd_list, gpd_list_node) + printf("%-20s %6s\n", genpd->name, + genpd->status == GPD_STATE_ACTIVE ? "on" : "off"); +} diff --git a/drivers/base/regmap/Kconfig b/drivers/base/regmap/Kconfig new file mode 100644 index 0000000000..c76908952a --- /dev/null +++ b/drivers/base/regmap/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config REGMAP_FORMATTED + bool + +config REGMAP_I2C + bool "I2C regmaps" if COMPILE_TEST + depends on I2C + select REGMAP_FORMATTED + +config REGMAP_SPI + bool "SPI regmaps" if COMPILE_TEST + depends on SPI + select REGMAP_FORMATTED diff --git a/drivers/base/regmap/Makefile b/drivers/base/regmap/Makefile index b136a72409..6911e07f0e 100644 --- a/drivers/base/regmap/Makefile +++ b/drivers/base/regmap/Makefile @@ -1,4 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only obj-y += regmap.o +obj-y += regmap-multi.o obj-y += regmap-mmio.o -obj-$(CONFIG_I2C) += regmap-i2c.o +obj-$(CONFIG_REGMAP_FORMATTED) += regmap-fmt.o +obj-$(CONFIG_REGMAP_I2C) += regmap-i2c.o +obj-$(CONFIG_REGMAP_SPI) += regmap-spi.o diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h index 05f8f8d622..ac3f0d3c0f 100644 --- a/drivers/base/regmap/internal.h +++ b/drivers/base/regmap/internal.h @@ -7,24 +7,51 @@ struct regmap_bus; +struct regmap_format { + size_t buf_size; + size_t reg_bytes; + size_t pad_bytes; + size_t val_bytes; + void (*format_write)(struct regmap *map, + unsigned int reg, unsigned int val); + void (*format_reg)(void *buf, unsigned int reg, unsigned int shift); + void (*format_val)(void *buf, unsigned int val, unsigned int shift); + unsigned int (*parse_val)(const void *buf); +}; + struct regmap { - struct device_d *dev; + struct device *dev; const struct regmap_bus *bus; const char *name; void *bus_context; struct list_head list; - int reg_bits; int reg_stride; - int pad_bits; - int val_bits; - int val_bytes; + void *work_buf; /* Scratch buffer used to format I/O */ + struct regmap_format format; + unsigned int read_flag_mask; + unsigned int write_flag_mask; + int reg_shift; unsigned int max_register; struct cdev cdev; + + int (*reg_read)(void *context, unsigned int reg, + unsigned int *val); + int (*reg_write)(void *context, unsigned int reg, + unsigned int val); }; -enum regmap_endian regmap_get_val_endian(struct device_d *dev, +enum regmap_endian regmap_get_val_endian(struct device *dev, const struct regmap_bus *bus, const struct regmap_config *config); +#ifdef CONFIG_REGMAP_FORMATTED +int regmap_formatted_init(struct regmap *map, const struct regmap_config *); +#else +static inline int regmap_formatted_init(struct regmap *map, const struct regmap_config *cfg) +{ + return -ENOSYS; +} +#endif + #endif diff --git a/drivers/base/regmap/regmap-fmt.c b/drivers/base/regmap/regmap-fmt.c new file mode 100644 index 0000000000..e7f6a8da80 --- /dev/null +++ b/drivers/base/regmap/regmap-fmt.c @@ -0,0 +1,574 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Formatted register map access API + * + * Copyright 2022 Ahmad Fatoum <a.fatoum@pengutronix.de> + * + * based on Kernel code: + * + * Copyright 2011 Wolfson Microelectronics plc + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> + */ + +#include <common.h> +#include <linux/regmap.h> +#include <linux/log2.h> +#include <asm/unaligned.h> + +#include "internal.h" + +static void regmap_format_12_20_write(struct regmap *map, + unsigned int reg, unsigned int val) +{ + u8 *out = map->work_buf; + + out[0] = reg >> 4; + out[1] = (reg << 4) | (val >> 16); + out[2] = val >> 8; + out[3] = val; +} + + +static void regmap_format_2_6_write(struct regmap *map, + unsigned int reg, unsigned int val) +{ + u8 *out = map->work_buf; + + *out = (reg << 6) | val; +} + +static void regmap_format_4_12_write(struct regmap *map, + unsigned int reg, unsigned int val) +{ + __be16 *out = map->work_buf; + *out = cpu_to_be16((reg << 12) | val); +} + +static void regmap_format_7_9_write(struct regmap *map, + unsigned int reg, unsigned int val) +{ + __be16 *out = map->work_buf; + *out = cpu_to_be16((reg << 9) | val); +} + +static void regmap_format_7_17_write(struct regmap *map, + unsigned int reg, unsigned int val) +{ + u8 *out = map->work_buf; + + out[2] = val; + out[1] = val >> 8; + out[0] = (val >> 16) | (reg << 1); +} + +static void regmap_format_10_14_write(struct regmap *map, + unsigned int reg, unsigned int val) +{ + u8 *out = map->work_buf; + + out[2] = val; + out[1] = (val >> 8) | (reg << 6); + out[0] = reg >> 2; +} + +static void regmap_format_8(void *buf, unsigned int val, unsigned int shift) +{ + u8 *b = buf; + + b[0] = val << shift; +} + +static void regmap_format_16_be(void *buf, unsigned int val, unsigned int shift) +{ + put_unaligned_be16(val << shift, buf); +} + +static void regmap_format_16_le(void *buf, unsigned int val, unsigned int shift) +{ + put_unaligned_le16(val << shift, buf); +} + +static void regmap_format_16_native(void *buf, unsigned int val, + unsigned int shift) +{ + u16 v = val << shift; + + memcpy(buf, &v, sizeof(v)); +} + +static void regmap_format_24(void *buf, unsigned int val, unsigned int shift) +{ + u8 *b = buf; + + val <<= shift; + + b[0] = val >> 16; + b[1] = val >> 8; + b[2] = val; +} + +static void regmap_format_32_be(void *buf, unsigned int val, unsigned int shift) +{ + put_unaligned_be32(val << shift, buf); +} + +static void regmap_format_32_le(void *buf, unsigned int val, unsigned int shift) +{ + put_unaligned_le32(val << shift, buf); +} + +static void regmap_format_32_native(void *buf, unsigned int val, + unsigned int shift) +{ + u32 v = val << shift; + + memcpy(buf, &v, sizeof(v)); +} + +#ifdef CONFIG_64BIT +static void regmap_format_64_be(void *buf, unsigned int val, unsigned int shift) +{ + put_unaligned_be64((u64) val << shift, buf); +} + +static void regmap_format_64_le(void *buf, unsigned int val, unsigned int shift) +{ + put_unaligned_le64((u64) val << shift, buf); +} + +static void regmap_format_64_native(void *buf, unsigned int val, + unsigned int shift) +{ + u64 v = (u64) val << shift; + + memcpy(buf, &v, sizeof(v)); +} +#endif + +static unsigned int regmap_parse_8(const void *buf) +{ + const u8 *b = buf; + + return b[0]; +} + +static unsigned int regmap_parse_16_be(const void *buf) +{ + return get_unaligned_be16(buf); +} + +static unsigned int regmap_parse_16_le(const void *buf) +{ + return get_unaligned_le16(buf); +} + +static unsigned int regmap_parse_16_native(const void *buf) +{ + u16 v; + + memcpy(&v, buf, sizeof(v)); + return v; +} + +static unsigned int regmap_parse_24(const void *buf) +{ + const u8 *b = buf; + unsigned int ret = b[2]; + ret |= ((unsigned int)b[1]) << 8; + ret |= ((unsigned int)b[0]) << 16; + + return ret; +} + +static unsigned int regmap_parse_32_be(const void *buf) +{ + return get_unaligned_be32(buf); +} + +static unsigned int regmap_parse_32_le(const void *buf) +{ + return get_unaligned_le32(buf); +} + +static unsigned int regmap_parse_32_native(const void *buf) +{ + u32 v; + + memcpy(&v, buf, sizeof(v)); + return v; +} + +#ifdef CONFIG_64BIT +static unsigned int regmap_parse_64_be(const void *buf) +{ + return get_unaligned_be64(buf); +} + +static unsigned int regmap_parse_64_le(const void *buf) +{ + return get_unaligned_le64(buf); +} + +static unsigned int regmap_parse_64_native(const void *buf) +{ + u64 v; + + memcpy(&v, buf, sizeof(v)); + return v; +} +#endif + + +static enum regmap_endian regmap_get_reg_endian(const struct regmap_bus *bus, + const struct regmap_config *config) +{ + enum regmap_endian endian; + + /* Retrieve the endianness specification from the regmap config */ + endian = config->reg_format_endian; + + /* If the regmap config specified a non-default value, use that */ + if (endian != REGMAP_ENDIAN_DEFAULT) + return endian; + + /* Retrieve the endianness specification from the bus config */ + if (bus && bus->reg_format_endian_default) + endian = bus->reg_format_endian_default; + + /* If the bus specified a non-default value, use that */ + if (endian != REGMAP_ENDIAN_DEFAULT) + return endian; + + /* Use this if no other value was found */ + return REGMAP_ENDIAN_BIG; +} + +static void regmap_set_work_buf_flag_mask(struct regmap *map, int max_bytes, + unsigned long mask) +{ + u8 *buf; + int i; + + if (!mask || !map->work_buf) + return; + + buf = map->work_buf; + + for (i = 0; i < max_bytes; i++) + buf[i] |= (mask >> (8 * i)) & 0xff; +} + +static int _regmap_raw_read(struct regmap *map, unsigned int reg, void *val, + unsigned int val_len, bool noinc) +{ + const struct regmap_bus *bus = map->bus; + + if (!bus->read) + return -EINVAL; + + map->format.format_reg(map->work_buf, reg, map->reg_shift); + regmap_set_work_buf_flag_mask(map, map->format.reg_bytes, + map->read_flag_mask); + + return bus->read(map->bus_context, map->work_buf, + map->format.reg_bytes + map->format.pad_bytes, + val, val_len); + +} + +static int _regmap_bus_read(void *context, unsigned int reg, + unsigned int *val) +{ + int ret; + struct regmap *map = context; + void *work_val = map->work_buf + map->format.reg_bytes + + map->format.pad_bytes; + + if (!map->format.parse_val) + return -EINVAL; + + ret = _regmap_raw_read(map, reg, work_val, map->format.val_bytes, false); + if (ret == 0) + *val = map->format.parse_val(work_val); + + return ret; +} + +static int _regmap_bus_formatted_write(void *context, unsigned int reg, + unsigned int val) +{ + struct regmap *map = context; + + map->format.format_write(map, reg, val); + + return map->bus->write(map->bus_context, map->work_buf, + map->format.buf_size); +} + +static int _regmap_raw_write_impl(struct regmap *map, unsigned int reg, + const void *val, size_t val_len, bool noinc) +{ + void *work_val = map->work_buf + map->format.reg_bytes + + map->format.pad_bytes; + + map->format.format_reg(map->work_buf, reg, map->reg_shift); + regmap_set_work_buf_flag_mask(map, map->format.reg_bytes, + map->write_flag_mask); + + /* + * Essentially all I/O mechanisms will be faster with a single + * buffer to write. Since register syncs often generate raw + * writes of single registers optimise that case. + */ + if (val != work_val && val_len == map->format.val_bytes) { + memcpy(work_val, val, map->format.val_bytes); + val = work_val; + } + + /* If we're doing a single register write we can probably just + * send the work_buf directly, otherwise try to do a gather + * write. + */ + return map->bus->write(map->bus_context, map->work_buf, + map->format.reg_bytes + + map->format.pad_bytes + + val_len); + +} + +static int _regmap_bus_raw_write(void *context, unsigned int reg, + unsigned int val) +{ + struct regmap *map = context; + + WARN_ON(!map->format.format_val); + + map->format.format_val(map->work_buf + map->format.reg_bytes + + map->format.pad_bytes, val, 0); + return _regmap_raw_write_impl(map, reg, + map->work_buf + + map->format.reg_bytes + + map->format.pad_bytes, + map->format.val_bytes, + false); +} + +int regmap_formatted_init(struct regmap *map, const struct regmap_config *config) +{ + enum regmap_endian reg_endian, val_endian; + const struct regmap_bus *bus = map->bus; + + map->format.buf_size = DIV_ROUND_UP(config->reg_bits + + config->val_bits + config->pad_bits, 8); + + map->work_buf = xzalloc(map->format.buf_size); + + if (config->read_flag_mask || config->write_flag_mask) { + map->read_flag_mask = config->read_flag_mask; + map->write_flag_mask = config->write_flag_mask; + } else { + map->read_flag_mask = bus->read_flag_mask; + } + + map->reg_read = _regmap_bus_read; + + reg_endian = regmap_get_reg_endian(bus, config); + val_endian = regmap_get_val_endian(map->dev, bus, config); + + switch (config->reg_bits + config->pad_bits % 8) { + case 2: + switch (config->val_bits) { + case 6: + map->format.format_write = regmap_format_2_6_write; + break; + default: + return -EINVAL; + } + break; + + case 4: + switch (config->val_bits) { + case 12: + map->format.format_write = regmap_format_4_12_write; + break; + default: + return -EINVAL; + } + break; + + case 7: + switch (config->val_bits) { + case 9: + map->format.format_write = regmap_format_7_9_write; + break; + case 17: + map->format.format_write = regmap_format_7_17_write; + break; + default: + return -EINVAL; + } + break; + + case 10: + switch (config->val_bits) { + case 14: + map->format.format_write = regmap_format_10_14_write; + break; + default: + return -EINVAL; + } + break; + + case 12: + switch (config->val_bits) { + case 20: + map->format.format_write = regmap_format_12_20_write; + break; + default: + return -EINVAL; + } + break; + + case 8: + map->format.format_reg = regmap_format_8; + break; + + case 16: + switch (reg_endian) { + case REGMAP_ENDIAN_BIG: + map->format.format_reg = regmap_format_16_be; + break; + case REGMAP_ENDIAN_LITTLE: + map->format.format_reg = regmap_format_16_le; + break; + case REGMAP_ENDIAN_NATIVE: + map->format.format_reg = regmap_format_16_native; + break; + default: + return -EINVAL; + } + break; + + case 24: + if (reg_endian != REGMAP_ENDIAN_BIG) + return -EINVAL; + map->format.format_reg = regmap_format_24; + break; + + case 32: + switch (reg_endian) { + case REGMAP_ENDIAN_BIG: + map->format.format_reg = regmap_format_32_be; + break; + case REGMAP_ENDIAN_LITTLE: + map->format.format_reg = regmap_format_32_le; + break; + case REGMAP_ENDIAN_NATIVE: + map->format.format_reg = regmap_format_32_native; + break; + default: + return -EINVAL; + } + break; + +#ifdef CONFIG_64BIT + case 64: + switch (reg_endian) { + case REGMAP_ENDIAN_BIG: + map->format.format_reg = regmap_format_64_be; + break; + case REGMAP_ENDIAN_LITTLE: + map->format.format_reg = regmap_format_64_le; + break; + case REGMAP_ENDIAN_NATIVE: + map->format.format_reg = regmap_format_64_native; + break; + default: + return -EINVAL; + } + break; +#endif + + default: + return -EINVAL; + } + + switch (config->val_bits) { + case 8: + map->format.format_val = regmap_format_8; + map->format.parse_val = regmap_parse_8; + break; + case 16: + switch (val_endian) { + case REGMAP_ENDIAN_BIG: + map->format.format_val = regmap_format_16_be; + map->format.parse_val = regmap_parse_16_be; + break; + case REGMAP_ENDIAN_LITTLE: + map->format.format_val = regmap_format_16_le; + map->format.parse_val = regmap_parse_16_le; + break; + case REGMAP_ENDIAN_NATIVE: + map->format.format_val = regmap_format_16_native; + map->format.parse_val = regmap_parse_16_native; + break; + default: + return -EINVAL; + } + break; + case 24: + if (val_endian != REGMAP_ENDIAN_BIG) + return -EINVAL; + map->format.format_val = regmap_format_24; + map->format.parse_val = regmap_parse_24; + break; + case 32: + switch (val_endian) { + case REGMAP_ENDIAN_BIG: + map->format.format_val = regmap_format_32_be; + map->format.parse_val = regmap_parse_32_be; + break; + case REGMAP_ENDIAN_LITTLE: + map->format.format_val = regmap_format_32_le; + map->format.parse_val = regmap_parse_32_le; + break; + case REGMAP_ENDIAN_NATIVE: + map->format.format_val = regmap_format_32_native; + map->format.parse_val = regmap_parse_32_native; + break; + default: + return -EINVAL; + } + break; +#ifdef CONFIG_64BIT + case 64: + switch (val_endian) { + case REGMAP_ENDIAN_BIG: + map->format.format_val = regmap_format_64_be; + map->format.parse_val = regmap_parse_64_be; + break; + case REGMAP_ENDIAN_LITTLE: + map->format.format_val = regmap_format_64_le; + map->format.parse_val = regmap_parse_64_le; + break; + case REGMAP_ENDIAN_NATIVE: + map->format.format_val = regmap_format_64_native; + map->format.parse_val = regmap_parse_64_native; + break; + default: + return -EINVAL; + } + break; +#endif + } + + if (map->format.format_write) + map->reg_write = _regmap_bus_formatted_write; + else if (map->format.format_val) + map->reg_write = _regmap_bus_raw_write; + else + return -EOPNOTSUPP; + + return 0; +} diff --git a/drivers/base/regmap/regmap-i2c.c b/drivers/base/regmap/regmap-i2c.c index 5e3705162c..13ba5866c0 100644 --- a/drivers/base/regmap/regmap-i2c.c +++ b/drivers/base/regmap/regmap-i2c.c @@ -4,37 +4,57 @@ */ #include <i2c/i2c.h> -#include <regmap.h> +#include <linux/regmap.h> -static int regmap_i2c_reg_read(void *client, unsigned int reg, unsigned int *val) +static int regmap_i2c_read(void *context, + const void *reg, size_t reg_size, + void *val, size_t val_size) { - u8 buf[1]; + struct device *dev = context; + struct i2c_client *i2c = to_i2c_client(dev); + struct i2c_msg xfer[2]; int ret; - ret = i2c_read_reg(client, reg, buf, 1); - if (ret != 1) - return ret; + xfer[0].addr = i2c->addr; + xfer[0].flags = 0; + xfer[0].len = reg_size; + xfer[0].buf = (void *)reg; - *val = buf[0]; - return 0; + xfer[1].addr = i2c->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = val_size; + xfer[1].buf = val; + + ret = i2c_transfer(i2c->adapter, xfer, 2); + if (ret == 2) + return 0; + else if (ret < 0) + return ret; + else + return -EIO; } -static int regmap_i2c_reg_write(void *client, unsigned int reg, unsigned int val) +static int regmap_i2c_write(void *context, const void *data, size_t count) { - u8 buf[] = { val & 0xff }; + struct device *dev = context; + struct i2c_client *i2c = to_i2c_client(dev); int ret; - ret = i2c_write_reg(client, reg, buf, 1); - if (ret != 1) + ret = i2c_master_send(i2c, data, count); + if (ret == count) + return 0; + else if (ret < 0) return ret; - - return 0; + else + return -EIO; } static const struct regmap_bus regmap_regmap_i2c_bus = { - .reg_write = regmap_i2c_reg_write, - .reg_read = regmap_i2c_reg_read, + .write = regmap_i2c_write, + .read = regmap_i2c_read, + .reg_format_endian_default = REGMAP_ENDIAN_BIG, + .val_format_endian_default = REGMAP_ENDIAN_BIG, }; struct regmap *regmap_init_i2c(struct i2c_client *client, @@ -42,3 +62,40 @@ struct regmap *regmap_init_i2c(struct i2c_client *client, { return regmap_init(&client->dev, ®map_regmap_i2c_bus, client, config); } + +static int regmap_smbus_byte_reg_read(void *client, unsigned int reg, unsigned int *val) +{ + int ret; + + if (reg > 0xff) + return -EINVAL; + + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) + return ret; + + *val = ret; + + return 0; +} + +static int regmap_smbus_byte_reg_write(void *client, unsigned int reg, unsigned int val) +{ + if (val > 0xff || reg > 0xff) + return -EINVAL; + + return i2c_smbus_write_byte_data(client, reg, val); +} + +static const struct regmap_bus regmap_smbus_byte = { + .reg_write = regmap_smbus_byte_reg_write, + .reg_read = regmap_smbus_byte_reg_read, +}; + +struct regmap *regmap_init_i2c_smbus(struct i2c_client *client, + const struct regmap_config *config) +{ + if (config->val_bits != 8 || config->reg_bits != 8) + return ERR_PTR(-ENOSYS); + return regmap_init(&client->dev, ®map_smbus_byte, client, config); +} diff --git a/drivers/base/regmap/regmap-mmio.c b/drivers/base/regmap/regmap-mmio.c index c8422ca46f..01b0a99631 100644 --- a/drivers/base/regmap/regmap-mmio.c +++ b/drivers/base/regmap/regmap-mmio.c @@ -7,7 +7,7 @@ #include <linux/clk.h> #include <linux/err.h> #include <io.h> -#include <regmap.h> +#include <linux/regmap.h> #include "internal.h" @@ -46,7 +46,7 @@ static int regmap_mmio_get_min_stride(size_t val_bits) case 8: /* The core treats 0 as 1 */ min_stride = 0; - return 0; + break; case 16: min_stride = 2; break; @@ -185,9 +185,9 @@ static const struct regmap_bus regmap_mmio = { .val_format_endian_default = REGMAP_ENDIAN_LITTLE, }; -static struct regmap_mmio_context *regmap_mmio_gen_context(struct device_d *dev, - void __iomem *regs, - const struct regmap_config *config) +static struct regmap_mmio_context *regmap_mmio_gen_context(struct device *dev, + void __iomem *regs, + const struct regmap_config *config) { struct regmap_mmio_context *ctx; int min_stride; @@ -277,7 +277,7 @@ err_free: return ERR_PTR(ret); } -struct regmap *regmap_init_mmio_clk(struct device_d *dev, +struct regmap *regmap_init_mmio_clk(struct device *dev, const char *clk_id, void __iomem *regs, const struct regmap_config *config) @@ -289,11 +289,15 @@ struct regmap *regmap_init_mmio_clk(struct device_d *dev, return ERR_CAST(ctx); if (clk_id) { - ctx->clk = clk_get(dev, clk_id); - if (IS_ERR(ctx->clk)) { + struct clk *clk; + + clk = clk_get(dev, clk_id); + if (IS_ERR(clk)) { kfree(ctx); - return ERR_CAST(ctx->clk); + return ERR_CAST(clk); } + + ctx->clk = clk; } return regmap_init(dev, ®map_mmio, ctx, config); diff --git a/drivers/base/regmap/regmap-multi.c b/drivers/base/regmap/regmap-multi.c new file mode 100644 index 0000000000..74f3648eb4 --- /dev/null +++ b/drivers/base/regmap/regmap-multi.c @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2022 Ahmad Fatoum <a.fatoum@pengutronix.de> + */ + +#include <common.h> +#include <fcntl.h> +#include <linux/regmap.h> +#include <linux/bitfield.h> +#include <linux/export.h> + +#include "internal.h" + +enum { MULTI_MAP_8, MULTI_MAP_16, MULTI_MAP_32, MULTI_MAP_64, MULTI_MAP_COUNT }; +struct regmap_multi { + struct cdev cdev; + struct regmap *map[MULTI_MAP_COUNT]; +}; + +static struct regmap *regmap_multi_cdev_get_map(struct cdev *cdev, unsigned rwsize) +{ + struct regmap_multi *multi = container_of(cdev, struct regmap_multi, cdev); + + switch (rwsize) { + case 8: + return multi->map[MULTI_MAP_64]; + case 4: + return multi->map[MULTI_MAP_32]; + case 2: + return multi->map[MULTI_MAP_16]; + case 1: + return multi->map[MULTI_MAP_8]; + } + + return NULL; +} + +static ssize_t regmap_multi_cdev_read(struct cdev *cdev, void *buf, size_t count, + loff_t offset, unsigned long flags) +{ + unsigned rwsize = FIELD_GET(O_RWSIZE_MASK, flags); + struct regmap *map; + + map = regmap_multi_cdev_get_map(cdev, rwsize); + if (!map) + return -EINVAL; + + count = ALIGN_DOWN(count, rwsize); + return regmap_bulk_read(map, offset, buf, count / rwsize) ?: count; +} + +static ssize_t regmap_multi_cdev_write(struct cdev *cdev, const void *buf, size_t count, + loff_t offset, unsigned long flags) +{ + unsigned rwsize = FIELD_GET(O_RWSIZE_MASK, flags); + struct regmap *map; + + map = regmap_multi_cdev_get_map(cdev, rwsize); + if (!map) + return -EINVAL; + + count = ALIGN_DOWN(count, rwsize); + return regmap_bulk_write(map, offset, buf, count / rwsize) ?: count; +} + +static struct cdev_operations regmap_multi_fops = { + .read = regmap_multi_cdev_read, + .write = regmap_multi_cdev_write, +}; + +int regmap_multi_register_cdev(struct regmap *map8, + struct regmap *map16, + struct regmap *map32, + struct regmap *map64) +{ + struct regmap *maps[MULTI_MAP_COUNT] = { map8, map16, map32, map64 }; + struct regmap_multi *multi; + struct cdev *cdev; + int i; + + multi = xzalloc(sizeof(*multi)); + cdev = &multi->cdev; + + cdev->ops = ®map_multi_fops; + cdev->size = LLONG_MAX; + + for (i = 0; i < MULTI_MAP_COUNT; i++) { + if (!maps[i]) + continue; + + multi->map[i] = maps[i]; + cdev->size = min_t(loff_t, regmap_size_bytes(maps[i]), cdev->size); + cdev->dev = cdev->dev ?: maps[i]->dev; + } + + if (!cdev->dev) { + free(multi); + return -EINVAL; + } + + cdev->name = xstrdup(dev_name(cdev->dev)); + + return devfs_create(cdev); +} diff --git a/drivers/base/regmap/regmap-spi.c b/drivers/base/regmap/regmap-spi.c new file mode 100644 index 0000000000..d15d59f635 --- /dev/null +++ b/drivers/base/regmap/regmap-spi.c @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Register map access API - SPI support +// +// Copyright 2011 Wolfson Microelectronics plc +// +// Author: Mark Brown <broonie@opensource.wolfsonmicro.com> + +#include <linux/regmap.h> +#include <spi/spi.h> + +static int regmap_spi_write(void *context, const void *data, size_t count) +{ + struct device *dev = context; + struct spi_device *spi = to_spi_device(dev); + + return spi_write(spi, data, count); +} + +static int regmap_spi_read(void *context, + const void *reg, size_t reg_size, + void *val, size_t val_size) +{ + struct device *dev = context; + struct spi_device *spi = to_spi_device(dev); + + return spi_write_then_read(spi, reg, reg_size, val, val_size); +} + +static const struct regmap_bus regmap_spi = { + .write = regmap_spi_write, + .read = regmap_spi_read, + .read_flag_mask = 0x80, + .reg_format_endian_default = REGMAP_ENDIAN_BIG, + .val_format_endian_default = REGMAP_ENDIAN_BIG, +}; + +struct regmap *regmap_init_spi(struct spi_device *spi, + const struct regmap_config *config) +{ + return regmap_init(&spi->dev, ®map_spi, &spi->dev, config); +} diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 35c462f962..7ad527954c 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -12,7 +12,7 @@ */ #include <common.h> -#include <regmap.h> +#include <linux/regmap.h> #include <malloc.h> #include <linux/log2.h> @@ -20,7 +20,7 @@ static LIST_HEAD(regmaps); -enum regmap_endian regmap_get_val_endian(struct device_d *dev, +enum regmap_endian regmap_get_val_endian(struct device *dev, const struct regmap_bus *bus, const struct regmap_config *config) { @@ -35,8 +35,8 @@ enum regmap_endian regmap_get_val_endian(struct device_d *dev, return endian; /* If the dev and dev->device_node exist try to get endianness from DT */ - if (dev && dev->device_node) { - np = dev->device_node; + if (dev && dev->of_node) { + np = dev->of_node; /* Parse the device's DT node for an endianness specification */ if (of_property_read_bool(np, "big-endian")) @@ -64,6 +64,23 @@ enum regmap_endian regmap_get_val_endian(struct device_d *dev, } EXPORT_SYMBOL_GPL(regmap_get_val_endian); +static int _regmap_bus_reg_read(void *context, unsigned int reg, + unsigned int *val) +{ + struct regmap *map = context; + + return map->bus->reg_read(map->bus_context, reg, val); +} + + +static int _regmap_bus_reg_write(void *context, unsigned int reg, + unsigned int val) +{ + struct regmap *map = context; + + return map->bus->reg_write(map->bus_context, reg, val); +} + /* * regmap_init - initialize and register a regmap * @@ -74,27 +91,39 @@ EXPORT_SYMBOL_GPL(regmap_get_val_endian); * * Returns a pointer to the new map or a ERR_PTR value on failure */ -struct regmap *regmap_init(struct device_d *dev, +struct regmap *regmap_init(struct device *dev, const struct regmap_bus *bus, void *bus_context, const struct regmap_config *config) { struct regmap *map; + int ret; map = xzalloc(sizeof(*map)); map->dev = dev; map->bus = bus; map->name = config->name; map->bus_context = bus_context; - map->reg_bits = config->reg_bits; + map->format.reg_bytes = DIV_ROUND_UP(config->reg_bits, 8); map->reg_stride = config->reg_stride; if (!map->reg_stride) map->reg_stride = 1; - map->pad_bits = config->pad_bits; - map->val_bits = config->val_bits; - map->val_bytes = DIV_ROUND_UP(config->val_bits, 8); + map->format.pad_bytes = config->pad_bits / 8; + map->format.val_bytes = DIV_ROUND_UP(config->val_bits, 8); + map->reg_shift = config->pad_bits % 8; map->max_register = config->max_register; + if (!bus->read || !bus->write) { + map->reg_read = _regmap_bus_reg_read; + map->reg_write = _regmap_bus_reg_write; + } else { + ret = regmap_formatted_init(map, config); + if (ret) { + free(map); + return ERR_PTR(ret); + } + } + list_add_tail(&map->list, ®maps); return map; @@ -108,7 +137,7 @@ struct regmap *regmap_init(struct device_d *dev, * * Returns a pointer to the regmap or a ERR_PTR value on failure */ -struct regmap *dev_get_regmap(struct device_d *dev, const char *name) +struct regmap *dev_get_regmap(struct device *dev, const char *name) { struct regmap *map; @@ -124,7 +153,7 @@ struct regmap *dev_get_regmap(struct device_d *dev, const char *name) return ERR_PTR(-ENOENT); } -struct device_d *regmap_get_device(struct regmap *map) +struct device *regmap_get_device(struct regmap *map) { return map->dev; } @@ -140,7 +169,7 @@ struct device_d *regmap_get_device(struct regmap *map) */ int regmap_write(struct regmap *map, unsigned int reg, unsigned int val) { - return map->bus->reg_write(map->bus_context, reg, val); + return map->reg_write(map, reg, val); } /* @@ -154,7 +183,7 @@ int regmap_write(struct regmap *map, unsigned int reg, unsigned int val) */ int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val) { - return map->bus->reg_read(map->bus_context, reg, val); + return map->reg_read(map, reg, val); } /** @@ -219,27 +248,27 @@ int regmap_write_bits(struct regmap *map, unsigned int reg, * @map: Register map to read from * @reg: First register to be read from * @val: Pointer to store read value - * @val_len: Size of data to read + * @val_count: Number of registers to read * * A value of zero will be returned on success, a negative errno will * be returned in error cases. */ int regmap_bulk_read(struct regmap *map, unsigned int reg, void *val, - size_t val_len) + size_t val_count) { - size_t val_bytes = map->val_bytes; - size_t val_count = val_len / val_bytes; unsigned int v; int ret, i; - if (val_len % val_bytes) - return -EINVAL; if (!IS_ALIGNED(reg, map->reg_stride)) return -EINVAL; if (val_count == 0) return -EINVAL; for (i = 0; i < val_count; i++) { + +#ifdef CONFIG_64BIT + u64 *u64 = val; +#endif u32 *u32 = val; u16 *u16 = val; u8 *u8 = val; @@ -248,7 +277,12 @@ int regmap_bulk_read(struct regmap *map, unsigned int reg, void *val, if (ret != 0) goto out; - switch (map->val_bytes) { + switch (map->format.val_bytes) { +#ifdef CONFIG_64BIT + case 8: + u64[i] = v; + break; +#endif case 4: u32[i] = v; break; @@ -274,20 +308,17 @@ int regmap_bulk_read(struct regmap *map, unsigned int reg, void *val, * @reg: Initial register to write to * @val: Block of data to be written, laid out for direct transmission to the * device - * @val_len: Length of data pointed to by val. + * @val_len: Number of registers to write * * A value of zero will be returned on success, a negative errno will * be returned in error cases. */ int regmap_bulk_write(struct regmap *map, unsigned int reg, - const void *val, size_t val_len) + const void *val, size_t val_count) { - size_t val_bytes = map->val_bytes; - size_t val_count = val_len / val_bytes; + size_t val_bytes = map->format.val_bytes; int ret, i; - if (val_len % val_bytes) - return -EINVAL; if (!IS_ALIGNED(reg, map->reg_stride)) return -EINVAL; if (val_count == 0) @@ -306,6 +337,11 @@ int regmap_bulk_write(struct regmap *map, unsigned int reg, case 4: ival = *(u32 *)(val + (i * val_bytes)); break; +#ifdef CONFIG_64BIT + case 8: + ival = *(u64 *)(val + (i * val_bytes)); + break; +#endif default: ret = -EINVAL; goto out; @@ -323,7 +359,7 @@ int regmap_bulk_write(struct regmap *map, unsigned int reg, int regmap_get_val_bytes(struct regmap *map) { - return map->val_bytes; + return map->format.val_bytes; } int regmap_get_max_register(struct regmap *map) @@ -338,22 +374,18 @@ int regmap_get_reg_stride(struct regmap *map) static int regmap_round_val_bytes(struct regmap *map) { - int val_bytes; - - val_bytes = roundup_pow_of_two(map->val_bits) >> 3; - if (!val_bytes) - val_bytes = 1; - - return val_bytes; + return map->format.val_bytes ?: 1; } static ssize_t regmap_cdev_read(struct cdev *cdev, void *buf, size_t count, loff_t offset, unsigned long flags) { struct regmap *map = container_of(cdev, struct regmap, cdev); + size_t val_bytes = map->format.val_bytes; int ret; - ret = regmap_bulk_read(map, offset, buf, count); + count = ALIGN_DOWN(count, val_bytes); + ret = regmap_bulk_read(map, offset, buf, count / val_bytes); if (ret) return ret; @@ -364,9 +396,11 @@ static ssize_t regmap_cdev_write(struct cdev *cdev, const void *buf, size_t coun unsigned long flags) { struct regmap *map = container_of(cdev, struct regmap, cdev); + size_t val_bytes = map->format.val_bytes; int ret; - ret = regmap_bulk_write(map, offset, buf, count); + count = ALIGN_DOWN(count, val_bytes); + ret = regmap_bulk_write(map, offset, buf, count / val_bytes); if (ret) return ret; @@ -379,6 +413,36 @@ static struct cdev_operations regmap_fops = { }; /* + * regmap_count_registers - returns the total number of registers + * + * @map: The map + * + * Returns the total number of registers in a regmap + */ +static size_t regmap_count_registers(struct regmap *map) +{ + /* + * max_register is in units of reg_stride, so we need to divide + * by the register stride before adding one to arrive at the + * total number of registers. + */ + return (map->max_register / map->reg_stride) + 1; +} + +/* + * regmap_size_bytes - computes the size of the regmap in bytes + * + * @map: The map + * + * Returns the number of bytes needed to hold all values in the + * regmap. + */ +size_t regmap_size_bytes(struct regmap *map) +{ + return regmap_round_val_bytes(map) * regmap_count_registers(map); +} + +/* * regmap_register_cdev - register a devfs file for a regmap * * @map: The map @@ -405,8 +469,7 @@ int regmap_register_cdev(struct regmap *map, const char *name) map->cdev.name = xstrdup(dev_name(map->dev)); } - map->cdev.size = regmap_round_val_bytes(map) * (map->max_register + 1) / - map->reg_stride; + map->cdev.size = regmap_size_bytes(map); map->cdev.dev = map->dev; map->cdev.ops = ®map_fops; diff --git a/drivers/base/resource.c b/drivers/base/resource.c index d0d3962077..0d6f200a9d 100644 --- a/drivers/base/resource.c +++ b/drivers/base/resource.c @@ -9,9 +9,9 @@ #include <xfuncs.h> #include <malloc.h> -struct device_d *device_alloc(const char *devname, int id) +struct device *device_alloc(const char *devname, int id) { - struct device_d *dev; + struct device *dev; dev = xzalloc(sizeof(*dev)); dev_set_name(dev, devname); @@ -20,7 +20,7 @@ struct device_d *device_alloc(const char *devname, int id) return dev; } -int device_add_data(struct device_d *dev, void *data, size_t size) +int device_add_data(struct device *dev, const void *data, size_t size) { free(dev->platform_data); @@ -32,7 +32,8 @@ int device_add_data(struct device_d *dev, void *data, size_t size) return 0; } -int device_add_resources(struct device_d *dev, const struct resource *res, int num) +int device_add_resources(struct device *dev, const struct resource *res, + int num) { dev->resource = xmemdup(res, sizeof(*res) * num); dev->num_resources = num; @@ -40,8 +41,9 @@ int device_add_resources(struct device_d *dev, const struct resource *res, int n return 0; } -int device_add_resource(struct device_d *dev, const char *resname, - resource_size_t start, resource_size_t size, unsigned int flags) +int device_add_resource(struct device *dev, const char *resname, + resource_size_t start, resource_size_t size, + unsigned int flags) { struct resource res = { .start = start, @@ -55,13 +57,15 @@ int device_add_resource(struct device_d *dev, const char *resname, return device_add_resources(dev, &res, 1); } -struct device_d *add_generic_device(const char* devname, int id, const char *resname, +struct device *add_child_device(struct device *parent, + const char* devname, int id, const char *resname, resource_size_t start, resource_size_t size, unsigned int flags, void *pdata) { - struct device_d *dev; + struct device *dev; dev = device_alloc(devname, id); + dev->parent = parent; dev->platform_data = pdata; device_add_resource(dev, resname, start, size, flags); @@ -69,12 +73,12 @@ struct device_d *add_generic_device(const char* devname, int id, const char *res return dev; } -EXPORT_SYMBOL(add_generic_device); +EXPORT_SYMBOL(add_child_device); -struct device_d *add_generic_device_res(const char* devname, int id, +struct device *add_generic_device_res(const char* devname, int id, struct resource *res, int nb, void *pdata) { - struct device_d *dev; + struct device *dev; dev = device_alloc(devname, id); dev->platform_data = pdata; @@ -87,7 +91,7 @@ struct device_d *add_generic_device_res(const char* devname, int id, EXPORT_SYMBOL(add_generic_device_res); #ifdef CONFIG_DRIVER_NET_DM9K -struct device_d *add_dm9000_device(int id, resource_size_t base, +struct device *add_dm9000_device(int id, resource_size_t base, resource_size_t data, int flags, void *pdata) { struct resource *res; @@ -123,7 +127,7 @@ EXPORT_SYMBOL(add_dm9000_device); #endif #ifdef CONFIG_USB_EHCI -struct device_d *add_usb_ehci_device(int id, resource_size_t hccr, +struct device *add_usb_ehci_device(int id, resource_size_t hccr, resource_size_t hcor, void *pdata) { struct resource *res; @@ -142,7 +146,7 @@ EXPORT_SYMBOL(add_usb_ehci_device); #endif #ifdef CONFIG_DRIVER_NET_KS8851_MLL -struct device_d *add_ks8851_device(int id, resource_size_t addr, +struct device *add_ks8851_device(int id, resource_size_t addr, resource_size_t addr_cmd, int flags, void *pdata) { struct resource *res; diff --git a/drivers/base/soc.c b/drivers/base/soc.c new file mode 100644 index 0000000000..a481f8987b --- /dev/null +++ b/drivers/base/soc.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: 2024 Marco Felsch, Pengutronix +/* + * Based on Linux drivers/base/soc.c: + * Copyright (C) ST-Ericsson SA 2011 + */ + +#include <common.h> +#include <init.h> +#include <of.h> + +#include <linux/slab.h> +#include <linux/sys_soc.h> +#include <linux/err.h> + +struct soc_device { + struct device dev; + struct soc_device_attribute *attr; +}; + +static struct bus_type soc_bus_type = { + .name = "soc", +}; +static bool soc_bus_registered; + +static void soc_device_add_params(struct soc_device *soc_dev) +{ + struct soc_device_attribute *attr = soc_dev->attr; + struct device *dev = &soc_dev->dev; + + if (attr->machine) + dev_add_param_string_fixed(dev, "machine", attr->machine); + if (attr->family) + dev_add_param_string_fixed(dev, "family", attr->family); + if (attr->revision) + dev_add_param_string_fixed(dev, "revision", attr->revision); + if (attr->serial_number) + dev_add_param_string_fixed(dev, "serial_number", attr->serial_number); + if (attr->soc_id) + dev_add_param_string_fixed(dev, "soc_id", attr->soc_id); +} + +static void soc_device_get_machine(struct soc_device_attribute *soc_dev_attr) +{ + struct device_node *np; + + if (soc_dev_attr->machine) + return; + + np = of_find_node_by_path("/"); + of_property_read_string(np, "model", &soc_dev_attr->machine); + of_node_put(np); +} + +static struct soc_device_attribute *early_soc_dev_attr; + +struct soc_device *soc_device_register(struct soc_device_attribute *soc_dev_attr) +{ + struct soc_device *soc_dev; + int ret; + + soc_device_get_machine(soc_dev_attr); + + if (!soc_bus_registered) { + if (early_soc_dev_attr) + return ERR_PTR(-EBUSY); + early_soc_dev_attr = soc_dev_attr; + return NULL; + } + + soc_dev = kzalloc(sizeof(*soc_dev), GFP_KERNEL); + if (!soc_dev) { + ret = -ENOMEM; + goto out1; + } + + soc_dev->attr = soc_dev_attr; + soc_dev->dev.bus = &soc_bus_type; + soc_dev->dev.id = DEVICE_ID_DYNAMIC; + + dev_set_name(&soc_dev->dev, "soc"); + + ret = device_register(&soc_dev->dev); + if (ret) { + put_device(&soc_dev->dev); + goto out2; + } + + soc_device_add_params(soc_dev); + + return soc_dev; + +out2: + kfree(soc_dev); +out1: + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(soc_device_register); + +/* Ensure soc_dev->attr is freed after calling soc_device_unregister. */ +void soc_device_unregister(struct soc_device *soc_dev) +{ + device_unregister(&soc_dev->dev); + kfree(soc_dev); + early_soc_dev_attr = NULL; +} +EXPORT_SYMBOL_GPL(soc_device_unregister); + +static int __init soc_bus_register(void) +{ + int ret; + + ret = bus_register(&soc_bus_type); + if (ret) + return ret; + soc_bus_registered = true; + + if (early_soc_dev_attr) + return PTR_ERR(soc_device_register(early_soc_dev_attr)); + + return 0; +} +core_initcall(soc_bus_register); |