* [PATCH v6 3/9] iio: inkern: api for manipulating ext_info of iio channels
From: Peter Rosin @ 2016-11-30 8:16 UTC (permalink / raw)
To: linux-kernel
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet, Arnd Bergmann,
Greg Kroah-Hartman, linux-i2c, devicetree, linux-iio, linux-doc
In-Reply-To: <1480493823-21462-1-git-send-email-peda@axentia.se>
Extend the inkern api with functions for reading and writing ext_info
of iio channels.
Signed-off-by: Peter Rosin <peda@axentia.se>
---
drivers/iio/inkern.c | 60 ++++++++++++++++++++++++++++++++++++++++++++
include/linux/iio/consumer.h | 37 +++++++++++++++++++++++++++
2 files changed, 97 insertions(+)
diff --git a/drivers/iio/inkern.c b/drivers/iio/inkern.c
index b0f4630a163f..4848b8129e6c 100644
--- a/drivers/iio/inkern.c
+++ b/drivers/iio/inkern.c
@@ -863,3 +863,63 @@ int iio_write_channel_raw(struct iio_channel *chan, int val)
return ret;
}
EXPORT_SYMBOL_GPL(iio_write_channel_raw);
+
+unsigned int iio_get_channel_ext_info_count(struct iio_channel *chan)
+{
+ const struct iio_chan_spec_ext_info *ext_info;
+ unsigned int i = 0;
+
+ if (!chan->channel->ext_info)
+ return i;
+
+ for (ext_info = chan->channel->ext_info; ext_info->name; ext_info++)
+ ++i;
+
+ return i;
+}
+EXPORT_SYMBOL_GPL(iio_get_channel_ext_info_count);
+
+static const struct iio_chan_spec_ext_info *iio_lookup_ext_info(
+ const struct iio_channel *chan,
+ const char *attr)
+{
+ const struct iio_chan_spec_ext_info *ext_info;
+
+ if (!chan->channel->ext_info)
+ return NULL;
+
+ for (ext_info = chan->channel->ext_info; ext_info->name; ++ext_info) {
+ if (!strcmp(attr, ext_info->name))
+ return ext_info;
+ }
+
+ return NULL;
+}
+
+ssize_t iio_read_channel_ext_info(struct iio_channel *chan,
+ const char *attr, char *buf)
+{
+ const struct iio_chan_spec_ext_info *ext_info;
+
+ ext_info = iio_lookup_ext_info(chan, attr);
+ if (!ext_info)
+ return -EINVAL;
+
+ return ext_info->read(chan->indio_dev, ext_info->private,
+ chan->channel, buf);
+}
+EXPORT_SYMBOL_GPL(iio_read_channel_ext_info);
+
+ssize_t iio_write_channel_ext_info(struct iio_channel *chan, const char *attr,
+ const char *buf, size_t len)
+{
+ const struct iio_chan_spec_ext_info *ext_info;
+
+ ext_info = iio_lookup_ext_info(chan, attr);
+ if (!ext_info)
+ return -EINVAL;
+
+ return ext_info->write(chan->indio_dev, ext_info->private,
+ chan->channel, buf, len);
+}
+EXPORT_SYMBOL_GPL(iio_write_channel_ext_info);
diff --git a/include/linux/iio/consumer.h b/include/linux/iio/consumer.h
index 47eeec3218b5..5e347a9805fd 100644
--- a/include/linux/iio/consumer.h
+++ b/include/linux/iio/consumer.h
@@ -312,4 +312,41 @@ int iio_read_channel_scale(struct iio_channel *chan, int *val,
int iio_convert_raw_to_processed(struct iio_channel *chan, int raw,
int *processed, unsigned int scale);
+/**
+ * iio_get_channel_ext_info_count() - get number of ext_info attributes
+ * connected to the channel.
+ * @chan: The channel being queried
+ *
+ * Returns the number of ext_info attributes
+ */
+unsigned int iio_get_channel_ext_info_count(struct iio_channel *chan);
+
+/**
+ * iio_read_channel_ext_info() - read ext_info attribute from a given channel
+ * @chan: The channel being queried.
+ * @attr: The ext_info attribute to read.
+ * @buf: Where to store the attribute value. Assumed to hold
+ * at least PAGE_SIZE bytes.
+ *
+ * Returns the number of bytes written to buf (perhaps w/o zero termination;
+ * it need not even be a string), or an error code.
+ */
+ssize_t iio_read_channel_ext_info(struct iio_channel *chan,
+ const char *attr, char *buf);
+
+/**
+ * iio_write_channel_ext_info() - write ext_info attribute from a given channel
+ * @chan: The channel being queried.
+ * @attr: The ext_info attribute to read.
+ * @buf: The new attribute value. Strings needs to be zero-
+ * terminated, but the terminator should not be included
+ * in the below len.
+ * @len: The size of the new attribute value.
+ *
+ * Returns the number of accepted bytes, which should be the same as len.
+ * An error code can also be returned.
+ */
+ssize_t iio_write_channel_ext_info(struct iio_channel *chan, const char *attr,
+ const char *buf, size_t len);
+
#endif
--
2.1.4
^ permalink raw reply related
* [PATCH v6 2/9] misc: minimal mux subsystem and gpio-based mux controller
From: Peter Rosin @ 2016-11-30 8:16 UTC (permalink / raw)
To: linux-kernel
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet, Arnd Bergmann,
Greg Kroah-Hartman, linux-i2c, devicetree, linux-iio, linux-doc
In-Reply-To: <1480493823-21462-1-git-send-email-peda@axentia.se>
Add a new minimalistic subsystem that handles multiplexer controllers.
When multiplexers are used in various places in the kernel, and the
same multiplexer controller can be used for several independent things,
there should be one place to implement support for said multiplexer
controller.
A single multiplexer controller can also be used to control several
parallel multiplexers, that are in turn used by different subsystems
in the kernel, leading to a need to coordinate multiplexer accesses.
The multiplexer subsystem handles this coordination.
This new mux controller subsystem initially comes with a single backend
driver that controls gpio based multiplexers. Even though not needed by
this initial driver, the mux controller subsystem is prepared to handle
chips with multiple (independent) mux controllers.
Signed-off-by: Peter Rosin <peda@axentia.se>
---
Documentation/driver-model/devres.txt | 6 +-
MAINTAINERS | 2 +
drivers/misc/Kconfig | 30 ++++
drivers/misc/Makefile | 2 +
drivers/misc/mux-core.c | 311 ++++++++++++++++++++++++++++++++++
drivers/misc/mux-gpio.c | 138 +++++++++++++++
include/linux/mux.h | 197 +++++++++++++++++++++
7 files changed, 685 insertions(+), 1 deletion(-)
create mode 100644 drivers/misc/mux-core.c
create mode 100644 drivers/misc/mux-gpio.c
create mode 100644 include/linux/mux.h
diff --git a/Documentation/driver-model/devres.txt b/Documentation/driver-model/devres.txt
index ca9d1eb46bc0..d64ede85b61b 100644
--- a/Documentation/driver-model/devres.txt
+++ b/Documentation/driver-model/devres.txt
@@ -330,7 +330,11 @@ MEM
devm_kzalloc()
MFD
- devm_mfd_add_devices()
+ devm_mfd_add_devices()
+
+MUX
+ devm_mux_control_get()
+ devm_mux_control_put()
PER-CPU MEM
devm_alloc_percpu()
diff --git a/MAINTAINERS b/MAINTAINERS
index 3d4d0efc2b64..dc7498682752 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8407,6 +8407,8 @@ MULTIPLEXER SUBSYSTEM
M: Peter Rosin <peda@axentia.se>
S: Maintained
F: Documentation/devicetree/bindings/misc/mux-*
+F: include/linux/mux.h
+F: drivers/misc/mux-*
MULTISOUND SOUND DRIVER
M: Andrew Veliath <andrewtv@usa.net>
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 64971baf11fa..2ce675e410c5 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -766,6 +766,36 @@ config PANEL_BOOT_MESSAGE
An empty message will only clear the display at driver init time. Any other
printf()-formatted message is valid with newline and escape codes.
+menuconfig MULTIPLEXER
+ bool "Multiplexer subsystem"
+ help
+ Multiplexer controller subsystem. Multiplexers are used in a
+ variety of settings, and this subsystem abstracts their use
+ so that the rest of the kernel sees a common interface. When
+ multiple parallel multiplexers are controlled by one single
+ multiplexer controller, this subsystem also coordinates the
+ multiplexer accesses.
+
+ If unsure, say no.
+
+if MULTIPLEXER
+
+config MUX_GPIO
+ tristate "GPIO-controlled Multiplexer"
+ depends on OF && GPIOLIB
+ help
+ GPIO-controlled Multiplexer controller.
+
+ The driver builds a single multiplexer controller using a number
+ of gpio pins. For N pins, there will be 2^N possible multiplexer
+ states. The GPIO pins can be connected (by the hardware) to several
+ multiplexers, which in that case will be operated in parallel.
+
+ To compile this driver as a module, choose M here: the module will
+ be called mux-gpio.
+
+endif
+
source "drivers/misc/c2port/Kconfig"
source "drivers/misc/eeprom/Kconfig"
source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 31983366090a..0befa2bba762 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -53,6 +53,8 @@ obj-$(CONFIG_ECHO) += echo/
obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o
obj-$(CONFIG_CXL_BASE) += cxl/
obj-$(CONFIG_PANEL) += panel.o
+obj-$(CONFIG_MULTIPLEXER) += mux-core.o
+obj-$(CONFIG_MUX_GPIO) += mux-gpio.o
lkdtm-$(CONFIG_LKDTM) += lkdtm_core.o
lkdtm-$(CONFIG_LKDTM) += lkdtm_bugs.o
diff --git a/drivers/misc/mux-core.c b/drivers/misc/mux-core.c
new file mode 100644
index 000000000000..cccaa7261a6e
--- /dev/null
+++ b/drivers/misc/mux-core.c
@@ -0,0 +1,311 @@
+/*
+ * Multiplexer subsystem
+ *
+ * Copyright (C) 2016 Axentia Technologies AB
+ *
+ * Author: Peter Rosin <peda@axentia.se>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#define pr_fmt(fmt) "mux-core: " fmt
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/idr.h>
+#include <linux/module.h>
+#include <linux/mux.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/slab.h>
+
+static struct class mux_class = {
+ .name = "mux",
+ .owner = THIS_MODULE,
+};
+
+static int __init mux_init(void)
+{
+ return class_register(&mux_class);
+}
+
+static DEFINE_IDA(mux_ida);
+
+static void mux_chip_release(struct device *dev)
+{
+ struct mux_chip *mux_chip = to_mux_chip(dev);
+
+ ida_simple_remove(&mux_ida, mux_chip->id);
+ kfree(mux_chip);
+}
+
+static struct device_type mux_type = {
+ .name = "mux-chip",
+ .release = mux_chip_release,
+};
+
+struct mux_chip *mux_chip_alloc(struct device *dev,
+ unsigned int controllers, size_t sizeof_priv)
+{
+ struct mux_chip *mux_chip;
+ int i;
+
+ if (!dev || !controllers)
+ return NULL;
+
+ mux_chip = kzalloc(sizeof(*mux_chip) +
+ controllers * sizeof(*mux_chip->mux) +
+ sizeof_priv, GFP_KERNEL);
+ if (!mux_chip)
+ return NULL;
+
+ mux_chip->mux = (struct mux_control *)(mux_chip + 1);
+ mux_chip->dev.class = &mux_class;
+ mux_chip->dev.type = &mux_type;
+ mux_chip->dev.parent = dev;
+ mux_chip->dev.of_node = dev->of_node;
+ dev_set_drvdata(&mux_chip->dev, mux_chip);
+
+ mux_chip->id = ida_simple_get(&mux_ida, 0, 0, GFP_KERNEL);
+ if (mux_chip->id < 0) {
+ pr_err("muxchipX failed to get a device id\n");
+ kfree(mux_chip);
+ return NULL;
+ }
+ dev_set_name(&mux_chip->dev, "muxchip%d", mux_chip->id);
+
+ mux_chip->controllers = controllers;
+ for (i = 0; i < controllers; ++i) {
+ struct mux_control *mux = &mux_chip->mux[i];
+
+ mux->chip = mux_chip;
+ init_rwsem(&mux->lock);
+ mux->cached_state = -1;
+ mux->idle_state = -1;
+ }
+
+ device_initialize(&mux_chip->dev);
+
+ return mux_chip;
+}
+EXPORT_SYMBOL_GPL(mux_chip_alloc);
+
+static int mux_control_set(struct mux_control *mux, int state)
+{
+ int ret = mux->chip->ops->set(mux, state);
+
+ mux->cached_state = ret < 0 ? -1 : state;
+
+ return ret;
+}
+
+int mux_chip_register(struct mux_chip *mux_chip)
+{
+ int i;
+ int ret;
+
+ for (i = 0; i < mux_chip->controllers; ++i) {
+ struct mux_control *mux = &mux_chip->mux[i];
+
+ if (mux->idle_state == mux->cached_state)
+ continue;
+
+ ret = mux_control_set(mux, mux->idle_state);
+ if (ret < 0)
+ return ret;
+ }
+
+ return device_add(&mux_chip->dev);
+}
+EXPORT_SYMBOL_GPL(mux_chip_register);
+
+void mux_chip_unregister(struct mux_chip *mux_chip)
+{
+ device_del(&mux_chip->dev);
+}
+EXPORT_SYMBOL_GPL(mux_chip_unregister);
+
+void mux_chip_free(struct mux_chip *mux_chip)
+{
+ if (!mux_chip)
+ return;
+ put_device(&mux_chip->dev);
+}
+EXPORT_SYMBOL_GPL(mux_chip_free);
+
+int mux_control_select(struct mux_control *mux, int state)
+{
+ int ret;
+
+ if (down_read_trylock(&mux->lock)) {
+ if (mux->cached_state == state)
+ return 0;
+
+ /* Sigh, the mux needs updating... */
+ up_read(&mux->lock);
+ }
+
+ /* ...or it's just contended. */
+ down_write(&mux->lock);
+
+ if (mux->cached_state == state) {
+ /*
+ * Hmmm, someone else changed the mux to my liking.
+ * That makes me wonder how long I waited for nothing?
+ */
+ downgrade_write(&mux->lock);
+ return 0;
+ }
+
+ ret = mux_control_set(mux, state);
+ if (ret < 0) {
+ if (mux->idle_state != -1)
+ mux_control_set(mux, mux->idle_state);
+
+ up_write(&mux->lock);
+ return ret;
+ }
+
+ downgrade_write(&mux->lock);
+
+ return 1;
+}
+EXPORT_SYMBOL_GPL(mux_control_select);
+
+int mux_control_deselect(struct mux_control *mux)
+{
+ int ret = 0;
+
+ if (mux->idle_state != -1 && mux->cached_state != mux->idle_state)
+ ret = mux_control_set(mux, mux->idle_state);
+
+ up_read(&mux->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(mux_control_deselect);
+
+static int of_dev_node_match(struct device *dev, const void *data)
+{
+ return dev->of_node == data;
+}
+
+static struct mux_chip *of_find_mux_chip_by_node(struct device_node *np)
+{
+ struct device *dev;
+
+ dev = class_find_device(&mux_class, NULL, np, of_dev_node_match);
+
+ return dev ? to_mux_chip(dev) : NULL;
+}
+
+struct mux_control *mux_control_get(struct device *dev, const char *mux_name)
+{
+ struct device_node *np = dev->of_node;
+ struct of_phandle_args args;
+ struct mux_chip *mux_chip;
+ unsigned int controller;
+ int index = 0;
+ int ret;
+
+ if (mux_name) {
+ index = of_property_match_string(np, "mux-control-names",
+ mux_name);
+ if (index < 0)
+ return ERR_PTR(index);
+ }
+
+ ret = of_parse_phandle_with_args(np,
+ "mux-controls", "#mux-control-cells",
+ index, &args);
+ if (ret) {
+ dev_err(dev, "%s: failed to get mux-control %s(%i)\n",
+ np->full_name, mux_name ?: "", index);
+ return ERR_PTR(ret);
+ }
+
+ mux_chip = of_find_mux_chip_by_node(args.np);
+ of_node_put(args.np);
+ if (!mux_chip)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ if (args.args_count > 1 ||
+ (!args.args_count && (mux_chip->controllers > 1))) {
+ dev_err(dev, "%s: wrong #mux-control-cells for %s\n",
+ np->full_name, args.np->full_name);
+ return ERR_PTR(-EINVAL);
+ }
+
+ controller = 0;
+ if (args.args_count)
+ controller = args.args[0];
+
+ if (controller >= mux_chip->controllers)
+ return ERR_PTR(-EINVAL);
+
+ get_device(&mux_chip->dev);
+ return &mux_chip->mux[controller];
+}
+EXPORT_SYMBOL_GPL(mux_control_get);
+
+void mux_control_put(struct mux_control *mux)
+{
+ put_device(&mux->chip->dev);
+}
+EXPORT_SYMBOL_GPL(mux_control_put);
+
+static void devm_mux_control_release(struct device *dev, void *res)
+{
+ struct mux_control *mux = *(struct mux_control **)res;
+
+ mux_control_put(mux);
+}
+
+struct mux_control *devm_mux_control_get(struct device *dev,
+ const char *mux_name)
+{
+ struct mux_control **ptr, *mux;
+
+ ptr = devres_alloc(devm_mux_control_release, sizeof(*ptr), GFP_KERNEL);
+ if (!ptr)
+ return ERR_PTR(-ENOMEM);
+
+ mux = mux_control_get(dev, mux_name);
+ if (IS_ERR(mux)) {
+ devres_free(ptr);
+ return mux;
+ }
+
+ *ptr = mux;
+ devres_add(dev, ptr);
+
+ return mux;
+}
+EXPORT_SYMBOL_GPL(devm_mux_control_get);
+
+static int devm_mux_control_match(struct device *dev, void *res, void *data)
+{
+ struct mux_control **r = res;
+
+ if (!r || !*r) {
+ WARN_ON(!r || !*r);
+ return 0;
+ }
+
+ return *r == data;
+}
+
+void devm_mux_control_put(struct device *dev, struct mux_control *mux)
+{
+ WARN_ON(devres_release(dev, devm_mux_control_release,
+ devm_mux_control_match, mux));
+}
+EXPORT_SYMBOL_GPL(devm_mux_control_put);
+
+subsys_initcall(mux_init);
+
+MODULE_DESCRIPTION("Multiplexer subsystem");
+MODULE_AUTHOR("Peter Rosin <peda@axentia.se");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/misc/mux-gpio.c b/drivers/misc/mux-gpio.c
new file mode 100644
index 000000000000..a107a9b96854
--- /dev/null
+++ b/drivers/misc/mux-gpio.c
@@ -0,0 +1,138 @@
+/*
+ * GPIO-controlled multiplexer driver
+ *
+ * Copyright (C) 2016 Axentia Technologies AB
+ *
+ * Author: Peter Rosin <peda@axentia.se>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/mux.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+
+struct mux_gpio {
+ struct gpio_descs *gpios;
+ int *val;
+};
+
+static int mux_gpio_set(struct mux_control *mux, int state)
+{
+ struct mux_gpio *mux_gpio = mux_chip_priv(mux->chip);
+ int i;
+
+ for (i = 0; i < mux_gpio->gpios->ndescs; i++)
+ mux_gpio->val[i] = (state >> i) & 1;
+
+ gpiod_set_array_value_cansleep(mux_gpio->gpios->ndescs,
+ mux_gpio->gpios->desc,
+ mux_gpio->val);
+
+ return 0;
+}
+
+static const struct mux_control_ops mux_gpio_ops = {
+ .set = mux_gpio_set,
+};
+
+static const struct of_device_id mux_gpio_dt_ids[] = {
+ { .compatible = "mux-gpio", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mux_gpio_dt_ids);
+
+static int mux_gpio_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = pdev->dev.of_node;
+ struct mux_chip *mux_chip;
+ struct mux_gpio *mux_gpio;
+ int pins;
+ u32 idle_state;
+ int ret;
+
+ if (!np)
+ return -ENODEV;
+
+ pins = gpiod_count(dev, "mux");
+ if (pins < 0)
+ return pins;
+
+ mux_chip = mux_chip_alloc(dev, 1, sizeof(*mux_gpio) +
+ pins * sizeof(*mux_gpio->val));
+ if (!mux_chip)
+ return -ENOMEM;
+
+ mux_gpio = mux_chip_priv(mux_chip);
+ mux_gpio->val = (int *)(mux_gpio + 1);
+ mux_chip->ops = &mux_gpio_ops;
+
+ platform_set_drvdata(pdev, mux_chip);
+
+ mux_gpio->gpios = devm_gpiod_get_array(dev, "mux", GPIOD_OUT_LOW);
+ if (IS_ERR(mux_gpio->gpios)) {
+ ret = PTR_ERR(mux_gpio->gpios);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to get gpios\n");
+ goto free_mux_chip;
+ }
+ WARN_ON(pins != mux_gpio->gpios->ndescs);
+ mux_chip->mux->states = 1 << pins;
+
+ ret = of_property_read_u32(np, "idle-state", &idle_state);
+ if (ret >= 0) {
+ if (idle_state >= mux_chip->mux->states) {
+ dev_err(dev, "invalid idle-state %u\n", idle_state);
+ ret = -EINVAL;
+ goto free_mux_chip;
+ }
+
+ mux_chip->mux->idle_state = idle_state;
+ }
+
+ ret = mux_chip_register(mux_chip);
+ if (ret < 0) {
+ dev_err(dev, "failed to register mux-chip\n");
+ goto free_mux_chip;
+ }
+
+ dev_info(dev, "%u-way mux-controller registered\n",
+ mux_chip->mux->states);
+
+ return 0;
+
+free_mux_chip:
+ mux_chip_free(mux_chip);
+ return ret;
+}
+
+static int mux_gpio_remove(struct platform_device *pdev)
+{
+ struct mux_chip *mux_chip = platform_get_drvdata(pdev);
+
+ mux_chip_unregister(mux_chip);
+ mux_chip_free(mux_chip);
+
+ return 0;
+}
+
+static struct platform_driver mux_gpio_driver = {
+ .driver = {
+ .name = "mux-gpio",
+ .of_match_table = of_match_ptr(mux_gpio_dt_ids),
+ },
+ .probe = mux_gpio_probe,
+ .remove = mux_gpio_remove,
+};
+module_platform_driver(mux_gpio_driver);
+
+MODULE_DESCRIPTION("GPIO-controlled multiplexer driver");
+MODULE_AUTHOR("Peter Rosin <peda@axentia.se");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/mux.h b/include/linux/mux.h
new file mode 100644
index 000000000000..88d607b7fde7
--- /dev/null
+++ b/include/linux/mux.h
@@ -0,0 +1,197 @@
+/*
+ * mux.h - definitions for the multiplexer interface
+ *
+ * Copyright (C) 2016 Axentia Technologies AB
+ *
+ * Author: Peter Rosin <peda@axentia.se>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _LINUX_MUX_H
+#define _LINUX_MUX_H
+
+#include <linux/device.h>
+#include <linux/rwsem.h>
+
+struct mux_chip;
+struct mux_control;
+struct platform_device;
+
+struct mux_control_ops {
+ int (*set)(struct mux_control *mux, int state);
+};
+
+/**
+ * struct mux_control - Represents a mux controller.
+ * @lock: Protects the mux controller state.
+ * @chip: The mux chip that is handling this mux controller.
+ * @states: The number of mux controller states.
+ * @cached_state: The current mux controller state, or -1 if none.
+ * @idle_state: The mux controller state to use when inactive, or -1
+ * for none.
+ */
+struct mux_control {
+ struct rw_semaphore lock; /* protects the state of the mux */
+
+ struct mux_chip *chip;
+
+ unsigned int states;
+ int cached_state;
+ int idle_state;
+};
+
+/**
+ * struct mux_chip - Represents a chip holding mux controllers.
+ * @controllers: Number of mux controllers handled by the chip.
+ * @mux: Array of mux controllers that is handled.
+ * @dev: Device structure.
+ * @id: Used to identify the device internally.
+ * @ops: Mux controller operations.
+ */
+struct mux_chip {
+ unsigned int controllers;
+ struct mux_control *mux;
+ struct device dev;
+ int id;
+
+ const struct mux_control_ops *ops;
+};
+
+#define to_mux_chip(x) container_of((x), struct mux_chip, dev)
+
+/**
+ * mux_chip_priv() - Get the extra memory reserved by mux_chip_alloc().
+ * @mux_chip: The mux-chip to get the private memory from.
+ *
+ * Return: Pointer to the private memory reserved by the allocator.
+ */
+static inline void *mux_chip_priv(struct mux_chip *mux_chip)
+{
+ return &mux_chip->mux[mux_chip->controllers];
+}
+
+/**
+ * mux_chip_alloc() - Allocate a mux-chip.
+ * @dev: The parent device implementing the mux interface.
+ * @controllers: The number of mux controllers to allocate for this chip.
+ * @sizeof_priv: Size of extra memory area for private use by the caller.
+ *
+ * Return: A pointer to the new mux-chip, NULL on failure.
+ */
+struct mux_chip *mux_chip_alloc(struct device *dev,
+ unsigned int controllers, size_t sizeof_priv);
+
+/**
+ * mux_chip_register() - Register a mux-chip, thus readying the controllers
+ * for use.
+ * @mux_chip: The mux-chip to register.
+ *
+ * Do not retry registration of the same mux-chip on failure. You should
+ * instead put it away with mux_chip_free() and allocate a new one, if you
+ * for some reason would like to retry registration.
+ *
+ * Return: Zero on success or a negative errno on error.
+ */
+int mux_chip_register(struct mux_chip *mux_chip);
+
+/**
+ * mux_chip_unregister() - Take the mux-chip off-line.
+ * @mux_chip: The mux-chip to unregister.
+ *
+ * mux_chip_unregister() reverses the effects of mux_chip_register().
+ * But not completely, you should not try to call mux_chip_register()
+ * on a mux-chip that has been registered before.
+ */
+void mux_chip_unregister(struct mux_chip *mux_chip);
+
+/**
+ * mux_chip_free() - Free the mux-chip for good.
+ * @mux_chip: The mux-chip to free.
+ *
+ * mux_chip_free() reverses the effects of mux_chip_alloc().
+ */
+void mux_chip_free(struct mux_chip *mux_chip);
+
+/**
+ * mux_control_select() - Select the given multiplexer state.
+ * @mux: The mux-control to request a change of state from.
+ * @state: The new requested state.
+ *
+ * Make sure to call mux_control_deselect() when the operation is complete and
+ * the mux-control is free for others to use, but do not call
+ * mux_control_deselect() if mux_control_select() fails.
+ *
+ * Return: 0 if the requested state was already active, or 1 it the
+ * mux-control state was changed to the requested state. Or a negavive
+ * errno on error.
+ *
+ * Note that the difference in return value of zero or one is of
+ * questionable value; especially if the mux-control has several independent
+ * consumers, which is something the consumers should perhaps not be making
+ * assumptions about.
+ */
+int mux_control_select(struct mux_control *mux, int state);
+
+/**
+ * mux_control_deselect() - Deselect the previously selected multiplexer state.
+ * @mux: The mux-control to deselect.
+ *
+ * Return: 0 on success and a negative errno on error. An error can only
+ * occur if the mux has an idle state. Note that even if an error occurs, the
+ * mux-control is unlocked for others to access.
+ */
+int mux_control_deselect(struct mux_control *mux);
+
+/**
+ * mux_control_get_index() - Get the index of the given mux controller
+ * @mux: The mux-control to the the index for.
+ *
+ * Return: The index of the mux controller within the mux chip the mux
+ * controller is a part of.
+ */
+static inline unsigned int mux_control_get_index(struct mux_control *mux)
+{
+ return mux - mux->chip->mux;
+}
+
+/**
+ * mux_control_get() - Get the mux-control for a device.
+ * @dev: The device that needs a mux-control.
+ * @mux_name: The name identifying the mux-control.
+ *
+ * Return: A pointer to the mux-control, or an ERR_PTR with a negative errno.
+ */
+struct mux_control *mux_control_get(struct device *dev, const char *mux_name);
+
+/**
+ * mux_control_put() - Put away the mux-control for good.
+ * @mux: The mux-control to put away.
+ *
+ * mux_control_put() reverses the effects of mux_control_get().
+ */
+void mux_control_put(struct mux_control *mux);
+
+/**
+ * devm_mux_control_get() - Get the mux-control for a device, with resource
+ * management.
+ * @dev: The device that needs a mux-control.
+ * @mux_name: The name identifying the mux-control.
+ *
+ * Return: Pointer to the mux-control, or an ERR_PTR with a negative errno.
+ */
+struct mux_control *devm_mux_control_get(struct device *dev,
+ const char *mux_name);
+
+/**
+ * devm_mux_control_put() - Resource-managed version mux_control_put().
+ * @dev: The device that originally got the mux-control.
+ * @mux: The mux-control to put away.
+ *
+ * Note that you do not normally need to call this function.
+ */
+void devm_mux_control_put(struct device *dev, struct mux_control *mux);
+
+#endif /* _LINUX_MUX_H */
--
2.1.4
^ permalink raw reply related
* [PATCH v6 1/9] dt-bindings: document devicetree bindings for mux-controllers and mux-gpio
From: Peter Rosin @ 2016-11-30 8:16 UTC (permalink / raw)
To: linux-kernel-u79uwXL29TY76Z2rM5mHXA
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet, Arnd Bergmann,
Greg Kroah-Hartman, linux-i2c-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-iio-u79uwXL29TY76Z2rM5mHXA,
linux-doc-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1480493823-21462-1-git-send-email-peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
Signed-off-by: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
---
.../devicetree/bindings/misc/mux-controller.txt | 127 +++++++++++++++++++++
.../devicetree/bindings/misc/mux-gpio.txt | 68 +++++++++++
MAINTAINERS | 5 +
3 files changed, 200 insertions(+)
create mode 100644 Documentation/devicetree/bindings/misc/mux-controller.txt
create mode 100644 Documentation/devicetree/bindings/misc/mux-gpio.txt
diff --git a/Documentation/devicetree/bindings/misc/mux-controller.txt b/Documentation/devicetree/bindings/misc/mux-controller.txt
new file mode 100644
index 000000000000..19c36b73173e
--- /dev/null
+++ b/Documentation/devicetree/bindings/misc/mux-controller.txt
@@ -0,0 +1,127 @@
+Common multiplexer controller bindings
+======================================
+
+A multiplexer (or mux) controller will have one, or several, consumer devices
+that uses the mux controller. Thus, a mux controller can possibly control
+several parallel multiplexers, presumably there will be at least one
+multiplexer needed by each consumer..
+
+A mux controller provides a number of states to its consumers, and the state
+space is a simple zero-based enumeration. I.e. 0-1 for a 2-way multiplexer,
+0-7 for an 8-way multiplexer, etc.
+
+
+Consumers
+---------
+
+Mux controller consumers should specify a list of mux controllers that they
+want to use with a property containing a 'mux-ctrl-list':
+
+ mux-ctrl-list ::= <single-mux-ctrl> [mux-ctrl-list]
+ single-mux-ctrl ::= <mux-ctrl-phandle> [mux-ctrl-specifier]
+ mux-ctrl-phandle : phandle to mux controller node
+ mux-ctrl-specifier : array of #mux-control-cells specifying the
+ given mux controller (controller specific)
+
+Mux controller properties should be named "mux-controls". The exact meaning of
+each mux controller property must be documented in the device tree binding for
+each consumer. An optional property "mux-control-names" may contain a list of
+strings to label each of the mux controllers listed in the "mux-controls"
+property.
+
+Drivers for devices that use more than a single mux controller can use the
+"mux-control-names" property to map the name of the mux controller requested by
+the mux_control_get() call to an index into the list given by the
+"mux-controls" property.
+
+mux-ctrl-specifier typically encodes the chip-relative mux controller number.
+If the mux controller chip only provides a single mux controller, the
+mux-ctrl-specifier can typically be left out.
+
+Example:
+
+ /* One consumer of a 2-way mux controller (one GPIO-line) */
+ mux: mux-controller {
+ compatible = "mux-gpio";
+ #mux-control-cells = <0>;
+
+ mux-gpios = <&pioA 0 GPIO_ACTIVE_HIGH>;
+ };
+
+ adc-mux {
+ compatible = "iio-mux";
+ io-channels = <&adc 0>;
+ io-channel-names = "parent";
+ mux-controls = <&mux>;
+ mux-control-names = "adc";
+
+ channels = "sync", "in";
+ };
+
+Note that in the example above, specifying the "mux-control-names" is redundant
+because there is only one mux controller in the list.
+
+ /*
+ * Two consumers (one for an ADC line and one for an i2c bus) of
+ * parallel 4-way multiplexers controlled by the same two GPIO-lines.
+ */
+ mux: mux-controller {
+ compatible = "mux-gpio";
+ #mux-control-cells = <0>;
+
+ mux-gpios = <&pioA 0 GPIO_ACTIVE_HIGH>,
+ <&pioA 1 GPIO_ACTIVE_HIGH>;
+ };
+
+ adc-mux {
+ compatible = "iio-mux";
+ io-channels = <&adc 0>;
+ io-channel-names = "parent";
+ mux-controls = <&mux>;
+
+ channels = "sync-1", "in", "out", "sync-2";
+ };
+
+ i2c-mux {
+ compatible = "i2c-mux-simple,mux-locked";
+ i2c-parent = <&i2c1>;
+ mux-controls = <&mux>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ i2c@0 {
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ssd1307: oled@3c {
+ /* ... */
+ };
+ };
+
+ i2c@3 {
+ reg = <3>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ pca9555: pca9555@20 {
+ /* ... */
+ };
+ };
+ };
+
+
+Mux controller nodes
+--------------------
+
+Mux controller nodes must specify the number of cells used for the
+specifier using the '#mux-control-cells' property.
+
+An example mux controller might look like this:
+
+ mux: adg792a@50 {
+ compatible = "adi,adg792a";
+ reg = <0x50>;
+ #mux-control-cells = <1>;
+ };
diff --git a/Documentation/devicetree/bindings/misc/mux-gpio.txt b/Documentation/devicetree/bindings/misc/mux-gpio.txt
new file mode 100644
index 000000000000..2ff814f082c8
--- /dev/null
+++ b/Documentation/devicetree/bindings/misc/mux-gpio.txt
@@ -0,0 +1,68 @@
+GPIO-based multiplexer controller bindings
+
+Define what GPIO pins are used to control a multiplexer. Or several
+multiplexers, if the same pins control more than one multiplexer.
+
+Required properties:
+- compatible : "mux-gpio"
+- mux-gpios : list of gpios used to control the multiplexer, least
+ significant bit first.
+- #mux-control-cells : <0>
+* Standard mux-controller bindings as decribed in mux-controller.txt
+
+Optional properties:
+- idle-state : if present, the state the mux will have when idle.
+
+The multiplexer state is defined as the number represented by the
+multiplexer GPIO pins, where the first pin is the least significant
+bit. An active pin is a binary 1, an inactive pin is a binary 0.
+
+Example:
+
+ mux: mux-controller {
+ compatible = "mux-gpio";
+ #mux-control-cells = <0>;
+
+ mux-gpios = <&pioA 0 GPIO_ACTIVE_HIGH>,
+ <&pioA 1 GPIO_ACTIVE_HIGH>;
+ };
+
+ adc-mux {
+ compatible = "iio-mux";
+ io-channels = <&adc 0>;
+ io-channel-names = "parent";
+
+ mux-controls = <&mux>;
+
+ channels = "sync-1", "in", "out", "sync-2";
+ };
+
+ i2c-mux {
+ compatible = "i2c-mux-simple,mux-locked";
+ i2c-parent = <&i2c1>;
+
+ mux-controls = <&mux>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ i2c@0 {
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ssd1307: oled@3c {
+ /* ... */
+ };
+ };
+
+ i2c@3 {
+ reg = <3>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ pca9555: pca9555@20 {
+ /* ... */
+ };
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index d8eb3843dbd4..3d4d0efc2b64 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8403,6 +8403,11 @@ S: Orphan
F: drivers/mmc/host/mmc_spi.c
F: include/linux/spi/mmc_spi.h
+MULTIPLEXER SUBSYSTEM
+M: Peter Rosin <peda-koto5C5qi+TLoDKTGw+V6w@public.gmane.org>
+S: Maintained
+F: Documentation/devicetree/bindings/misc/mux-*
+
MULTISOUND SOUND DRIVER
M: Andrew Veliath <andrewtv-Jdbf3xiKgS8@public.gmane.org>
S: Maintained
--
2.1.4
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [PATCH v6 0/9] mux controller abstraction and iio/i2c muxes
From: Peter Rosin @ 2016-11-30 8:16 UTC (permalink / raw)
To: linux-kernel
Cc: Peter Rosin, Wolfram Sang, Rob Herring, Mark Rutland,
Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
Peter Meerwald-Stadler, Jonathan Corbet, Arnd Bergmann,
Greg Kroah-Hartman, linux-i2c, devicetree, linux-iio, linux-doc
Hi!
v5 -> v6 changes
- fix stupidity in mux_chip_priv, mux_gpio_remove and adg792a_remove.
- change the devicetree bindings for the iio-mux to use a list of strings
(channels property) instead of a list children.
v4 -> v5 changes
- remove support for fancier dt layouts and go back to the phandle
approach from v2 and before, killing the horrible non-working
refcounting crap from v4 and avoiding a bunch of life-time issues
in v3.
- introduce the concept of a mux-chip, that can hold one or more
mux-controllers (inspired by the pwm subsystem).
- add dt #mux-control-cells property needed to get to the desired
mux controller if a mux chip provides more than one.
- take away the option to build the mux-core as a module.
- if the mux controller has an idle state, make sure the mux controller
is set up in the idle state initially (when it should be idle).
- do not use a variable length array on the stack in mux_gpio_set to
temporarily store the gpio state, preallocate space instead.
- fix resource leak on one failure path in mux_gpio_probe.
- driver for Analog Devices ADG792A/G, literally the first mux chip
I found on the Internet with an i2c interface (that was not a
dedicated i2c multiplexer like PCA9547) which I used to verify
that the abstractions in the mux core are up to the task. Untested,
just proof of concept that at least looks pretty and compiles...
- various touch-ups.
v3 -> v4 changes
- rebased onto next-20161122 (depends on recent _available iio changes).
- added support for having the mux-controller in a child node of a
mux-consumer if it is a sole consumer, to hopefully even further satisfy
the complaint from Rob (and later Lars-Peter) about dt complexity.
- the above came at the cost of some rather horrible refcounting code,
please review and suggest how it should be done...
- changed to register a device class instead of a bus.
- pass in the parent device into mux_control_alloc and require less
work from mux-control drivers.
- changed device names from mux:control%d to mux%d
- move kernel-doc from mux-core.c to mux.h (and add some bits).
- give the gpio driver a chance to update all mux pins at once.
- factor out iio ext_info lookup into new helper function. /Lars-Peter
- use an unsigned type for the iio ext_info count. /Lars-Peter
- unified "brag strings" in the file headers.
v2 -> v3 changes
- have the mux-controller in the parent node of any mux-controller consumer,
to hopefully satisfy complaint from Rob about dt complexity.
- improve commit message of the mux subsystem commit, making it more
general, as requested by Jonathan.
- remove priv member from struct mux_control and calculate it on the
fly. /Jonathan
- make the function comments in mux-core.c kernel doc. /Jonathan
- add devm_mux_control_* to Documentation/driver.model/devres.txt. /Jonathan
- add common dt bindings for mux-controllers, refer to them from the
mux-gpio bindings. /Rob
- clarify how the gpio pins map to the mux state. /Rob
- separate CONFIG_ variables for the mux core and the mux gpio driver.
- improve Kconfig help texts.
- make CONFIG_MUX_GPIO depend on CONFIG_GPIOLIB.
- keep track of the number of mux states in the mux core.
- since the iio channel number is used as mux state, it was possible
to drop the state member from the mux_child struct.
- cleanup dt bindings for i2c-mux-simple, it had some of copy-paste
problems from ots origin (i2c-mux-gpio).
- select the mux control subsystem in config for the i2c-mux-simple driver.
- add entries to MAINTAINERS and my sign-off, I'm now satisfied and know
nothing in this to be ashamed of.
v1 -> v2 changes
- fixup export of mux_control_put reported by kbuild
- drop devicetree iio-ext-info property as noted by Lars-Peter,
and replace the functionality by exposing all ext_info
attributes of the parent channel for each of the muxed
channels. A cache on top of that and each muxed channel
gets its own view of the ext_info of the parent channel.
- implement idle-state for muxes
- clear out the cache on failure in order to force a mux
update on the following use
- cleanup the probe of i2c-mux-simple driver
- fix a bug in the i2c-mux-simple driver, where failure in
the selection of the mux caused a deadlock when the mux
was later unconditionally deselected.
I have a piece of hardware that is using the same 3 GPIO pins
to control four 8-way muxes. Three of them control ADC lines
to an ADS1015 chip with an iio driver, and the last one
controls the SDA line of an i2c bus. We have some deployed
code to handle this, but you do not want to see it or ever
hear about it. I'm not sure why I even mention it. Anyway,
the situation has nagged me to no end for quite some time.
So, after first getting more intimate with the i2c muxing code
and later discovering the drivers/iio/inkern.c file and
writing a couple of drivers making use of it, I came up with
what I think is an acceptable solution; add a generic mux
controller driver (and subsystem) that is shared between all
instances, and combine that with an iio mux driver and a new
generic i2c mux driver. The new i2c mux I called "simple"
since it is only hooking the i2c muxing and the new mux
controller (much like the alsa simple card driver does for ASoC).
One thing that I would like to do, but don't see a solution
for, is to move the mux control code that is present in
various drivers in drivers/i2c/muxes to this new minimalistic
muxing subsystem, thus converting all present i2c muxes (but
perhaps not gates and arbitrators) to be i2c-mux-simple muxes.
I'm using an rwsem to lock a mux, but that isn't really a
perfect fit. Is there a better locking primitive that I don't
know about that fits better? I had a mutex at one point, but
that didn't allow any concurrent accesses at all. At least
the rwsem allows concurrent access as long as all users
agree on the mux state, but I suspect that the rwsem will
degrade to the mutex situation pretty quickly if there is
any contention.
Also, the "mux" name feels a bit ambitious, there are many muxes
in the world, and this tiny bit of code is probably not good
enough to be a nice fit for all...
Cheers,
Peter
Peter Rosin (9):
dt-bindings: document devicetree bindings for mux-controllers and
mux-gpio
misc: minimal mux subsystem and gpio-based mux controller
iio: inkern: api for manipulating ext_info of iio channels
dt-bindings: iio: iio-mux: document iio-mux bindings
iio: multiplexer: new iio category and iio-mux driver
dt-bindings: i2c: i2c-mux-simple: document i2c-mux-simple bindings
i2c: i2c-mux-simple: new driver
dt-bindings: mux-adg792a: document devicetree bindings for ADG792A/G
mux
misc: mux-adg792a: add mux controller driver for ADG792A/G
.../devicetree/bindings/i2c/i2c-mux-simple.txt | 81 ++++
.../bindings/iio/multiplexer/iio-mux.txt | 40 ++
.../devicetree/bindings/misc/mux-adg792a.txt | 64 +++
.../devicetree/bindings/misc/mux-controller.txt | 127 ++++++
.../devicetree/bindings/misc/mux-gpio.txt | 68 +++
Documentation/driver-model/devres.txt | 6 +-
MAINTAINERS | 14 +
drivers/i2c/muxes/Kconfig | 13 +
drivers/i2c/muxes/Makefile | 1 +
drivers/i2c/muxes/i2c-mux-simple.c | 179 ++++++++
drivers/iio/Kconfig | 1 +
drivers/iio/Makefile | 1 +
drivers/iio/inkern.c | 60 +++
drivers/iio/multiplexer/Kconfig | 18 +
drivers/iio/multiplexer/Makefile | 6 +
drivers/iio/multiplexer/iio-mux.c | 456 +++++++++++++++++++++
drivers/misc/Kconfig | 42 ++
drivers/misc/Makefile | 3 +
drivers/misc/mux-adg792a.c | 154 +++++++
drivers/misc/mux-core.c | 311 ++++++++++++++
drivers/misc/mux-gpio.c | 138 +++++++
include/linux/iio/consumer.h | 37 ++
include/linux/mux.h | 197 +++++++++
23 files changed, 2016 insertions(+), 1 deletion(-)
create mode 100644 Documentation/devicetree/bindings/i2c/i2c-mux-simple.txt
create mode 100644 Documentation/devicetree/bindings/iio/multiplexer/iio-mux.txt
create mode 100644 Documentation/devicetree/bindings/misc/mux-adg792a.txt
create mode 100644 Documentation/devicetree/bindings/misc/mux-controller.txt
create mode 100644 Documentation/devicetree/bindings/misc/mux-gpio.txt
create mode 100644 drivers/i2c/muxes/i2c-mux-simple.c
create mode 100644 drivers/iio/multiplexer/Kconfig
create mode 100644 drivers/iio/multiplexer/Makefile
create mode 100644 drivers/iio/multiplexer/iio-mux.c
create mode 100644 drivers/misc/mux-adg792a.c
create mode 100644 drivers/misc/mux-core.c
create mode 100644 drivers/misc/mux-gpio.c
create mode 100644 include/linux/mux.h
--
2.1.4
^ permalink raw reply
* Re: [PATCH 00/39] mtd: nand: denali: 2nd round of Denali NAND IP patch bomb
From: Masahiro Yamada @ 2016-11-30 8:13 UTC (permalink / raw)
To: Boris Brezillon
Cc: linux-mtd, devicetree, Linux Kernel Mailing List, Marek Vasut,
Brian Norris, Richard Weinberger, David Woodhouse,
Cyrille Pitchen, Rob Herring, Mark Rutland, Andy Shevchenko
In-Reply-To: <20161127160459.5894be93@bbrezillon>
2016-11-28 0:04 GMT+09:00 Boris Brezillon <boris.brezillon@free-electrons.com>:
> +Andy
>
> Hi Masahiro,
>
> On Sun, 27 Nov 2016 03:05:46 +0900
> Masahiro Yamada <yamada.masahiro@socionext.com> wrote:
>
>> As I said in the 1st round series, I am tackling on this driver
>> to use it for my SoCs.
>>
>> The previous series was just cosmetic things, but this series
>> includes *real* changes.
>>
>> After some more cleanups, I will start to add changes that
>> are really necessary.
>> One of the biggest problems I want to solve is a bunch of
>> hard-coded parameters that prevent me from using this driver for
>> my SoCs.
>>
>> I will introduce capability flags that are associated with DT
>> compatible and make platform-dependent parameters overridable.
>>
>> I still have lots of reworks to get done (so probably 3rd round
>> series will come), but I hope it is getting better and
>> I am showing a big picture now.
>>
>
> Thanks for posting this 2nd round of patches, I know have a clearer
> view of what you're trying to achieve.
> Could you be a bit more specific about the remaining rework (your 3rd
> round)?
We still have plenty of time, and no reason to hurry now.
So, you do not have to apply this series
until you see an even bigger picture.
I will try my best to include as many of my plans as possible
in this round.
I may end up with dropping my on-going work to the ML occasionally
because I want early feedback in case I am doing something wrong.
--
Best Regards
Masahiro Yamada
^ permalink raw reply
* Re: [PATCH v7 4/8] drm/sunxi: Add DT bindings documentation of Allwinner HDMI
From: Jean-Francois Moine @ 2016-11-30 8:12 UTC (permalink / raw)
To: Laurent Pinchart
Cc: dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW, Dave Airlie,
Maxime Ripard, Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-sunxi-/JYPxA39Uh5TLH3MbocFFw,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <4502748.8rUF7ESxa4@avalon>
On Tue, 29 Nov 2016 22:10:01 +0200
Laurent Pinchart <laurent.pinchart-ryLnwIuWjnjg/C1BVhZhaw@public.gmane.org> wrote:
> Hi Jean-François,
>
> On Tuesday 29 Nov 2016 21:04:55 Jean-Francois Moine wrote:
> > On Tue, 29 Nov 2016 21:33 +0200 Laurent Pinchart wrote:
> > >>> You need a third port for the HDMI encoder output, connected to an
> > >>> HDMI connector DT node.
> > >>
> > >> I don't see what you mean. The HDMI device is both the encoder
> > >> and connector (as the TDA998x):
> > >
> > > The driver might create both an encoder and a connector, but I very much
> > > doubt that the "allwinner,sun8i-a83t-hdmi" hardware contains a connector,
> > > unless the SoC package has an HDMI connector coming out of it :-)
> > >
> > >> plane -> DE2 mixer ---> TCON -----> HDMI -----> display device
> > >> ----- plane ------ - CRTC - - encoder \
> > >> connector -- (HDMI cable)
> > >> audio-controller - - audio-codec /
> >
> > The schema is the same as the Dove Cubox: the TDA998x is just a chip
> > with some wires going out and the physical connector is supposed to be
> > at the end of the wires.
>
> I've missed the Dove Cubox DT bindings when they were submitted. Fortunately
> (or unfortunately for you, depending on how you look at it ;-)) I've paid more
> attention this time.
>
> > Here, the HDMI pins of the SoC go to a pure hardware chip and then to
> > the physical connector. Which software entity do you want to add?
>
> I don't want to add a software entity, I just want to model the connector in
> DT as it's present in the system. Even though that's more common for other bus
> types than HDMI (LVDS for instance) it wouldn't be inconceivable to connect
> the HDMI signals to an on-board chim instead of an HDMI connector, so the HDMI
> encoder output should be modelled by a port and connected to a connector DT
> node in this case.
Well, I don't see what this connector can be.
May you give me a DT example?
--
Ken ar c'hentañ | ** Breizh ha Linux atav! **
Jef | http://moinejf.free.fr/
--
You received this message because you are subscribed to the Google Groups "linux-sunxi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
For more options, visit https://groups.google.com/d/optout.
^ permalink raw reply
* Re: [PATCH v7 0/8] drm: sun8i: Add DE2 HDMI video support
From: Laurent Pinchart @ 2016-11-30 8:08 UTC (permalink / raw)
To: Jernej Skrabec
Cc: linux-sunxi, moinejf-GANU6spQydw, airlied-cv59FeDIM0c,
robh+dt-DgEjT+Ai2ygdnm+yROfE0A, devicetree-u79uwXL29TY76Z2rM5mHXA,
dri-devel-PD4FTy7X32lNgt0PjOBp9y5qC8QIuHrW,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
maxime.ripard-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8,
kieran.bingham-ryLnwIuWjnjg/C1BVhZhaw
In-Reply-To: <239b60f3-3ea7-4aa7-8e8d-353e1a1d4ee5-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org>
Hi Jernej,
On Tuesday 29 Nov 2016 15:24:25 Jernej Skrabec wrote:
> Dne torek, 29. november 2016 23.56.31 UTC+1 je oseba Laurent Pinchart
> napisala:
> > On Tuesday 29 Nov 2016 14:47:20 Jernej Skrabec wrote:
> >> Dne torek, 29. november 2016 22.37.03 UTC+1 je oseba Maxime Ripard
> > napisala:
> >>> On Tue, Nov 29, 2016 at 11:18:35AM +0100, Jean-Francois Moine wrote:
> >>>> This patchset series adds HDMI video support to the Allwinner
> >>>> sun8i SoCs which include the display engine 2 (DE2).
> >>>> The driver contains the code for the A83T and H3 SoCs, and
> >>>> some H3 boards, but it could be used/extended for other SoCs
> >>>> (A64, H2, H5) and boards (Banana PIs, Orange PIs).
> >>>
> >>> Honestly, I'm getting a bit worried by the fact that you ignore
> >>> reviews.
> >>>
> >>> On the important reviews that you got that are to be seen as major
> >>> issues that block the inclusion, we have:
> >>> - The fact that the HDMI driver is actually just a designware IP,
> >>> and while you should use the driver that already exists, you just
> >>> duplicated all that code.
> >>
> >> That might be hard thing to do. A83T fits perfectly, but H3 and newer
> >> SoCs do not. They are using completely different HDMI phy. Decoupling
> >> controller and phy code means rewritting a good portion of the code,
> >> unless some tricks are applied, like calling phy function pointers, if
> >> they are defined.
> >
> > Same HDMI TX but different HDMI TX PHY ? Kieran is working on decoupling
> > the PHY configuration code for a Renesas SoC, that might be of interest to
> > you.
>
> Exactly. I'm developing only U-Boot driver, but Jean-Francois will probably
> have more interest in this.
We'll post patches as soon as they're ready.
By the way, do you know if the H3 and newer SoCs use a different PHY from
Synopsys, or a custom PHY developed by Allwinner ?
> >> Register addresses also differ, but that can be easily solved by using
> >> undocumented magic value to restore them.
> >
> > I love that :-)
>
> Is it allowed to use magic number which was found in binary blob? I'm new in
> all this.
I don't really see a problem with that, we have many drivers in the kernel
that have been developed through reverse-engineering. You should not include
large pieces of code that have been obtained through decompilation of a
proprietary binary blob as those could be protected by copyright, but writing
to undocumented registers based on information found through usage of a binary
driver isn't a problem. (Please remember that I'm not a lawyer though)
> >>> - The fact that you ignored Rob (v6) and I (v5) comment on using OF
> >>> graph to model the connection between the display engine and the
> >>> TCON. Something that Laurent also pointed out in this version.
> >>>
> >>> - The fact that you ignored that you needed an HDMI connector node
> >>> as a child of the HDMI controller. This has been reported by Rob
> >>> (v6) and yet again in this version by Laurent.
> >>>
> >>> - And finally the fact that we can't have several display engine in
> >>> parallel, if needs be. This has happened in the past already on
> >>> Allwinner SoCs, so it's definitely something we should consider in
> >>> the DT bindings, since we can't break them.
> >>>
> >>> Until those are fixed, I cannot see how this driver can be merged,
> >>> unfortunately.
--
Regards,
Laurent Pinchart
^ permalink raw reply
* Re: [PATCH 00/39] mtd: nand: denali: 2nd round of Denali NAND IP patch bomb
From: Masahiro Yamada @ 2016-11-30 8:02 UTC (permalink / raw)
To: Boris Brezillon
Cc: linux-mtd-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
devicetree-u79uwXL29TY76Z2rM5mHXA, Linux Kernel Mailing List,
Marek Vasut, Brian Norris, Richard Weinberger, David Woodhouse,
Cyrille Pitchen, Rob Herring, Mark Rutland, Andy Shevchenko
In-Reply-To: <20161127160459.5894be93@bbrezillon>
Hi.
2016-11-28 0:04 GMT+09:00 Boris Brezillon <boris.brezillon-wi1+55ScJUtKEb57/3fJTNBPR1lH4CV8@public.gmane.org>:
> +Andy
>
> Hi Masahiro,
>
> On Sun, 27 Nov 2016 03:05:46 +0900
> Masahiro Yamada <yamada.masahiro-uWyLwvC0a2jby3iVrkZq2A@public.gmane.org> wrote:
>
>> As I said in the 1st round series, I am tackling on this driver
>> to use it for my SoCs.
>>
>> The previous series was just cosmetic things, but this series
>> includes *real* changes.
>>
>> After some more cleanups, I will start to add changes that
>> are really necessary.
>> One of the biggest problems I want to solve is a bunch of
>> hard-coded parameters that prevent me from using this driver for
>> my SoCs.
>>
>> I will introduce capability flags that are associated with DT
>> compatible and make platform-dependent parameters overridable.
>>
>> I still have lots of reworks to get done (so probably 3rd round
>> series will come), but I hope it is getting better and
>> I am showing a big picture now.
>>
>
> Thanks for posting this 2nd round of patches, I know have a clearer
> view of what you're trying to achieve.
> Could you be a bit more specific about the remaining rework (your 3rd
> round)?
[1]
I want to remove
get_samsung_nand_para()
get_onfi_nand_para()
The driver should not hard-code timing parameters of Samsung specific
chips. For ONFI, it is duplicating effort of the core framework.
I am thinking if it would be possible to implement
chip->setup_data_interface() in order to set up
timings in a generic way.
[2]
Remove driver-internal bounce buffer.
The current Denali driver allocate DMA_BIDIRECTIONAL buffer
to use it as a driver-internal bounce buffer.
The hardware transfer page data into the bounce buffer,
then CPU copies from the bounce buffer to a given buf (and oob_poi).
This is not efficient.
So, I want to set NAND_USE_BOUNCE_BUFFER flag
and do dma_map_single directly for a given buffer.
[3]
Fix raw and oob callbacks.
I asked in another thread,
the current driver just puts the physically accessed OOB data
into oob_poi, which is not a collection of ECC data.
Raw write/read() are wrong as well.
After fixing those, enable BBT scan by removing the following flag:
/* skip the scan for now until we have OOB read and write support */
chip->options |= NAND_SKIP_BBTSCAN;
> Also, if you don't mind, I'd like to have reviews and testing from intel
> users before applying the series. Can you Cc Andy (and possibly other
> intel maintainers) for the next round.
Sure.
Anyway, this series already missed the pull-req for 4.10-rc1,
we have plenty of time until 4.11-rc1.
Review/test from Intel engineers are very appreciated
because I have no access to their boards.
--
Best Regards
Masahiro Yamada
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* [PATCH] arm64: dts: add zx296718's topcrm node
From: Baoyou Xie @ 2016-11-30 7:33 UTC (permalink / raw)
To: robh+dt-DgEjT+Ai2ygdnm+yROfE0A, mark.rutland-5wv7dgnIgG8,
catalin.marinas-5wv7dgnIgG8, will.deacon-5wv7dgnIgG8,
shawnguo-DgEjT+Ai2ygdnm+yROfE0A, jun.nie-QSEj5FYQhm4dnm+yROfE0A
Cc: baoyou.xie-QSEj5FYQhm4dnm+yROfE0A,
xie.baoyou-Th6q7B73Y6EnDS1+zs4M5A,
chen.chaokai-Th6q7B73Y6EnDS1+zs4M5A,
wang.qiang01-Th6q7B73Y6EnDS1+zs4M5A,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-kernel-u79uwXL29TY76Z2rM5mHXA
Enable topcrm clock node for zx296718, which is used for
CPU's frequency change.
Furthermore, this patch adds the CPU clock phandle in CPU's node
and uses operating-points-v2 to register operating points.
So it can be used by cpufreq-dt driver.
Signed-off-by: Baoyou Xie <baoyou.xie-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
---
arch/arm64/boot/dts/zte/zx296718.dtsi | 48 +++++++++++++++++++++++++++++++++++
1 file changed, 48 insertions(+)
diff --git a/arch/arm64/boot/dts/zte/zx296718.dtsi b/arch/arm64/boot/dts/zte/zx296718.dtsi
index 6b239a3..f9eb37d 100644
--- a/arch/arm64/boot/dts/zte/zx296718.dtsi
+++ b/arch/arm64/boot/dts/zte/zx296718.dtsi
@@ -44,6 +44,7 @@
#include <dt-bindings/input/input.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/clock/zx296718-clock.h>
/ {
compatible = "zte,zx296718";
@@ -81,6 +82,8 @@
compatible = "arm,cortex-a53","arm,armv8";
reg = <0x0 0x0>;
enable-method = "psci";
+ clocks = <&topcrm A53_GATE>;
+ operating-points-v2 = <&cluster0_opp>;
};
cpu1: cpu@1 {
@@ -88,6 +91,7 @@
compatible = "arm,cortex-a53","arm,armv8";
reg = <0x0 0x1>;
enable-method = "psci";
+ operating-points-v2 = <&cluster0_opp>;
};
cpu2: cpu@2 {
@@ -95,6 +99,7 @@
compatible = "arm,cortex-a53","arm,armv8";
reg = <0x0 0x2>;
enable-method = "psci";
+ operating-points-v2 = <&cluster0_opp>;
};
cpu3: cpu@3 {
@@ -102,6 +107,43 @@
compatible = "arm,cortex-a53","arm,armv8";
reg = <0x0 0x3>;
enable-method = "psci";
+ operating-points-v2 = <&cluster0_opp>;
+ };
+ };
+
+ cluster0_opp: opp_table0 {
+ compatible = "operating-points-v2";
+ opp-shared;
+
+ opp@1000000000 {
+ opp-hz = /bits/ 64 <500000000>;
+ opp-microvolt = <857000>;
+ clock-latency-ns = <500000>;
+ };
+ opp@1100000000 {
+ opp-hz = /bits/ 64 <648000000>;
+ opp-microvolt = <857000>;
+ clock-latency-ns = <500000>;
+ };
+ opp@1200000000 {
+ opp-hz = /bits/ 64 <800000000>;
+ opp-microvolt = <882000>;
+ clock-latency-ns = <500000>;
+ };
+ opp@1300000000 {
+ opp-hz = /bits/ 64 <1000000000>;
+ opp-microvolt = <892000>;
+ clock-latency-ns = <500000>;
+ };
+ opp@1400000000 {
+ opp-hz = /bits/ 64 <1188000000>;
+ opp-microvolt = <1009000>;
+ clock-latency-ns = <500000>;
+ };
+ opp@1500000000 {
+ opp-hz = /bits/ 64 <1312000000>;
+ opp-microvolt = <1052000>;
+ clock-latency-ns = <500000>;
};
};
@@ -279,6 +321,12 @@
dma-requests = <32>;
};
+ topcrm: clock-controller@01461000 {
+ compatible = "zte,zx296718-topcrm";
+ reg = <0x01461000 0x1000>;
+ #clock-cells = <1>;
+ };
+
sysctrl: sysctrl@1463000 {
compatible = "zte,zx296718-sysctrl", "syscon";
reg = <0x1463000 0x1000>;
--
2.7.4
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* Re: [PATCH V8 2/6] thermal: bcm2835: add thermal driver for bcm2835 soc
From: Eduardo Valentin @ 2016-11-30 6:39 UTC (permalink / raw)
To: Eric Anholt
Cc: Martin Sperl, Zhang Rui, Rob Herring, Pawel Moll, Mark Rutland,
Stephen Warren, Lee Jones, Russell King, Florian Fainelli,
Catalin Marinas, Will Deacon, linux-pm, devicetree,
linux-rpi-kernel, linux-arm-kernel
In-Reply-To: <87lgw2lyu9.fsf@eliezer.anholt.net>
Hello,
On Tue, Nov 29, 2016 at 02:12:46PM -0800, Eric Anholt wrote:
> Eduardo Valentin <edubezval@gmail.com> writes:
>
> > Hello Eric, Martin,
> >
> > On Mon, Nov 28, 2016 at 12:30:38PM -0800, Eric Anholt wrote:
> >> Either the device was initialized by the firmware before handing off to
> >> ARM (today's firmware) or it never will be (potential future firmware).
> >
> > And do you have a way to check if the firmware has the initialization
> > code or not? By firmware version, for example. Or even, chip version,
> > maybe?
>
> We would know if it's not present because the register would be in its
> power-on reset state, which is what the code is checking for.
This just looks odd for a driver, in its probe, to check if device is in
reset state, only then initializes it. if not, it assumes everything is fine.
Besides that, as described in the code, sounds like a quirk.
+ * right now the FW does set up the HW-block, so we are not
+ * touching the configuration registers.
+ * But if the HW is not enabled, then set it up
+ * using "sane" values used by the firmware right now.
And based on what you are describing now, it is not a quirk, but it is by design (??)
Again, does it hurt to always initialize the device to a *sane* config?
BR,
^ permalink raw reply
* Re: [PATCH V2 09/10] thermal: da9062/61: Thermal junction temperature monitoring driver
From: Eduardo Valentin @ 2016-11-30 6:09 UTC (permalink / raw)
To: Steve Twiss
Cc: LINUX-KERNEL, LINUX-PM, Zhang Rui, DEVICETREE, Dmitry Torokhov,
Guenter Roeck, LINUX-INPUT, LINUX-WATCHDOG, Lee Jones,
Liam Girdwood, Mark Brown, Mark Rutland, Rob Herring,
Support Opensource, Wim Van Sebroeck
In-Reply-To: <6ED8E3B22081A4459DAC7699F3695FB7018CCEC2F8@SW-EX-MBX02.diasemi.com>
Hey Steve,
On Tue, Nov 29, 2016 at 11:11:59AM +0000, Steve Twiss wrote:
> Hi Eduardo,
>
> Thanks for your response.
>
> On 29 November 2016 01:24, Eduardo Valentin, wrote:
>
> > On Wed, Oct 26, 2016 at 05:56:39PM +0100, Steve Twiss wrote:
> > > +config DA9062_THERMAL
> > > + tristate "DA9062/DA9061 Dialog Semiconductor thermal driver"
> > > + depends on MFD_DA9062
> > > + depends on OF
> > > + help
> > > + Enable this for the Dialog Semiconductor thermal sensor driver.
> > > + This will report PMIC junction over-temperature for one thermal trip
> > > + zone.
> > > + Compatible with the DA9062 and DA9061 PMICs.
> >
> > Is there any hardware documentation available for this chip that can be
> > pointed out?
>
> As part of this patch set, I added a link to the the datasheet into the top-level MFD
> component of the device tree binding: https://patchwork.kernel.org/patch/9426791/
> You can get all the information for DA9062 and DA9061 from the patch update in that
> link.
Thanks for getting me up to speed.
>
> [...]
<cut>
> > Does this mean that the chip temperature is above or equal to 125C, is
> > this really a safe temperature to keep it running?
>
> There is more information in the datasheet under the section titles "Junction
> temperature supervision". The value of 125 degC comes from the "warning
> temperature threshold" and the event is triggered when "the junction temperature
> rises above the first threshold (TEMP_WARN), the event E_TEMP is asserted".
>
> This suggests strictly greater than 125. However, there is no way for the chip to
> know the exact temperature. The mechanism is interrupt based and triggering
> only happens when the temperature rises above the threshold level.
Understood. My original concern was if the driver would be too nice with
the event. But given that the hardware has its own shutdown protection,
looks like we are OK here.
>
> [...]
> > > +static irqreturn_t da9062_thermal_irq_handler(int irq, void *data)
> > > +{
> > > + struct da9062_thermal *thermal = data;
> > > +
> > > + disable_irq_nosync(thermal->irq);
> > > + schedule_delayed_work(&thermal->work, 0);
> >
> > Can you please elaborate a little on why you have an one shot threaded IRQ
> > handler that is only disabling the IRQ then, scheduling a work with delay of 0?
> >
> > To my understanding, this is exactly what you get when you run your
> > threaded IRQ handler, when you configure it as one shot.
> >
> > Have you tried simply handling the same work done in your workqueue
> > handler in your threaded IRQ? That should simplify your code and get the
> > same result you are expecting.
> >
> > If you are not getting the IRQ disabled on the threaded IRQ when
> > configured as one shot, something else seams to be broken.
>
> Over-temperature triggering is event based: an interrupt happens when the
> temperature rises above 125 degC. However, this event based system changes into
> a polling operation to detect when the temperature falls below the threshold
> level again. This asymmetry in the chip's behaviour is the reasoning behind
> why I am not using the thermal core's built-in polling functionality.
>
> When over-temperature is reached, the interrupt from the DA9061/2 will be
> repeatedly triggered. The IRQ is disabled when the first over-temperature event
> happens and the status bit is polled using the work-queue until it becomes false.
> After that, the IRQ is re-enabled again so a new critical temperature event can
> be detected.
>
> After the interrupt has happened, event bit for the interrupt switches into a status
> bit and is used for polling and detecting when the temperature drops below the
> threshold.
OK. got it. Then, I am assuming your strategy here is to keep periodically issuing uevents
(HOT trip point) to userland, hoping that something would change the
system power consumption, then, relying on the hardware shutdown protection
if nothing happens.
I would say, your above explanation, and the uevent based strategy,
deserves to be at least in the commit message, preferably in the driver
documentation, so people know what to expect from the driver.
Now, if my understand is correct, would it make sense to have still a
failsafe mechanism in the driver? Maybe having a max number of polling?
The data sheet does not mention anything, but does one have any silicon
lifetime implication if one leaves the PMIC running for very long time
in a temperature between Twarn and Tcrit?
>
> https://lkml.org/lkml/2016/10/20/372
> https://lkml.org/lkml/2016/10/20/433
>
> [...]
> > > + thermal->zone = thermal_zone_device_register(thermal->config->name,
> > > + 1, 0, thermal,
> > > + &da9062_thermal_ops, NULL, 0,
> > > + 0);
> >
> > Did you try using of-thermal?
> >
> > So you would avoid having specific DT properties for something that
> > there is already a defined property?
>
> In an earlier RFC, I examined a work-around by hijacking and toggling the
> thermal core's built-in polling function when I needed to poll the temperature
> through get_temp(). However I thought this was quite dangerous, since it would
> not be using a formal thermal core interface.
>
> https://patchwork.kernel.org/patch/9387241/
>
my point was more under the lines of having this driver using the
of-thermal DT support to get the polling value, instead of you having a
manufacturer specific property:
Documentation/devicetree/bindings/thermal/thermal.txt
But given that your case is more specific, I start to see why you
avoided using it. But still, you could probably get the polling
values from of-thermal, populated in the tz struct, then using them from
the tz, when handling the IRQ event.
Probably your regular polling should always be set to 0, and the passive
polling to the value you want.
then again, this comment is more from a DT perspective than from the
driver code itself. Just trying to avoid specific properties that may
get described by what is already defined.
> [...]
> > > + ret = request_threaded_irq(thermal->irq, NULL,
> > > + da9062_thermal_irq_handler,
> > > + IRQF_TRIGGER_LOW | IRQF_ONESHOT,
> > > + "THERMAL", thermal);
> >
> > How about using the devm_ version?
>
> I wanted to explicitly free_irq() before calling thermal_zone_device_unregister()
> in the remove function.
>
Ok
> Regards,
> Steve
^ permalink raw reply
* [PATCH 2/2] Synopsys USB 2.0 Device Controller (UDC) Driver
From: Raviteja Garimella @ 2016-11-30 6:05 UTC (permalink / raw)
To: Rob Herring, Mark Rutland, Greg Kroah-Hartman, Felipe Balbi
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
bcm-kernel-feedback-list-dY08KVG/lbpWk0Htik3J/w,
linux-usb-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1480485910-7797-1-git-send-email-raviteja.garimella-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>
This is driver for Synopsys Designware Cores USB Device
Controller (UDC) Subsystem with the AMBA Advanced High-Performance
Bus (AHB). This driver works with Synopsys UDC20 products.
Signed-off-by: Raviteja Garimella <raviteja.garimella-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>
---
drivers/usb/gadget/udc/Kconfig | 12 +
drivers/usb/gadget/udc/Makefile | 1 +
drivers/usb/gadget/udc/snps_udc.c | 1751 +++++++++++++++++++++++++++++++++++++
drivers/usb/gadget/udc/snps_udc.h | 1071 +++++++++++++++++++++++
4 files changed, 2835 insertions(+)
create mode 100644 drivers/usb/gadget/udc/snps_udc.c
create mode 100644 drivers/usb/gadget/udc/snps_udc.h
diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig
index 658b8da..28cd679 100644
--- a/drivers/usb/gadget/udc/Kconfig
+++ b/drivers/usb/gadget/udc/Kconfig
@@ -239,6 +239,18 @@ config USB_MV_U3D
MARVELL PXA2128 Processor series include a super speed USB3.0 device
controller, which support super speed USB peripheral.
+config USB_SNP_UDC
+ tristate "Synopsys USB 2.0 Device controller"
+ select USB_GADGET_DUALSPEED
+ depends on (ARM || ARM64) && USB_GADGET
+ default ARCH_BCM_IPROC
+ help
+ This adds Device support for Synopsys Designware core
+ AHB subsystem USB2.0 Device Controller(UDC) .
+
+ This driver works with Synopsys UDC20 products.
+ If unsure, say N.
+
#
# Controllers available in both integrated and discrete versions
#
diff --git a/drivers/usb/gadget/udc/Makefile b/drivers/usb/gadget/udc/Makefile
index 98e74ed..2b63a2b 100644
--- a/drivers/usb/gadget/udc/Makefile
+++ b/drivers/usb/gadget/udc/Makefile
@@ -36,4 +36,5 @@ obj-$(CONFIG_USB_FOTG210_UDC) += fotg210-udc.o
obj-$(CONFIG_USB_MV_U3D) += mv_u3d_core.o
obj-$(CONFIG_USB_GR_UDC) += gr_udc.o
obj-$(CONFIG_USB_GADGET_XILINX) += udc-xilinx.o
+obj-$(CONFIG_USB_SNP_UDC) += snps_udc.o
obj-$(CONFIG_USB_BDC_UDC) += bdc/
diff --git a/drivers/usb/gadget/udc/snps_udc.c b/drivers/usb/gadget/udc/snps_udc.c
new file mode 100644
index 0000000..d8c46ce
--- /dev/null
+++ b/drivers/usb/gadget/udc/snps_udc.c
@@ -0,0 +1,1751 @@
+/*
+ * snps_udc.c - Synopsys USB 2.0 Device Controller driver
+ *
+ * Copyright (C) 2016 Broadcom
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/errno.h>
+#include <linux/extcon.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_gpio.h>
+#include <linux/platform_device.h>
+#include <linux/phy/phy.h>
+#include <linux/proc_fs.h>
+#include <linux/types.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+#include <linux/version.h>
+#include "snps_udc.h"
+
+#define DRIVER_DESC "Driver for Synopsys Designware core UDC"
+
+static void ep0_setup_init(struct snps_udc_ep *ep, int status)
+{
+ struct snps_udc *udc = ep->udc;
+
+ ep->dma.virt->setup.status = DMA_STS_BUF_HOST_READY;
+ ep->dirn = USB_DIR_OUT;
+ ep->stopped = 0;
+
+ if (!status) {
+ clear_ep_nak(udc->regs, ep->num, USB_DIR_OUT);
+ clear_ep_nak(udc->regs, ep->num, USB_DIR_IN);
+ } else {
+ enable_ep_stall(udc->regs, ep->num, USB_DIR_IN);
+ enable_ep_stall(udc->regs, ep->num, USB_DIR_OUT);
+ }
+
+ enable_udc_ep_irq(udc->regs, ep->num, USB_DIR_OUT);
+ enable_ep_dma(udc->regs, ep->num, USB_DIR_OUT);
+
+ dev_dbg(udc->dev, "%s setup buffer initialized\n", ep->name);
+}
+
+static void ep_dma_init(struct snps_udc_ep *ep)
+{
+ struct snps_udc *udc = ep->udc;
+ u32 desc_cnt = (DESC_CNT - 1);
+ u32 i;
+
+ ep->dma.virt = &ep->udc->dma.virt->ep[ep->num];
+ ep->dma.phys = &ep->udc->dma.phys->ep[ep->num];
+
+ ep->dma.virt->setup.status = DMA_STS_BUF_HOST_BUSY;
+ set_setup_buf_ptr(udc->regs, ep->num, USB_DIR_OUT,
+ &ep->dma.phys->setup);
+
+ for (i = 0; i < DESC_CNT; i++) {
+ ep->dma.virt->desc[i].status = DMA_STS_BUF_HOST_BUSY;
+ ep->dma.virt->desc[i].next_desc_addr =
+ (dma_addr_t)&ep->dma.phys->desc[i + 1];
+ }
+ ep->dma.virt->desc[desc_cnt].next_desc_addr =
+ (dma_addr_t)&ep->dma.phys->desc[0];
+
+ set_data_desc_ptr(udc->regs, ep->num, USB_DIR_OUT,
+ &ep->dma.phys->desc[0]);
+ set_data_desc_ptr(udc->regs, ep->num, USB_DIR_IN,
+ &ep->dma.phys->desc[0]);
+
+ dev_dbg(udc->dev, " %s dma initialized\n", ep->name);
+}
+
+static void ep_data_dma_init(struct snps_udc_ep *ep)
+{
+ struct ep_xfer_req *ep_req;
+
+ dev_dbg(ep->udc->dev, "enter: %s\n", __func__);
+
+ ep_req = list_first_entry(&ep->queue, struct ep_xfer_req, queue);
+
+ if (ep_req->dma_aligned) {
+ ep_req->dma_addr_orig = ep_req->usb_req.dma;
+ ep_req->usb_req.dma = ep->dma.aligned_addr;
+ if (ep->dirn == USB_DIR_IN)
+ memcpy(ep->dma.aligned_buf, ep_req->usb_req.buf,
+ ep_req->usb_req.length);
+ }
+
+ ep->dma.done = 0;
+ ep->dma.len_done = 0;
+ ep->dma.len_rem = ep->dma.usb_req->length;
+ ep->dma.buf_addr = ep->dma.usb_req->dma;
+ ep->dma.status = DMA_STS_RX_SUCCESS;
+
+ if ((ep->dirn == USB_DIR_IN) &&
+ (ep->type != USB_ENDPOINT_XFER_ISOC)) {
+ if (in_bf_mode)
+ ep->dma.len_max = ep->dma.usb_req->length;
+ else
+ ep->dma.len_max = ep->usb_ep.maxpacket;
+ } else {
+ if (out_bf_mode)
+ ep->dma.len_max = ep->dma.usb_req->length;
+ else
+ ep->dma.len_max = ep->usb_ep.maxpacket;
+ }
+
+ dma_desc_chain_reset(ep);
+}
+
+static void ep_data_dma_finish(struct snps_udc_ep *ep)
+{
+ struct snps_udc *udc = ep->udc;
+ struct ep_xfer_req *ep_req;
+
+ disable_udc_ep_irq(udc->regs, ep->num, ep->dirn);
+ disable_ep_dma(udc->regs, ep->num, ep->dirn);
+
+ ep_req = list_first_entry(&ep->queue, struct ep_xfer_req, queue);
+
+ if (ep_req->dma_aligned) {
+ if (ep->dirn == USB_DIR_OUT)
+ memcpy(ep_req->usb_req.buf,
+ ep->dma.aligned_buf, ep_req->usb_req.length);
+ ep_req->usb_req.dma = ep_req->dma_addr_orig;
+ }
+ dev_dbg(udc->dev, "%s dma finished\n", ep->name);
+}
+
+static void ep_data_dma_add(struct snps_udc_ep *ep)
+{
+ struct data_desc *desc = NULL;
+ u32 status;
+ u32 len;
+
+ if (!ep->dma.len_rem)
+ ep->dma.usb_req->zero = 1;
+
+ ep->dma.last = ep->dma.usb_req->zero;
+
+ while (!dma_desc_chain_is_full(ep) &&
+ (ep->dma.len_rem || ep->dma.usb_req->zero)) {
+ desc = dma_desc_chain_alloc(ep);
+ len = (ep->dma.len_rem < ep->dma.len_max) ?
+ ep->dma.len_rem : ep->dma.len_max;
+ ep->dma.len_rem -= len;
+ status = 0;
+
+ if (len <= ep->dma.len_max ||
+ (out_bf_mode && (len <= ep->dma.len_max))) {
+ if (in_bf_mode ||
+ !((ep->dirn == USB_DIR_IN) &&
+ (ep->type == USB_ENDPOINT_XFER_BULK) &&
+ (len != 0) &&
+ (len % ep->usb_ep.maxpacket == 0)))
+ ep->dma.usb_req->zero = 0;
+ }
+
+ if ((ep->dirn == USB_DIR_IN) &&
+ (ep->type == USB_ENDPOINT_XFER_ISOC)) {
+ ep->dma.frame_num += ep->dma.frame_incr;
+ dev_dbg(ep->udc->dev, "%s: DMA started: frame_num=%d.%d\n",
+ ep->name, (ep->dma.frame_num >> 3),
+ (ep->dma.frame_num & 0x7));
+ status |= ((ep->dma.frame_num <<
+ DMA_STS_FRAME_NUM_SHIFT)
+ & DMA_STS_FRAME_NUM_MASK);
+ }
+
+ desc->buf_addr = ep->dma.buf_addr;
+ status |= (len << DMA_STS_BYTE_CNT_SHIFT);
+ desc->status = status | DMA_STS_BUF_HOST_READY;
+ /* Ensure all writes are done before going for next descriptor*/
+ wmb();
+ ep->dma.buf_addr += len;
+
+ if ((ep->dirn == USB_DIR_IN) &&
+ (ep->type == USB_ENDPOINT_XFER_ISOC))
+ break;
+ }
+
+ if (desc)
+ desc->status |= DMA_STS_LAST_DESC;
+
+ dev_dbg(ep->udc->dev, "%s dma data added\n", ep->name);
+}
+
+static void ep_data_dma_remove(struct snps_udc_ep *ep)
+{
+ struct data_desc *desc;
+ u32 status;
+ u32 len = 0;
+
+ while (!dma_desc_chain_is_empty(ep)) {
+ desc = dma_desc_chain_head(ep);
+ status = desc->status;
+ desc->status = DMA_STS_BUF_HOST_BUSY;
+ /* Ensure all writes are done before going for next descriptor*/
+ wmb();
+ len = (status & DMA_STS_NISO_BYTE_CNT_MASK) >>
+ DMA_STS_NISO_BYTE_CNT_SHIFT;
+
+ if ((ep->dirn == USB_DIR_IN) || (status &
+ DMA_STS_LAST_DESC)) {
+ ep->dma.len_done += len;
+ ep->dma.usb_req->actual += len;
+ }
+
+ if ((status & DMA_STS_RX_MASK) != DMA_STS_RX_SUCCESS) {
+ ep->dma.status = status & DMA_STS_RX_MASK;
+ ep->dma.usb_req->status = -EIO;
+ dev_warn(ep->udc->dev, "%s: DMA error\n", ep->name);
+ }
+
+ if ((ep->dirn == USB_DIR_IN) &&
+ (ep->type == USB_ENDPOINT_XFER_ISOC)) {
+ if (ep->dma.usb_req->actual ==
+ ep->dma.usb_req->length)
+ ep->dma.usb_req->status = 0;
+ dma_desc_chain_reset(ep);
+ } else {
+ dma_desc_chain_free(ep);
+ }
+ }
+
+ if ((!ep->dma.len_rem || (len < ep->usb_ep.maxpacket)) &&
+ (ep->dma.usb_req->status == -EINPROGRESS))
+ ep->dma.usb_req->status = 0;
+
+ dev_dbg(ep->udc->dev, "%s dma data removed\n", ep->name);
+}
+
+static int fifo_ram_alloc(struct snps_udc_ep *ep, u32 max_pkt_size)
+{
+ u32 rx_cnt;
+ u32 tx_cnt;
+
+ switch (EP_DIRN_TYPE(ep->dirn, ep->type)) {
+ case EP_DIRN_TYPE(USB_DIR_OUT, USB_ENDPOINT_XFER_BULK):
+ case EP_DIRN_TYPE(USB_DIR_OUT, USB_ENDPOINT_XFER_INT):
+ case EP_DIRN_TYPE(USB_DIR_OUT, USB_ENDPOINT_XFER_ISOC):
+ rx_cnt = FIFO_SZ_U8(max_pkt_size);
+ tx_cnt = 0;
+ break;
+
+ case EP_DIRN_TYPE(USB_DIR_IN, USB_ENDPOINT_XFER_BULK):
+ case EP_DIRN_TYPE(USB_DIR_IN, USB_ENDPOINT_XFER_INT):
+ rx_cnt = 0;
+ tx_cnt = FIFO_SZ_U8(max_pkt_size);
+ break;
+
+ case EP_DIRN_TYPE(USB_DIR_IN, USB_ENDPOINT_XFER_ISOC):
+ rx_cnt = 0;
+ tx_cnt = 2 * FIFO_SZ_U8(max_pkt_size);
+ break;
+
+ case EP_DIRN_TYPE(USB_DIR_IN, USB_ENDPOINT_XFER_CONTROL):
+ case EP_DIRN_TYPE(USB_DIR_OUT, USB_ENDPOINT_XFER_CONTROL):
+ rx_cnt = FIFO_SZ_U8(max_pkt_size);
+ tx_cnt = rx_cnt;
+ break;
+
+ default:
+ dev_err(ep->udc->dev, "%s: invalid EP attributes\n", ep->name);
+ return -ENODEV;
+ }
+
+ dev_dbg(ep->udc->dev, "rx req=%u free=%u: tx req=%u free=%u\n",
+ rx_cnt, ep->udc->rx_fifo_space, tx_cnt, ep->udc->tx_fifo_space);
+
+ if ((ep->udc->rx_fifo_space < rx_cnt) ||
+ (ep->udc->tx_fifo_space < tx_cnt)) {
+ dev_err(ep->udc->dev, "%s: fifo alloc failed\n", ep->name);
+ return -ENOSPC;
+ }
+
+ ep->rx_fifo_size = rx_cnt;
+ ep->tx_fifo_size = tx_cnt;
+
+ if (mrx_fifo)
+ ep->udc->rx_fifo_space -= rx_cnt;
+
+ ep->udc->tx_fifo_space -= tx_cnt;
+
+ return 0;
+}
+
+static void fifo_ram_free(struct snps_udc_ep *ep)
+{
+ if (mrx_fifo)
+ ep->udc->rx_fifo_space += ep->rx_fifo_size;
+
+ ep->udc->tx_fifo_space += ep->tx_fifo_size;
+
+ ep->rx_fifo_size = 0;
+ ep->tx_fifo_size = 0;
+}
+
+static int ep_cfg(struct snps_udc_ep *ep, u32 type,
+ u32 max_pkt_size)
+{
+ struct snps_udc *udc = ep->udc;
+
+ ep->type = type;
+ if (fifo_ram_alloc(ep, max_pkt_size) != 0)
+ return -ENOSPC;
+
+ ep->type = type;
+ ep->usb_ep.maxpacket = max_pkt_size;
+
+ if (ep->udc->conn_type)
+ init_ep_reg(udc->regs, ep->num, ep->type, ep->dirn,
+ max_pkt_size);
+ dev_dbg(udc->dev, "ep_cfg: %s: type=%u dirn=0x%x pkt=%u\n",
+ ep->usb_ep.name, type, ep->dirn, max_pkt_size);
+
+ return 0;
+}
+
+static void epreq_xfer_done(struct snps_udc_ep *ep,
+ struct ep_xfer_req *ep_req, int status)
+{
+ struct snps_udc *udc = ep->udc;
+ u32 stopped;
+
+ list_del_init(&ep_req->queue);
+
+ if (ep_req->usb_req.status == -EINPROGRESS)
+ ep_req->usb_req.status = status;
+
+ if (ep_req->dma_aligned) {
+ ep_req->dma_aligned = 0;
+ } else if (ep_req->dma_mapped) {
+ dma_unmap_single(ep->udc->gadget.dev.parent,
+ ep_req->usb_req.dma,
+ (ep_req->usb_req.length ?
+ ep_req->usb_req.length : 1),
+ (ep->dirn == USB_DIR_IN ? DMA_TO_DEVICE :
+ DMA_FROM_DEVICE));
+ ep_req->dma_mapped = 0;
+ ep_req->usb_req.dma = DMA_ADDR_INVALID;
+ }
+
+ dev_dbg(udc->dev, "%s xfer done req=0x%p buf=0x%p len=%d actual=%d\n",
+ ep->name, &ep_req->usb_req, ep_req->usb_req.buf,
+ ep_req->usb_req.length, ep_req->usb_req.actual);
+
+ stopped = ep->stopped;
+ ep->stopped = 1;
+ spin_unlock(&ep->udc->lock);
+ ep_req->usb_req.complete(&ep->usb_ep, &ep_req->usb_req);
+ spin_lock(&ep->udc->lock);
+ ep->stopped = stopped;
+}
+
+static void epreq_xfer_process(struct snps_udc_ep *ep)
+{
+ struct snps_udc *udc = ep->udc;
+ struct ep_xfer_req *ep_req;
+
+ dev_dbg(udc->dev, "%s: xfer request\n", ep->name);
+
+ if (!ep->dma.usb_req) {
+ dev_dbg(udc->dev, "%s: No dma usb request\n", ep->name);
+ return;
+ }
+
+ disable_ep_dma(udc->regs, ep->num, ep->dirn);
+ ep_data_dma_remove(ep);
+
+ if (ep->dma.usb_req->status != -EINPROGRESS) {
+ ep_data_dma_finish(ep);
+
+ if ((ep->type == USB_ENDPOINT_XFER_CONTROL) &&
+ (ep->dirn == USB_DIR_IN) &&
+ (ep->dma.usb_req->status == 0)) {
+ ep->dirn = USB_DIR_OUT;
+ ep->b_ep_addr = ep->num | ep->dirn;
+ ep->dma.usb_req->status = -EINPROGRESS;
+ ep->dma.usb_req->actual = 0;
+ ep->dma.usb_req->length = 0;
+ ep_data_dma_init(ep);
+ } else {
+ if (in_bf_mode && is_ep_in() && is_ep_bulk() &&
+ (ep->dma.usb_req->length != 0) &&
+ (ep->dma.usb_req->length %
+ ep->usb_ep.maxpacket == 0) &&
+ (ep->dma.last)) {
+ ep->dma.usb_req->status = -EINPROGRESS;
+ ep->dma.usb_req->actual = 0;
+ ep->dma.usb_req->length = 0;
+ } else if (!list_empty(&ep->queue))
+ epreq_xfer_done(ep,
+ list_first_entry(&ep->queue,
+ struct
+ ep_xfer_req,
+ queue), 0);
+
+ if (ep->type == USB_ENDPOINT_XFER_CONTROL)
+ ep0_setup_init(ep, 0);
+
+ if (is_ep_in() && is_ep_bulk() &&
+ !list_empty(&ep->queue)) {
+ ep->in_xfer_done = true;
+ clear_ep_nak(udc->regs, ep->num, ep->dirn);
+ enable_udc_ep_irq(udc->regs, ep->num, ep->dirn);
+ return;
+ }
+
+ if (list_empty(&ep->queue)) {
+ ep->dma.usb_req = NULL;
+ } else {
+ ep_req = list_first_entry(&ep->queue,
+ struct ep_xfer_req,
+ queue);
+ ep->dma.usb_req = &ep_req->usb_req;
+ ep_data_dma_init(ep);
+ }
+ }
+ }
+
+ if (ep->dma.usb_req) {
+ ep_data_dma_add(ep);
+ enable_udc_ep_irq(udc->regs, ep->num, ep->dirn);
+ clear_ep_nak(udc->regs, ep->num, ep->dirn);
+ enable_ep_dma(udc->regs, ep->num, ep->dirn);
+ }
+}
+
+static void epreq_xfer_error(struct snps_udc_ep *ep, int status)
+{
+ if (!ep->dma.usb_req) {
+ dev_err(ep->udc->dev, "%s: No DMA usb request\n", ep->name);
+ return;
+ }
+
+ ep->dma.usb_req->status = status;
+ epreq_xfer_process(ep);
+}
+
+static void epreq_xfer_add(struct snps_udc_ep *ep,
+ struct ep_xfer_req *ep_req)
+{
+ struct snps_udc *udc = ep->udc;
+
+ list_add_tail(&ep_req->queue, &ep->queue);
+ if (ep->stopped)
+ return;
+
+ if ((ep->dirn == USB_DIR_IN) &&
+ (ep->type == USB_ENDPOINT_XFER_ISOC) &&
+ (ep->dma.usb_req) &&
+ (ep->dma.frame_num == FRAME_NUM_INVALID)) {
+ ep_data_dma_finish(ep);
+ ep->dma.usb_req = NULL;
+ epreq_xfer_done(ep,
+ list_first_entry(&ep->queue,
+ struct ep_xfer_req,
+ queue),
+ -EREMOTEIO);
+ }
+
+ if (ep->dma.usb_req) {
+ dev_dbg(udc->dev, "%s: busy\n", ep->name);
+ } else if (!in_isoc_delay_disabled && (ep->dirn == USB_DIR_IN) &&
+ (ep->type == USB_ENDPOINT_XFER_ISOC) &&
+ (ep->dma.frame_num == FRAME_NUM_INVALID)) {
+ dev_dbg(udc->dev, "%s: ISOC delay xfer start\n", ep->name);
+ ep->dma.usb_req = &(list_first_entry(&ep->queue,
+ struct ep_xfer_req, queue))->usb_req;
+ ep_data_dma_init(ep);
+ clear_ep_nak(udc->regs, ep->num, ep->dirn);
+ enable_udc_ep_irq(udc->regs, ep->num, ep->dirn);
+
+ } else {
+ if (in_isoc_delay_disabled && (ep->dirn == USB_DIR_IN) &&
+ (ep->type == USB_ENDPOINT_XFER_ISOC) &&
+ (ep->dma.frame_num == FRAME_NUM_INVALID)) {
+ ep->dma.frame_num = get_last_rx_frnum(udc->regs);
+ }
+
+ if (is_ep_in() && is_ep_bulk() && !ep->dma.usb_req) {
+ ep->in_xfer_done = true;
+ clear_ep_nak(udc->regs, ep->num, ep->dirn);
+ enable_udc_ep_irq(udc->regs, ep->num, ep->dirn);
+ return;
+ }
+
+ ep_req = list_first_entry(&ep->queue,
+ struct ep_xfer_req, queue);
+ ep->dma.usb_req = &ep_req->usb_req;
+ ep_data_dma_init(ep);
+ ep_data_dma_add(ep);
+ enable_udc_ep_irq(udc->regs, ep->num, ep->dirn);
+ clear_ep_nak(udc->regs, ep->num, ep->dirn);
+ enable_ep_dma(udc->regs, ep->num, ep->dirn);
+ }
+
+ dev_dbg(udc->dev, "%s: xfer add ep request\n", ep->name);
+}
+
+static void epreq_queue_flush(struct snps_udc_ep *ep, int status)
+{
+ struct snps_udc *udc = ep->udc;
+ struct ep_xfer_req *ep_req;
+
+ ep->stopped = 1;
+
+ while (!list_empty(&ep->queue)) {
+ ep_req = list_first_entry(&ep->queue,
+ struct ep_xfer_req, queue);
+ epreq_xfer_done(ep, ep_req, status);
+ }
+
+ ep->dma.usb_req = NULL;
+ if ((is_ep_in() && is_ep_bulk()) || !ep->num) {
+ set_ep_fifo_flush(udc->regs, ep->num, ep->dirn);
+ clear_ep_fifo_flush(udc->regs, ep->num, ep->dirn);
+ }
+
+ dev_dbg(udc->dev, "%s: EP queue flushed\n", ep->usb_ep.name);
+}
+
+static void ep0_setup_rx(struct snps_udc_ep *ep,
+ struct usb_ctrlrequest *setup)
+{
+ struct snps_udc *udc = ep->udc;
+ int status;
+ u32 val;
+ u32 idx;
+ u32 len;
+
+ val = le16_to_cpu(setup->wValue);
+ idx = le16_to_cpu(setup->wIndex);
+ len = le16_to_cpu(setup->wLength);
+
+ ep->dirn = setup->bRequestType & USB_ENDPOINT_DIR_MASK;
+
+ dev_dbg(udc->dev, "%s: SETUP %02x.%02x v%04x i%04x l %04x\n",
+ ep->name, setup->bRequestType, setup->bRequest,
+ val, idx, len);
+
+ if (ep->num != 0) {
+ status = -EOPNOTSUPP;
+ } else {
+ spin_unlock(&udc->lock);
+ status = udc->gadget_driver->setup(&udc->gadget, setup);
+ spin_lock(&udc->lock);
+ }
+
+ if (status < 0)
+ ep0_setup_init(ep, status);
+ else if (len == 0)
+ ep0_setup_init(ep, 0);
+}
+
+static void irq_ep_out_setup(struct snps_udc_ep *ep)
+{
+ struct setup_desc *desc = &ep->dma.virt->setup;
+ u32 status = desc->status;
+
+ dev_dbg(ep->udc->dev, "irq set up %s desc status: 0x%x\n",
+ ep->name, status);
+
+ if ((status & DMA_STS_BUF_MASK) != DMA_STS_BUF_DMA_DONE) {
+ ep0_setup_init(ep, 0);
+ } else if ((status & DMA_STS_RX_MASK) != DMA_STS_RX_SUCCESS) {
+ ep0_setup_init(ep, 0);
+ } else {
+ desc->status = (status & ~DMA_STS_BUF_MASK)
+ | DMA_STS_BUF_HOST_BUSY;
+ ep0_setup_rx(ep, (struct usb_ctrlrequest *)&desc->data1);
+ }
+}
+
+static void irq_process_epout(struct snps_udc_ep *ep)
+{
+ struct snps_udc *udc = ep->udc;
+ u32 status;
+
+ status = get_ep_status(udc->regs, ep->num, USB_DIR_OUT);
+ clear_ep_status(udc->regs, ep->num, USB_DIR_OUT, status);
+
+ status &= EP_STS_ALL;
+
+ if (!status)
+ return;
+
+ if ((ep->dirn != USB_DIR_OUT) &&
+ (ep->type != USB_ENDPOINT_XFER_CONTROL)) {
+ dev_err(udc->dev, "%s: unexpected interrupt\n", ep->name);
+ return;
+ }
+
+ if (status & OUT_DMA_DATA_DONE) {
+ status &= ~OUT_DMA_DATA_DONE;
+ epreq_xfer_process(ep);
+ }
+
+ if (status & OUT_DMA_SETUP_DONE) {
+ status &= ~OUT_DMA_SETUP_DONE;
+ irq_ep_out_setup(ep);
+ }
+
+ if (status & DMA_BUF_NOT_AVAIL) {
+ status &= ~DMA_BUF_NOT_AVAIL;
+ dev_dbg(udc->dev, "%s: DMA BUF NOT AVAIL\n", ep->name);
+ epreq_xfer_process(ep);
+ }
+
+ if (status & DMA_ERROR) {
+ status &= ~DMA_ERROR;
+ dev_err(udc->dev, "%s: DMA ERROR\n", ep->usb_ep.name);
+ epreq_xfer_error(ep, -EIO);
+ }
+
+ if (status)
+ dev_err(udc->dev, "%s: unknown status=0x%x\n",
+ ep->name, status);
+}
+
+static void irq_process_epin(struct snps_udc_ep *ep)
+{
+ struct snps_udc *udc = ep->udc;
+ struct ep_xfer_req *ep_req;
+ u32 status;
+
+ status = get_ep_status(udc->regs, ep->num, USB_DIR_IN);
+ clear_ep_status(udc->regs, ep->num, USB_DIR_IN, status);
+
+ if (!status)
+ return;
+
+ if (ep->dirn != USB_DIR_IN) {
+ dev_err(udc->dev, "%s: unexpected OUT endpoint\n", ep->name);
+ return;
+ }
+
+ if ((ep->type == USB_ENDPOINT_XFER_ISOC) &&
+ (status & (IN_XFER_DONE | DMA_BUF_NOT_AVAIL))) {
+ dev_warn(ep->udc->dev, "%s: ISOC IN unexpected status=0x%x\n",
+ ep->name, status);
+ }
+
+ if (status & IN_TOKEN_RX) {
+ status &= ~IN_TOKEN_RX;
+ if (!ep->dma.usb_req && list_empty(&ep->queue))
+ enable_ep_nak(udc->regs, ep->num, USB_DIR_IN);
+
+ if (ep->type == USB_ENDPOINT_XFER_ISOC) {
+ ep->dma.frame_num = get_frnum_last_rx(udc->regs);
+ dev_dbg(udc->dev, "%s: ISOC IN\n", ep->name);
+ if (ep->dma.usb_req) {
+ ep->dma.usb_req->status = -EREMOTEIO;
+ epreq_xfer_process(ep);
+ }
+ }
+ }
+
+ if (is_ep_bulk() && !list_empty(&ep->queue) &&
+ ep->in_xfer_done) {
+ ep->in_xfer_done = false;
+ ep_req = list_first_entry(&ep->queue,
+ struct ep_xfer_req, queue);
+ ep->dma.usb_req = &ep_req->usb_req;
+
+ ep_data_dma_init(ep);
+ ep_data_dma_add(ep);
+ clear_ep_nak(udc->regs, ep->num, ep->dirn);
+ enable_udc_ep_irq(udc->regs, ep->num, ep->dirn);
+ enable_ep_dma(udc->regs, ep->num, ep->dirn);
+ }
+
+ if (status & IN_DMA_DONE) {
+ status &= ~IN_DMA_DONE;
+ clear_ep_nak(udc->regs, ep->num, USB_DIR_IN);
+
+ if (ep->type == USB_ENDPOINT_XFER_ISOC) {
+ dev_dbg(udc->dev, "%s: ISOC IN\n", ep->usb_ep.name);
+ epreq_xfer_process(ep);
+ } else if (ep->dma.done & IN_XFER_DONE) {
+ dev_dbg(udc->dev, "%s: late IN DMA done rec'd\n",
+ ep->name);
+ epreq_xfer_process(ep);
+ } else {
+ ep->dma.done = IN_DMA_DONE;
+ }
+ }
+
+ if (status & IN_XFER_DONE) {
+ status &= ~(IN_XFER_DONE);
+ status &= ~(IN_FIFO_EMPTY);
+
+ if (ep->dma.done & IN_DMA_DONE)
+ epreq_xfer_process(ep);
+ else
+ ep->dma.done = IN_XFER_DONE;
+ }
+
+ status &= ~(IN_FIFO_EMPTY);
+
+ if (status & DMA_BUF_NOT_AVAIL) {
+ dev_err(udc->dev, "%s: DMA BUF NOT AVAIL\n", ep->name);
+ status &= ~(DMA_BUF_NOT_AVAIL);
+ epreq_xfer_process(ep);
+ }
+
+ if (status & DMA_ERROR) {
+ status &= ~DMA_ERROR;
+ dev_err(udc->dev, "%s: DMA ERROR\n", ep->name);
+ epreq_xfer_error(ep, -EIO);
+ }
+
+ if (status)
+ dev_err(udc->dev, "%s: unknown status=0x%x\n",
+ ep->name, status);
+}
+
+static void ep_irq_process(struct snps_udc *udc, u32 irq_in, u32 irq_out)
+{
+ u32 mask = 1;
+ u32 num;
+
+ for (num = 0; num < UDC_MAX_EP; num++) {
+ if (irq_in & mask)
+ irq_process_epin(&udc->ep[num]);
+
+ if (irq_out & mask)
+ irq_process_epout(&udc->ep[num]);
+
+ mask <<= 1;
+ }
+}
+
+static void irq_process_set_intf(struct snps_udc *udc)
+{
+ struct usb_ctrlrequest setup;
+ u32 ep_num;
+ u16 intf;
+ u16 alt;
+
+ intf = (uint16_t)get_intf_num(udc->regs);
+ alt = (uint16_t)get_alt_num(udc->regs);
+
+ setup.bRequestType = USB_DIR_OUT | USB_TYPE_STANDARD
+ | USB_RECIP_INTERFACE;
+ setup.bRequest = USB_REQ_SET_INTERFACE;
+ setup.wValue = cpu_to_le16(alt);
+ setup.wIndex = cpu_to_le16(intf);
+ setup.wLength = 0;
+
+ for (ep_num = 0; ep_num < UDC_MAX_EP; ep_num++) {
+ set_ep_alt_num(udc->regs, ep_num, alt);
+ set_ep_intf_num(udc->regs, ep_num, intf);
+ }
+ dev_info(udc->dev, "SET INTF=%d ALT=%d\n", intf, alt);
+
+ ep0_setup_rx(&udc->ep[0], &setup);
+ set_setup_done(udc->regs);
+}
+
+static void irq_process_set_cfg(struct snps_udc *udc)
+{
+ struct usb_ctrlrequest setup;
+ u32 ep_num;
+ u16 cfg;
+
+ cfg = (u16)get_cfg_num(udc->regs);
+
+ setup.bRequestType = USB_DIR_OUT | USB_TYPE_STANDARD
+ | USB_RECIP_DEVICE;
+ setup.bRequest = USB_REQ_SET_CONFIGURATION;
+ setup.wValue = cpu_to_le16(cfg);
+ setup.wIndex = 0;
+ setup.wLength = 0;
+
+ for (ep_num = 0; ep_num < UDC_MAX_EP; ep_num++)
+ set_epcfg_reg(udc->regs, ep_num, cfg);
+
+ dev_info(udc->dev, "SET CFG=%d\n", cfg);
+
+ ep0_setup_rx(&udc->ep[0], &setup);
+ set_setup_done(udc->regs);
+}
+
+static void irq_process_speed_enum(struct snps_udc *udc)
+{
+ u32 speed = udc->gadget.speed;
+
+ switch (get_enum_speed(udc->regs)) {
+ case SPEED_HIGH:
+ dev_info(udc->dev, "HIGH SPEED\n");
+ udc->gadget.speed = USB_SPEED_HIGH;
+ break;
+ case SPEED_FULL:
+ dev_info(udc->dev, "FULL SPEED\n");
+ udc->gadget.speed = USB_SPEED_FULL;
+ break;
+ case SPEED_LOW:
+ dev_warn(udc->dev, "LOW SPEED not supported\n");
+ udc->gadget.speed = USB_SPEED_LOW;
+ break;
+ default:
+ dev_err(udc->dev, "Unknown SPEED = 0x%x\n",
+ get_enum_speed(udc->regs));
+ break;
+ }
+
+ if ((speed == USB_SPEED_UNKNOWN) &&
+ (udc->gadget.speed != USB_SPEED_UNKNOWN)) {
+ ep0_setup_init(&udc->ep[0], 0);
+ clear_devnak(udc->regs);
+ }
+}
+
+static void irq_process_bus_idle(struct snps_udc *udc)
+{
+ int num;
+
+ for (num = 0; num < UDC_MAX_EP; num++) {
+ set_ep_fifo_flush(udc->regs, num, EP_DIRN_IN);
+ clear_ep_fifo_flush(udc->regs, num, EP_DIRN_IN);
+ }
+}
+
+static void dev_irq_process(struct snps_udc *udc, u32 irq)
+{
+ if (irq & IRQ_BUS_RESET)
+ dev_info(udc->dev, "BUS RESET\n");
+
+ if (irq & IRQ_BUS_SUSPEND)
+ dev_dbg(udc->dev, "BUS SUSPEND\n");
+
+ if (irq & IRQ_BUS_IDLE) {
+ dev_dbg(udc->dev, "BUS IDLE\n");
+ irq_process_bus_idle(udc);
+ }
+
+ if (irq & IRQ_SPEED_ENUM_DONE) {
+ dev_dbg(udc->dev, "BUS speed enum done\n");
+ irq_process_speed_enum(udc);
+ }
+
+ if (irq & IRQ_SET_CFG) {
+ dev_dbg(udc->dev, "SET CFG\n");
+ irq_process_set_cfg(udc);
+ }
+
+ if (irq & IRQ_SET_INTF) {
+ dev_dbg(udc->dev, "SET INTF\n");
+ irq_process_set_intf(udc);
+ }
+}
+
+static irqreturn_t snps_udc_irq(int irq, void *dev)
+{
+ struct snps_udc *udc = (struct snps_udc *)dev;
+ u32 devintr, epin_intr, epout_intr;
+ unsigned long flags;
+
+ spin_lock_irqsave(&udc->lock, flags);
+
+ devintr = get_irq_active(udc->regs);
+ epin_intr = get_ep_irq_active(udc->regs, USB_DIR_IN);
+ epout_intr = get_ep_irq_active(udc->regs, USB_DIR_OUT);
+
+ clear_udc_dev_irq(udc->regs, devintr);
+ clear_udc_ep_irq_list(udc->regs, USB_DIR_IN, epin_intr);
+ clear_udc_ep_irq_list(udc->regs, USB_DIR_OUT, epout_intr);
+
+ if (!udc->gadget_driver) {
+ spin_unlock_irqrestore(&udc->lock, flags);
+ return IRQ_NONE;
+ }
+
+ /* SET_CFG and SET_INTF interrupts are handled last */
+ dev_irq_process(udc, devintr & ~(IRQ_SET_CFG | IRQ_SET_INTF));
+ ep_irq_process(udc, epin_intr, epout_intr);
+ dev_irq_process(udc, devintr & (IRQ_SET_CFG | IRQ_SET_INTF));
+
+ spin_unlock_irqrestore(&udc->lock, flags);
+ dev_dbg(udc->dev, "UDC interrupts: Dev=0x%x EpIn=0x%x EpOut=0x%x\n",
+ devintr, epin_intr, epout_intr);
+
+ return IRQ_HANDLED;
+}
+
+static int snps_ep_enable(struct usb_ep *usb_ep,
+ const struct usb_endpoint_descriptor *desc)
+{
+ struct snps_udc_ep *ep;
+ struct snps_udc *udc;
+ unsigned long flags;
+ u32 max_pkt_size;
+ u32 xfertype;
+
+ ep = container_of(usb_ep, struct snps_udc_ep, usb_ep);
+ udc = ep->udc;
+
+ if (!usb_ep || (ep->b_ep_addr != desc->bEndpointAddress)) {
+ dev_err(udc->dev, "invalid endpoint (%p)\n", usb_ep);
+ return -EINVAL;
+ }
+
+ if (!desc || (desc->bDescriptorType != USB_DT_ENDPOINT)) {
+ dev_err(udc->dev, "ep%d: invalid descriptor=%p\n",
+ ep->num, desc);
+ return -EINVAL;
+ }
+
+ if (desc == ep->desc) {
+ dev_err(udc->dev, "ep%d: already enabled\n", ep->num);
+ return -EEXIST;
+ }
+
+ if (ep->desc) {
+ dev_err(udc->dev, "ep%d:already enabled wth other descr\n",
+ ep->num);
+ return -EBUSY;
+ }
+
+ if (!udc->gadget_driver) {
+ dev_warn(udc->dev, "%s: invalid device state\n", ep->name);
+ return -ESHUTDOWN;
+ }
+
+ xfertype = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
+ max_pkt_size = le16_to_cpu(desc->wMaxPacketSize) & 0x7FF;
+
+ if (!max_pkt_size || (max_pkt_size > ep->max_pkt_size)) {
+ dev_err(udc->dev, "%s: invalid max pkt size\n", ep->name);
+ return -ERANGE;
+ }
+
+ if ((ep->dirn == USB_DIR_IN) &&
+ (xfertype == USB_ENDPOINT_XFER_ISOC)) {
+ if ((desc->bInterval < 1) || (desc->bInterval > 16)) {
+ dev_err(udc->dev, "%s: invalid binterval\n", ep->name);
+ return -ERANGE;
+ }
+ ep->dma.frame_num = FRAME_NUM_INVALID;
+ ep->dma.frame_incr = 1 << (desc->bInterval - 1);
+ }
+
+ spin_lock_irqsave(&udc->lock, flags);
+
+ if (ep_cfg(ep, xfertype, max_pkt_size) != 0) {
+ spin_unlock_irqrestore(&udc->lock, flags);
+ dev_err(udc->dev, "%s: not enough FIFO space\n", ep->name);
+ return -ENOSPC;
+ }
+
+ set_epcfg_reg(udc->regs, ep->num, get_cfg_num(udc->regs));
+
+ ep->desc = desc;
+ ep->stopped = 0;
+ ep->usb_ep.maxpacket = max_pkt_size;
+
+ spin_unlock_irqrestore(&udc->lock, flags);
+
+ dev_dbg(udc->dev, "%s: enabled: type: 0x%x, max_pkt_size: %d\n",
+ ep->name, xfertype, max_pkt_size);
+
+ return 0;
+}
+
+static int snps_ep_disable(struct usb_ep *usb_ep)
+{
+ struct snps_udc_ep *ep;
+ struct snps_udc *udc;
+ unsigned long flags;
+
+ ep = container_of(usb_ep, struct snps_udc_ep, usb_ep);
+ udc = ep->udc;
+
+ if (!usb_ep || !ep->desc) {
+ dev_err(udc->dev, "%s: invalid endpoint\n", ep->usb_ep.name);
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&udc->lock, flags);
+
+ epreq_queue_flush(ep, -ESHUTDOWN);
+ ep->desc = NULL;
+ ep->usb_ep.maxpacket = ep->max_pkt_size;
+ fifo_ram_free(ep);
+
+ spin_unlock_irqrestore(&udc->lock, flags);
+
+ return 0;
+}
+
+static struct usb_request *
+snps_ep_alloc_request(struct usb_ep *usb_ep, gfp_t gfp_flags)
+{
+ struct ep_xfer_req *ep_req;
+
+ if (!usb_ep)
+ return NULL;
+
+ ep_req = kzalloc(sizeof(*ep_req), gfp_flags);
+ if (ep_req) {
+ INIT_LIST_HEAD(&ep_req->queue);
+ ep_req->usb_req.dma = DMA_ADDR_INVALID;
+ pr_debug("%s: ep alloc req\n", usb_ep->name);
+ return &ep_req->usb_req;
+ }
+
+ return NULL;
+}
+
+static void snps_ep_free_request(struct usb_ep *usb_ep,
+ struct usb_request *usb_req)
+{
+ struct ep_xfer_req *ep_req;
+
+ ep_req = container_of(usb_req, struct ep_xfer_req, usb_req);
+
+ if (usb_req) {
+ pr_debug("%s: freed\n", usb_ep->name);
+ kfree(ep_req);
+ }
+}
+
+static int snps_ep_queue(struct usb_ep *usb_ep,
+ struct usb_request *usb_req, gfp_t gfp_flags)
+{
+ struct ep_xfer_req *ep_req;
+ struct snps_udc_ep *ep;
+ struct snps_udc *udc;
+ unsigned long flags;
+
+ ep = container_of(usb_ep, struct snps_udc_ep, usb_ep);
+ ep_req = container_of(usb_req, struct ep_xfer_req, usb_req);
+
+ dev_dbg(ep->udc->dev, "%s: %s\n", __func__, ep->usb_ep.name);
+ if (!usb_ep || !usb_req || !ep_req->usb_req.complete ||
+ !ep_req->usb_req.buf || !list_empty(&ep_req->queue)) {
+ dev_dbg(ep->udc->dev, "%s:invalid queue request\n", ep->name);
+ return -EINVAL;
+ }
+
+ if (!ep->desc && (ep->num != 0)) {
+ dev_err(ep->udc->dev, "%s: invalid EP state\n", ep->name);
+ return -EFAULT;
+ }
+
+ if ((ep->type == USB_ENDPOINT_XFER_CONTROL) &&
+ !list_empty(&ep->queue)) {
+ dev_err(ep->udc->dev, "%s: EP queue not empty\n", ep->name);
+ return -EPERM;
+ }
+
+ if (usb_req->length > 0xffff) {
+ dev_err(ep->udc->dev, "%s: request too big\n", ep->name);
+ return -E2BIG;
+ }
+
+ if ((ep->type == USB_ENDPOINT_XFER_ISOC) &&
+ (ep->dirn == USB_DIR_IN) &&
+ (usb_req->length > ep->usb_ep.maxpacket)) {
+ dev_err(ep->udc->dev, "%s: request > scheduled bandwidth, length=%u\n",
+ ep->name, usb_req->length);
+ return -EFBIG;
+ }
+
+ udc = ep->udc;
+ if (!udc->gadget_driver) {
+ dev_err(udc->dev, "%s: invalid device state\n", ep->name);
+ return -ESHUTDOWN;
+ }
+
+ if (((unsigned long)ep_req->usb_req.buf) & 0x3UL) {
+ dev_dbg(udc->dev, "%s: invalid buffer alignment: addr=0x%p\n",
+ ep->usb_ep.name, ep_req->usb_req.buf);
+
+ if ((ep->dma.aligned_buf) &&
+ (ep->dma.aligned_len < ep_req->usb_req.length)) {
+ dma_free_coherent(NULL, ep->dma.aligned_len,
+ ep->dma.aligned_buf,
+ ep->dma.aligned_addr);
+ ep->dma.aligned_buf = NULL;
+ }
+
+ if (!ep->dma.aligned_buf) {
+ ep->dma.aligned_len = ep_req->usb_req.length;
+ ep->dma.aligned_buf = dma_alloc_coherent(NULL,
+ ep->dma.aligned_len, &ep->dma.aligned_addr,
+ GFP_ATOMIC);
+ }
+
+ if (!ep->dma.aligned_buf) {
+ dev_err(udc->dev, "%s: ep dma alloc failed\n",
+ ep->name);
+ return -ENOMEM;
+ }
+
+ ep_req->dma_aligned = 1;
+ } else if ((ep_req->usb_req.dma == DMA_ADDR_INVALID) ||
+ (ep_req->usb_req.dma == 0)) {
+ ep_req->dma_mapped = 1;
+ ep_req->usb_req.dma = dma_map_single(
+ ep->udc->gadget.dev.parent,
+ ep_req->usb_req.buf,
+ (ep_req->usb_req.length ?
+ ep_req->usb_req.length : 1),
+ (ep->dirn == USB_DIR_IN ? DMA_TO_DEVICE : DMA_FROM_DEVICE));
+ if (dma_mapping_error(ep->udc->gadget.dev.parent,
+ ep_req->usb_req.dma)) {
+ dev_err(ep->udc->gadget.dev.parent,
+ "failed to map buffer\n");
+ return -EFAULT;
+ }
+ }
+
+ spin_lock_irqsave(&udc->lock, flags);
+
+ ep_req->usb_req.status = -EINPROGRESS;
+ ep_req->usb_req.actual = 0;
+
+ if ((ep->type == USB_ENDPOINT_XFER_CONTROL) &&
+ (ep->dirn == USB_DIR_OUT) &&
+ (ep_req->usb_req.length == 0)) {
+ epreq_xfer_done(ep, ep_req, 0);
+ } else {
+ if (ep_req->usb_req.length == 0)
+ ep_req->usb_req.zero = 1;
+
+ epreq_xfer_add(ep, ep_req);
+ }
+
+ spin_unlock_irqrestore(&udc->lock, flags);
+
+ return 0;
+}
+
+static int snps_ep_dequeue(struct usb_ep *usb_ep,
+ struct usb_request *usb_req)
+{
+ struct ep_xfer_req *ep_req;
+ struct snps_udc_ep *ep;
+ unsigned long flags;
+
+ ep = container_of(usb_ep, struct snps_udc_ep, usb_ep);
+ ep_req = container_of(usb_req, struct ep_xfer_req, usb_req);
+
+ if (!usb_ep || !usb_req) {
+ dev_err(ep->udc->dev, "%s: invalid dequeue request\n",
+ ep->name);
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&ep->udc->lock, flags);
+
+ list_for_each_entry(ep_req, &ep->queue, queue) {
+ if (&ep_req->usb_req == usb_req)
+ break;
+ }
+
+ if (&ep_req->usb_req != usb_req) {
+ spin_unlock_irqrestore(&ep->udc->lock, flags);
+ dev_err(ep->udc->dev, "%s: request not queued\n", ep->name);
+ return -ENOLINK;
+ }
+
+ epreq_xfer_done(ep, ep_req, -ECONNRESET);
+ spin_unlock_irqrestore(&ep->udc->lock, flags);
+
+ dev_dbg(ep->udc->dev, "%s: req=0x%p\n", ep->name, usb_req);
+ return 0;
+}
+
+static int snps_ep_set_halt(struct usb_ep *usb_ep, int halt)
+{
+ struct snps_udc_ep *ep;
+ unsigned long flags;
+ struct snps_udc *udc;
+
+ ep = container_of(usb_ep, struct snps_udc_ep, usb_ep);
+ udc = ep->udc;
+ if (!usb_ep) {
+ dev_err(udc->dev, "%s: invalid halt request\n", ep->name);
+ return -EINVAL;
+ }
+
+ if (ep->type == USB_ENDPOINT_XFER_ISOC) {
+ dev_err(udc->dev, "%s: unsupported halt req\n", ep->name);
+ return -EOPNOTSUPP;
+ }
+
+ if (halt && (ep->dirn == USB_DIR_IN) &&
+ !list_empty(&ep->queue)) {
+ dev_err(udc->dev, "%s: EP IN queue not empty\n", ep->name);
+ return -EAGAIN;
+ }
+
+ if (!halt && (ep->type == USB_ENDPOINT_XFER_CONTROL)) {
+ dev_err(udc->dev, "%s: CTRL HALT clear\n", ep->name);
+ return -EPROTO;
+ }
+
+ spin_lock_irqsave(&ep->udc->lock, flags);
+
+ if (!halt) {
+ disable_ep_stall(udc->regs, ep->num, ep->dirn);
+ } else if (ep->type != USB_ENDPOINT_XFER_CONTROL) {
+ enable_ep_stall(udc->regs, ep->num, ep->dirn);
+ } else {
+ enable_ep_stall(udc->regs, ep->num, USB_DIR_IN);
+ enable_ep_stall(udc->regs, ep->num, USB_DIR_OUT);
+ }
+
+ spin_unlock_irqrestore(&ep->udc->lock, flags);
+
+ dev_dbg(udc->dev, "%s: HALT %s done\n", ep->name,
+ halt ? "SET" : "CLR");
+
+ return 0;
+}
+
+static struct usb_ep_ops snps_ep_ops = {
+ .enable = snps_ep_enable,
+ .disable = snps_ep_disable,
+
+ .alloc_request = snps_ep_alloc_request,
+ .free_request = snps_ep_free_request,
+
+ .queue = snps_ep_queue,
+ .dequeue = snps_ep_dequeue,
+
+ .set_halt = snps_ep_set_halt,
+};
+
+static int eps_init(struct snps_udc *udc)
+{
+ struct snps_udc_ep *ep;
+ int i, ret;
+
+ /* Initialize Endpoint 0 */
+ ep = &udc->ep[0];
+ ep->udc = udc;
+ ep->num = 0;
+ ep->in_xfer_done = true;
+ ep->dirn = USB_DIR_OUT;
+ ep->b_ep_addr = ep->num | ep->dirn;
+ strncpy(ep->name, "ep0", sizeof(ep->name));
+ ep->usb_ep.name = ep->name;
+ ep->max_pkt_size = EP_CTRL_MAX_PKT_SIZE;
+ usb_ep_set_maxpacket_limit(&ep->usb_ep, EP_CTRL_MAX_PKT_SIZE);
+ ep->usb_ep.ops = &snps_ep_ops;
+ ep->stopped = 0;
+ ep->usb_ep.caps.type_control = true;
+ ep->usb_ep.caps.dir_in = true;
+ ep->usb_ep.caps.dir_out = true;
+ INIT_LIST_HEAD(&ep->queue);
+ ep->type = USB_ENDPOINT_XFER_CONTROL;
+ ep->usb_ep.maxpacket = EP_CTRL_MAX_PKT_SIZE;
+
+ if (udc->conn_type)
+ ep_dma_init(ep);
+
+ dev_dbg(udc->dev, "%s: type: 0x%x, Dir:0x%x, Max Size: %d\n",
+ ep->name, ep->type, ep->dirn, ep->max_pkt_size);
+
+ /* Initialize remaining endpoints */
+ for (i = 1; i < UDC_MAX_EP; i++) {
+ ep = &udc->ep[i];
+ ep->udc = udc;
+ ep->max_pkt_size = EP_MAX_PKT_SIZE;
+ usb_ep_set_maxpacket_limit(&ep->usb_ep, EP_MAX_PKT_SIZE);
+ ep->usb_ep.ops = &snps_ep_ops;
+ ep->in_xfer_done = true;
+ ep->num = i;
+ if (i % 2) {
+ snprintf(ep->name, sizeof(ep->name), "ep%din", i);
+ ep->dirn = EP_DIRN_IN;
+ ep->usb_ep.caps.dir_in = true;
+ } else {
+ snprintf(ep->name, sizeof(ep->name), "ep%dout", i);
+ ep->dirn = EP_DIRN_OUT;
+ ep->usb_ep.caps.dir_out = true;
+ }
+ ep->usb_ep.name = ep->name;
+ ep->b_ep_addr = ep->num | ep->dirn;
+
+ ep->usb_ep.caps.type_iso = true;
+ ep->usb_ep.caps.type_bulk = true;
+ ep->usb_ep.caps.type_int = true;
+ ep->stopped = 0;
+ ep->usb_ep.maxpacket = EP_MAX_PKT_SIZE;
+
+ INIT_LIST_HEAD(&ep->queue);
+ if (udc->conn_type)
+ ep_dma_init(ep);
+
+ dev_dbg(udc->dev, "%s: type: 0x%x, Dir: 0x%x, Max Size: %d\n",
+ ep->name, ep->type, ep->dirn, ep->max_pkt_size);
+ }
+
+ udc->rx_fifo_space = OUT_RX_FIFO_MEM_SIZE;
+ udc->tx_fifo_space = IN_TX_FIFO_MEM_SIZE;
+ ret = ep_cfg(&udc->ep[0], USB_ENDPOINT_XFER_CONTROL,
+ EP_CTRL_MAX_PKT_SIZE);
+ if (ret) {
+ dev_err(udc->dev, "Synopsys-UDC: error configuring endpoints\n");
+ return ret;
+ }
+
+ dev_dbg(udc->dev, "Synopsys UDC Endpoints initialized\n");
+ return 0;
+}
+
+static void start_udc(struct snps_udc *udc)
+{
+ int i;
+
+ init_udc_reg(udc->regs);
+
+ udc->rx_fifo_space = OUT_RX_FIFO_MEM_SIZE;
+ udc->tx_fifo_space = IN_TX_FIFO_MEM_SIZE;
+
+ eps_init(udc);
+ enable_self_pwr(udc->regs);
+
+ enable_udc_dev_irq(udc->regs, IRQ_SPEED_ENUM_DONE | IRQ_BUS_SUSPEND |
+ IRQ_BUS_IDLE | IRQ_BUS_RESET | IRQ_SET_INTF |
+ IRQ_SET_CFG);
+
+ for (i = 0; i < UDC_MAX_EP; ++i) {
+ if (udc->ep[i].usb_ep.name) {
+ enable_udc_ep_irq(udc->regs,
+ udc->ep[i].num, USB_DIR_OUT);
+ enable_udc_ep_irq(udc->regs,
+ udc->ep[i].num, USB_DIR_IN);
+ }
+ }
+
+ clear_devnak(udc->regs);
+ enable_ctrl_dma(udc->regs);
+ bus_connect(udc->regs);
+
+ dev_dbg(udc->dev, "Synopsys UDC started\n");
+}
+
+static void stop_udc(struct snps_udc *udc)
+{
+ finish_udc(udc->regs);
+
+ udc->gadget.speed = USB_SPEED_UNKNOWN;
+ epreq_queue_flush(&udc->ep[0], -ESHUTDOWN);
+ udc->ep[0].desc = NULL;
+
+ bus_disconnect(udc->regs);
+
+ if (udc->gadget_driver && udc->gadget_driver->disconnect) {
+ spin_unlock(&udc->lock);
+ udc->gadget_driver->disconnect(&udc->gadget);
+ spin_lock(&udc->lock);
+ }
+
+ dev_dbg(udc->dev, "Synopsys UDC stopped\n");
+}
+
+static int snps_gadget_pullup(struct usb_gadget *gadget, int is_on)
+{
+ struct snps_udc *udc;
+ unsigned long flags;
+
+ udc = container_of(gadget, struct snps_udc, gadget);
+
+ spin_lock_irqsave(&udc->lock, flags);
+
+ if (!udc->gadget_driver) {
+ spin_unlock_irqrestore(&udc->lock, flags);
+ return 0;
+ }
+
+ if (is_on && udc->pullup_on) {
+ start_udc(udc);
+ udc->ep[0].stopped = 0;
+ dev_info(udc->dev, "Synopsys UDC device connected\n");
+ } else if (!is_on && !udc->pullup_on) {
+ stop_udc(udc);
+ udc->ep[0].stopped = 1;
+ dev_info(udc->dev, "Synopsys UDC device Disconnected\n");
+ }
+
+ spin_unlock_irqrestore(&udc->lock, flags);
+
+ return 0;
+}
+
+static int snps_gadget_start(struct usb_gadget *gadget,
+ struct usb_gadget_driver *driver)
+{
+ struct snps_udc *udc;
+ unsigned long flags;
+
+ udc = container_of(gadget, struct snps_udc, gadget);
+
+ if (udc->gadget_driver)
+ return -EBUSY;
+
+ spin_lock_irqsave(&udc->lock, flags);
+
+ driver->driver.bus = NULL;
+ udc->gadget_driver = driver;
+ udc->gadget.dev.driver = &driver->driver;
+ udc->ep[0].stopped = 0;
+
+ spin_unlock_irqrestore(&udc->lock, flags);
+
+ /* when cable is connected at boot time */
+ if (udc->conn_type)
+ schedule_delayed_work(&udc->drd_work, USBD_WQ_DELAY_MS);
+ dev_dbg(udc->dev, "%s: Done\n", __func__);
+
+ return 0;
+}
+
+static int snps_gadget_stop(struct usb_gadget *gadget)
+{
+ struct snps_udc_ep *ep;
+ struct snps_udc *udc;
+ unsigned long flags;
+
+ udc = container_of(gadget, struct snps_udc, gadget);
+
+ spin_lock_irqsave(&udc->lock, flags);
+ stop_udc(udc);
+ udc->gadget.dev.driver = NULL;
+ udc->gadget_driver = NULL;
+
+ list_for_each_entry(ep, &udc->gadget.ep_list, usb_ep.ep_list) {
+ epreq_queue_flush(ep, -ESHUTDOWN);
+ if (ep->desc)
+ ep->desc = NULL;
+ }
+
+ spin_unlock_irqrestore(&udc->lock, flags);
+
+ dev_dbg(udc->dev, "%s: Done\n", __func__);
+
+ return 0;
+}
+
+static struct usb_gadget_ops snps_gadget_ops = {
+ .pullup = snps_gadget_pullup,
+ .udc_start = snps_gadget_start,
+ .udc_stop = snps_gadget_stop,
+};
+
+void snps_udc_drd_work(struct work_struct *work)
+{
+ struct snps_udc *udc;
+
+ udc = container_of(to_delayed_work(work),
+ struct snps_udc, drd_work);
+
+ if (udc->conn_type) {
+ dev_dbg(udc->dev, "idle -> device\n");
+ if (udc->gadget_driver) {
+ udc->pullup_on = 1;
+ snps_gadget_pullup(&udc->gadget, 1);
+ }
+ } else {
+ dev_dbg(udc->dev, "device -> idle\n");
+ udc->pullup_on = 0;
+ snps_gadget_pullup(&udc->gadget, 0);
+ }
+}
+
+static int usbd_connect_notify(struct notifier_block *self,
+ unsigned long event, void *ptr)
+{
+ struct snps_udc *udc = container_of(self, struct snps_udc, nb);
+
+ dev_dbg(udc->dev, "%s: event: %lu\n", __func__, event);
+
+ udc->conn_type = event;
+
+ schedule_delayed_work(&udc->drd_work, USBD_WQ_DELAY_MS);
+
+ return NOTIFY_OK;
+}
+
+static void free_udc_dma(struct platform_device *pdev, struct snps_udc *udc)
+{
+ u32 num;
+
+ dma_free_coherent(&pdev->dev, sizeof(struct ep_desc_array),
+ udc->dma.virt, (dma_addr_t)udc->dma.phys);
+
+ for (num = 0; num < UDC_MAX_EP; num++) {
+ if (udc->ep[num].dma.aligned_buf) {
+ dma_free_coherent(NULL, udc->ep[num].dma.aligned_len,
+ udc->ep[num].dma.aligned_buf,
+ udc->ep[num].dma.aligned_addr);
+ udc->ep[num].dma.aligned_buf = NULL;
+ }
+ }
+}
+
+static int snps_udc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *res;
+ struct snps_udc *udc;
+ int i, ret;
+
+ udc = devm_kzalloc(dev, sizeof(*udc), GFP_KERNEL);
+ if (!udc)
+ return -ENOMEM;
+
+ spin_lock_init(&udc->lock);
+ udc->dev = dev;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ udc->regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(udc->regs))
+ return PTR_ERR(udc->regs);
+
+ udc->irq = irq_of_parse_and_map(dev->of_node, 0);
+ if (udc->irq <= 0) {
+ dev_err(dev, "Can't parse and map interrupt\n");
+ return -EINVAL;
+ }
+
+ udc->udc_phy = devm_phy_get(dev, "usb2drd");
+ if (IS_ERR(udc->udc_phy)) {
+ dev_err(dev, "Failed to obtain phy from device tree\n");
+ return PTR_ERR(udc->udc_phy);
+ }
+
+ ret = phy_init(udc->udc_phy);
+ if (ret) {
+ dev_err(dev, "UDC phy init failed");
+ return ret;
+ }
+
+ ret = phy_power_on(udc->udc_phy);
+ if (ret) {
+ dev_err(dev, "UDC phy power on failed");
+ phy_exit(udc->udc_phy);
+ return ret;
+ }
+
+ udc->edev = extcon_get_edev_by_phandle(dev, 0);
+ if (IS_ERR(udc->edev)) {
+ if (PTR_ERR(udc->edev) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+ dev_err(dev, "Invalid or missing extcon\n");
+ ret = PTR_ERR(udc->edev);
+ goto exit_phy;
+ }
+
+ udc->nb.notifier_call = usbd_connect_notify;
+ ret = extcon_register_notifier(udc->edev, EXTCON_USB, &udc->nb);
+ if (ret < 0) {
+ dev_err(dev, "Can't register extcon device\n");
+ goto exit_phy;
+ }
+
+ ret = extcon_get_cable_state_(udc->edev, EXTCON_USB);
+ if (ret < 0) {
+ dev_err(dev, "Can't get cable state\n");
+ goto exit_extcon;
+ } else if (ret) {
+ udc->conn_type = ret;
+ }
+
+ udc->dma.virt = dma_alloc_coherent(&pdev->dev,
+ sizeof(struct ep_desc_array),
+ (dma_addr_t *)&udc->dma.phys,
+ GFP_KERNEL);
+ if (!udc->dma.virt) {
+ dev_err(dev, "Failed to allocate memory for ep\n");
+ ret = -ENOMEM;
+ goto exit_extcon;
+ }
+
+ INIT_DELAYED_WORK(&udc->drd_work, snps_udc_drd_work);
+
+ ret = devm_request_irq(dev, udc->irq, snps_udc_irq, IRQF_SHARED,
+ "snps-udc", udc);
+ if (ret < 0) {
+ dev_err(dev, "Request irq %d failed for UDC\n", udc->irq);
+ goto exit_dma;
+ }
+
+ /* Gagdet structure init */
+ udc->gadget.name = "snps-udc";
+ udc->gadget.speed = USB_SPEED_UNKNOWN;
+ udc->gadget.max_speed = USB_SPEED_HIGH;
+ udc->gadget.ops = &snps_gadget_ops;
+ udc->gadget.ep0 = &udc->ep[0].usb_ep;
+ INIT_LIST_HEAD(&udc->gadget.ep_list);
+
+ eps_init(udc);
+ for (i = 1; i < UDC_MAX_EP; i++) {
+ list_add_tail(&udc->ep[i].usb_ep.ep_list,
+ &udc->gadget.ep_list);
+ }
+
+ ret = usb_add_gadget_udc(&pdev->dev, &udc->gadget);
+ if (ret) {
+ dev_err(dev, "Error adding gadget udc: %d\n", ret);
+ goto exit_dma;
+ }
+
+ platform_set_drvdata(pdev, udc);
+ dev_info(dev, "Synopsys UDC driver probe successful\n");
+
+ return 0;
+exit_dma:
+ free_udc_dma(pdev, udc);
+exit_extcon:
+ extcon_unregister_notifier(udc->edev, EXTCON_USB, &udc->nb);
+exit_phy:
+ phy_power_off(udc->udc_phy);
+ phy_exit(udc->udc_phy);
+
+ return ret;
+}
+
+static int snps_udc_remove(struct platform_device *pdev)
+{
+ struct snps_udc *udc;
+
+ udc = platform_get_drvdata(pdev);
+
+ usb_del_gadget_udc(&udc->gadget);
+
+ platform_set_drvdata(pdev, NULL);
+
+ if (udc->drd_wq) {
+ flush_workqueue(udc->drd_wq);
+ destroy_workqueue(udc->drd_wq);
+ }
+
+ free_udc_dma(pdev, udc);
+ phy_power_off(udc->udc_phy);
+ phy_exit(udc->udc_phy);
+ extcon_unregister_notifier(udc->edev, EXTCON_USB, &udc->nb);
+
+ dev_info(&pdev->dev, "Synopsys UDC driver removed\n");
+
+ return 0;
+}
+
+static void snps_udc_shutdown(struct platform_device *pdev)
+{
+ struct snps_udc *udc = platform_get_drvdata(pdev);
+
+ snps_gadget_stop(&udc->gadget);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int snps_udc_suspend(struct device *dev)
+{
+ struct snps_udc *udc;
+
+ udc = dev_get_drvdata(dev);
+
+ if (extcon_get_cable_state_(udc->edev, EXTCON_USB) > 0) {
+ dev_dbg(udc->dev, "device -> idle\n");
+ snps_gadget_pullup(&udc->gadget, 0);
+ }
+ phy_power_off(udc->udc_phy);
+ phy_exit(udc->udc_phy);
+
+ return 0;
+}
+
+static int snps_udc_resume(struct device *dev)
+{
+ struct snps_udc *udc;
+ int ret;
+
+ udc = dev_get_drvdata(dev);
+
+ ret = phy_init(udc->udc_phy);
+ if (ret) {
+ dev_err(udc->dev, "UDC phy init failure");
+ return ret;
+ }
+
+ ret = phy_power_on(udc->udc_phy);
+ if (ret) {
+ dev_err(udc->dev, "UDC phy power on failure");
+ phy_exit(udc->udc_phy);
+ return ret;
+ }
+
+ if (extcon_get_cable_state_(udc->edev, EXTCON_USB) > 0) {
+ dev_dbg(udc->dev, "idle -> device\n");
+ snps_gadget_pullup(&udc->gadget, 1);
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops snps_udc_pm_ops = {
+ .suspend = snps_udc_suspend,
+ .resume = snps_udc_resume,
+};
+#endif
+
+static const struct of_device_id of_udc_match[] = {
+ { .compatible = "snps,dw-ahb-udc", },
+ { }
+};
+
+MODULE_DEVICE_TABLE(of, of_udc_match);
+
+static struct platform_driver snps_udc_driver = {
+ .probe = snps_udc_probe,
+ .remove = snps_udc_remove,
+ .shutdown = snps_udc_shutdown,
+ .driver = {
+ .name = "snps-udc",
+ .of_match_table = of_match_ptr(of_udc_match),
+#ifdef CONFIG_PM_SLEEP
+ .pm = &snps_udc_pm_ops,
+#endif
+ },
+};
+
+module_platform_driver(snps_udc_driver);
+
+MODULE_ALIAS("platform:snps-udc");
+MODULE_AUTHOR("Broadcom");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/gadget/udc/snps_udc.h b/drivers/usb/gadget/udc/snps_udc.h
new file mode 100644
index 0000000..0355d59
--- /dev/null
+++ b/drivers/usb/gadget/udc/snps_udc.h
@@ -0,0 +1,1071 @@
+/*
+ * Copyright (C) 2016 Broadcom
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __SNPS_UDC_H
+#define __SNPS_UDC_H
+
+/* UDC speeds */
+#define SPEED_UNKNOWN (0)
+#define SPEED_LOW (1)
+#define SPEED_FULL (2)
+#define SPEED_HIGH (3)
+
+/* Endpoint directions */
+#define EP_DIRN_IN (0x80)
+#define EP_DIRN_OUT (0x00)
+#define EP_DIRN_MASK (0x80)
+
+/* Endpoint types */
+#define EP_TYPE_CTRL (0)
+#define EP_TYPE_ISOC (1)
+#define EP_TYPE_BULK (2)
+#define EP_TYPE_INTR (3)
+#define EP_TYPE_MASK (0x03)
+
+/* Max supported endpoints */
+#define UDC_MAX_EP (10)
+
+#define EP_MAX_PKT_SIZE 512
+#define EP_CTRL_MAX_PKT_SIZE 64
+#define OUT_RX_FIFO_MEM_SIZE 4096
+#define IN_TX_FIFO_MEM_SIZE 4096
+
+#define is_ep_in() ((ep->dirn) == USB_DIR_IN)
+#define is_ep_out() ((ep->dirn) == USB_DIR_OUT)
+#define is_ep_bulk() ((ep->type) == USB_ENDPOINT_XFER_BULK)
+
+#define DESC_CNT (1)
+
+#define EP_DMA_DESC_IDX_MASK (DESC_CNT - 1)
+#define EP_DMA_DESC_IDX(num) ((num) & EP_DMA_DESC_IDX_MASK)
+
+#define USB_MODE_IDLE (1)
+#define USB_MODE_DEVICE (2)
+
+#define FIFO_SZ_U32(pkt_sz) (((pkt_sz) + 3) / sizeof(u32))
+#define FIFO_SZ_U8(sz) (FIFO_SZ_U32(sz) * sizeof(u32))
+#define USBD_WQ_DELAY_MS msecs_to_jiffies(100)
+/* Register Masks and definitions */
+
+/* Endpoint Control Registers*/
+#define EP_CTRL_OUT_FLUSH_ENABLE BIT(12)
+#define EP_CTRL_OUT_CLOSE_DESC BIT(11)
+#define EP_CTRL_IN_SEND_NULL BIT(10)
+#define EP_CTRL_OUT_DMA_ENABLE BIT(9)
+#define EP_CTRL_NAK_CLEAR BIT(8)
+#define EP_CTRL_NAK_SET BIT(7)
+#define EP_CTRL_NAK_IN_PROGRESS BIT(6)
+#define EP_CTRL_TYPE_SHIFT (4)
+#define EP_CTRL_TYPE_MASK (3 << EP_CTRL_TYPE_SHIFT)
+#define EP_CTRL_IN_DMA_ENABLE BIT(3)
+#define EP_CTRL_SNOOP_ENABLE BIT(2)
+#define EP_CTRL_IN_FLUSH_ENABLE BIT(1)
+#define EP_CTRL_STALL_ENABLE BIT(0)
+
+/* Endpoint Status Registers */
+#define EP_STS_CLOSE_DESC_CLEAR BIT(28)
+#define EP_STS_IN_XFER_DONE BIT(27)
+#define EP_STS_STALL_SET_RX BIT(26)
+#define EP_STS_STALL_CLEAR_RX BIT(25)
+#define EP_STS_IN_FIFO_EMPTY BIT(24)
+#define EP_STS_IN_DMA_DONE BIT(10)
+#define EP_STS_AHB_BUS_ERROR BIT(9)
+#define EP_STS_OUT_FIFO_EMPTY BIT(8)
+#define EP_STS_DMA_BUF_NOT_AVAIL BIT(7)
+#define EP_STS_IN_TOKEN_RX BIT(6)
+#define EP_STS_OUT_DMA_SETUP_DONE BIT(5)
+#define EP_STS_OUT_DMA_DATA_DONE BIT(4)
+
+/* Buffer Regs for EP In, Receive Packet Frame Num Regs for EP Out */
+#define EP_REG2_OUT_ISOC_PID_SHIFT (16)
+#define EP_REG2_OUT_ISOC_PID_MASK (3 << EP_REG2_OUT_ISOC_PID_SHIFT)
+#define EP_REG2_IN_DEPTH_SHIFT (0)
+#define EP_REG2_IN_DEPTH_MASK (0xffff << EP_REG2_IN_DEPTH_SHIFT)
+#define EP_REG2_OUT_FRAME_NUM_SHIFT EP_REG2_IN_DEPTH_SHIFT
+#define EP_REG2_OUT_FRAME_NUM_MASK EP_REG2_IN_DEPTH_MASK
+
+/* Max Packet Size Regs for EP In, Buffer Size Regs for EP Out */
+#define EP_REG3_OUT_DEPTH_SHIFT (16)
+#define EP_REG3_OUT_DEPTH_MASK (0xffff << EP_REG3_OUT_DEPTH_SHIFT)
+#define EP_REG3_PKT_MAX_SHIFT (0)
+#define EP_REG3_PKT_MAX_MASK (0xffff << EP_REG3_PKT_MAX_SHIFT)
+
+/* Endpoint Config Registers */
+#define EP_CFG_DIRN_IN BIT(4)
+#define EP_CFG_DIRN_OUT (0)
+#define EP_CFG_PKT_MAX_SHIFT (19)
+#define EP_CFG_PKT_MAX_MASK (0x7ff << EP_CFG_PKT_MAX_SHIFT)
+#define EP_CFG_ALT_NUM_SHIFT (15)
+#define EP_CFG_ALT_NUM_MASK (0xf << EP_CFG_ALT_NUM_SHIFT)
+#define EP_CFG_INTF_NUM_SHIFT (11)
+#define EP_CFG_INTF_NUM_MASK (0xf << EP_CFG_INTF_NUM_SHIFT)
+#define EP_CFG_CFG_NUM_SHIFT (7)
+#define EP_CFG_CFG_NUM_MASK (0xf << EP_CFG_CFG_NUM_SHIFT)
+#define EP_CFG_TYPE_SHIFT (5)
+#define EP_CFG_TYPE_MASK (0x3 << EP_CFG_TYPE_SHIFT)
+#define EP_CFG_FIFO_NUM_SHIFT (0)
+#define EP_CFG_FIFO_NUM_MASK (0xf << EP_CFG_FIFO_NUM_SHIFT)
+
+/* Endpoint Interrupt Registers */
+#define EP_INTR_OUT_SHIFT (16)
+#define EP_INTR_OUT_MASK (0xffff << EP_INTR_OUT_SHIFT)
+#define EP_INTR_IN_SHIFT (0)
+#define EP_INTR_IN_MASK (0xffff << EP_INTR_IN_SHIFT)
+
+/* Device Config Register */
+#define CFG_ULPI_DDR_ENABLE BIT(19)
+#define CFG_SET_DESCRIPTOR_ENABLE BIT(18)
+#define CFG_CSR_PROGRAM_ENABLE BIT(17)
+#define CFG_HALT_STALL_ENABLE BIT(16)
+#define CFG_HS_TIMEOUT_CALIB_SHIFT (13)
+#define CFG_HS_TIMEOUT_CALIB_MASK (7 << CFG_HS_TIMEOUT_CALIB_SHIFT)
+#define CFG_FS_TIMEOUT_CALIB_SHIFT (10)
+#define CFG_FS_TIMEOUT_CALIB_MASK (7 << CFG_FS_TIMEOUT_CALIB_SHIFT)
+#define CFG_STS_1_ENABLE BIT(8)
+#define CFG_STS_ENABLE BIT(7)
+#define CFG_UTMI_BI_DIRN_ENABLE BIT(6)
+#define CFG_UTMI_8BIT_ENABLE BIT(5)
+#define CFG_SYNC_FRAME_ENABLE BIT(4)
+#define CFG_SELF_PWR_ENABLE BIT(3)
+#define CFG_REMOTE_WAKEUP_ENABLE BIT(2)
+#define CFG_SPD_SHIFT (0)
+#define CFG_SPD_MASK (3 << CFG_SPD_SHIFT)
+#define CFG_SPD_HS (0 << CFG_SPD_SHIFT)
+#define CFG_SPD_FS BIT(0)
+#define CFG_SPD_LS (2 << CFG_SPD_SHIFT)
+#define CFG_SPD_FS_48MHZ (3 << CFG_SPD_SHIFT)
+
+/* Device Control Register*/
+#define CTRL_DMA_OUT_THRESH_LEN_SHIFT (24)
+#define CTRL_DMA_OUT_THRESH_LEN_MASK (0xff << CTRL_DMA_OUT_THRESH_LEN_SHIFT)
+#define CTRL_DMA_BURST_LEN_SHIFT (16)
+#define CTRL_DMA_BURST_LEN_MASK (0xff << CTRL_DMA_BURST_LEN_SHIFT)
+#define CTRL_OUT_FIFO_FLUSH_ENABLE BIT(14)
+#define CTRL_CSR_DONE BIT(13)
+#define CTRL_OUT_ALL_NAK BIT(12)
+#define CTRL_DISCONNECT_ENABLE BIT(10)
+#define CTRL_DMA_MODE_ENABLE BIT(9)
+#define CTRL_DMA_BURST_ENABLE BIT(8)
+#define CTRL_DMA_OUT_THRESH_ENABLE BIT(7)
+#define CTRL_DMA_BUFF_FILL_MODE_ENABLE BIT(6)
+#define CTRL_ENDIAN_BIG_ENABLE BIT(5)
+#define CTRL_DMA_DESC_UPDATE_ENABLE BIT(4)
+#define CTRL_DMA_IN_ENABLE BIT(3)
+#define CTRL_DMA_OUT_ENABLE BIT(2)
+#define CTRL_RESUME_SIGNAL_ENABLE BIT(0)
+#define CTRL_LE_ENABLE (0)
+
+/* Device Status Register */
+#define STS_SOF_FRAME_NUM_SHIFT (18)
+#define STS_SOF_FRAME_NUM_MASK (0x3ffff << STS_SOF_FRAME_NUM_SHIFT)
+#define STS_REMOTE_WAKEUP_ALLOWED BIT(17)
+#define STS_PHY_ERROR BIT(16)
+#define STS_OUT_FIFO_EMPTY BIT(15)
+#define STS_SPD_SHIFT (13)
+#define STS_SPD_MASK (3 << STS_SPD_SHIFT)
+#define STS_SPD_HS (0 << STS_SPD_SHIFT)
+#define STS_SPD_FS BIT(13)
+#define STS_SPD_LS (2 << STS_SPD_SHIFT)
+#define STS_SPD_FS_48MHZ (3 << STS_SPD_SHIFT)
+#define STS_BUS_SUSPENDED BIT(12)
+#define STS_ALT_NUM_SHIFT (8)
+#define STS_ALT_NUM_MASK (0xf << STS_SPD_SHIFT)
+#define STS_INTF_NUM_SHIFT (4)
+#define STS_INTF_NUM_MASK (0xf << STS_INTF_NUM_SHIFT)
+#define STS_CFG_NUM_SHIFT (0)
+#define STS_CFG_NUM_MASK (0xf << STS_CFG_NUM_SHIFT)
+
+/* Device Interrupt Register */
+#define INTR_REMOTE_WAKEUP_DELTA BIT(7)
+#define INTR_SPD_ENUM_DONE BIT(6)
+#define INTR_SOF_RX BIT(5)
+#define INTR_BUS_SUSPEND BIT(4)
+#define INTR_BUS_RESET BIT(3)
+#define INTR_BUS_IDLE BIT(2)
+#define INTR_SET_INTF_RX BIT(1)
+#define INTR_SET_CFG_RX BIT(0)
+
+#define DMA_STS_BUF_SHIFT (30)
+#define DMA_STS_BUF_HOST_READY (0 << DMA_STS_BUF_SHIFT)
+#define DMA_STS_BUF_DMA_BUSY BIT(30)
+#define DMA_STS_BUF_DMA_DONE (2 << DMA_STS_BUF_SHIFT)
+#define DMA_STS_BUF_HOST_BUSY (3 << DMA_STS_BUF_SHIFT)
+#define DMA_STS_BUF_MASK (3 << DMA_STS_BUF_SHIFT)
+#define DMA_STS_RX_SHIFT (28)
+#define DMA_STS_RX_SUCCESS (0 << DMA_STS_RX_SHIFT)
+#define DMA_STS_RX_ERR_DESC BIT(28)
+#define DMA_STS_RX_ERR_BUF (3 << DMA_STS_RX_SHIFT)
+#define DMA_STS_RX_MASK (3 << DMA_STS_RX_SHIFT)
+#define DMA_STS_CFG_NUM_SHIFT (24)
+#define DMA_STS_CFG_NUM_MASK (0xf << DMA_STS_CFG_NUM_SHIFT)
+#define DMA_STS_INTF_NUM_SHIFT (20)
+#define DMA_STS_INTF_NUM_MASK (0xf << DMA_STS_INTF_NUM_SHIFT)
+#define DMA_STS_LAST_DESC BIT(27)
+#define DMA_STS_FRAME_NUM_SHIFT (16)
+#define DMA_STS_FRAME_NUM_MASK (0x7ff << DMA_STS_FRAME_NUM_SHIFT)
+#define DMA_STS_BYTE_CNT_SHIFT (0)
+#define DMA_STS_ISO_PID_SHIFT (14)
+#define DMA_STS_ISO_PID_MASK (0x3 << DMA_STS_ISO_PID_SHIFT)
+#define DMA_STS_ISO_BYTE_CNT_SHIFT (DMA_STS_BYTE_CNT_SHIFT)
+#define DMA_STS_ISO_BYTE_CNT_MASK (0x3fff << DMA_STS_ISO_BYTE_CNT_SHIFT)
+#define DMA_STS_NISO_BYTE_CNT_SHIFT (DMA_STS_BYTE_CNT_SHIFT)
+#define DMA_STS_NISO_BYTE_CNT_MASK (0xffff << DMA_STS_NISO_BYTE_CNT_SHIFT)
+
+/* UDC Interrupts */
+#define UDC_IRQ_ALL (IRQ_REMOTEWAKEUP_DELTA | \
+ IRQ_SPEED_ENUM_DONE | \
+ IRQ_BUS_SUSPEND | \
+ IRQ_BUS_RESET | \
+ IRQ_BUS_IDLE | \
+ IRQ_SET_INTF | \
+ IRQ_SET_CFG)
+#define IRQ_REMOTEWAKEUP_DELTA INTR_REMOTE_WAKEUP_DELTA
+#define IRQ_SPEED_ENUM_DONE INTR_SPD_ENUM_DONE
+#define IRQ_SOF_DETECTED INTR_SOF_RX
+#define IRQ_BUS_SUSPEND INTR_BUS_SUSPEND
+#define IRQ_BUS_RESET INTR_BUS_RESET
+#define IRQ_BUS_IDLE INTR_BUS_IDLE
+#define IRQ_SET_INTF INTR_SET_INTF_RX
+#define IRQ_SET_CFG INTR_SET_CFG_RX
+
+/* Endpoint status */
+#define EP_STS_ALL (DMA_ERROR | \
+ DMA_BUF_NOT_AVAIL | \
+ IN_TOKEN_RX | \
+ IN_DMA_DONE | \
+ IN_XFER_DONE | \
+ OUT_DMA_DATA_DONE | \
+ OUT_DMA_SETUP_DONE)
+
+#define DMA_ERROR EP_STS_AHB_BUS_ERROR
+#define DMA_BUF_NOT_AVAIL EP_STS_DMA_BUF_NOT_AVAIL
+#define IN_TOKEN_RX EP_STS_IN_TOKEN_RX
+#define IN_DMA_DONE EP_STS_IN_DMA_DONE
+#define IN_FIFO_EMPTY EP_STS_IN_FIFO_EMPTY
+#define IN_XFER_DONE EP_STS_IN_XFER_DONE
+#define OUT_DMA_DATA_DONE EP_STS_OUT_DMA_DATA_DONE
+#define OUT_DMA_SETUP_DONE EP_STS_OUT_DMA_SETUP_DONE
+
+#define DMA_ADDR_INVALID (~(dma_addr_t)0)
+#define DIRN_STR(dirn) ((dirn) == USB_DIR_IN ? "IN" : "OUT")
+#define EP_DIRN_TYPE(d, t) (((d) << 8) | (t))
+
+/* Used for ISOC IN transfers for frame alignment. */
+#define FRAME_NUM_INVALID (~(u32)0)
+
+/* UDC config parameters */
+
+/* If multiple RX FIFO controllers are implemented for
+ * OUT Endpoints, MRX_FIFO is enabled.
+ * Multi RX FIFO controllers are not implemented in RTL.
+ */
+#define MRX_FIFO 0
+#if MRX_FIFO
+static bool mrx_fifo = true;
+#else
+static bool mrx_fifo;
+#endif
+
+/* Buffer Fill mode is enabled for IN transfers,
+ * disabled for OUT transfers.
+ */
+#define IN_DMA_BUF_FILL_EN 1
+#if IN_DMA_BUF_FILL_EN
+static bool in_bf_mode = true;
+#else
+static bool in_bf_mode;
+#endif
+
+#define OUT_DMA_BUF_FILL_EN 0
+#if OUT_DMA_BUF_FILL_EN
+static bool out_bf_mode = true;
+#else
+static bool out_bf_mode;
+#endif
+/*
+ * If it desired that frames start being DMA'd w/o frame
+ * alignment, define ISOC_IN_XFER_DELAY_DISABLE.
+ * If frame alignment is used, this delay is not disabled.
+ */
+#define ISOC_IN_XFER_DELAY_DISABLE 0
+#if ISOC_IN_XFER_DELAY_DISABLE
+static bool in_isoc_delay_disabled = true;
+#else
+static bool in_isoc_delay_disabled;
+#endif
+
+/* Endpoint IN/OUT registers
+ * Register space is reserved for 16 endpoints, but the controller
+ * actually supports 10 endpoints only.
+ */
+#define EP_CNT (16)
+struct snps_ep_regs {
+ u32 ctrl; /* EP control */
+ u32 status; /* EP status */
+ u32 epreg2; /* Buffer for IN, Rec Pkt Frame num for OUT */
+ u32 epreg3; /* Max pkt size for IN, Buf size for OUT */
+ u32 setupbuf; /* Rsvd for IN, EP setup buffer ptr for OUT */
+ u32 datadesc; /* EP data descriptor pointer */
+ u32 rsvd[2];
+};
+
+/* UDC registers */
+struct snps_udc_regs {
+ struct snps_ep_regs ep_in[EP_CNT];
+ struct snps_ep_regs ep_out[EP_CNT];
+ u32 devcfg;
+ u32 devctrl;
+ u32 devstatus;
+ u32 devintrstat;
+ u32 devintrmask;
+ u32 epintrstat;
+ u32 epintrmask;
+ u32 testmode;
+ u32 releasenum;
+ u32 rsvd[56];
+ u32 epcfg[EP_CNT];
+ u32 rsvd1[175];
+ u32 rx_fifo[256];
+ u32 tx_fifo[256];
+ u32 strap;
+};
+
+/* Endpoint SETUP buffer */
+struct setup_desc {
+ u32 status;
+ u32 reserved;
+ u32 data1;
+ u32 data2;
+};
+
+/* Endpoint In/Out data descriptor */
+struct data_desc {
+ u32 status;
+ u32 reserved;
+ u32 buf_addr;
+ u32 next_desc_addr;
+};
+
+/* Endpoint descriptor layout. */
+struct ep_dma_desc {
+ struct setup_desc setup;
+ struct data_desc desc[DESC_CNT];
+};
+
+/* Endpoint descriptor array for Synopsys UDC */
+struct ep_desc_array {
+ struct ep_dma_desc ep[UDC_MAX_EP];
+};
+
+struct snps_udc;
+
+/* Endpoint data structure (for each endpoint) */
+struct snps_udc_ep {
+ struct usb_ep usb_ep;
+ const struct usb_endpoint_descriptor *desc;
+ struct list_head queue;
+ struct snps_udc *udc;
+ char name[14];
+ bool in_xfer_done;
+ u32 num;
+ u32 dirn;
+ u32 type; /* USB_ENDPOINT_XFER_xxx */
+ u32 b_ep_addr; /* dirn | type */
+ u32 max_pkt_size;
+ u32 rx_fifo_size; /* Rx FIFO ram allocated */
+ u32 tx_fifo_size; /* Tx FIFO ram allocated */
+ u32 stopped:1;
+ struct {
+ struct ep_dma_desc *virt;
+ struct ep_dma_desc *phys;
+ struct usb_request *usb_req;/* Current request being DMA'd */
+ u32 len_max; /* to use with a descriptor */
+ u32 len_done; /* Length of request DMA'd so far */
+ u32 len_rem; /* Length of request left to DMA */
+ u32 add_idx; /* descriptor chain index */
+ u32 remove_idx; /* descriptor chain index */
+ u32 buf_addr; /* Location in request to DMA */
+ u32 frame_num; /* Frame number for ISOC transfers */
+ u32 frame_incr; /* Frame number increment (period) */
+ u32 status;
+ u32 done; /* DMA/USB xfer completion indication */
+ void *aligned_buf; /* used if usb_req buf not aligned */
+ dma_addr_t aligned_addr;/* Aligned buffer physical address */
+ u32 aligned_len; /* Aligned buffer length */
+ u32 last;
+ } dma;
+};
+
+/* Endpoint xfer request structure */
+struct ep_xfer_req {
+ struct usb_request usb_req;
+ struct list_head queue;
+ dma_addr_t dma_addr_orig;
+ u32 dma_mapped:1;
+ u32 dma_aligned:1;
+};
+
+/* Controller data structure */
+struct snps_udc {
+ struct usb_gadget gadget;
+ struct usb_gadget_driver *gadget_driver;
+ struct device *dev;
+ void __iomem *regs;
+ int irq;
+ struct completion *dev_release;
+ spinlock_t lock; /* UDC spin lock variable */
+ u32 rx_fifo_space;
+ u32 tx_fifo_space;
+ struct snps_udc_ep ep[UDC_MAX_EP];
+ struct {
+ struct ep_desc_array *virt;
+ struct ep_desc_array *phys;
+ } dma;
+ struct gpio_desc *vbus_gpiod;
+ u32 vbus_active:1;
+ u32 pullup_on:1;
+ struct phy *udc_phy;
+ u32 mode;
+ struct extcon_dev *edev;
+ struct extcon_specific_cable_nb extcon_nb;
+ struct notifier_block nb;
+ struct delayed_work drd_work;
+ struct workqueue_struct *drd_wq;
+ u32 conn_type;
+};
+
+#define REG_WR(reg, val) writel(val, ®)
+#define REG_MOD_AND(reg, val) writel(val & readl(®), ®)
+#define REG_MOD_OR(reg, val) writel(val | readl(®), ®)
+#define REG_MOD_MASK(reg, mask, val) writel(val | (mask & readl(®)), ®)
+#define REG_RD(reg) readl(®)
+
+static inline void dump_regs(struct snps_udc_regs *regs)
+{
+ pr_debug("DEVCFG: 0x%x\n", REG_RD(regs->devcfg));
+ pr_debug("DEVCTRL: 0x%x\n", REG_RD(regs->devctrl));
+ pr_debug("DEVSTS: 0x%x\n", REG_RD(regs->devstatus));
+ pr_debug("DEVINTRMASK: 0x%x\n", REG_RD(regs->devintrmask));
+ pr_debug("DEVINTRSTS: 0x%x\n", REG_RD(regs->devintrstat));
+ pr_debug("EPINTRMASK: 0x%x\n", REG_RD(regs->epintrmask));
+ pr_debug("EPINTRSTS: 0x%x\n", REG_RD(regs->epintrstat));
+}
+
+static inline void bus_connect(struct snps_udc_regs *regs)
+{
+ REG_MOD_AND(regs->devctrl, ~CTRL_DISCONNECT_ENABLE);
+}
+
+static inline void bus_disconnect(struct snps_udc_regs *regs)
+{
+ REG_MOD_OR(regs->devctrl, CTRL_DISCONNECT_ENABLE);
+}
+
+static inline bool is_bus_suspend(struct snps_udc_regs *regs)
+{
+ return REG_RD(regs->devstatus) &
+ STS_BUS_SUSPENDED ? true : false;
+}
+
+static inline u32 get_alt_num(struct snps_udc_regs *regs)
+{
+ return (REG_RD(regs->devstatus) & STS_ALT_NUM_MASK)
+ >> STS_ALT_NUM_SHIFT;
+}
+
+static inline u32 get_cfg_num(struct snps_udc_regs *regs)
+{
+ return (REG_RD(regs->devstatus) & STS_CFG_NUM_MASK)
+ >> STS_CFG_NUM_SHIFT;
+}
+
+static inline u32 get_intf_num(struct snps_udc_regs *regs)
+{
+ return (REG_RD(regs->devstatus) & STS_INTF_NUM_MASK)
+ >> STS_INTF_NUM_SHIFT;
+}
+
+static inline void disable_ctrl_dma(struct snps_udc_regs *regs)
+{
+ REG_MOD_AND(regs->devctrl, ~(CTRL_DMA_IN_ENABLE |
+ CTRL_DMA_OUT_ENABLE));
+}
+
+static inline void enable_ctrl_dma(struct snps_udc_regs *regs)
+{
+ REG_MOD_OR(regs->devctrl, (CTRL_DMA_IN_ENABLE |
+ CTRL_DMA_OUT_ENABLE));
+}
+
+static inline bool is_ctrl_dma_enable(struct snps_udc_regs *regs)
+{
+ return REG_RD(regs->devctrl) &
+ CTRL_DMA_OUT_ENABLE ? true : false;
+}
+
+static inline void disable_epin_dma(struct snps_udc_regs *regs)
+{
+ REG_MOD_AND(regs->devctrl, ~(CTRL_DMA_IN_ENABLE));
+}
+
+static inline void enable_epin_dma(struct snps_udc_regs *regs)
+{
+ REG_MOD_OR(regs->devctrl, (CTRL_DMA_IN_ENABLE));
+}
+
+static inline bool is_epin_dma_enable(struct snps_udc_regs *regs)
+{
+ return REG_RD(regs->devctrl) &
+ CTRL_DMA_IN_ENABLE ? true : false;
+}
+
+static inline void disable_epout_dma(struct snps_udc_regs *regs)
+{
+ REG_MOD_AND(regs->devctrl, ~(CTRL_DMA_OUT_ENABLE));
+}
+
+static inline void enable_epout_dma(struct snps_udc_regs *regs)
+{
+ REG_MOD_OR(regs->devctrl, (CTRL_DMA_OUT_ENABLE));
+}
+
+static inline bool is_epout_dma_enable(struct snps_udc_regs *regs)
+{
+ return REG_RD(regs->devctrl) &
+ CTRL_DMA_OUT_ENABLE ? true : false;
+}
+
+static inline u32 get_frnum_last_rx(struct snps_udc_regs *regs)
+{
+ return (REG_RD(regs->devstatus) &
+ STS_SOF_FRAME_NUM_MASK) >> STS_SOF_FRAME_NUM_SHIFT;
+}
+
+static inline u32 get_irq_active(struct snps_udc_regs *regs)
+{
+ return REG_RD(regs->devintrstat);
+}
+
+static inline void clear_udc_dev_irq(struct snps_udc_regs *regs, u32 mask)
+{
+ REG_WR(regs->devintrstat, mask);
+}
+
+static inline void disable_udc_dev_irq(struct snps_udc_regs *regs, u32 mask)
+{
+ REG_MOD_OR(regs->devintrmask, mask);
+}
+
+static inline void enable_udc_dev_irq(struct snps_udc_regs *regs, u32 mask)
+{
+ REG_MOD_AND(regs->devintrmask, ~mask);
+}
+
+static inline u32 mask_irq(struct snps_udc_regs *regs)
+{
+ return (~REG_RD(regs->devintrmask)) & UDC_IRQ_ALL;
+}
+
+static inline void clear_devnak(struct snps_udc_regs *regs)
+{
+ REG_MOD_AND(regs->devctrl, ~CTRL_OUT_ALL_NAK);
+}
+
+static inline void set_devnak(struct snps_udc_regs *regs)
+{
+ REG_MOD_OR(regs->devctrl, CTRL_OUT_ALL_NAK);
+}
+
+static inline bool is_phy_error(struct snps_udc_regs *regs)
+{
+ return REG_RD(regs->devstatus) &
+ STS_PHY_ERROR ? true : false;
+}
+
+static inline bool is_rmtwkp(struct snps_udc_regs *regs)
+{
+ return REG_RD(regs->devstatus) &
+ STS_REMOTE_WAKEUP_ALLOWED ? true : false;
+}
+
+static inline void clear_rmtwkup(struct snps_udc_regs *regs)
+{
+ REG_MOD_AND(regs->devcfg, ~CFG_REMOTE_WAKEUP_ENABLE);
+}
+
+static inline void set_rmtwkp(struct snps_udc_regs *regs)
+{
+ REG_MOD_OR(regs->devcfg, CFG_REMOTE_WAKEUP_ENABLE);
+}
+
+static inline void start_rmtwkp(struct snps_udc_regs *regs)
+{
+ REG_MOD_OR(regs->devctrl, CTRL_RESUME_SIGNAL_ENABLE);
+}
+
+static inline void stop_rmtwkp(struct snps_udc_regs *regs)
+{
+ REG_MOD_AND(regs->devctrl, ~CTRL_RESUME_SIGNAL_ENABLE);
+}
+
+static inline void disable_self_pwr(struct snps_udc_regs *regs)
+{
+ REG_MOD_AND(regs->devcfg, ~CFG_SELF_PWR_ENABLE);
+}
+
+static inline void enable_self_pwr(struct snps_udc_regs *regs)
+{
+ REG_MOD_OR(regs->devcfg, CFG_SELF_PWR_ENABLE);
+}
+
+static inline void disable_set_desc(struct snps_udc_regs *regs)
+{
+ REG_MOD_AND(regs->devcfg, ~CFG_SET_DESCRIPTOR_ENABLE);
+}
+
+static inline void enable_set_desc(struct snps_udc_regs *regs)
+{
+ REG_MOD_OR(regs->devcfg, CFG_SET_DESCRIPTOR_ENABLE);
+}
+
+static inline void set_setup_done(struct snps_udc_regs *regs)
+{
+ REG_MOD_OR(regs->devctrl, CTRL_CSR_DONE);
+}
+
+static inline u32 get_enum_speed(struct snps_udc_regs *regs)
+{
+ switch (REG_RD(regs->devstatus) & STS_SPD_MASK) {
+ case STS_SPD_LS:
+ return SPEED_LOW;
+ case STS_SPD_HS:
+ return SPEED_HIGH;
+ case STS_SPD_FS:
+ case STS_SPD_FS_48MHZ:
+ return SPEED_FULL;
+ default:
+ return 0;
+ }
+}
+
+static inline void set_speed_requested(struct snps_udc_regs *regs, u32 speed)
+{
+ REG_MOD_AND(regs->devcfg, ~CFG_SPD_MASK);
+
+ switch (speed) {
+ case SPEED_LOW:
+ REG_MOD_OR(regs->devcfg, CFG_SPD_LS);
+ break;
+
+ case SPEED_HIGH:
+ REG_MOD_OR(regs->devcfg, CFG_SPD_HS);
+ break;
+
+ case SPEED_FULL:
+ default:
+ REG_MOD_OR(regs->devcfg, CFG_SPD_FS);
+ break;
+ }
+}
+
+static inline void init_ep_reg(struct snps_udc_regs *regs, u32 num, u32 type,
+ u32 dirn, u32 max_pkt_size)
+{
+ if ((type == EP_TYPE_CTRL) || (dirn == EP_DIRN_OUT)) {
+ REG_WR(regs->ep_out[num].ctrl,
+ (type << EP_CTRL_TYPE_SHIFT));
+ REG_WR(regs->ep_out[num].status,
+ regs->ep_out[num].status);
+ REG_WR(regs->ep_out[num].epreg2, 0);
+ REG_WR(regs->ep_out[num].epreg3,
+ ((max_pkt_size >> 2) << 16) | max_pkt_size);
+
+ if (mrx_fifo)
+ REG_MOD_OR(regs->ep_out[num].epreg3,
+ (FIFO_SZ_U32(max_pkt_size) <<
+ EP_REG3_OUT_DEPTH_SHIFT));
+ }
+ if ((type == EP_TYPE_CTRL) || (dirn == EP_DIRN_IN)) {
+ REG_WR(regs->ep_in[num].ctrl,
+ (type << EP_CTRL_TYPE_SHIFT));
+ REG_WR(regs->ep_in[num].epreg3,
+ (max_pkt_size << EP_REG3_PKT_MAX_SHIFT));
+ REG_WR(regs->ep_in[num].epreg2,
+ (max_pkt_size >> 2));
+ REG_MOD_OR(regs->ep_in[num].ctrl,
+ EP_CTRL_IN_FLUSH_ENABLE);
+ REG_MOD_AND(regs->ep_in[num].ctrl,
+ ~EP_CTRL_IN_FLUSH_ENABLE);
+ REG_MOD_AND(regs->ep_in[num].ctrl,
+ EP_CTRL_NAK_SET);
+ }
+ REG_WR(regs->epcfg[num],
+ (num << EP_CFG_FIFO_NUM_SHIFT) |
+ (type << EP_CFG_TYPE_SHIFT) |
+ (max_pkt_size << EP_CFG_PKT_MAX_SHIFT) |
+ ((dirn == EP_DIRN_OUT) ? EP_CFG_DIRN_OUT : EP_CFG_DIRN_IN));
+}
+
+static inline void set_ep_alt_num(struct snps_udc_regs *regs, u32 num, u32 alt)
+{
+ REG_MOD_MASK(regs->epcfg[num], ~EP_CFG_ALT_NUM_MASK,
+ (alt << EP_CFG_ALT_NUM_SHIFT));
+}
+
+static inline void set_epcfg_reg(struct snps_udc_regs *regs, u32 num, u32 cfg)
+{
+ REG_MOD_MASK(regs->epcfg[num], ~EP_CFG_CFG_NUM_MASK,
+ (cfg << EP_CFG_CFG_NUM_SHIFT));
+}
+
+static inline void set_ep_intf_num(struct snps_udc_regs *regs, u32 num,
+ u32 intf)
+{
+ REG_MOD_MASK(regs->epcfg[num], ~EP_CFG_INTF_NUM_MASK,
+ (intf << EP_CFG_INTF_NUM_SHIFT));
+}
+
+static inline void disable_ep_dma(struct snps_udc_regs *regs, u32 num, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT) {
+ if (mrx_fifo)
+ REG_MOD_AND(regs->ep_out[num].ctrl,
+ ~EP_CTRL_OUT_DMA_ENABLE);
+ } else {
+ REG_MOD_AND(regs->ep_in[num].ctrl,
+ ~EP_CTRL_IN_DMA_ENABLE);
+ }
+}
+
+static inline void enable_ep_dma(struct snps_udc_regs *regs,
+ u32 num, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT) {
+ if (mrx_fifo)
+ REG_MOD_OR(regs->ep_out[num].ctrl,
+ EP_CTRL_OUT_DMA_ENABLE);
+ else
+ REG_MOD_OR(regs->devctrl,
+ CTRL_DMA_OUT_ENABLE);
+ } else
+ REG_MOD_OR(regs->ep_in[num].ctrl,
+ EP_CTRL_IN_DMA_ENABLE);
+}
+
+static inline void set_setup_buf_ptr(struct snps_udc_regs *regs,
+ u32 num, u32 dirn, void *addr)
+{
+ if (dirn == EP_DIRN_OUT)
+ REG_WR(regs->ep_out[num].setupbuf, (dma_addr_t)addr);
+}
+
+static inline void set_data_desc_ptr(struct snps_udc_regs *regs,
+ u32 num, u32 dirn, void *addr)
+{
+ if (dirn == EP_DIRN_OUT)
+ REG_WR(regs->ep_out[num].datadesc, (dma_addr_t)addr);
+ else
+ REG_WR(regs->ep_in[num].datadesc, (dma_addr_t)addr);
+}
+
+static inline bool is_ep_fifo_empty(struct snps_udc_regs *regs,
+ u32 num, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT) {
+ if (mrx_fifo)
+ return REG_RD(regs->ep_out[num].status) &
+ EP_STS_OUT_FIFO_EMPTY ? true : false;
+ else
+ return REG_RD(regs->devstatus) &
+ STS_OUT_FIFO_EMPTY ? true : false;
+ }
+ return REG_RD(regs->ep_in[num].status) &
+ EP_STS_IN_FIFO_EMPTY ? true : false;
+}
+
+static inline void clear_ep_fifo_flush(struct snps_udc_regs *regs,
+ u32 num, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT) {
+ if (mrx_fifo)
+ REG_MOD_AND(regs->ep_out[num].ctrl,
+ ~EP_CTRL_OUT_FLUSH_ENABLE);
+ else
+ REG_MOD_AND(regs->devctrl,
+ ~CTRL_OUT_FIFO_FLUSH_ENABLE);
+ } else {
+ REG_MOD_AND(regs->ep_in[num].ctrl,
+ ~EP_CTRL_IN_FLUSH_ENABLE);
+ }
+}
+
+static inline void set_ep_fifo_flush(struct snps_udc_regs *regs,
+ u32 num, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT) {
+ if (mrx_fifo)
+ REG_MOD_OR(regs->ep_out[num].ctrl,
+ EP_CTRL_OUT_FLUSH_ENABLE);
+ else
+ REG_MOD_OR(regs->devctrl,
+ CTRL_OUT_FIFO_FLUSH_ENABLE);
+ } else {
+ REG_MOD_OR(regs->ep_in[num].ctrl,
+ EP_CTRL_IN_FLUSH_ENABLE);
+ }
+}
+
+static inline u32 get_ep_frnum(struct snps_udc_regs *regs,
+ u32 num, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT)
+ return (regs->ep_out[num].epreg2 &
+ EP_REG2_OUT_FRAME_NUM_MASK) >>
+ EP_REG2_OUT_FRAME_NUM_SHIFT;
+ return 0;
+}
+
+static inline void clear_udc_ep_irq(struct snps_udc_regs *regs,
+ u32 num, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT)
+ REG_WR(regs->epintrstat, (1 << num) <<
+ EP_INTR_OUT_SHIFT);
+ else
+ REG_WR(regs->epintrstat, (1 << num) <<
+ EP_INTR_IN_SHIFT);
+}
+
+static inline void disable_udc_ep_irq(struct snps_udc_regs *regs,
+ u32 num, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT) {
+ REG_MOD_OR(regs->epintrmask, ((1 << num) <<
+ EP_INTR_OUT_SHIFT));
+ } else {
+ REG_MOD_OR(regs->epintrmask, ((1 << num) <<
+ EP_INTR_IN_SHIFT));
+ }
+}
+
+static inline void enable_udc_ep_irq(struct snps_udc_regs *regs,
+ u32 num, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT) {
+ REG_MOD_AND(regs->epintrmask, ~((1 << num) <<
+ EP_INTR_OUT_SHIFT));
+ } else {
+ REG_MOD_AND(regs->epintrmask, ~((1 << num) <<
+ EP_INTR_IN_SHIFT));
+ }
+}
+
+static inline u32 get_ep_irq_active(struct snps_udc_regs *regs, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT)
+ return (REG_RD(regs->epintrstat) & EP_INTR_OUT_MASK)
+ >> EP_INTR_OUT_SHIFT;
+
+ return (REG_RD(regs->epintrstat) & EP_INTR_IN_MASK)
+ >> EP_INTR_IN_SHIFT;
+}
+
+static inline void clear_udc_ep_irq_list(struct snps_udc_regs *regs,
+ u32 dirn, u32 mask)
+{
+ if (dirn == EP_DIRN_OUT)
+ REG_WR(regs->epintrstat, (mask << EP_INTR_OUT_SHIFT));
+ else
+ REG_WR(regs->epintrstat, (mask << EP_INTR_IN_SHIFT));
+}
+
+static inline u32 get_ep_status(struct snps_udc_regs *regs, u32 num, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT)
+ return REG_RD(regs->ep_out[num].status);
+
+ return REG_RD(regs->ep_in[num].status);
+}
+
+static inline void clear_ep_status(struct snps_udc_regs *regs,
+ u32 num, u32 dirn, u32 mask)
+{
+ if (dirn == EP_DIRN_OUT)
+ REG_WR(regs->ep_out[num].status, mask);
+ else
+ REG_WR(regs->ep_in[num].status, mask);
+}
+
+static inline void clear_ep_nak(struct snps_udc_regs *regs,
+ u32 num, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT)
+ REG_MOD_OR(regs->ep_out[num].ctrl, EP_CTRL_NAK_CLEAR);
+ else
+ REG_MOD_OR(regs->ep_in[num].ctrl, EP_CTRL_NAK_CLEAR);
+}
+
+static inline void enable_ep_nak(struct snps_udc_regs *regs,
+ u32 num, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT)
+ REG_MOD_OR(regs->ep_out[num].ctrl, EP_CTRL_NAK_SET);
+ else
+ REG_MOD_OR(regs->ep_in[num].ctrl, EP_CTRL_NAK_SET);
+}
+
+static inline void disable_ep_nak(struct snps_udc_regs *regs,
+ u32 num, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT)
+ REG_MOD_AND(regs->ep_out[num].ctrl, ~EP_CTRL_NAK_SET);
+ else
+ REG_MOD_AND(regs->ep_in[num].ctrl, ~EP_CTRL_NAK_SET);
+}
+
+static inline bool is_ep_nak_inprog(struct snps_udc_regs *regs,
+ u32 num, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT)
+ return REG_RD(regs->ep_out[num].ctrl) &
+ EP_CTRL_NAK_IN_PROGRESS ? true : false;
+
+ return REG_RD(regs->ep_in[num].ctrl) &
+ EP_CTRL_NAK_IN_PROGRESS ? true : false;
+}
+
+static inline void disable_ep_stall(struct snps_udc_regs *regs,
+ u32 num, u32 dirn)
+{
+ if (dirn == EP_DIRN_OUT)
+ REG_MOD_AND(regs->ep_out[num].ctrl,
+ ~EP_CTRL_STALL_ENABLE);
+ else
+ REG_MOD_AND(regs->ep_in[num].ctrl,
+ ~EP_CTRL_STALL_ENABLE);
+}
+
+static inline void enable_ep_stall(struct snps_udc_regs *regs,
+ u32 num, u32 dirn)
+{
+ if (mrx_fifo && !(REG_RD(regs->ep_out[num].status) &
+ EP_STS_OUT_FIFO_EMPTY))
+ return;
+ else if (!mrx_fifo && !(REG_RD(regs->devstatus) &
+ STS_OUT_FIFO_EMPTY))
+ return;
+
+ if (dirn == EP_DIRN_OUT)
+ REG_MOD_OR(regs->ep_out[num].ctrl,
+ EP_CTRL_STALL_ENABLE);
+ else
+ REG_MOD_OR(regs->ep_in[num].ctrl,
+ EP_CTRL_STALL_ENABLE);
+}
+
+static inline u32 get_last_rx_frnum(struct snps_udc_regs *regs)
+{
+ return (REG_RD(regs->devstatus) & STS_SOF_FRAME_NUM_MASK)
+ >> STS_SOF_FRAME_NUM_SHIFT;
+}
+
+static inline void finish_udc(struct snps_udc_regs *regs)
+{
+ u32 ep_num;
+
+ disable_ctrl_dma(regs);
+ disable_udc_dev_irq(regs, UDC_IRQ_ALL);
+ clear_udc_dev_irq(regs, UDC_IRQ_ALL);
+
+ for (ep_num = 0; ep_num < UDC_MAX_EP; ep_num++) {
+ disable_udc_ep_irq(regs, ep_num, EP_DIRN_IN);
+ clear_udc_ep_irq(regs, ep_num, EP_DIRN_IN);
+ clear_ep_status(regs, ep_num, EP_DIRN_IN,
+ get_ep_status(regs, ep_num,
+ EP_DIRN_IN));
+
+ disable_udc_ep_irq(regs, ep_num, EP_DIRN_OUT);
+ clear_udc_ep_irq(regs, ep_num, EP_DIRN_OUT);
+ clear_ep_status(regs, ep_num, EP_DIRN_OUT,
+ get_ep_status(regs, ep_num,
+ EP_DIRN_OUT));
+ }
+}
+
+static inline void init_udc_reg(struct snps_udc_regs *regs)
+{
+ finish_udc(regs);
+ REG_WR(regs->devcfg, CFG_SET_DESCRIPTOR_ENABLE
+ | CFG_UTMI_8BIT_ENABLE
+ | CFG_CSR_PROGRAM_ENABLE
+ | CFG_SPD_HS);
+ REG_WR(regs->devctrl, CTRL_LE_ENABLE
+ | CTRL_DISCONNECT_ENABLE
+ | CTRL_DMA_MODE_ENABLE
+ | CTRL_DMA_DESC_UPDATE_ENABLE
+ | CTRL_OUT_ALL_NAK
+ | CTRL_DMA_OUT_THRESH_LEN_MASK
+ | CTRL_DMA_BURST_LEN_MASK
+ | CTRL_DMA_BURST_ENABLE
+ | CTRL_OUT_FIFO_FLUSH_ENABLE
+ );
+
+ if (mrx_fifo)
+ REG_MOD_AND(regs->devctrl, ~CTRL_OUT_FIFO_FLUSH_ENABLE);
+
+ if (out_bf_mode)
+ REG_MOD_OR(regs->devctrl, CTRL_DMA_BUFF_FILL_MODE_ENABLE);
+
+ REG_WR(regs->devintrmask, IRQ_BUS_IDLE | IRQ_SOF_DETECTED);
+ REG_WR(regs->epintrmask, 0);
+}
+
+static inline struct data_desc *dma_desc_chain_alloc(struct snps_udc_ep *ep)
+{
+ u32 idx;
+
+ idx = ep->dma.add_idx++;
+
+ return &ep->dma.virt->desc[EP_DMA_DESC_IDX(idx)];
+}
+
+static inline int dma_desc_chain_is_empty(struct snps_udc_ep *ep)
+{
+ return ep->dma.add_idx == ep->dma.remove_idx;
+}
+
+static inline void dma_desc_chain_free(struct snps_udc_ep *ep)
+{
+ ep->dma.remove_idx++;
+}
+
+static inline int dma_desc_chain_is_full(struct snps_udc_ep *ep)
+{
+ return !dma_desc_chain_is_empty(ep) &&
+ (EP_DMA_DESC_IDX(ep->dma.add_idx) ==
+ EP_DMA_DESC_IDX(ep->dma.remove_idx));
+}
+
+static inline struct data_desc *dma_desc_chain_head(struct snps_udc_ep *ep)
+{
+ u32 index = EP_DMA_DESC_IDX(ep->dma.remove_idx);
+
+ return &ep->dma.virt->desc[index];
+}
+
+static inline void dma_desc_chain_reset(struct snps_udc_ep *ep)
+{
+ ep->dma.add_idx = 0;
+ ep->dma.remove_idx = 0;
+}
+#endif
--
2.1.0
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [PATCH 1/2] Add DT bindings documentation for Synopsys UDC driver
From: Raviteja Garimella @ 2016-11-30 6:05 UTC (permalink / raw)
To: Rob Herring, Mark Rutland, Greg Kroah-Hartman, Felipe Balbi
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
bcm-kernel-feedback-list-dY08KVG/lbpWk0Htik3J/w,
linux-usb-u79uwXL29TY76Z2rM5mHXA
In-Reply-To: <1480485910-7797-1-git-send-email-raviteja.garimella-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>
This patch adds documentation for Synopsis Designware Cores AHB
Subsystem Device Controller (UDC).
Signed-off-by: Raviteja Garimella <raviteja.garimella-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>
---
.../devicetree/bindings/usb/snps,dw-ahb-udc.txt | 29 ++++++++++++++++++++++
1 file changed, 29 insertions(+)
create mode 100644 Documentation/devicetree/bindings/usb/snps,dw-ahb-udc.txt
diff --git a/Documentation/devicetree/bindings/usb/snps,dw-ahb-udc.txt b/Documentation/devicetree/bindings/usb/snps,dw-ahb-udc.txt
new file mode 100644
index 0000000..64e1fbf
--- /dev/null
+++ b/Documentation/devicetree/bindings/usb/snps,dw-ahb-udc.txt
@@ -0,0 +1,29 @@
+Synopsys USB Device controller.
+
+The device node is used for Synopsys Designware Cores AHB
+Subsystem Device Controller (UDC).
+
+Required properties:
+ - compatible: should be "snps,dw-ahbudc"
+ - reg: Offset and length of UDC register set
+ - interrupts: description of interrupt line
+ - phys: phandle to phy node.
+ - phy-names: name of phy node. Must be usb2drd.
+ - extcon: phandle to the extcon device
+
+Example:
+
+ usbdrd_phy: phy@6501c000 {
+ #phy-cells = <0>;
+ compatible = "brcm,ns2-drd-phy";
+ reg = <0x66000000 0x1000>,
+ }
+
+ udc_dwc: usb@664e0000 {
+ compatible = "snps,dw-ahb-udc";
+ reg = <0x664e0000 0x2000>;
+ interrupts = <GIC_SPI 424 IRQ_TYPE_LEVEL_HIGH>;
+ phys = <&usbdrd_phy>;
+ phy-names = "usb2drd";
+ extcon = <&usbdrd_phy>";
+ };
--
2.1.0
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [PATCH 0/2] Support for Synopsys UDC for ARM platforms
From: Raviteja Garimella @ 2016-11-30 6:05 UTC (permalink / raw)
To: Rob Herring, Mark Rutland, Greg Kroah-Hartman, Felipe Balbi
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
bcm-kernel-feedback-list-dY08KVG/lbpWk0Htik3J/w,
linux-usb-u79uwXL29TY76Z2rM5mHXA
This patchset adds support for Synposys Designware core AHB-UDC
(USB Device controller) for Arm platfoms.
New UDC driver is added to drivers/usb/gadget directory along with
updating the Kconfig and Makefile.
DT bindings documentation is also added for the same.
Device tree entry for the same in NS2 dtsi will be sent for review
once the DRD phy driver code is pushed (which is being reviewed in a
separate patch series).
This patchset is tested on Broadcom NS2 BCM958712K reference board.
Repo: https://github.com/Broadcom/arm64-linux.git
Branch: udc_v1
Raviteja Garimella (2):
Add DT bindings documentation for Synopsys UDC driver
Synopsys USB 2.0 Device Controller (UDC) Driver
.../devicetree/bindings/usb/snps,dw-ahb-udc.txt | 29 +
drivers/usb/gadget/udc/Kconfig | 12 +
drivers/usb/gadget/udc/Makefile | 1 +
drivers/usb/gadget/udc/snps_udc.c | 1751 ++++++++++++++++++++
drivers/usb/gadget/udc/snps_udc.h | 1071 ++++++++++++
5 files changed, 2864 insertions(+)
create mode 100644 Documentation/devicetree/bindings/usb/snps,dw-ahb-udc.txt
create mode 100644 drivers/usb/gadget/udc/snps_udc.c
create mode 100644 drivers/usb/gadget/udc/snps_udc.h
--
2.1.0
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* [PATCH 3/3] DT nodes for Broadcom Northstar2 USB DRD Phy
From: Raviteja Garimella @ 2016-11-30 5:55 UTC (permalink / raw)
To: Rob Herring, Mark Rutland, Kishon Vijay Abraham I, Ray Jui,
Scott Branden, Jon Mason, Catalin Marinas, Will Deacon
Cc: devicetree, linux-kernel, bcm-kernel-feedback-list,
linux-arm-kernel
In-Reply-To: <1480485338-23451-1-git-send-email-raviteja.garimella@broadcom.com>
This patch adds device tree nodes for USB Dual Role Device Phy for
Broadcom's Northstar2 SoC.
Signed-off-by: Raviteja Garimella <raviteja.garimella@broadcom.com>
---
arch/arm64/boot/dts/broadcom/ns2.dtsi | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/arch/arm64/boot/dts/broadcom/ns2.dtsi b/arch/arm64/boot/dts/broadcom/ns2.dtsi
index d95dc40..e9d3496 100644
--- a/arch/arm64/boot/dts/broadcom/ns2.dtsi
+++ b/arch/arm64/boot/dts/broadcom/ns2.dtsi
@@ -299,6 +299,20 @@
};
};
+ usbdrd_phy: phy@66000960 {
+ #phy-cells = <0>;
+ compatible = "brcm,ns2-drd-phy";
+ reg = <0x66000960 0x24>,
+ <0x67012800 0x4>,
+ <0x6501d148 0x4>,
+ <0x664d0700 0x4>;
+ reg-names = "icfg", "rst-ctrl",
+ "crmu-ctrl", "usb2-strap";
+ id-gpios = <&gpio_g 30 0>;
+ vbus-gpios = <&gpio_g 31 0>;
+ status = "disabled";
+ };
+
pwm: pwm@66010000 {
compatible = "brcm,iproc-pwm";
reg = <0x66010000 0x28>;
--
2.1.0
^ permalink raw reply related
* [PATCH 2/3] Broadcom USB DRD Phy driver for Northstar2
From: Raviteja Garimella @ 2016-11-30 5:55 UTC (permalink / raw)
To: Rob Herring, Mark Rutland, Kishon Vijay Abraham I, Ray Jui,
Scott Branden, Jon Mason, Catalin Marinas, Will Deacon
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
bcm-kernel-feedback-list-dY08KVG/lbpWk0Htik3J/w,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <1480485338-23451-1-git-send-email-raviteja.garimella-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>
This is driver for USB DRD Phy used in Broadcom's Northstar2
SoC. The phy can be configured to be in Device mode or Host
mode based on the type of cable connected to the port. The
driver registers to extcon framework to get appropriate
connect events for Host/Device cables connect/disconnect
states based on VBUS and ID interrupts.
Signed-off-by: Raviteja Garimella <raviteja.garimella-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>
---
drivers/phy/Kconfig | 13 +
drivers/phy/Makefile | 1 +
drivers/phy/phy-bcm-ns2-usbdrd.c | 587 +++++++++++++++++++++++++++++++++++++++
3 files changed, 601 insertions(+)
create mode 100644 drivers/phy/phy-bcm-ns2-usbdrd.c
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index fe00f91..b3b6a73 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -479,6 +479,19 @@ config PHY_CYGNUS_PCIE
Enable this to support the Broadcom Cygnus PCIe PHY.
If unsure, say N.
+config PHY_NS2_USB_DRD
+ tristate "Broadcom Northstar2 USB DRD PHY support"
+ depends on OF && (ARCH_BCM_IPROC || COMPILE_TEST)
+ select GENERIC_PHY
+ select EXTCON
+ default ARCH_BCM_IPROC
+ help
+ Enable this to support the Broadcom Northstar2 USB DRD PHY.
+ This driver initializes the PHY in either HOST or DEVICE mode.
+ The host or device configuration is read from device tree.
+
+ If unsure, say N.
+
source "drivers/phy/tegra/Kconfig"
config PHY_NS2_PCIE
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index a534cf5..b733ba2 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -58,5 +58,6 @@ obj-$(CONFIG_PHY_TUSB1210) += phy-tusb1210.o
obj-$(CONFIG_PHY_BRCM_SATA) += phy-brcm-sata.o
obj-$(CONFIG_PHY_PISTACHIO_USB) += phy-pistachio-usb.o
obj-$(CONFIG_PHY_CYGNUS_PCIE) += phy-bcm-cygnus-pcie.o
+obj-$(CONFIG_PHY_NS2_USB_DRD) += phy-bcm-ns2-usbdrd.o
obj-$(CONFIG_ARCH_TEGRA) += tegra/
obj-$(CONFIG_PHY_NS2_PCIE) += phy-bcm-ns2-pcie.o
diff --git a/drivers/phy/phy-bcm-ns2-usbdrd.c b/drivers/phy/phy-bcm-ns2-usbdrd.c
new file mode 100644
index 0000000..460040d
--- /dev/null
+++ b/drivers/phy/phy-bcm-ns2-usbdrd.c
@@ -0,0 +1,587 @@
+/*
+ * Copyright (C) 2016 Broadcom
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/delay.h>
+#include <linux/extcon.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#define ICFG_DRD_AFE 0x0
+#define ICFG_MISC_STAT 0x18
+#define ICFG_DRD_P0CTL 0x1C
+#define ICFG_STRAP_CTRL 0x20
+#define ICFG_FSM_CTRL 0x24
+
+#define IDM_RST_BIT BIT(0)
+#define AFE_CORERDY_VDDC BIT(18)
+#define PHY_PLL_RESETB BIT(15)
+#define PHY_RESETB BIT(14)
+#define PHY_PLL_LOCK BIT(0)
+
+#define DRD_DEV_MODE BIT(20)
+#define OHCI_OVRCUR_POL BIT(11)
+#define ICFG_OFF_MODE BIT(6)
+#define PLL_LOCK_RETRY 1000
+
+#define EVT_DEVICE 0
+#define EVT_HOST 1
+#define EVT_IDLE 2
+
+#define DRD_HOST_MODE (BIT(2) | BIT(3))
+#define DRD_DEVICE_MODE (BIT(4) | BIT(5))
+#define DRD_HOST_VAL 0x803
+#define DRD_DEV_VAL 0x807
+#define GPIO_DELAY 20
+#define PHY_WQ_DELAY msecs_to_jiffies(600)
+
+struct ns2_phy_data;
+struct ns2_phy_driver {
+ void __iomem *icfgdrd_regs;
+ void __iomem *idmdrd_rst_ctrl;
+ void __iomem *crmu_usb2_ctrl;
+ void __iomem *usb2h_strap_reg;
+ spinlock_t lock; /* spin lock for phy driver */
+ bool host_mode;
+ struct ns2_phy_data *data;
+ struct extcon_specific_cable_nb extcon_dev;
+ struct extcon_specific_cable_nb extcon_host;
+ struct notifier_block host_nb;
+ struct notifier_block dev_nb;
+ struct delayed_work conn_work;
+ struct extcon_dev *edev;
+ struct gpio_desc *vbus_gpiod;
+ struct gpio_desc *id_gpiod;
+ int id_irq;
+ int vbus_irq;
+ unsigned long debounce_jiffies;
+ struct delayed_work wq_extcon;
+};
+
+struct ns2_phy_data {
+ struct ns2_phy_driver *driver;
+ struct phy *phy;
+ int new_state;
+ bool poweron;
+};
+
+static const unsigned int usb_extcon_cable[] = {
+ EXTCON_USB,
+ EXTCON_USB_HOST,
+ EXTCON_NONE,
+};
+
+static inline int pll_lock_stat(u32 usb_reg, int reg_mask,
+ struct ns2_phy_driver *driver)
+{
+ int retry = PLL_LOCK_RETRY;
+ u32 val;
+
+ do {
+ udelay(1);
+ val = readl(driver->icfgdrd_regs + usb_reg);
+ if (val & reg_mask)
+ return 0;
+ } while (--retry > 0);
+
+ return -EBUSY;
+}
+
+static int ns2_drd_phy_init(struct phy *phy)
+{
+ struct ns2_phy_data *data = phy_get_drvdata(phy);
+ struct ns2_phy_driver *driver = data->driver;
+ unsigned long flags;
+ u32 val;
+
+ spin_lock_irqsave(&driver->lock, flags);
+
+ val = readl(driver->icfgdrd_regs + ICFG_FSM_CTRL);
+
+ if (data->new_state == EVT_HOST) {
+ val &= ~DRD_DEVICE_MODE;
+ val |= DRD_HOST_MODE;
+ } else {
+ val &= ~DRD_HOST_MODE;
+ val |= DRD_DEVICE_MODE;
+ }
+ writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL);
+
+ spin_unlock_irqrestore(&driver->lock, flags);
+ return 0;
+}
+
+static int ns2_drd_phy_shutdown(struct phy *phy)
+{
+ struct ns2_phy_data *data = phy_get_drvdata(phy);
+ struct ns2_phy_driver *driver = data->driver;
+ unsigned long flags;
+ u32 val;
+
+ spin_lock_irqsave(&driver->lock, flags);
+ if (!data->poweron)
+ goto exit;
+
+ val = readl(driver->crmu_usb2_ctrl);
+ val &= ~AFE_CORERDY_VDDC;
+ writel(val, driver->crmu_usb2_ctrl);
+
+ driver->host_mode = 0;
+ val = readl(driver->crmu_usb2_ctrl);
+ val &= ~DRD_DEV_MODE;
+ writel(val, driver->crmu_usb2_ctrl);
+
+ /* Disable Host and Device Mode */
+ val = readl(driver->icfgdrd_regs + ICFG_FSM_CTRL);
+ val &= ~(DRD_HOST_MODE | DRD_DEVICE_MODE | ICFG_OFF_MODE);
+ writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL);
+
+ data->poweron = 0;
+exit:
+ spin_unlock_irqrestore(&driver->lock, flags);
+ return 0;
+}
+
+static int ns2_drd_phy_poweron(struct phy *phy)
+{
+ struct ns2_phy_data *data = phy_get_drvdata(phy);
+ struct ns2_phy_driver *driver = data->driver;
+ u32 extcon_event = data->new_state;
+ unsigned long flags;
+ int ret;
+ u32 val;
+
+ spin_lock_irqsave(&driver->lock, flags);
+ if (extcon_event == EVT_DEVICE) {
+ if (data->poweron)
+ goto exit;
+
+ writel(DRD_DEV_VAL, driver->icfgdrd_regs + ICFG_DRD_P0CTL);
+
+ val = readl(driver->icfgdrd_regs + ICFG_FSM_CTRL);
+ val &= ~(DRD_HOST_MODE | ICFG_OFF_MODE);
+ val |= DRD_DEVICE_MODE;
+ writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL);
+
+ val = readl(driver->idmdrd_rst_ctrl);
+ val &= ~IDM_RST_BIT;
+ writel(val, driver->idmdrd_rst_ctrl);
+
+ val = readl(driver->crmu_usb2_ctrl);
+ val |= (AFE_CORERDY_VDDC | DRD_DEV_MODE);
+ writel(val, driver->crmu_usb2_ctrl);
+
+ /* Bring PHY and PHY_PLL out of Reset */
+ val = readl(driver->crmu_usb2_ctrl);
+ val |= (PHY_PLL_RESETB | PHY_RESETB);
+ writel(val, driver->crmu_usb2_ctrl);
+
+ ret = pll_lock_stat(ICFG_MISC_STAT, PHY_PLL_LOCK, driver);
+ if (ret < 0) {
+ dev_err(&phy->dev, "Phy PLL lock failed\n");
+ goto err_shutdown;
+ }
+ } else {
+ if (data->poweron && driver->host_mode)
+ goto exit;
+
+ writel(DRD_HOST_VAL, driver->icfgdrd_regs + ICFG_DRD_P0CTL);
+
+ val = readl(driver->icfgdrd_regs + ICFG_FSM_CTRL);
+ val &= ~(DRD_DEVICE_MODE | ICFG_OFF_MODE);
+ val |= DRD_HOST_MODE;
+ writel(val, driver->icfgdrd_regs + ICFG_FSM_CTRL);
+
+ val = readl(driver->crmu_usb2_ctrl);
+ val |= AFE_CORERDY_VDDC;
+ writel(val, driver->crmu_usb2_ctrl);
+
+ ret = pll_lock_stat(ICFG_MISC_STAT, PHY_PLL_LOCK, driver);
+ if (ret < 0) {
+ dev_err(&phy->dev, "Phy PLL lock failed\n");
+ goto err_shutdown;
+ }
+
+ val = readl(driver->idmdrd_rst_ctrl);
+ val &= ~IDM_RST_BIT;
+ writel(val, driver->idmdrd_rst_ctrl);
+
+ /* port over current Polarity */
+ val = readl(driver->usb2h_strap_reg);
+ val |= OHCI_OVRCUR_POL;
+ writel(val, driver->usb2h_strap_reg);
+
+ driver->host_mode = 1;
+ }
+
+ data->poweron = 1;
+exit:
+ spin_unlock_irqrestore(&driver->lock, flags);
+ return 0;
+
+err_shutdown:
+ data->poweron = 1;
+ spin_unlock_irqrestore(&driver->lock, flags);
+ ns2_drd_phy_shutdown(phy);
+ return ret;
+}
+
+static void connect_work(struct work_struct *work)
+{
+ struct ns2_phy_driver *driver;
+ struct ns2_phy_data *data;
+ u32 extcon_event;
+
+ driver = container_of(to_delayed_work(work),
+ struct ns2_phy_driver, conn_work);
+ data = driver->data;
+ extcon_event = data->new_state;
+
+ if (extcon_event == EVT_DEVICE || extcon_event == EVT_HOST) {
+ ns2_drd_phy_init(data->phy);
+ ns2_drd_phy_poweron(data->phy);
+ } else if (extcon_event == EVT_IDLE) {
+ ns2_drd_phy_shutdown(data->phy);
+ }
+}
+
+static int drd_device_notify(struct notifier_block *self,
+ unsigned long event, void *ptr)
+{
+ struct ns2_phy_driver *driver = container_of(self,
+ struct ns2_phy_driver, dev_nb);
+
+ if (event) {
+ pr_debug("Device connected\n");
+ driver->data->new_state = EVT_DEVICE;
+ schedule_delayed_work(&driver->conn_work, 0);
+ } else {
+ pr_debug("Device disconnected\n");
+ driver->data->new_state = EVT_IDLE;
+ schedule_delayed_work(&driver->conn_work, PHY_WQ_DELAY);
+ }
+
+ return NOTIFY_DONE;
+}
+
+static int drd_host_notify(struct notifier_block *self,
+ unsigned long event, void *ptr)
+{
+ struct ns2_phy_driver *driver = container_of(self,
+ struct ns2_phy_driver, host_nb);
+
+ if (event) {
+ pr_debug("Host connected\n");
+ driver->data->new_state = EVT_HOST;
+ schedule_delayed_work(&driver->conn_work, 0);
+ } else {
+ pr_debug("Host disconnected\n");
+ driver->data->new_state = EVT_IDLE;
+ schedule_delayed_work(&driver->conn_work, PHY_WQ_DELAY);
+ }
+
+ return NOTIFY_DONE;
+}
+
+static void extcon_work(struct work_struct *work)
+{
+ struct ns2_phy_driver *driver;
+ int vbus;
+ int id;
+
+ driver = container_of(to_delayed_work(work),
+ struct ns2_phy_driver, wq_extcon);
+
+ id = gpiod_get_value_cansleep(driver->id_gpiod);
+ vbus = gpiod_get_value_cansleep(driver->vbus_gpiod);
+
+ if (!id && vbus) {
+ extcon_set_cable_state_(driver->edev, EXTCON_USB_HOST, true);
+ } else if (id && !vbus) {
+ extcon_set_cable_state_(driver->edev, EXTCON_USB_HOST, false);
+ extcon_set_cable_state_(driver->edev, EXTCON_USB, false);
+ } else if (id && vbus) {
+ extcon_set_cable_state_(driver->edev, EXTCON_USB, true);
+ }
+}
+
+static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
+{
+ struct ns2_phy_driver *driver = dev_id;
+
+ queue_delayed_work(system_power_efficient_wq, &driver->wq_extcon,
+ driver->debounce_jiffies);
+
+ return IRQ_HANDLED;
+}
+
+static int register_extcon_notifier(struct ns2_phy_driver *phy_driver,
+ struct device *dev)
+{
+ struct extcon_dev *edev;
+ int ret;
+
+ phy_driver->host_nb.notifier_call = drd_host_notify;
+ phy_driver->dev_nb.notifier_call = drd_device_notify;
+
+ edev = phy_driver->edev;
+
+ /* Register for device change notification */
+ ret = extcon_register_notifier(edev, EXTCON_USB,
+ &phy_driver->dev_nb);
+ if (ret < 0) {
+ dev_err(dev, "can't register extcon_dev for %s\n", edev->name);
+ return ret;
+ }
+
+ /* Register for host change notification */
+ ret = extcon_register_notifier(edev, EXTCON_USB_HOST,
+ &phy_driver->host_nb);
+ if (ret < 0) {
+ dev_err(dev, "can't register extcon_dev for %s\n", edev->name);
+ goto err_dev;
+ }
+
+ /* Check the device cable connect state */
+ ret = extcon_get_cable_state_(edev, EXTCON_USB);
+ if (ret < 0) {
+ dev_err(dev, "can't get extcon_dev state for %s\n", edev->name);
+ goto err_host;
+ } else if (ret) {
+ phy_driver->data->new_state = EVT_DEVICE;
+ }
+
+ /* Check the host cable connect state */
+ ret = extcon_get_cable_state_(edev, EXTCON_USB_HOST);
+ if (ret < 0) {
+ dev_err(dev, "can't get extcon_dev state for %s\n", edev->name);
+ goto err_host;
+ } else if (ret) {
+ phy_driver->data->new_state = EVT_HOST;
+ }
+
+ return 0;
+
+err_host:
+ ret = extcon_unregister_notifier(edev, EXTCON_USB_HOST,
+ &phy_driver->host_nb);
+err_dev:
+ ret = extcon_unregister_notifier(edev, EXTCON_USB,
+ &phy_driver->dev_nb);
+ return ret;
+}
+
+static struct phy_ops ops = {
+ .init = ns2_drd_phy_init,
+ .power_on = ns2_drd_phy_poweron,
+ .power_off = ns2_drd_phy_shutdown,
+};
+
+static const struct of_device_id ns2_drd_phy_dt_ids[] = {
+ { .compatible = "brcm,ns2-drd-phy", },
+ { }
+};
+
+static int ns2_drd_phy_remove(struct platform_device *pdev)
+{
+ struct ns2_phy_driver *driver = dev_get_drvdata(&pdev->dev);
+
+ if (driver->edev) {
+ extcon_unregister_notifier(driver->edev, EXTCON_USB_HOST,
+ &driver->host_nb);
+ extcon_unregister_notifier(driver->edev, EXTCON_USB,
+ &driver->dev_nb);
+ }
+
+ return 0;
+}
+static int ns2_drd_phy_probe(struct platform_device *pdev)
+{
+ struct phy_provider *phy_provider;
+ struct device *dev = &pdev->dev;
+ struct ns2_phy_driver *driver;
+ struct ns2_phy_data *data;
+ struct resource *res;
+ int ret;
+ u32 val;
+
+ driver = devm_kzalloc(dev, sizeof(struct ns2_phy_driver),
+ GFP_KERNEL);
+ if (!driver)
+ return -ENOMEM;
+
+ driver->data = devm_kzalloc(dev, sizeof(struct ns2_phy_data),
+ GFP_KERNEL);
+ if (!driver->data)
+ return -ENOMEM;
+
+ spin_lock_init(&driver->lock);
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "icfg");
+ driver->icfgdrd_regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(driver->icfgdrd_regs))
+ return PTR_ERR(driver->icfgdrd_regs);
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rst-ctrl");
+ driver->idmdrd_rst_ctrl = devm_ioremap_resource(dev, res);
+ if (IS_ERR(driver->idmdrd_rst_ctrl))
+ return PTR_ERR(driver->idmdrd_rst_ctrl);
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "crmu-ctrl");
+ driver->crmu_usb2_ctrl = devm_ioremap_resource(dev, res);
+ if (IS_ERR(driver->crmu_usb2_ctrl))
+ return PTR_ERR(driver->crmu_usb2_ctrl);
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "usb2-strap");
+ driver->usb2h_strap_reg = devm_ioremap_resource(dev, res);
+ if (IS_ERR(driver->usb2h_strap_reg))
+ return PTR_ERR(driver->usb2h_strap_reg);
+
+ /* create extcon */
+ driver->id_gpiod = devm_gpiod_get(&pdev->dev, "id", GPIOD_IN);
+ if (IS_ERR(driver->id_gpiod)) {
+ dev_err(dev, "failed to get ID GPIO\n");
+ return PTR_ERR(driver->id_gpiod);
+ }
+ driver->vbus_gpiod = devm_gpiod_get(&pdev->dev, "vbus", GPIOD_IN);
+ if (IS_ERR(driver->vbus_gpiod)) {
+ dev_err(dev, "failed to get VBUS GPIO\n");
+ return PTR_ERR(driver->vbus_gpiod);
+ }
+
+ driver->edev = devm_extcon_dev_allocate(dev, usb_extcon_cable);
+ if (IS_ERR(driver->edev)) {
+ dev_err(dev, "failed to allocate extcon device\n");
+ return -ENOMEM;
+ }
+
+ ret = devm_extcon_dev_register(dev, driver->edev);
+ if (ret < 0) {
+ dev_err(dev, "failed to register extcon device\n");
+ goto extcon_free;
+ }
+
+ ret = gpiod_set_debounce(driver->id_gpiod, GPIO_DELAY * 1000);
+ if (ret < 0)
+ driver->debounce_jiffies = msecs_to_jiffies(GPIO_DELAY);
+
+ INIT_DELAYED_WORK(&driver->wq_extcon, extcon_work);
+
+ driver->id_irq = gpiod_to_irq(driver->id_gpiod);
+ if (driver->id_irq < 0) {
+ dev_err(dev, "failed to get ID IRQ\n");
+ ret = driver->id_irq;
+ goto extcon_unregister;
+ }
+ driver->vbus_irq = gpiod_to_irq(driver->vbus_gpiod);
+ if (driver->vbus_irq < 0) {
+ dev_err(dev, "failed to get ID IRQ\n");
+ ret = driver->vbus_irq;
+ goto extcon_unregister;
+ }
+
+ ret = devm_request_threaded_irq(dev, driver->id_irq, NULL,
+ gpio_irq_handler,
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ "usb_id", driver);
+ if (ret < 0) {
+ dev_err(dev, "failed to request handler for ID IRQ\n");
+ goto extcon_unregister;
+ }
+ ret = devm_request_threaded_irq(dev, driver->vbus_irq, NULL,
+ gpio_irq_handler,
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ "usb_vbus", driver);
+ if (ret < 0) {
+ dev_err(dev, "failed to request handler for VBUS IRQ\n");
+ goto extcon_unregister;
+ }
+
+ dev_set_drvdata(dev, driver);
+ driver->host_mode = 0;
+
+ /* Shutdown all ports. They can be powered up as required */
+ val = readl(driver->crmu_usb2_ctrl);
+ val &= ~(AFE_CORERDY_VDDC | PHY_RESETB);
+ writel(val, driver->crmu_usb2_ctrl);
+
+ data = driver->data;
+ data->phy = devm_phy_create(dev, dev->of_node, &ops);
+ if (IS_ERR(data->phy)) {
+ dev_err(dev, "Failed to create usb drd phy\n");
+ ret = PTR_ERR(data->phy);
+ goto extcon_unregister;
+ }
+
+ data->driver = driver;
+ data->poweron = 0;
+ phy_set_drvdata(data->phy, data);
+
+ phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+ if (IS_ERR(phy_provider)) {
+ dev_err(dev, "Failed to register as phy provider\n");
+ ret = PTR_ERR(phy_provider);
+ goto extcon_unregister;
+ }
+
+ INIT_DELAYED_WORK(&driver->conn_work, connect_work);
+ platform_set_drvdata(pdev, driver);
+
+ ret = register_extcon_notifier(driver, dev);
+ if (ret < 0) {
+ dev_err(dev, "register extcon notifier failed (%d)\n", ret);
+ goto extcon_unregister;
+ }
+ dev_info(dev, "Registered %s\n", driver->edev->name);
+ queue_delayed_work(system_power_efficient_wq, &driver->wq_extcon,
+ driver->debounce_jiffies);
+
+ return 0;
+
+extcon_unregister:
+ devm_extcon_dev_unregister(dev, driver->edev);
+extcon_free:
+ devm_extcon_dev_free(dev, driver->edev);
+ return ret;
+}
+
+MODULE_DEVICE_TABLE(of, ns2_drd_phy_dt_ids);
+
+static struct platform_driver ns2_drd_phy_driver = {
+ .probe = ns2_drd_phy_probe,
+ .remove = ns2_drd_phy_remove,
+ .driver = {
+ .name = "bcm-ns2-usbphy",
+ .of_match_table = of_match_ptr(ns2_drd_phy_dt_ids),
+ },
+};
+module_platform_driver(ns2_drd_phy_driver);
+
+MODULE_ALIAS("platform:bcm-ns2-drd-phy");
+MODULE_AUTHOR("Broadcom");
+MODULE_DESCRIPTION("Broadcom NS2 USB2 PHY driver");
+MODULE_LICENSE("GPL v2");
--
2.1.0
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [PATCH 1/3] Add DT bindings documentation for NS2 USB DRD phy
From: Raviteja Garimella @ 2016-11-30 5:55 UTC (permalink / raw)
To: Rob Herring, Mark Rutland, Kishon Vijay Abraham I, Ray Jui,
Scott Branden, Jon Mason, Catalin Marinas, Will Deacon
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
bcm-kernel-feedback-list-dY08KVG/lbpWk0Htik3J/w,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
In-Reply-To: <1480485338-23451-1-git-send-email-raviteja.garimella-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>
This patch adds documentation for NS2 DRD Phy driver DT bindings
Signed-off-by: Raviteja Garimella <raviteja.garimella-dY08KVG/lbpWk0Htik3J/w@public.gmane.org>
---
.../devicetree/bindings/phy/brcm,ns2-drd-phy.txt | 40 ++++++++++++++++++++++
1 file changed, 40 insertions(+)
create mode 100644 Documentation/devicetree/bindings/phy/brcm,ns2-drd-phy.txt
diff --git a/Documentation/devicetree/bindings/phy/brcm,ns2-drd-phy.txt b/Documentation/devicetree/bindings/phy/brcm,ns2-drd-phy.txt
new file mode 100644
index 0000000..5857f99
--- /dev/null
+++ b/Documentation/devicetree/bindings/phy/brcm,ns2-drd-phy.txt
@@ -0,0 +1,40 @@
+BROADCOM NORTHSTAR2 USB2 (DUAL ROLE DEVICE) PHY
+
+Required properties:
+ - compatible: brcm,ns2-drd-phy
+ - reg: offset and length of the NS2 PHY related registers.
+ - reg-names
+ The below registers must be provided.
+ icfg - for DRD ICFG configurations
+ rst-ctrl - for DRD IDM reset
+ crmu-ctrl - for CRMU core vdd, PHY and PHY PLL reset
+ usb2-strap - for port over current polarity reversal
+ - #phy-cells: Must be 0. No args required.
+ - vbus-gpios: vbus gpio binding
+ - id-gpios: id gpio binding
+
+Refer to phy/phy-bindings.txt for the generic PHY binding properties
+
+Example:
+ gpio_g: gpio@660a0000 {
+ compatible = "brcm,iproc-gpio";
+ reg = <0x660a0000 0x50>;
+ ngpios = <32>;
+ #gpio-cells = <2>;
+ gpio-controller;
+ interrupt-controller;
+ interrupts = <GIC_SPI 400 IRQ_TYPE_LEVEL_HIGH>;
+ };
+
+ usbdrd_phy: phy@66000960 {
+ #phy-cells = <0>;
+ compatible = "brcm,ns2-drd-phy";
+ reg = <0x66000960 0x24>,
+ <0x67012800 0x4>,
+ <0x6501d148 0x4>,
+ <0x664d0700 0x4>;
+ reg-names = "icfg", "rst-ctrl",
+ "crmu-ctrl", "usb2-strap";
+ id-gpios = <&gpio_g 30 0>;
+ vbus-gpios = <&gpio_g 31 0>;
+ };
--
2.1.0
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [PATCH 0/3] Support for USB DRD Phy driver for NS2
From: Raviteja Garimella @ 2016-11-30 5:55 UTC (permalink / raw)
To: Rob Herring, Mark Rutland, Kishon Vijay Abraham I, Ray Jui,
Scott Branden, Jon Mason, Catalin Marinas, Will Deacon
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
bcm-kernel-feedback-list-dY08KVG/lbpWk0Htik3J/w,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
This patch adds support for USB Dual Role Device Phy for Broadcom
Northstar2 SoC. Apart from the new phy driver, this patchset contains
changes to Kconfig, Makefile, and Device tree files.
This patchset is tested on Broadcom NS2 BCM958712K reference board.
Repo: https://github.com/Broadcom/arm64-linux.git
Branch: ns2_drdphy
Raviteja Garimella (3):
Add DT bindings documentation for NS2 USB DRD phy
Broadcom USB DRD Phy driver for Northstar2
DT nodes for Broadcom Northstar2 USB DRD Phy
.../devicetree/bindings/phy/brcm,ns2-drd-phy.txt | 40 ++
arch/arm64/boot/dts/broadcom/ns2.dtsi | 14 +
drivers/phy/Kconfig | 13 +
drivers/phy/Makefile | 1 +
drivers/phy/phy-bcm-ns2-usbdrd.c | 587 +++++++++++++++++++++
5 files changed, 655 insertions(+)
create mode 100644 Documentation/devicetree/bindings/phy/brcm,ns2-drd-phy.txt
create mode 100644 drivers/phy/phy-bcm-ns2-usbdrd.c
--
2.1.0
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply
* [PATCH V6 10/10] PM / OPP: Don't assume platform doesn't have regulators
From: Viresh Kumar @ 2016-11-30 5:14 UTC (permalink / raw)
To: Rafael Wysocki, Viresh Kumar, Nishanth Menon, Stephen Boyd
Cc: linaro-kernel-cunTk1MwBs8s++Sfvej+rw,
linux-pm-u79uwXL29TY76Z2rM5mHXA, Vincent Guittot,
robh-DgEjT+Ai2ygdnm+yROfE0A, d-gerlach-l0cyMroinI0,
broonie-DgEjT+Ai2ygdnm+yROfE0A, devicetree-u79uwXL29TY76Z2rM5mHXA,
Viresh Kumar
In-Reply-To: <cover.1480481312.git.viresh.kumar-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
If the regulators aren't set explicitly by the platform, the OPP core
assumes that the platform doesn't have any regulator and uses the
clk-only callback.
If the platform failed to register a regulator with the core, then this
can turn out to be a dangerous assumption as the OPP core will try to
change clk without changing regulators.
Handle that properly by making sure that the DT didn't have any entries
for supply voltages as well.
Signed-off-by: Viresh Kumar <viresh.kumar-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
Reviewed-by: Stephen Boyd <sboyd-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
---
drivers/base/power/opp/core.c | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/drivers/base/power/opp/core.c b/drivers/base/power/opp/core.c
index f60d7f943b64..979403448cd6 100644
--- a/drivers/base/power/opp/core.c
+++ b/drivers/base/power/opp/core.c
@@ -748,7 +748,20 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
/* Only frequency scaling */
if (!regulators) {
+ unsigned long u_volt = opp->supplies[0].u_volt;
+
rcu_read_unlock();
+
+ /*
+ * DT contained supply ratings? Consider platform failed to set
+ * regulators.
+ */
+ if (unlikely(u_volt)) {
+ dev_err(dev, "%s: Regulator not registered with OPP core\n",
+ __func__);
+ return -EINVAL;
+ }
+
return _generic_set_opp_clk_only(dev, clk, old_freq, freq);
}
--
2.7.1.410.g6faf27b
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [PATCH V6 09/10] PM / OPP: Don't WARN on multiple calls to dev_pm_opp_set_regulators()
From: Viresh Kumar @ 2016-11-30 5:14 UTC (permalink / raw)
To: Rafael Wysocki, Viresh Kumar, Nishanth Menon, Stephen Boyd
Cc: linaro-kernel-cunTk1MwBs8s++Sfvej+rw,
linux-pm-u79uwXL29TY76Z2rM5mHXA, Vincent Guittot,
robh-DgEjT+Ai2ygdnm+yROfE0A, d-gerlach-l0cyMroinI0,
broonie-DgEjT+Ai2ygdnm+yROfE0A, devicetree-u79uwXL29TY76Z2rM5mHXA,
Viresh Kumar
In-Reply-To: <cover.1480481312.git.viresh.kumar-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
If a platform specific OPP driver has called this routine first and set
the regulators, then the second call from cpufreq-dt driver will hit the
WARN_ON(). Remove the WARN_ON(), but continue to return error in such
cases.
Signed-off-by: Viresh Kumar <viresh.kumar-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
Reviewed-by: Stephen Boyd <sboyd-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
Tested-by: Dave Gerlach <d-gerlach-l0cyMroinI0@public.gmane.org>
---
drivers/base/power/opp/core.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/base/power/opp/core.c b/drivers/base/power/opp/core.c
index 1fb188b424c7..f60d7f943b64 100644
--- a/drivers/base/power/opp/core.c
+++ b/drivers/base/power/opp/core.c
@@ -1485,7 +1485,7 @@ struct opp_table *dev_pm_opp_set_regulators(struct device *dev,
}
/* Already have regulators set */
- if (WARN_ON(opp_table->regulators)) {
+ if (opp_table->regulators) {
opp_table = ERR_PTR(-EBUSY);
goto err;
}
--
2.7.1.410.g6faf27b
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [PATCH V6 08/10] PM / OPP: Allow platform specific custom set_opp() callbacks
From: Viresh Kumar @ 2016-11-30 5:14 UTC (permalink / raw)
To: Rafael Wysocki, Viresh Kumar, Nishanth Menon, Stephen Boyd
Cc: linaro-kernel-cunTk1MwBs8s++Sfvej+rw,
linux-pm-u79uwXL29TY76Z2rM5mHXA, Vincent Guittot,
robh-DgEjT+Ai2ygdnm+yROfE0A, d-gerlach-l0cyMroinI0,
broonie-DgEjT+Ai2ygdnm+yROfE0A, devicetree-u79uwXL29TY76Z2rM5mHXA,
Viresh Kumar
In-Reply-To: <cover.1480481312.git.viresh.kumar-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
The generic set_opp() handler isn't sufficient for platforms with
complex DVFS. For example, some TI platforms have multiple regulators
for a CPU device. The order in which various supplies need to be
programmed is only known to the platform code and its best to leave it
to it.
This patch implements APIs to register platform specific set_opp()
callback.
Signed-off-by: Viresh Kumar <viresh.kumar-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
Tested-by: Dave Gerlach <d-gerlach-l0cyMroinI0@public.gmane.org>
---
V4->V5:
- s/custom OPP set rate/custom set OPP/
- set_opp() doesn't have a separate 'dev' argument now.
---
drivers/base/power/opp/core.c | 114 +++++++++++++++++++++++++++++++++++++++++-
drivers/base/power/opp/opp.h | 2 +
include/linux/pm_opp.h | 10 ++++
3 files changed, 125 insertions(+), 1 deletion(-)
diff --git a/drivers/base/power/opp/core.c b/drivers/base/power/opp/core.c
index 0a53ae132d7f..1fb188b424c7 100644
--- a/drivers/base/power/opp/core.c
+++ b/drivers/base/power/opp/core.c
@@ -687,6 +687,7 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
{
struct opp_table *opp_table;
unsigned long freq, old_freq;
+ int (*set_opp)(struct dev_pm_set_opp_data *data);
struct dev_pm_opp *old_opp, *opp;
struct regulator **regulators;
struct dev_pm_set_opp_data *data;
@@ -751,6 +752,11 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
return _generic_set_opp_clk_only(dev, clk, old_freq, freq);
}
+ if (opp_table->set_opp)
+ set_opp = opp_table->set_opp;
+ else
+ set_opp = _generic_set_opp;
+
data = opp_table->set_opp_data;
data->regulators = regulators;
data->regulator_count = opp_table->regulator_count;
@@ -769,7 +775,7 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
rcu_read_unlock();
- return _generic_set_opp(data);
+ return set_opp(data);
}
EXPORT_SYMBOL_GPL(dev_pm_opp_set_rate);
@@ -903,6 +909,9 @@ static void _remove_opp_table(struct opp_table *opp_table)
if (opp_table->regulators)
return;
+ if (opp_table->set_opp)
+ return;
+
/* Release clk */
if (!IS_ERR(opp_table->clk))
clk_put(opp_table->clk);
@@ -1570,6 +1579,109 @@ void dev_pm_opp_put_regulators(struct opp_table *opp_table)
EXPORT_SYMBOL_GPL(dev_pm_opp_put_regulators);
/**
+ * dev_pm_opp_register_set_opp_helper() - Register custom set OPP helper
+ * @dev: Device for which the helper is getting registered.
+ * @set_opp: Custom set OPP helper.
+ *
+ * This is useful to support complex platforms (like platforms with multiple
+ * regulators per device), instead of the generic OPP set rate helper.
+ *
+ * This must be called before any OPPs are initialized for the device.
+ *
+ * Locking: The internal opp_table and opp structures are RCU protected.
+ * Hence this function internally uses RCU updater strategy with mutex locks
+ * to keep the integrity of the internal data structures. Callers should ensure
+ * that this function is *NOT* called under RCU protection or in contexts where
+ * mutex cannot be locked.
+ */
+int dev_pm_opp_register_set_opp_helper(struct device *dev,
+ int (*set_opp)(struct dev_pm_set_opp_data *data))
+{
+ struct opp_table *opp_table;
+ int ret;
+
+ if (!set_opp)
+ return -EINVAL;
+
+ mutex_lock(&opp_table_lock);
+
+ opp_table = _add_opp_table(dev);
+ if (!opp_table) {
+ ret = -ENOMEM;
+ goto unlock;
+ }
+
+ /* This should be called before OPPs are initialized */
+ if (WARN_ON(!list_empty(&opp_table->opp_list))) {
+ ret = -EBUSY;
+ goto err;
+ }
+
+ /* Already have custom set_opp helper */
+ if (WARN_ON(opp_table->set_opp)) {
+ ret = -EBUSY;
+ goto err;
+ }
+
+ opp_table->set_opp = set_opp;
+
+ mutex_unlock(&opp_table_lock);
+ return 0;
+
+err:
+ _remove_opp_table(opp_table);
+unlock:
+ mutex_unlock(&opp_table_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(dev_pm_opp_register_set_opp_helper);
+
+/**
+ * dev_pm_opp_register_put_opp_helper() - Releases resources blocked for
+ * set_opp helper
+ * @dev: Device for which custom set_opp helper has to be cleared.
+ *
+ * Locking: The internal opp_table and opp structures are RCU protected.
+ * Hence this function internally uses RCU updater strategy with mutex locks
+ * to keep the integrity of the internal data structures. Callers should ensure
+ * that this function is *NOT* called under RCU protection or in contexts where
+ * mutex cannot be locked.
+ */
+void dev_pm_opp_register_put_opp_helper(struct device *dev)
+{
+ struct opp_table *opp_table;
+
+ mutex_lock(&opp_table_lock);
+
+ /* Check for existing table for 'dev' first */
+ opp_table = _find_opp_table(dev);
+ if (IS_ERR(opp_table)) {
+ dev_err(dev, "Failed to find opp_table: %ld\n",
+ PTR_ERR(opp_table));
+ goto unlock;
+ }
+
+ if (!opp_table->set_opp) {
+ dev_err(dev, "%s: Doesn't have custom set_opp helper set\n",
+ __func__);
+ goto unlock;
+ }
+
+ /* Make sure there are no concurrent readers while updating opp_table */
+ WARN_ON(!list_empty(&opp_table->opp_list));
+
+ opp_table->set_opp = NULL;
+
+ /* Try freeing opp_table if this was the last blocking resource */
+ _remove_opp_table(opp_table);
+
+unlock:
+ mutex_unlock(&opp_table_lock);
+}
+EXPORT_SYMBOL_GPL(dev_pm_opp_register_put_opp_helper);
+
+/**
* dev_pm_opp_add() - Add an OPP table from a table definitions
* @dev: device for which we do this operation
* @freq: Frequency in Hz for this OPP
diff --git a/drivers/base/power/opp/opp.h b/drivers/base/power/opp/opp.h
index a05e43912c6b..af9f2b849a66 100644
--- a/drivers/base/power/opp/opp.h
+++ b/drivers/base/power/opp/opp.h
@@ -141,6 +141,7 @@ enum opp_table_access {
* @clk: Device's clock handle
* @regulators: Supply regulators
* @regulator_count: Number of power supply regulators
+ * @set_opp: Platform specific set_opp callback
* @set_opp_data: Data to be passed to set_opp callback
* @dentry: debugfs dentry pointer of the real device directory (not links).
* @dentry_name: Name of the real dentry.
@@ -179,6 +180,7 @@ struct opp_table {
struct regulator **regulators;
unsigned int regulator_count;
+ int (*set_opp)(struct dev_pm_set_opp_data *data);
struct dev_pm_set_opp_data *set_opp_data;
#ifdef CONFIG_DEBUG_FS
diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h
index 779b40a9287d..0edd88f93904 100644
--- a/include/linux/pm_opp.h
+++ b/include/linux/pm_opp.h
@@ -116,6 +116,8 @@ int dev_pm_opp_set_prop_name(struct device *dev, const char *name);
void dev_pm_opp_put_prop_name(struct device *dev);
struct opp_table *dev_pm_opp_set_regulators(struct device *dev, const char * const names[], unsigned int count);
void dev_pm_opp_put_regulators(struct opp_table *opp_table);
+int dev_pm_opp_register_set_opp_helper(struct device *dev, int (*set_opp)(struct dev_pm_set_opp_data *data));
+void dev_pm_opp_register_put_opp_helper(struct device *dev);
int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq);
int dev_pm_opp_set_sharing_cpus(struct device *cpu_dev, const struct cpumask *cpumask);
int dev_pm_opp_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask);
@@ -215,6 +217,14 @@ static inline int dev_pm_opp_set_supported_hw(struct device *dev,
static inline void dev_pm_opp_put_supported_hw(struct device *dev) {}
+static inline int dev_pm_opp_register_set_opp_helper(struct device *dev,
+ int (*set_opp)(struct dev_pm_set_opp_data *data))
+{
+ return -ENOTSUPP;
+}
+
+static inline void dev_pm_opp_register_put_opp_helper(struct device *dev) {}
+
static inline int dev_pm_opp_set_prop_name(struct device *dev, const char *name)
{
return -ENOTSUPP;
--
2.7.1.410.g6faf27b
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [PATCH V6 07/10] PM / OPP: Separate out _generic_set_opp()
From: Viresh Kumar @ 2016-11-30 5:14 UTC (permalink / raw)
To: Rafael Wysocki, Viresh Kumar, Nishanth Menon, Stephen Boyd
Cc: linaro-kernel, linux-pm, Vincent Guittot, robh, d-gerlach,
broonie, devicetree, Viresh Kumar
In-Reply-To: <cover.1480481312.git.viresh.kumar@linaro.org>
Later patches would add support for custom set_opp() callbacks. This
patch separates out the code for _generic_set_opp() handler in order to
prepare for that.
Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
Tested-by: Dave Gerlach <d-gerlach@ti.com>
Reviewed-by: Stephen Boyd <sboyd@codeaurora.org>
---
V4->V5:
- Make 'dev' part of struct dev_pm_set_opp_data
- Fix commit log: s/opp_set_rate/set_opp
- Same in a comment as well
---
drivers/base/power/opp/core.c | 181 +++++++++++++++++++++++++++++-------------
drivers/base/power/opp/opp.h | 3 +
include/linux/pm_opp.h | 35 ++++++++
3 files changed, 166 insertions(+), 53 deletions(-)
diff --git a/drivers/base/power/opp/core.c b/drivers/base/power/opp/core.c
index f0dd0bd29ce8..0a53ae132d7f 100644
--- a/drivers/base/power/opp/core.c
+++ b/drivers/base/power/opp/core.c
@@ -610,6 +610,69 @@ static int _set_opp_voltage(struct device *dev, struct regulator *reg,
return ret;
}
+static inline int
+_generic_set_opp_clk_only(struct device *dev, struct clk *clk,
+ unsigned long old_freq, unsigned long freq)
+{
+ int ret;
+
+ ret = clk_set_rate(clk, freq);
+ if (ret) {
+ dev_err(dev, "%s: failed to set clock rate: %d\n", __func__,
+ ret);
+ }
+
+ return ret;
+}
+
+static int _generic_set_opp(struct dev_pm_set_opp_data *data)
+{
+ struct dev_pm_opp_supply *old_supply = data->old_opp.supplies;
+ struct dev_pm_opp_supply *new_supply = data->new_opp.supplies;
+ unsigned long old_freq = data->old_opp.rate, freq = data->new_opp.rate;
+ struct regulator *reg = data->regulators[0];
+ struct device *dev= data->dev;
+ int ret;
+
+ /* This function only supports single regulator per device */
+ if (WARN_ON(data->regulator_count > 1)) {
+ dev_err(dev, "multiple regulators are not supported\n");
+ return -EINVAL;
+ }
+
+ /* Scaling up? Scale voltage before frequency */
+ if (freq > old_freq) {
+ ret = _set_opp_voltage(dev, reg, new_supply);
+ if (ret)
+ goto restore_voltage;
+ }
+
+ /* Change frequency */
+ ret = _generic_set_opp_clk_only(dev, data->clk, old_freq, freq);
+ if (ret)
+ goto restore_voltage;
+
+ /* Scaling down? Scale voltage after frequency */
+ if (freq < old_freq) {
+ ret = _set_opp_voltage(dev, reg, new_supply);
+ if (ret)
+ goto restore_freq;
+ }
+
+ return 0;
+
+restore_freq:
+ if (_generic_set_opp_clk_only(dev, data->clk, freq, old_freq))
+ dev_err(dev, "%s: failed to restore old-freq (%lu Hz)\n",
+ __func__, old_freq);
+restore_voltage:
+ /* This shouldn't harm even if the voltages weren't updated earlier */
+ if (old_supply->u_volt)
+ _set_opp_voltage(dev, reg, old_supply);
+
+ return ret;
+}
+
/**
* dev_pm_opp_set_rate() - Configure new OPP based on frequency
* @dev: device for which we do this operation
@@ -623,12 +686,12 @@ static int _set_opp_voltage(struct device *dev, struct regulator *reg,
int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
{
struct opp_table *opp_table;
+ unsigned long freq, old_freq;
struct dev_pm_opp *old_opp, *opp;
- struct regulator *reg = ERR_PTR(-ENXIO);
+ struct regulator **regulators;
+ struct dev_pm_set_opp_data *data;
struct clk *clk;
- unsigned long freq, old_freq;
- struct dev_pm_opp_supply old_supply, new_supply;
- int ret;
+ int ret, size;
if (unlikely(!target_freq)) {
dev_err(dev, "%s: Invalid target frequency %lu\n", __func__,
@@ -677,64 +740,36 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
return ret;
}
- if (opp_table->regulators) {
- /* This function only supports single regulator per device */
- if (WARN_ON(opp_table->regulator_count > 1)) {
- dev_err(dev, "multiple regulators not supported\n");
- rcu_read_unlock();
- return -EINVAL;
- }
+ dev_dbg(dev, "%s: switching OPP: %lu Hz --> %lu Hz\n", __func__,
+ old_freq, freq);
- reg = opp_table->regulators[0];
+ regulators = opp_table->regulators;
+
+ /* Only frequency scaling */
+ if (!regulators) {
+ rcu_read_unlock();
+ return _generic_set_opp_clk_only(dev, clk, old_freq, freq);
}
+ data = opp_table->set_opp_data;
+ data->regulators = regulators;
+ data->regulator_count = opp_table->regulator_count;
+ data->clk = clk;
+ data->dev = dev;
+
+ data->old_opp.rate = old_freq;
+ size = sizeof(*opp->supplies) * opp_table->regulator_count;
if (IS_ERR(old_opp))
- old_supply.u_volt = 0;
+ memset(data->old_opp.supplies, 0, size);
else
- memcpy(&old_supply, old_opp->supplies, sizeof(old_supply));
+ memcpy(data->old_opp.supplies, old_opp->supplies, size);
- memcpy(&new_supply, opp->supplies, sizeof(new_supply));
+ data->new_opp.rate = freq;
+ memcpy(data->new_opp.supplies, opp->supplies, size);
rcu_read_unlock();
- /* Scaling up? Scale voltage before frequency */
- if (freq > old_freq) {
- ret = _set_opp_voltage(dev, reg, &new_supply);
- if (ret)
- goto restore_voltage;
- }
-
- /* Change frequency */
-
- dev_dbg(dev, "%s: switching OPP: %lu Hz --> %lu Hz\n",
- __func__, old_freq, freq);
-
- ret = clk_set_rate(clk, freq);
- if (ret) {
- dev_err(dev, "%s: failed to set clock rate: %d\n", __func__,
- ret);
- goto restore_voltage;
- }
-
- /* Scaling down? Scale voltage after frequency */
- if (freq < old_freq) {
- ret = _set_opp_voltage(dev, reg, &new_supply);
- if (ret)
- goto restore_freq;
- }
-
- return 0;
-
-restore_freq:
- if (clk_set_rate(clk, old_freq))
- dev_err(dev, "%s: failed to restore old-freq (%lu Hz)\n",
- __func__, old_freq);
-restore_voltage:
- /* This shouldn't harm even if the voltages weren't updated earlier */
- if (old_supply.u_volt)
- _set_opp_voltage(dev, reg, &old_supply);
-
- return ret;
+ return _generic_set_opp(data);
}
EXPORT_SYMBOL_GPL(dev_pm_opp_set_rate);
@@ -1368,6 +1403,38 @@ void dev_pm_opp_put_prop_name(struct device *dev)
}
EXPORT_SYMBOL_GPL(dev_pm_opp_put_prop_name);
+static int _allocate_set_opp_data(struct opp_table *opp_table)
+{
+ struct dev_pm_set_opp_data *data;
+ int len, count = opp_table->regulator_count;
+
+ if (WARN_ON(!count))
+ return -EINVAL;
+
+ /* space for set_opp_data */
+ len = sizeof(*data);
+
+ /* space for old_opp.supplies and new_opp.supplies */
+ len += 2 * sizeof(struct dev_pm_opp_supply) * count;
+
+ data = kzalloc(len, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->old_opp.supplies = (void *)(data + 1);
+ data->new_opp.supplies = data->old_opp.supplies + count;
+
+ opp_table->set_opp_data = data;
+
+ return 0;
+}
+
+static void _free_set_opp_data(struct opp_table *opp_table)
+{
+ kfree(opp_table->set_opp_data);
+ opp_table->set_opp_data = NULL;
+}
+
/**
* dev_pm_opp_set_regulators() - Set regulator names for the device
* @dev: Device for which regulator name is being set.
@@ -1437,6 +1504,11 @@ struct opp_table *dev_pm_opp_set_regulators(struct device *dev,
opp_table->regulator_count = count;
+ /* Allocate block only once to pass to set_opp() routines */
+ ret = _allocate_set_opp_data(opp_table);
+ if (ret)
+ goto free_regulators;
+
mutex_unlock(&opp_table_lock);
return opp_table;
@@ -1446,6 +1518,7 @@ struct opp_table *dev_pm_opp_set_regulators(struct device *dev,
kfree(opp_table->regulators);
opp_table->regulators = NULL;
+ opp_table->regulator_count = 0;
err:
_remove_opp_table(opp_table);
unlock:
@@ -1482,6 +1555,8 @@ void dev_pm_opp_put_regulators(struct opp_table *opp_table)
for (i = opp_table->regulator_count - 1; i >= 0; i--)
regulator_put(opp_table->regulators[i]);
+ _free_set_opp_data(opp_table);
+
kfree(opp_table->regulators);
opp_table->regulators = NULL;
opp_table->regulator_count = 0;
diff --git a/drivers/base/power/opp/opp.h b/drivers/base/power/opp/opp.h
index 5b0f7e53bede..a05e43912c6b 100644
--- a/drivers/base/power/opp/opp.h
+++ b/drivers/base/power/opp/opp.h
@@ -141,6 +141,7 @@ enum opp_table_access {
* @clk: Device's clock handle
* @regulators: Supply regulators
* @regulator_count: Number of power supply regulators
+ * @set_opp_data: Data to be passed to set_opp callback
* @dentry: debugfs dentry pointer of the real device directory (not links).
* @dentry_name: Name of the real dentry.
*
@@ -178,6 +179,8 @@ struct opp_table {
struct regulator **regulators;
unsigned int regulator_count;
+ struct dev_pm_set_opp_data *set_opp_data;
+
#ifdef CONFIG_DEBUG_FS
struct dentry *dentry;
char dentry_name[NAME_MAX];
diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h
index 9a825ae78653..779b40a9287d 100644
--- a/include/linux/pm_opp.h
+++ b/include/linux/pm_opp.h
@@ -17,6 +17,8 @@
#include <linux/err.h>
#include <linux/notifier.h>
+struct clk;
+struct regulator;
struct dev_pm_opp;
struct device;
struct opp_table;
@@ -41,6 +43,39 @@ struct dev_pm_opp_supply {
unsigned long u_amp;
};
+/**
+ * struct dev_pm_opp_info - OPP freq/voltage/current values
+ * @rate: Target clk rate in hz
+ * @supplies: Array of voltage/current values for all power supplies
+ *
+ * This structure stores the freq/voltage/current values for a single OPP.
+ */
+struct dev_pm_opp_info {
+ unsigned long rate;
+ struct dev_pm_opp_supply *supplies;
+};
+
+/**
+ * struct dev_pm_set_opp_data - Set OPP data
+ * @old_opp: Old OPP info
+ * @new_opp: New OPP info
+ * @regulators: Array of regulator pointers
+ * @regulator_count: Number of regulators
+ * @clk: Pointer to clk
+ * @dev: Pointer to the struct device
+ *
+ * This structure contains all information required for setting an OPP.
+ */
+struct dev_pm_set_opp_data {
+ struct dev_pm_opp_info old_opp;
+ struct dev_pm_opp_info new_opp;
+
+ struct regulator **regulators;
+ unsigned int regulator_count;
+ struct clk *clk;
+ struct device *dev;
+};
+
#if defined(CONFIG_PM_OPP)
unsigned long dev_pm_opp_get_voltage(struct dev_pm_opp *opp);
--
2.7.1.410.g6faf27b
^ permalink raw reply related
* [PATCH V6 06/10] PM / OPP: Add infrastructure to manage multiple regulators
From: Viresh Kumar @ 2016-11-30 5:14 UTC (permalink / raw)
To: Rafael Wysocki, Viresh Kumar, Nishanth Menon, Stephen Boyd
Cc: linaro-kernel-cunTk1MwBs8s++Sfvej+rw,
linux-pm-u79uwXL29TY76Z2rM5mHXA, Vincent Guittot,
robh-DgEjT+Ai2ygdnm+yROfE0A, d-gerlach-l0cyMroinI0,
broonie-DgEjT+Ai2ygdnm+yROfE0A, devicetree-u79uwXL29TY76Z2rM5mHXA,
Viresh Kumar
In-Reply-To: <cover.1480481312.git.viresh.kumar-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
This patch adds infrastructure to manage multiple regulators and updates
the only user (cpufreq-dt) of dev_pm_opp_set{put}_regulator().
This is preparatory work for adding full support for devices with
multiple regulators.
Signed-off-by: Viresh Kumar <viresh.kumar-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
Tested-by: Dave Gerlach <d-gerlach-l0cyMroinI0@public.gmane.org>
---
V4->V5:
- Don't allocate from within rcu locked section.
- s/+//
---
drivers/base/power/opp/core.c | 245 +++++++++++++++++++++++++++------------
drivers/base/power/opp/debugfs.c | 52 +++++++--
drivers/base/power/opp/of.c | 103 +++++++++++-----
drivers/base/power/opp/opp.h | 10 +-
drivers/cpufreq/cpufreq-dt.c | 9 +-
include/linux/pm_opp.h | 8 +-
6 files changed, 302 insertions(+), 125 deletions(-)
diff --git a/drivers/base/power/opp/core.c b/drivers/base/power/opp/core.c
index 8328ff06cc08..f0dd0bd29ce8 100644
--- a/drivers/base/power/opp/core.c
+++ b/drivers/base/power/opp/core.c
@@ -93,6 +93,8 @@ struct opp_table *_find_opp_table(struct device *dev)
* Return: voltage in micro volt corresponding to the opp, else
* return 0
*
+ * This is useful only for devices with single power supply.
+ *
* Locking: This function must be called under rcu_read_lock(). opp is a rcu
* protected pointer. This means that opp which could have been fetched by
* opp_find_freq_{exact,ceil,floor} functions is valid as long as we are
@@ -112,7 +114,7 @@ unsigned long dev_pm_opp_get_voltage(struct dev_pm_opp *opp)
if (IS_ERR_OR_NULL(tmp_opp))
pr_err("%s: Invalid parameters\n", __func__);
else
- v = tmp_opp->supply.u_volt;
+ v = tmp_opp->supplies[0].u_volt;
return v;
}
@@ -210,6 +212,24 @@ unsigned long dev_pm_opp_get_max_clock_latency(struct device *dev)
}
EXPORT_SYMBOL_GPL(dev_pm_opp_get_max_clock_latency);
+static int _get_regulator_count(struct device *dev)
+{
+ struct opp_table *opp_table;
+ int count;
+
+ rcu_read_lock();
+
+ opp_table = _find_opp_table(dev);
+ if (!IS_ERR(opp_table))
+ count = opp_table->regulator_count;
+ else
+ count = 0;
+
+ rcu_read_unlock();
+
+ return count;
+}
+
/**
* dev_pm_opp_get_max_volt_latency() - Get max voltage latency in nanoseconds
* @dev: device for which we do this operation
@@ -222,34 +242,51 @@ unsigned long dev_pm_opp_get_max_volt_latency(struct device *dev)
{
struct opp_table *opp_table;
struct dev_pm_opp *opp;
- struct regulator *reg;
+ struct regulator *reg, **regulators;
unsigned long latency_ns = 0;
- unsigned long min_uV = ~0, max_uV = 0;
- int ret;
+ int ret, i, count;
+ struct {
+ unsigned long min;
+ unsigned long max;
+ } *uV;
+
+ count = _get_regulator_count(dev);
+
+ /* Regulator may not be required for the device */
+ if (!count)
+ return 0;
+
+ regulators = kmalloc_array(count, sizeof(*regulators), GFP_KERNEL);
+ if (!regulators)
+ return 0;
+
+ uV = kmalloc_array(count, sizeof(*uV), GFP_KERNEL);
+ if (!uV)
+ goto free_regulators;
rcu_read_lock();
opp_table = _find_opp_table(dev);
if (IS_ERR(opp_table)) {
rcu_read_unlock();
- return 0;
+ goto free_uV;
}
- reg = opp_table->regulator;
- if (IS_ERR(reg)) {
- /* Regulator may not be required for device */
- rcu_read_unlock();
- return 0;
- }
+ memcpy(regulators, opp_table->regulators, count * sizeof(*regulators));
- list_for_each_entry_rcu(opp, &opp_table->opp_list, node) {
- if (!opp->available)
- continue;
+ for (i = 0; i < count; i++) {
+ uV[i].min = ~0;
+ uV[i].max = 0;
+
+ list_for_each_entry_rcu(opp, &opp_table->opp_list, node) {
+ if (!opp->available)
+ continue;
- if (opp->supply.u_volt_min < min_uV)
- min_uV = opp->supply.u_volt_min;
- if (opp->supply.u_volt_max > max_uV)
- max_uV = opp->supply.u_volt_max;
+ if (opp->supplies[i].u_volt_min < uV[i].min)
+ uV[i].min = opp->supplies[i].u_volt_min;
+ if (opp->supplies[i].u_volt_max > uV[i].max)
+ uV[i].max = opp->supplies[i].u_volt_max;
+ }
}
rcu_read_unlock();
@@ -258,9 +295,16 @@ unsigned long dev_pm_opp_get_max_volt_latency(struct device *dev)
* The caller needs to ensure that opp_table (and hence the regulator)
* isn't freed, while we are executing this routine.
*/
- ret = regulator_set_voltage_time(reg, min_uV, max_uV);
- if (ret > 0)
- latency_ns = ret * 1000;
+ for (i = 0; reg = regulators[i], i < count; i++) {
+ ret = regulator_set_voltage_time(reg, uV[i].min, uV[i].max);
+ if (ret > 0)
+ latency_ns += ret * 1000;
+ }
+
+free_uV:
+ kfree(uV);
+free_regulators:
+ kfree(regulators);
return latency_ns;
}
@@ -580,7 +624,7 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
{
struct opp_table *opp_table;
struct dev_pm_opp *old_opp, *opp;
- struct regulator *reg;
+ struct regulator *reg = ERR_PTR(-ENXIO);
struct clk *clk;
unsigned long freq, old_freq;
struct dev_pm_opp_supply old_supply, new_supply;
@@ -633,14 +677,23 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
return ret;
}
+ if (opp_table->regulators) {
+ /* This function only supports single regulator per device */
+ if (WARN_ON(opp_table->regulator_count > 1)) {
+ dev_err(dev, "multiple regulators not supported\n");
+ rcu_read_unlock();
+ return -EINVAL;
+ }
+
+ reg = opp_table->regulators[0];
+ }
+
if (IS_ERR(old_opp))
old_supply.u_volt = 0;
else
- memcpy(&old_supply, &old_opp->supply, sizeof(old_supply));
+ memcpy(&old_supply, old_opp->supplies, sizeof(old_supply));
- memcpy(&new_supply, &opp->supply, sizeof(new_supply));
-
- reg = opp_table->regulator;
+ memcpy(&new_supply, opp->supplies, sizeof(new_supply));
rcu_read_unlock();
@@ -764,9 +817,6 @@ static struct opp_table *_add_opp_table(struct device *dev)
_of_init_opp_table(opp_table, dev);
- /* Set regulator to a non-NULL error value */
- opp_table->regulator = ERR_PTR(-ENXIO);
-
/* Find clk for the device */
opp_table->clk = clk_get(dev, NULL);
if (IS_ERR(opp_table->clk)) {
@@ -815,7 +865,7 @@ static void _remove_opp_table(struct opp_table *opp_table)
if (opp_table->prop_name)
return;
- if (!IS_ERR(opp_table->regulator))
+ if (opp_table->regulators)
return;
/* Release clk */
@@ -924,35 +974,50 @@ struct dev_pm_opp *_allocate_opp(struct device *dev,
struct opp_table **opp_table)
{
struct dev_pm_opp *opp;
+ int count, supply_size;
+ struct opp_table *table;
- /* allocate new OPP node */
- opp = kzalloc(sizeof(*opp), GFP_KERNEL);
- if (!opp)
+ table = _add_opp_table(dev);
+ if (!table)
return NULL;
- INIT_LIST_HEAD(&opp->node);
+ /* Allocate space for at least one supply */
+ count = table->regulator_count ? table->regulator_count : 1;
+ supply_size = sizeof(*opp->supplies) * count;
- *opp_table = _add_opp_table(dev);
- if (!*opp_table) {
- kfree(opp);
+ /* allocate new OPP node and supplies structures */
+ opp = kzalloc(sizeof(*opp) + supply_size, GFP_KERNEL);
+ if (!opp) {
+ kfree(table);
return NULL;
}
+ /* Put the supplies at the end of the OPP structure as an empty array */
+ opp->supplies = (struct dev_pm_opp_supply *)(opp + 1);
+ INIT_LIST_HEAD(&opp->node);
+
+ *opp_table = table;
+
return opp;
}
static bool _opp_supported_by_regulators(struct dev_pm_opp *opp,
struct opp_table *opp_table)
{
- struct regulator *reg = opp_table->regulator;
-
- if (!IS_ERR(reg) &&
- !regulator_is_supported_voltage(reg, opp->supply.u_volt_min,
- opp->supply.u_volt_max)) {
- pr_warn("%s: OPP minuV: %lu maxuV: %lu, not supported by regulator\n",
- __func__, opp->supply.u_volt_min,
- opp->supply.u_volt_max);
- return false;
+ struct regulator *reg;
+ int i;
+
+ for (i = 0; i < opp_table->regulator_count; i++) {
+ reg = opp_table->regulators[i];
+
+ if (!regulator_is_supported_voltage(reg,
+ opp->supplies[i].u_volt_min,
+ opp->supplies[i].u_volt_max)) {
+ pr_warn("%s: OPP minuV: %lu maxuV: %lu, not supported by regulator\n",
+ __func__, opp->supplies[i].u_volt_min,
+ opp->supplies[i].u_volt_max);
+ return false;
+ }
}
return true;
@@ -984,12 +1049,13 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp,
/* Duplicate OPPs */
dev_warn(dev, "%s: duplicate OPPs detected. Existing: freq: %lu, volt: %lu, enabled: %d. New: freq: %lu, volt: %lu, enabled: %d\n",
- __func__, opp->rate, opp->supply.u_volt,
- opp->available, new_opp->rate, new_opp->supply.u_volt,
- new_opp->available);
+ __func__, opp->rate, opp->supplies[0].u_volt,
+ opp->available, new_opp->rate,
+ new_opp->supplies[0].u_volt, new_opp->available);
+ /* Should we compare voltages for all regulators here ? */
return opp->available &&
- new_opp->supply.u_volt == opp->supply.u_volt ? 0 : -EEXIST;
+ new_opp->supplies[0].u_volt == opp->supplies[0].u_volt ? 0 : -EEXIST;
}
new_opp->opp_table = opp_table;
@@ -1056,9 +1122,9 @@ int _opp_add_v1(struct device *dev, unsigned long freq, long u_volt,
/* populate the opp table */
new_opp->rate = freq;
tol = u_volt * opp_table->voltage_tolerance_v1 / 100;
- new_opp->supply.u_volt = u_volt;
- new_opp->supply.u_volt_min = u_volt - tol;
- new_opp->supply.u_volt_max = u_volt + tol;
+ new_opp->supplies[0].u_volt = u_volt;
+ new_opp->supplies[0].u_volt_min = u_volt - tol;
+ new_opp->supplies[0].u_volt_max = u_volt + tol;
new_opp->available = true;
new_opp->dynamic = dynamic;
@@ -1303,12 +1369,14 @@ void dev_pm_opp_put_prop_name(struct device *dev)
EXPORT_SYMBOL_GPL(dev_pm_opp_put_prop_name);
/**
- * dev_pm_opp_set_regulator() - Set regulator name for the device
+ * dev_pm_opp_set_regulators() - Set regulator names for the device
* @dev: Device for which regulator name is being set.
- * @name: Name of the regulator.
+ * @names: Array of pointers to the names of the regulator.
+ * @count: Number of regulators.
*
* In order to support OPP switching, OPP layer needs to know the name of the
- * device's regulator, as the core would be required to switch voltages as well.
+ * device's regulators, as the core would be required to switch voltages as
+ * well.
*
* This must be called before any OPPs are initialized for the device.
*
@@ -1318,10 +1386,13 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_put_prop_name);
* that this function is *NOT* called under RCU protection or in contexts where
* mutex cannot be locked.
*/
-struct opp_table *dev_pm_opp_set_regulator(struct device *dev, const char *name)
+struct opp_table *dev_pm_opp_set_regulators(struct device *dev,
+ const char * const names[],
+ unsigned int count)
{
struct opp_table *opp_table;
struct regulator *reg;
+ int ret, i;
mutex_lock(&opp_table_lock);
@@ -1337,26 +1408,44 @@ struct opp_table *dev_pm_opp_set_regulator(struct device *dev, const char *name)
goto err;
}
- /* Already have a regulator set */
- if (WARN_ON(!IS_ERR(opp_table->regulator))) {
+ /* Already have regulators set */
+ if (WARN_ON(opp_table->regulators)) {
opp_table = ERR_PTR(-EBUSY);
goto err;
}
- /* Allocate the regulator */
- reg = regulator_get_optional(dev, name);
- if (IS_ERR(reg)) {
- opp_table = (struct opp_table *)reg;
- if (PTR_ERR(reg) != -EPROBE_DEFER)
- dev_err(dev, "%s: no regulator (%s) found: %ld\n",
- __func__, name, PTR_ERR(reg));
+
+ opp_table->regulators = kmalloc_array(count,
+ sizeof(*opp_table->regulators),
+ GFP_KERNEL);
+ if (!opp_table->regulators) {
+ opp_table = ERR_PTR(-ENOMEM);
goto err;
}
- opp_table->regulator = reg;
+ for (i = 0; i < count; i++) {
+ reg = regulator_get_optional(dev, names[i]);
+ if (IS_ERR(reg)) {
+ opp_table = (struct opp_table *)reg;
+ if (PTR_ERR(reg) != -EPROBE_DEFER)
+ dev_err(dev, "%s: no regulator (%s) found: %ld\n",
+ __func__, names[i], PTR_ERR(reg));
+ goto free_regulators;
+ }
+
+ opp_table->regulators[i] = reg;
+ }
+
+ opp_table->regulator_count = count;
mutex_unlock(&opp_table_lock);
return opp_table;
+free_regulators:
+ while (i != 0)
+ regulator_put(opp_table->regulators[--i]);
+
+ kfree(opp_table->regulators);
+ opp_table->regulators = NULL;
err:
_remove_opp_table(opp_table);
unlock:
@@ -1364,10 +1453,10 @@ struct opp_table *dev_pm_opp_set_regulator(struct device *dev, const char *name)
return opp_table;
}
-EXPORT_SYMBOL_GPL(dev_pm_opp_set_regulator);
+EXPORT_SYMBOL_GPL(dev_pm_opp_set_regulators);
/**
- * dev_pm_opp_put_regulator() - Releases resources blocked for regulator
+ * dev_pm_opp_put_regulators() - Releases resources blocked for regulators
* @opp_table: opp_table returned from dev_pm_opp_set_regulator
*
* Locking: The internal opp_table and opp structures are RCU protected.
@@ -1376,20 +1465,26 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_set_regulator);
* that this function is *NOT* called under RCU protection or in contexts where
* mutex cannot be locked.
*/
-void dev_pm_opp_put_regulator(struct opp_table *opp_table)
+void dev_pm_opp_put_regulators(struct opp_table *opp_table)
{
+ int i;
+
mutex_lock(&opp_table_lock);
- if (IS_ERR(opp_table->regulator)) {
- pr_err("%s: Doesn't have regulator set\n", __func__);
+ if (!opp_table->regulators) {
+ pr_err("%s: Doesn't have regulators set\n", __func__);
goto unlock;
}
/* Make sure there are no concurrent readers while updating opp_table */
WARN_ON(!list_empty(&opp_table->opp_list));
- regulator_put(opp_table->regulator);
- opp_table->regulator = ERR_PTR(-ENXIO);
+ for (i = opp_table->regulator_count - 1; i >= 0; i--)
+ regulator_put(opp_table->regulators[i]);
+
+ kfree(opp_table->regulators);
+ opp_table->regulators = NULL;
+ opp_table->regulator_count = 0;
/* Try freeing opp_table if this was the last blocking resource */
_remove_opp_table(opp_table);
@@ -1397,7 +1492,7 @@ void dev_pm_opp_put_regulator(struct opp_table *opp_table)
unlock:
mutex_unlock(&opp_table_lock);
}
-EXPORT_SYMBOL_GPL(dev_pm_opp_put_regulator);
+EXPORT_SYMBOL_GPL(dev_pm_opp_put_regulators);
/**
* dev_pm_opp_add() - Add an OPP table from a table definitions
diff --git a/drivers/base/power/opp/debugfs.c b/drivers/base/power/opp/debugfs.c
index c897676ca35f..95f433db4ac7 100644
--- a/drivers/base/power/opp/debugfs.c
+++ b/drivers/base/power/opp/debugfs.c
@@ -15,6 +15,7 @@
#include <linux/err.h>
#include <linux/init.h>
#include <linux/limits.h>
+#include <linux/slab.h>
#include "opp.h"
@@ -34,6 +35,46 @@ void opp_debug_remove_one(struct dev_pm_opp *opp)
debugfs_remove_recursive(opp->dentry);
}
+static bool opp_debug_create_supplies(struct dev_pm_opp *opp,
+ struct opp_table *opp_table,
+ struct dentry *pdentry)
+{
+ struct dentry *d;
+ int i = 0;
+ char *name;
+
+ /* Always create at least supply-0 directory */
+ do {
+ name = kasprintf(GFP_KERNEL, "supply-%d", i);
+
+ /* Create per-opp directory */
+ d = debugfs_create_dir(name, pdentry);
+
+ kfree(name);
+
+ if (!d)
+ return false;
+
+ if (!debugfs_create_ulong("u_volt_target", S_IRUGO, d,
+ &opp->supplies[i].u_volt))
+ return false;
+
+ if (!debugfs_create_ulong("u_volt_min", S_IRUGO, d,
+ &opp->supplies[i].u_volt_min))
+ return false;
+
+ if (!debugfs_create_ulong("u_volt_max", S_IRUGO, d,
+ &opp->supplies[i].u_volt_max))
+ return false;
+
+ if (!debugfs_create_ulong("u_amp", S_IRUGO, d,
+ &opp->supplies[i].u_amp))
+ return false;
+ } while (++i < opp_table->regulator_count);
+
+ return true;
+}
+
int opp_debug_create_one(struct dev_pm_opp *opp, struct opp_table *opp_table)
{
struct dentry *pdentry = opp_table->dentry;
@@ -63,16 +104,7 @@ int opp_debug_create_one(struct dev_pm_opp *opp, struct opp_table *opp_table)
if (!debugfs_create_ulong("rate_hz", S_IRUGO, d, &opp->rate))
return -ENOMEM;
- if (!debugfs_create_ulong("u_volt_target", S_IRUGO, d, &opp->supply.u_volt))
- return -ENOMEM;
-
- if (!debugfs_create_ulong("u_volt_min", S_IRUGO, d, &opp->supply.u_volt_min))
- return -ENOMEM;
-
- if (!debugfs_create_ulong("u_volt_max", S_IRUGO, d, &opp->supply.u_volt_max))
- return -ENOMEM;
-
- if (!debugfs_create_ulong("u_amp", S_IRUGO, d, &opp->supply.u_amp))
+ if (!opp_debug_create_supplies(opp, opp_table, d))
return -ENOMEM;
if (!debugfs_create_ulong("clock_latency_ns", S_IRUGO, d,
diff --git a/drivers/base/power/opp/of.c b/drivers/base/power/opp/of.c
index bdf409d42126..3f7d2591b173 100644
--- a/drivers/base/power/opp/of.c
+++ b/drivers/base/power/opp/of.c
@@ -17,6 +17,7 @@
#include <linux/errno.h>
#include <linux/device.h>
#include <linux/of.h>
+#include <linux/slab.h>
#include <linux/export.h>
#include "opp.h"
@@ -101,16 +102,16 @@ static bool _opp_is_supported(struct device *dev, struct opp_table *opp_table,
return true;
}
-/* TODO: Support multiple regulators */
static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev,
struct opp_table *opp_table)
{
- u32 microvolt[3] = {0};
- u32 val;
- int count, ret;
+ u32 *microvolt, *microamp = NULL;
+ int supplies, vcount, icount, ret, i, j;
struct property *prop = NULL;
char name[NAME_MAX];
+ supplies = opp_table->regulator_count ? opp_table->regulator_count : 1;
+
/* Search for "opp-microvolt-<name>" */
if (opp_table->prop_name) {
snprintf(name, sizeof(name), "opp-microvolt-%s",
@@ -128,34 +129,29 @@ static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev,
return 0;
}
- count = of_property_count_u32_elems(opp->np, name);
- if (count < 0) {
+ vcount = of_property_count_u32_elems(opp->np, name);
+ if (vcount < 0) {
dev_err(dev, "%s: Invalid %s property (%d)\n",
- __func__, name, count);
- return count;
+ __func__, name, vcount);
+ return vcount;
}
- /* There can be one or three elements here */
- if (count != 1 && count != 3) {
- dev_err(dev, "%s: Invalid number of elements in %s property (%d)\n",
- __func__, name, count);
+ /* There can be one or three elements per supply */
+ if (vcount != supplies && vcount != supplies * 3) {
+ dev_err(dev, "%s: Invalid number of elements in %s property (%d) with supplies (%d)\n",
+ __func__, name, vcount, supplies);
return -EINVAL;
}
- ret = of_property_read_u32_array(opp->np, name, microvolt, count);
+ microvolt = kmalloc_array(vcount, sizeof(*microvolt), GFP_KERNEL);
+ if (!microvolt)
+ return -ENOMEM;
+
+ ret = of_property_read_u32_array(opp->np, name, microvolt, vcount);
if (ret) {
dev_err(dev, "%s: error parsing %s: %d\n", __func__, name, ret);
- return -EINVAL;
- }
-
- opp->supply.u_volt = microvolt[0];
-
- if (count == 1) {
- opp->supply.u_volt_min = opp->supply.u_volt;
- opp->supply.u_volt_max = opp->supply.u_volt;
- } else {
- opp->supply.u_volt_min = microvolt[1];
- opp->supply.u_volt_max = microvolt[2];
+ ret = -EINVAL;
+ goto free_microvolt;
}
/* Search for "opp-microamp-<name>" */
@@ -172,10 +168,59 @@ static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev,
prop = of_find_property(opp->np, name, NULL);
}
- if (prop && !of_property_read_u32(opp->np, name, &val))
- opp->supply.u_amp = val;
+ if (prop) {
+ icount = of_property_count_u32_elems(opp->np, name);
+ if (icount < 0) {
+ dev_err(dev, "%s: Invalid %s property (%d)\n", __func__,
+ name, icount);
+ ret = icount;
+ goto free_microvolt;
+ }
- return 0;
+ if (icount != supplies) {
+ dev_err(dev, "%s: Invalid number of elements in %s property (%d) with supplies (%d)\n",
+ __func__, name, icount, supplies);
+ ret = -EINVAL;
+ goto free_microvolt;
+ }
+
+ microamp = kmalloc_array(icount, sizeof(*microamp), GFP_KERNEL);
+ if (!microamp) {
+ ret = -EINVAL;
+ goto free_microvolt;
+ }
+
+ ret = of_property_read_u32_array(opp->np, name, microamp,
+ icount);
+ if (ret) {
+ dev_err(dev, "%s: error parsing %s: %d\n", __func__,
+ name, ret);
+ ret = -EINVAL;
+ goto free_microamp;
+ }
+ }
+
+ for (i = 0, j = 0; i < supplies; i++) {
+ opp->supplies[i].u_volt = microvolt[j++];
+
+ if (vcount == supplies) {
+ opp->supplies[i].u_volt_min = opp->supplies[i].u_volt;
+ opp->supplies[i].u_volt_max = opp->supplies[i].u_volt;
+ } else {
+ opp->supplies[i].u_volt_min = microvolt[j++];
+ opp->supplies[i].u_volt_max = microvolt[j++];
+ }
+
+ if (microamp)
+ opp->supplies[i].u_amp = microamp[i];
+ }
+
+free_microamp:
+ kfree(microamp);
+free_microvolt:
+ kfree(microvolt);
+
+ return ret;
}
/**
@@ -304,8 +349,8 @@ static int _opp_add_static_v2(struct device *dev, struct device_node *np)
pr_debug("%s: turbo:%d rate:%lu uv:%lu uvmin:%lu uvmax:%lu latency:%lu\n",
__func__, new_opp->turbo, new_opp->rate,
- new_opp->supply.u_volt, new_opp->supply.u_volt_min,
- new_opp->supply.u_volt_max, new_opp->clock_latency_ns);
+ new_opp->supplies[0].u_volt, new_opp->supplies[0].u_volt_min,
+ new_opp->supplies[0].u_volt_max, new_opp->clock_latency_ns);
/*
* Notify the changes in the availability of the operable
diff --git a/drivers/base/power/opp/opp.h b/drivers/base/power/opp/opp.h
index 8a02516542c2..5b0f7e53bede 100644
--- a/drivers/base/power/opp/opp.h
+++ b/drivers/base/power/opp/opp.h
@@ -61,7 +61,7 @@ extern struct list_head opp_tables;
* @turbo: true if turbo (boost) OPP
* @suspend: true if suspend OPP
* @rate: Frequency in hertz
- * @supply: Power supply voltage/current values
+ * @supplies: Power supplies voltage/current values
* @clock_latency_ns: Latency (in nanoseconds) of switching to this OPP's
* frequency from any other OPP's frequency.
* @opp_table: points back to the opp_table struct this opp belongs to
@@ -80,7 +80,7 @@ struct dev_pm_opp {
bool suspend;
unsigned long rate;
- struct dev_pm_opp_supply supply;
+ struct dev_pm_opp_supply *supplies;
unsigned long clock_latency_ns;
@@ -139,7 +139,8 @@ enum opp_table_access {
* @supported_hw_count: Number of elements in supported_hw array.
* @prop_name: A name to postfix to many DT properties, while parsing them.
* @clk: Device's clock handle
- * @regulator: Supply regulator
+ * @regulators: Supply regulators
+ * @regulator_count: Number of power supply regulators
* @dentry: debugfs dentry pointer of the real device directory (not links).
* @dentry_name: Name of the real dentry.
*
@@ -174,7 +175,8 @@ struct opp_table {
unsigned int supported_hw_count;
const char *prop_name;
struct clk *clk;
- struct regulator *regulator;
+ struct regulator **regulators;
+ unsigned int regulator_count;
#ifdef CONFIG_DEBUG_FS
struct dentry *dentry;
diff --git a/drivers/cpufreq/cpufreq-dt.c b/drivers/cpufreq/cpufreq-dt.c
index 4d3ec92cbabf..1cd1fcfe835a 100644
--- a/drivers/cpufreq/cpufreq-dt.c
+++ b/drivers/cpufreq/cpufreq-dt.c
@@ -188,7 +188,10 @@ static int cpufreq_init(struct cpufreq_policy *policy)
*/
name = find_supply_name(cpu_dev);
if (name) {
- opp_table = dev_pm_opp_set_regulator(cpu_dev, name);
+ const char *names[] = {name};
+
+ opp_table = dev_pm_opp_set_regulators(cpu_dev, names,
+ ARRAY_SIZE(names));
if (IS_ERR(opp_table)) {
ret = PTR_ERR(opp_table);
dev_err(cpu_dev, "Failed to set regulator for cpu%d: %d\n",
@@ -289,7 +292,7 @@ static int cpufreq_init(struct cpufreq_policy *policy)
out_free_opp:
dev_pm_opp_of_cpumask_remove_table(policy->cpus);
if (name)
- dev_pm_opp_put_regulator(opp_table);
+ dev_pm_opp_put_regulators(opp_table);
out_put_clk:
clk_put(cpu_clk);
@@ -304,7 +307,7 @@ static int cpufreq_exit(struct cpufreq_policy *policy)
dev_pm_opp_free_cpufreq_table(priv->cpu_dev, &policy->freq_table);
dev_pm_opp_of_cpumask_remove_table(policy->related_cpus);
if (priv->reg_name)
- dev_pm_opp_put_regulator(priv->opp_table);
+ dev_pm_opp_put_regulators(priv->opp_table);
clk_put(policy->clk);
kfree(priv);
diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h
index 824f7268f687..9a825ae78653 100644
--- a/include/linux/pm_opp.h
+++ b/include/linux/pm_opp.h
@@ -79,8 +79,8 @@ int dev_pm_opp_set_supported_hw(struct device *dev, const u32 *versions,
void dev_pm_opp_put_supported_hw(struct device *dev);
int dev_pm_opp_set_prop_name(struct device *dev, const char *name);
void dev_pm_opp_put_prop_name(struct device *dev);
-struct opp_table *dev_pm_opp_set_regulator(struct device *dev, const char *name);
-void dev_pm_opp_put_regulator(struct opp_table *opp_table);
+struct opp_table *dev_pm_opp_set_regulators(struct device *dev, const char * const names[], unsigned int count);
+void dev_pm_opp_put_regulators(struct opp_table *opp_table);
int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq);
int dev_pm_opp_set_sharing_cpus(struct device *cpu_dev, const struct cpumask *cpumask);
int dev_pm_opp_get_sharing_cpus(struct device *cpu_dev, struct cpumask *cpumask);
@@ -187,12 +187,12 @@ static inline int dev_pm_opp_set_prop_name(struct device *dev, const char *name)
static inline void dev_pm_opp_put_prop_name(struct device *dev) {}
-static inline struct opp_table *dev_pm_opp_set_regulator(struct device *dev, const char *name)
+static inline struct opp_table *dev_pm_opp_set_regulators(struct device *dev, const char * const names[], unsigned int count)
{
return ERR_PTR(-ENOTSUPP);
}
-static inline void dev_pm_opp_put_regulator(struct opp_table *opp_table) {}
+static inline void dev_pm_opp_put_regulators(struct opp_table *opp_table) {}
static inline int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
{
--
2.7.1.410.g6faf27b
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related
* [PATCH V6 05/10] PM / OPP: Pass struct dev_pm_opp_supply to _set_opp_voltage()
From: Viresh Kumar @ 2016-11-30 5:14 UTC (permalink / raw)
To: Rafael Wysocki, Viresh Kumar, Nishanth Menon, Stephen Boyd
Cc: linaro-kernel, linux-pm, Vincent Guittot, robh, d-gerlach,
broonie, devicetree, Viresh Kumar
In-Reply-To: <cover.1480481312.git.viresh.kumar@linaro.org>
Pass the entire supply structure instead of all of its fields.
Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
Tested-by: Dave Gerlach <d-gerlach@ti.com>
Reviewed-by: Stephen Boyd <sboyd@codeaurora.org>
---
drivers/base/power/opp/core.c | 44 +++++++++++++++++--------------------------
1 file changed, 17 insertions(+), 27 deletions(-)
diff --git a/drivers/base/power/opp/core.c b/drivers/base/power/opp/core.c
index f706a961815d..8328ff06cc08 100644
--- a/drivers/base/power/opp/core.c
+++ b/drivers/base/power/opp/core.c
@@ -542,8 +542,7 @@ static struct clk *_get_opp_clk(struct device *dev)
}
static int _set_opp_voltage(struct device *dev, struct regulator *reg,
- unsigned long u_volt, unsigned long u_volt_min,
- unsigned long u_volt_max)
+ struct dev_pm_opp_supply *supply)
{
int ret;
@@ -554,14 +553,15 @@ static int _set_opp_voltage(struct device *dev, struct regulator *reg,
return 0;
}
- dev_dbg(dev, "%s: voltages (mV): %lu %lu %lu\n", __func__, u_volt_min,
- u_volt, u_volt_max);
+ dev_dbg(dev, "%s: voltages (mV): %lu %lu %lu\n", __func__,
+ supply->u_volt_min, supply->u_volt, supply->u_volt_max);
- ret = regulator_set_voltage_triplet(reg, u_volt_min, u_volt,
- u_volt_max);
+ ret = regulator_set_voltage_triplet(reg, supply->u_volt_min,
+ supply->u_volt, supply->u_volt_max);
if (ret)
dev_err(dev, "%s: failed to set voltage (%lu %lu %lu mV): %d\n",
- __func__, u_volt_min, u_volt, u_volt_max, ret);
+ __func__, supply->u_volt_min, supply->u_volt,
+ supply->u_volt_max, ret);
return ret;
}
@@ -583,8 +583,7 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
struct regulator *reg;
struct clk *clk;
unsigned long freq, old_freq;
- unsigned long u_volt, u_volt_min, u_volt_max;
- unsigned long old_u_volt, old_u_volt_min, old_u_volt_max;
+ struct dev_pm_opp_supply old_supply, new_supply;
int ret;
if (unlikely(!target_freq)) {
@@ -634,17 +633,12 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
return ret;
}
- if (IS_ERR(old_opp)) {
- old_u_volt = 0;
- } else {
- old_u_volt = old_opp->supply.u_volt;
- old_u_volt_min = old_opp->supply.u_volt_min;
- old_u_volt_max = old_opp->supply.u_volt_max;
- }
+ if (IS_ERR(old_opp))
+ old_supply.u_volt = 0;
+ else
+ memcpy(&old_supply, &old_opp->supply, sizeof(old_supply));
- u_volt = opp->supply.u_volt;
- u_volt_min = opp->supply.u_volt_min;
- u_volt_max = opp->supply.u_volt_max;
+ memcpy(&new_supply, &opp->supply, sizeof(new_supply));
reg = opp_table->regulator;
@@ -652,8 +646,7 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
/* Scaling up? Scale voltage before frequency */
if (freq > old_freq) {
- ret = _set_opp_voltage(dev, reg, u_volt, u_volt_min,
- u_volt_max);
+ ret = _set_opp_voltage(dev, reg, &new_supply);
if (ret)
goto restore_voltage;
}
@@ -672,8 +665,7 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
/* Scaling down? Scale voltage after frequency */
if (freq < old_freq) {
- ret = _set_opp_voltage(dev, reg, u_volt, u_volt_min,
- u_volt_max);
+ ret = _set_opp_voltage(dev, reg, &new_supply);
if (ret)
goto restore_freq;
}
@@ -686,10 +678,8 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
__func__, old_freq);
restore_voltage:
/* This shouldn't harm even if the voltages weren't updated earlier */
- if (old_u_volt) {
- _set_opp_voltage(dev, reg, old_u_volt, old_u_volt_min,
- old_u_volt_max);
- }
+ if (old_supply.u_volt)
+ _set_opp_voltage(dev, reg, &old_supply);
return ret;
}
--
2.7.1.410.g6faf27b
^ permalink raw reply related
* [PATCH V6 04/10] PM / OPP: Manage supply's voltage/current in a separate structure
From: Viresh Kumar @ 2016-11-30 5:14 UTC (permalink / raw)
To: Rafael Wysocki, Viresh Kumar, Nishanth Menon, Stephen Boyd
Cc: linaro-kernel, linux-pm, Vincent Guittot, robh, d-gerlach,
broonie, devicetree, Viresh Kumar
In-Reply-To: <cover.1480481312.git.viresh.kumar@linaro.org>
This is a preparatory step for multiple regulator per device support.
Move the voltage/current variables to a new structure.
Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
Tested-by: Dave Gerlach <d-gerlach@ti.com>
Reviewed-by: Stephen Boyd <sboyd@codeaurora.org>
---
drivers/base/power/opp/core.c | 44 +++++++++++++++++++++-------------------
drivers/base/power/opp/debugfs.c | 8 ++++----
drivers/base/power/opp/of.c | 18 ++++++++--------
drivers/base/power/opp/opp.h | 11 +++-------
include/linux/pm_opp.h | 16 +++++++++++++++
5 files changed, 55 insertions(+), 42 deletions(-)
diff --git a/drivers/base/power/opp/core.c b/drivers/base/power/opp/core.c
index 705137da3954..f706a961815d 100644
--- a/drivers/base/power/opp/core.c
+++ b/drivers/base/power/opp/core.c
@@ -112,7 +112,7 @@ unsigned long dev_pm_opp_get_voltage(struct dev_pm_opp *opp)
if (IS_ERR_OR_NULL(tmp_opp))
pr_err("%s: Invalid parameters\n", __func__);
else
- v = tmp_opp->u_volt;
+ v = tmp_opp->supply.u_volt;
return v;
}
@@ -246,10 +246,10 @@ unsigned long dev_pm_opp_get_max_volt_latency(struct device *dev)
if (!opp->available)
continue;
- if (opp->u_volt_min < min_uV)
- min_uV = opp->u_volt_min;
- if (opp->u_volt_max > max_uV)
- max_uV = opp->u_volt_max;
+ if (opp->supply.u_volt_min < min_uV)
+ min_uV = opp->supply.u_volt_min;
+ if (opp->supply.u_volt_max > max_uV)
+ max_uV = opp->supply.u_volt_max;
}
rcu_read_unlock();
@@ -637,14 +637,14 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
if (IS_ERR(old_opp)) {
old_u_volt = 0;
} else {
- old_u_volt = old_opp->u_volt;
- old_u_volt_min = old_opp->u_volt_min;
- old_u_volt_max = old_opp->u_volt_max;
+ old_u_volt = old_opp->supply.u_volt;
+ old_u_volt_min = old_opp->supply.u_volt_min;
+ old_u_volt_max = old_opp->supply.u_volt_max;
}
- u_volt = opp->u_volt;
- u_volt_min = opp->u_volt_min;
- u_volt_max = opp->u_volt_max;
+ u_volt = opp->supply.u_volt;
+ u_volt_min = opp->supply.u_volt_min;
+ u_volt_max = opp->supply.u_volt_max;
reg = opp_table->regulator;
@@ -957,10 +957,11 @@ static bool _opp_supported_by_regulators(struct dev_pm_opp *opp,
struct regulator *reg = opp_table->regulator;
if (!IS_ERR(reg) &&
- !regulator_is_supported_voltage(reg, opp->u_volt_min,
- opp->u_volt_max)) {
+ !regulator_is_supported_voltage(reg, opp->supply.u_volt_min,
+ opp->supply.u_volt_max)) {
pr_warn("%s: OPP minuV: %lu maxuV: %lu, not supported by regulator\n",
- __func__, opp->u_volt_min, opp->u_volt_max);
+ __func__, opp->supply.u_volt_min,
+ opp->supply.u_volt_max);
return false;
}
@@ -993,11 +994,12 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp,
/* Duplicate OPPs */
dev_warn(dev, "%s: duplicate OPPs detected. Existing: freq: %lu, volt: %lu, enabled: %d. New: freq: %lu, volt: %lu, enabled: %d\n",
- __func__, opp->rate, opp->u_volt, opp->available,
- new_opp->rate, new_opp->u_volt, new_opp->available);
+ __func__, opp->rate, opp->supply.u_volt,
+ opp->available, new_opp->rate, new_opp->supply.u_volt,
+ new_opp->available);
- return opp->available && new_opp->u_volt == opp->u_volt ?
- 0 : -EEXIST;
+ return opp->available &&
+ new_opp->supply.u_volt == opp->supply.u_volt ? 0 : -EEXIST;
}
new_opp->opp_table = opp_table;
@@ -1064,9 +1066,9 @@ int _opp_add_v1(struct device *dev, unsigned long freq, long u_volt,
/* populate the opp table */
new_opp->rate = freq;
tol = u_volt * opp_table->voltage_tolerance_v1 / 100;
- new_opp->u_volt = u_volt;
- new_opp->u_volt_min = u_volt - tol;
- new_opp->u_volt_max = u_volt + tol;
+ new_opp->supply.u_volt = u_volt;
+ new_opp->supply.u_volt_min = u_volt - tol;
+ new_opp->supply.u_volt_max = u_volt + tol;
new_opp->available = true;
new_opp->dynamic = dynamic;
diff --git a/drivers/base/power/opp/debugfs.c b/drivers/base/power/opp/debugfs.c
index ef1ae6b52042..c897676ca35f 100644
--- a/drivers/base/power/opp/debugfs.c
+++ b/drivers/base/power/opp/debugfs.c
@@ -63,16 +63,16 @@ int opp_debug_create_one(struct dev_pm_opp *opp, struct opp_table *opp_table)
if (!debugfs_create_ulong("rate_hz", S_IRUGO, d, &opp->rate))
return -ENOMEM;
- if (!debugfs_create_ulong("u_volt_target", S_IRUGO, d, &opp->u_volt))
+ if (!debugfs_create_ulong("u_volt_target", S_IRUGO, d, &opp->supply.u_volt))
return -ENOMEM;
- if (!debugfs_create_ulong("u_volt_min", S_IRUGO, d, &opp->u_volt_min))
+ if (!debugfs_create_ulong("u_volt_min", S_IRUGO, d, &opp->supply.u_volt_min))
return -ENOMEM;
- if (!debugfs_create_ulong("u_volt_max", S_IRUGO, d, &opp->u_volt_max))
+ if (!debugfs_create_ulong("u_volt_max", S_IRUGO, d, &opp->supply.u_volt_max))
return -ENOMEM;
- if (!debugfs_create_ulong("u_amp", S_IRUGO, d, &opp->u_amp))
+ if (!debugfs_create_ulong("u_amp", S_IRUGO, d, &opp->supply.u_amp))
return -ENOMEM;
if (!debugfs_create_ulong("clock_latency_ns", S_IRUGO, d,
diff --git a/drivers/base/power/opp/of.c b/drivers/base/power/opp/of.c
index 5b3755e49731..bdf409d42126 100644
--- a/drivers/base/power/opp/of.c
+++ b/drivers/base/power/opp/of.c
@@ -148,14 +148,14 @@ static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev,
return -EINVAL;
}
- opp->u_volt = microvolt[0];
+ opp->supply.u_volt = microvolt[0];
if (count == 1) {
- opp->u_volt_min = opp->u_volt;
- opp->u_volt_max = opp->u_volt;
+ opp->supply.u_volt_min = opp->supply.u_volt;
+ opp->supply.u_volt_max = opp->supply.u_volt;
} else {
- opp->u_volt_min = microvolt[1];
- opp->u_volt_max = microvolt[2];
+ opp->supply.u_volt_min = microvolt[1];
+ opp->supply.u_volt_max = microvolt[2];
}
/* Search for "opp-microamp-<name>" */
@@ -173,7 +173,7 @@ static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev,
}
if (prop && !of_property_read_u32(opp->np, name, &val))
- opp->u_amp = val;
+ opp->supply.u_amp = val;
return 0;
}
@@ -303,9 +303,9 @@ static int _opp_add_static_v2(struct device *dev, struct device_node *np)
mutex_unlock(&opp_table_lock);
pr_debug("%s: turbo:%d rate:%lu uv:%lu uvmin:%lu uvmax:%lu latency:%lu\n",
- __func__, new_opp->turbo, new_opp->rate, new_opp->u_volt,
- new_opp->u_volt_min, new_opp->u_volt_max,
- new_opp->clock_latency_ns);
+ __func__, new_opp->turbo, new_opp->rate,
+ new_opp->supply.u_volt, new_opp->supply.u_volt_min,
+ new_opp->supply.u_volt_max, new_opp->clock_latency_ns);
/*
* Notify the changes in the availability of the operable
diff --git a/drivers/base/power/opp/opp.h b/drivers/base/power/opp/opp.h
index 96cd30ac6c1d..8a02516542c2 100644
--- a/drivers/base/power/opp/opp.h
+++ b/drivers/base/power/opp/opp.h
@@ -61,10 +61,7 @@ extern struct list_head opp_tables;
* @turbo: true if turbo (boost) OPP
* @suspend: true if suspend OPP
* @rate: Frequency in hertz
- * @u_volt: Target voltage in microvolts corresponding to this OPP
- * @u_volt_min: Minimum voltage in microvolts corresponding to this OPP
- * @u_volt_max: Maximum voltage in microvolts corresponding to this OPP
- * @u_amp: Maximum current drawn by the device in microamperes
+ * @supply: Power supply voltage/current values
* @clock_latency_ns: Latency (in nanoseconds) of switching to this OPP's
* frequency from any other OPP's frequency.
* @opp_table: points back to the opp_table struct this opp belongs to
@@ -83,10 +80,8 @@ struct dev_pm_opp {
bool suspend;
unsigned long rate;
- unsigned long u_volt;
- unsigned long u_volt_min;
- unsigned long u_volt_max;
- unsigned long u_amp;
+ struct dev_pm_opp_supply supply;
+
unsigned long clock_latency_ns;
struct opp_table *opp_table;
diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h
index f6bc76501912..824f7268f687 100644
--- a/include/linux/pm_opp.h
+++ b/include/linux/pm_opp.h
@@ -25,6 +25,22 @@ enum dev_pm_opp_event {
OPP_EVENT_ADD, OPP_EVENT_REMOVE, OPP_EVENT_ENABLE, OPP_EVENT_DISABLE,
};
+/**
+ * struct dev_pm_opp_supply - Power supply voltage/current values
+ * @u_volt: Target voltage in microvolts corresponding to this OPP
+ * @u_volt_min: Minimum voltage in microvolts corresponding to this OPP
+ * @u_volt_max: Maximum voltage in microvolts corresponding to this OPP
+ * @u_amp: Maximum current drawn by the device in microamperes
+ *
+ * This structure stores the voltage/current values for a single power supply.
+ */
+struct dev_pm_opp_supply {
+ unsigned long u_volt;
+ unsigned long u_volt_min;
+ unsigned long u_volt_max;
+ unsigned long u_amp;
+};
+
#if defined(CONFIG_PM_OPP)
unsigned long dev_pm_opp_get_voltage(struct dev_pm_opp *opp);
--
2.7.1.410.g6faf27b
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox