* [PATCH v6 01/12] ACPI / property: Add possiblity to retrieve parent firmware node
From: Sakari Ailus @ 2017-03-28 7:52 UTC (permalink / raw)
To: linux-acpi-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA, rafael-DgEjT+Ai2ygdnm+yROfE0A
Cc: sudeep.holla-5wv7dgnIgG8, lorenzo.pieralisi-5wv7dgnIgG8,
mika.westerberg-VuQAYsv1563Yd54FQh9/CA, mark.rutland-5wv7dgnIgG8,
broonie-DgEjT+Ai2ygdnm+yROfE0A, robh-DgEjT+Ai2ygdnm+yROfE0A,
ahs3-H+wXaHxf7aLQT0dZR+AlfA, frowand.list-Re5JQEeQqe8AvxtiuMwx3w
In-Reply-To: <1490687547-18311-1-git-send-email-sakari.ailus-VuQAYsv1563Yd54FQh9/CA@public.gmane.org>
From: Mika Westerberg <mika.westerberg-VuQAYsv1563Yd54FQh9/CA@public.gmane.org>
Sometimes it is useful to be able to navigate firmware node hierarchy
upwards toward parent nodes. ACPI device nodes are pretty much already
supported because ACPICA provides acpi_get_parent(). ACPI data nodes,
however, are all below the same parent ACPI device. Their hierarchy is
created by "linking" each other using references in the value field.
Add parent pointer to the parent data node while we create them so it is
easy to navigate the hierarchy backwards. We use this parent pointer in a
new function acpi_node_get_parent() that is able to extract parent of both
ACPI firmware node types.
Signed-off-by: Mika Westerberg <mika.westerberg-VuQAYsv1563Yd54FQh9/CA@public.gmane.org>
---
drivers/acpi/property.c | 72 ++++++++++++++++++++++++++++++++++++++-----------
include/acpi/acpi_bus.h | 1 +
include/linux/acpi.h | 7 +++++
3 files changed, 65 insertions(+), 15 deletions(-)
diff --git a/drivers/acpi/property.c b/drivers/acpi/property.c
index 3afddcd..587c9d0 100644
--- a/drivers/acpi/property.c
+++ b/drivers/acpi/property.c
@@ -37,14 +37,16 @@ static const u8 ads_uuid[16] = {
static bool acpi_enumerate_nondev_subnodes(acpi_handle scope,
const union acpi_object *desc,
- struct acpi_device_data *data);
+ struct acpi_device_data *data,
+ struct fwnode_handle *parent);
static bool acpi_extract_properties(const union acpi_object *desc,
struct acpi_device_data *data);
static bool acpi_nondev_subnode_extract(const union acpi_object *desc,
acpi_handle handle,
const union acpi_object *link,
- struct list_head *list)
+ struct list_head *list,
+ struct fwnode_handle *parent)
{
struct acpi_data_node *dn;
bool result;
@@ -55,6 +57,7 @@ static bool acpi_nondev_subnode_extract(const union acpi_object *desc,
dn->name = link->package.elements[0].string.pointer;
dn->fwnode.type = FWNODE_ACPI_DATA;
+ dn->parent = parent;
INIT_LIST_HEAD(&dn->data.subnodes);
result = acpi_extract_properties(desc, &dn->data);
@@ -71,9 +74,11 @@ static bool acpi_nondev_subnode_extract(const union acpi_object *desc,
*/
status = acpi_get_parent(handle, &scope);
if (ACPI_SUCCESS(status)
- && acpi_enumerate_nondev_subnodes(scope, desc, &dn->data))
+ && acpi_enumerate_nondev_subnodes(scope, desc, &dn->data,
+ &dn->fwnode))
result = true;
- } else if (acpi_enumerate_nondev_subnodes(NULL, desc, &dn->data)) {
+ } else if (acpi_enumerate_nondev_subnodes(NULL, desc, &dn->data,
+ &dn->fwnode)) {
result = true;
}
@@ -91,7 +96,8 @@ static bool acpi_nondev_subnode_extract(const union acpi_object *desc,
static bool acpi_nondev_subnode_data_ok(acpi_handle handle,
const union acpi_object *link,
- struct list_head *list)
+ struct list_head *list,
+ struct fwnode_handle *parent)
{
struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER };
acpi_status status;
@@ -101,7 +107,8 @@ static bool acpi_nondev_subnode_data_ok(acpi_handle handle,
if (ACPI_FAILURE(status))
return false;
- if (acpi_nondev_subnode_extract(buf.pointer, handle, link, list))
+ if (acpi_nondev_subnode_extract(buf.pointer, handle, link, list,
+ parent))
return true;
ACPI_FREE(buf.pointer);
@@ -110,7 +117,8 @@ static bool acpi_nondev_subnode_data_ok(acpi_handle handle,
static bool acpi_nondev_subnode_ok(acpi_handle scope,
const union acpi_object *link,
- struct list_head *list)
+ struct list_head *list,
+ struct fwnode_handle *parent)
{
acpi_handle handle;
acpi_status status;
@@ -123,12 +131,13 @@ static bool acpi_nondev_subnode_ok(acpi_handle scope,
if (ACPI_FAILURE(status))
return false;
- return acpi_nondev_subnode_data_ok(handle, link, list);
+ return acpi_nondev_subnode_data_ok(handle, link, list, parent);
}
static int acpi_add_nondev_subnodes(acpi_handle scope,
const union acpi_object *links,
- struct list_head *list)
+ struct list_head *list,
+ struct fwnode_handle *parent)
{
bool ret = false;
int i;
@@ -150,15 +159,18 @@ static int acpi_add_nondev_subnodes(acpi_handle scope,
/* The second one may be a string, a reference or a package. */
switch (link->package.elements[1].type) {
case ACPI_TYPE_STRING:
- result = acpi_nondev_subnode_ok(scope, link, list);
+ result = acpi_nondev_subnode_ok(scope, link, list,
+ parent);
break;
case ACPI_TYPE_LOCAL_REFERENCE:
handle = link->package.elements[1].reference.handle;
- result = acpi_nondev_subnode_data_ok(handle, link, list);
+ result = acpi_nondev_subnode_data_ok(handle, link, list,
+ parent);
break;
case ACPI_TYPE_PACKAGE:
desc = &link->package.elements[1];
- result = acpi_nondev_subnode_extract(desc, NULL, link, list);
+ result = acpi_nondev_subnode_extract(desc, NULL, link,
+ list, parent);
break;
default:
result = false;
@@ -172,7 +184,8 @@ static int acpi_add_nondev_subnodes(acpi_handle scope,
static bool acpi_enumerate_nondev_subnodes(acpi_handle scope,
const union acpi_object *desc,
- struct acpi_device_data *data)
+ struct acpi_device_data *data,
+ struct fwnode_handle *parent)
{
int i;
@@ -194,7 +207,8 @@ static bool acpi_enumerate_nondev_subnodes(acpi_handle scope,
if (memcmp(uuid->buffer.pointer, ads_uuid, sizeof(ads_uuid)))
continue;
- return acpi_add_nondev_subnodes(scope, links, &data->subnodes);
+ return acpi_add_nondev_subnodes(scope, links, &data->subnodes,
+ parent);
}
return false;
@@ -345,7 +359,8 @@ void acpi_init_properties(struct acpi_device *adev)
if (acpi_of)
acpi_init_of_compatible(adev);
}
- if (acpi_enumerate_nondev_subnodes(adev->handle, buf.pointer, &adev->data))
+ if (acpi_enumerate_nondev_subnodes(adev->handle, buf.pointer,
+ &adev->data, acpi_fwnode_handle(adev)))
adev->data.pointer = buf.pointer;
if (!adev->data.pointer) {
@@ -920,3 +935,30 @@ struct fwnode_handle *acpi_get_next_subnode(struct device *dev,
}
return NULL;
}
+
+/**
+ * acpi_node_get_parent - Return parent fwnode of this fwnode
+ * @fwnode: Firmware node whose parent to get
+ *
+ * Returns parent node of an ACPI device or data firmware node or %NULL if
+ * not available.
+ */
+struct fwnode_handle *acpi_node_get_parent(struct fwnode_handle *fwnode)
+{
+ if (is_acpi_data_node(fwnode)) {
+ /* All data nodes have parent pointer so just return that */
+ return to_acpi_data_node(fwnode)->parent;
+ } else if (is_acpi_device_node(fwnode)) {
+ acpi_handle handle, parent_handle;
+
+ handle = to_acpi_device_node(fwnode)->handle;
+ if (ACPI_SUCCESS(acpi_get_parent(handle, &parent_handle))) {
+ struct acpi_device *adev;
+
+ if (!acpi_bus_get_device(parent_handle, &adev))
+ return acpi_fwnode_handle(adev);
+ }
+ }
+
+ return NULL;
+}
diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h
index ef0ae8a..49cca52 100644
--- a/include/acpi/acpi_bus.h
+++ b/include/acpi/acpi_bus.h
@@ -386,6 +386,7 @@ struct acpi_data_node {
const char *name;
acpi_handle handle;
struct fwnode_handle fwnode;
+ struct fwnode_handle *parent;
struct acpi_device_data data;
struct list_head sibling;
struct kobject kobj;
diff --git a/include/linux/acpi.h b/include/linux/acpi.h
index 673acda..a6f1b74 100644
--- a/include/linux/acpi.h
+++ b/include/linux/acpi.h
@@ -1002,6 +1002,7 @@ int acpi_dev_prop_read(struct acpi_device *adev, const char *propname,
struct fwnode_handle *acpi_get_next_subnode(struct device *dev,
struct fwnode_handle *subnode);
+struct fwnode_handle *acpi_node_get_parent(struct fwnode_handle *fwnode);
struct acpi_probe_entry;
typedef bool (*acpi_probe_entry_validate_subtbl)(struct acpi_subtable_header *,
@@ -1124,6 +1125,12 @@ static inline struct fwnode_handle *acpi_get_next_subnode(struct device *dev,
return NULL;
}
+static inline struct fwnode_handle *
+acpi_node_get_parent(struct fwnode_handle *fwnode)
+{
+ return NULL;
+}
+
#define ACPI_DECLARE_PROBE_ENTRY(table, name, table_id, subtable, valid, data, fn) \
static const void * __acpi_table_##name[] \
__attribute__((unused)) \
--
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
* [PATCH v6 00/12] ACPI graph support
From: Sakari Ailus @ 2017-03-28 7:52 UTC (permalink / raw)
To: linux-acpi, devicetree, rafael
Cc: sudeep.holla, lorenzo.pieralisi, mika.westerberg, mark.rutland,
broonie, robh, ahs3, frowand.list
Hello everyone,
Rafael: this set has no external dependencies, it can be applied without
the two other sets. Tested with the V4L2 fwnode patches both on ACPI and
DT. I'll still address Mika's comments in the bugfix set and repost the
other patch with dependency information.
The patches apply on both v4.11-rc1 and linux-next. I'd like to base my
V4L2 patches on this set; could you create me an immutable branch on
v4.11-rc1 when you apply the patches, please? The rest of the patches are
not needed for here since they make no interface changes to the fwnode
framework.
Thanks.
This is the sixth version of the ACPI graph support patchset.
The previous RFC version of the set is available here:
<URL:http://www.spinics.net/lists/linux-acpi/msg69547.html>
There are also v1, v2, v3, v4 and v5 which can be found here:
<URL:http://www.spinics.net/lists/linux-acpi/msg71661.html>
<URL:http://www.spinics.net/lists/linux-acpi/msg71809.html>
<URL:http://www.spinics.net/lists/linux-acpi/msg72171.html>
<URL:http://www.spinics.net/lists/linux-acpi/msg72346.html>
<URL:http://www.spinics.net/lists/linux-acpi/msg72641.html>
This set contains patches written by Mika Westerberg and by myself. The
patchset brings support for graphs to ACPI. The functionality achieved by
these patches is very similar to what the Device tree provides: the port
and the endpoint concept are being employed. The patches make use of the
_DSD property and data extensions to achieve this. The fwnode interface is
extended by graph functionality; this way graph information originating
from both OF and ACPI may be accessed using the same interface, without
being aware of the underlying firmware interface.
The last patch of the set contains ASL documentation including an example.
The entire set may also be found here (on mediatree.git master, but it
also applies cleanly on linux-next):
<URL:https://git.linuxtv.org/sailus/media_tree.git/log/?h=acpi-graph>
The resulting fwnode graph interface has been tested using V4L2 async with
fwnode matching and smiapp and omap3isp drivers, with appropriate changes
to make use of the fwnode interface in drivers.
I'm additionally moving the firmware implementation specific code from
drivers/base/property.c to firmware specific locations. I'll post that
patchset soon. It'll be available here:
<URL:https://git.linuxtv.org/sailus/media_tree.git/log/?h=acpi-graph-cleaned>
The V4L2 patches can be found here. The fwnode graph interface is used by
the newly added V4L2 fwnode framework which replaces the V4L2 OF
framework, with equivalent functionality.
<URL:https://git.linuxtv.org/sailus/media_tree.git/log/?h=v4l2-acpi>
changes since v5.1:
- Drop patch "of: Add nop implementation of of_get_next_parent()". It is
no longer needed.
changes since v5:
- Read the "endpoint" property on ACPI to read the endpoint number instead
of relying on the index.
- Call fwnode_get_parent() to get the parent, independently of the FW
type. In other words, squashed "device property: Implement
fwnode_get_next_parent() using fwnode interface" to this one.
- Clarify port and endpoint node object naming. Using both port and endpoint
cleanly resolves object naming by using the appropriate port and
endpoint numbers.
- Endpoint node numbers are unique within a port, not an endpoint.
changes since v4:
- Use port and endpoint numbers in references. This is more reliable, as
the port and endpoint numbers are more stable than their location in the
data extension array. Because of this, also require the "endpoint"
property to be there. The documentation (the last patch) is changed
accordingly.
- Move dev_fwnode() implementation back to property.c where it was.
EXPORT_SYMBOL_GPL() it, and add a prototype in include/linux/property.h.
This removes the need for patches "[PATCH v4 10/16] irqchip/gic: Add
missing forward declaration for struct device" and "of: No need to
include linux/property.h, linux/fwnode.h is sufficient".
- Fix fwnode_graph_parse_endpoint() KernelDoc documentation.
- Add documentation for struct fwnode_endpoint.
- Removed patch "[PATCH v4 09/16] driver core: Arrange headers
alphabetically" from the set.
changes since v3:
- Rebase on current PM tree including 4.11-rc1 merge --- there were a few
conflicts.
changes since v2.2:
- Drop device_fwnode_handle() function in favour of moving the existing
dev_fwnode() function from drivers/base/property.c to linux/property.h
(now patch 12).
- Unmerge two unrelated patches accidentally merged between RFC v1 and
PATCH v1 of the series. The patch adding device_fwnode_handle() was
accidentally merged with "device property: Obtain device's fwnode
independently of FW type".
- Remove redundant forward declaration of struct device in
linux/property.h.
changes since v2:
- Include linux/property.h for property fwnode API in additional drivers
(current patch 11).
changes since v1:
- Fix a few checkpatch.pl warnings in Mika's patches (too long lines),
- remove the "endpoint" property specifying the endpoint id. The endpoint
id is a software concept and the index in the endpoint array can be used
instead if needed. The changes are in patches "device property: Add
support for fwnode endpoints" and "ACPI / DSD: Document references,
ports and endpoints" and
- add patch "irqchip/gic: Add missing forward declaration for
struct device" (patch 9) to fix compilation warning on arm64 caused
by "of: No need to include linux/property.h, linux/fwnode.h is
sufficient" (now patch 10)
changes since RFC v1:
- Rebased the set --- there were a few conflicts.
- Fixed a bug in ACPI graph parsing. (Thanks to Mika!)
- Remove one layer (the "ports" node) of the _DSD hierarchical data
structure. Change the documentation accordingly. Instead, rely on the
presence of "port" and "endpoint" properties to identify port and
endpoint nodes.
- Add a reference the DSD property rule document [1].
Mika Westerberg (6):
ACPI / property: Add possiblity to retrieve parent firmware node
device property: Add fwnode_get_parent()
ACPI / property: Add fwnode_get_next_child_node()
device property: Add fwnode_get_named_child_node()
ACPI / property: Add support for remote endpoints
device property: Add support for remote endpoints
Sakari Ailus (6):
device property: Add fwnode_handle_get()
of: Add of_fwnode_handle() to convert device nodes to fwnode_handle
device property: Make dev_fwnode() public
device property: Add support for fwnode endpoints
device property: Add fwnode_get_next_parent()
ACPI / DSD: Document references, ports and endpoints
Documentation/acpi/dsd/graph.txt | 162 +++++++++++++++++++++++
drivers/acpi/property.c | 237 +++++++++++++++++++++++++++++----
drivers/base/property.c | 275 +++++++++++++++++++++++++++++++++++++--
include/acpi/acpi_bus.h | 1 +
include/linux/acpi.h | 38 +++++-
include/linux/fwnode.h | 12 ++
include/linux/of.h | 4 +
include/linux/property.h | 26 ++++
8 files changed, 712 insertions(+), 43 deletions(-)
create mode 100644 Documentation/acpi/dsd/graph.txt
--
Kind regards,
Sakari
^ permalink raw reply
* Re: [PATCH v3 06/11] IIO: ADC: add stm32 DFSDM support for PDM microphone
From: Arnaud Pouliquen @ 2017-03-28 7:45 UTC (permalink / raw)
To: Jonathan Cameron, Rob Herring, Mark Rutland, Hartmut Knaack,
Lars-Peter Clausen, Peter Meerwald-Stadler, Jaroslav Kysela,
Takashi Iwai, Liam Girdwood, Mark Brown
Cc: devicetree@vger.kernel.org, alsa-devel@alsa-project.org,
Olivier MOYSAN, kernel@stlinux.com, linux-iio@vger.kernel.org,
Maxime Coquelin, linux-arm-kernel@lists.infradead.org,
Alexandre TORGUE
In-Reply-To: <35e8a42e-3703-692e-6023-8498bebeb837@kernel.org>
On 03/25/2017 04:59 PM, Jonathan Cameron wrote:
> On 20/03/17 11:29, Arnaud Pouliquen wrote:
>> Please find my comments in-line
>>
>> Thanks and Regards,
>> Arnaud
>>
>> On 03/19/2017 11:38 PM, Jonathan Cameron wrote:
>>> On 17/03/17 14:08, Arnaud Pouliquen wrote:
>>>> Add DFSDM driver to handle PDM audio microphones.
>>>>
>>>> Signed-off-by: Arnaud Pouliquen <arnaud.pouliquen@st.com>
>>> So key element here is that we really need to have a proper way of
>>> doing DMA buffer consumers from IIO (as you said in your cover letter).
>>>
>>> Not entirely obvious how to do this. Would like some input from Lars
>>> if possible. I'm afraid I don't have an suitable hardware to try anything
>>> out on (or the time really!). This will obviously be quite different from
>>> the single scan stuff you have seen is there at the moment.
>> I saw 2 other drivers ti_am335x_adc.c and stm32_adc.c) that use cyclic
>> DMA. I suppose that problematics are similar. Perhaps the extra
>> constrains in DFSDM is the in-kernel API used to get the data.
> Absolutely. Cyclic dma is becoming more and more common on SoC ADCs.
> There are a few more fpga based ones in Analog devices tree as well -
> most of those actually do the dma buffer route rather than pushing through
> the normal buffer interface (kfifo effectively). Right now we just
> don't have any means of accessing these dma buffers in kernel.
>
> Unlike the normal consumer interface, I doubt we'll want to do demuxing
> of the stream for this sort of usecase - feels like exclusive use is more
> likely.
>>
>>>
>>> Whether this whole approach makes sense vs doing the dma in the alsa driver
>>> isn't clear to me.
>>>
>>> On the plus side, presumably we'll get love dma ADC support for free
>>> as part of doing it this way ;)
>>>
>>> It's been a while since I looked at the IIO dma buffer stuff at all. Will
>>> try and refresh my memory sometime this week.
>>
>> Ok, i will wait your feedback (and Lars's one) before updating my code
>> for next version.
> It may well be the case that it will take us sometime (when I say us, I mean
> you and Lars ;) to pin down how to do dma buffer consumers, so it may be
> a case of taking the driver in a state close to the current one with the
> intent to switch to a generic way of handling it later.
I had a short discussion with Lars on IIO IRC, seems that he also does
not see a straightforward solution for this IP.
The Fact that audio part is in a separate file should help to rework it
in a generic way.
So i will propose a V4 for upstream, integrating discussed fixes.
Thanks
Arnaud
>
> As long as bindings and userspace don't change we can of course mess with
> anything we like.
>
> Jonathan
>>>
>>> Jonathan
>>>> ---
>>>> drivers/iio/adc/Kconfig | 13 +
>>>> drivers/iio/adc/Makefile | 1 +
>>>> drivers/iio/adc/stm32-dfsdm-audio.c | 720 ++++++++++++++++++++++++++++++
>>>> include/linux/iio/adc/stm32-dfsdm-audio.h | 41 ++
>>>> 4 files changed, 775 insertions(+)
>>>> create mode 100644 drivers/iio/adc/stm32-dfsdm-audio.c
>>>> create mode 100644 include/linux/iio/adc/stm32-dfsdm-audio.h
>>>>
>>>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>>>> index 3e0eb11..c108933 100644
>>>> --- a/drivers/iio/adc/Kconfig
>>>> +++ b/drivers/iio/adc/Kconfig
>>>> @@ -478,6 +478,19 @@ config STM32_DFSDM_ADC
>>>> This driver can also be built as a module. If so, the module
>>>> will be called stm32-dfsdm-adc.
>>>>
>>>> +config STM32_DFSDM_AUDIO
>>>> + tristate "STMicroelectronics STM32 dfsdm audio
>>>> + depends on (ARCH_STM32 && OF) || COMPILE_TEST
>>>> + select STM32_DFSDM_CORE
>>>> + select REGMAP_MMIO
>>>> + select IIO_BUFFER_DMAENGINE
>>>> + help
>>>> + Select this option to support Audio PDM micophone for
>>>> + STMicroelectronics STM32 digital filter for sigma delta converter.
>>>> +
>>>> + This driver can also be built as a module. If so, the module
>>>> + will be called stm32-dfsdm-audio.
>>>> +
>>>> config STX104
>>>> tristate "Apex Embedded Systems STX104 driver"
>>>> depends on X86 && ISA_BUS_API
>>>> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
>>>> index 161f271..79f975d 100644
>>>> --- a/drivers/iio/adc/Makefile
>>>> +++ b/drivers/iio/adc/Makefile
>>>> @@ -44,6 +44,7 @@ obj-$(CONFIG_STX104) += stx104.o
>>>> obj-$(CONFIG_STM32_ADC_CORE) += stm32-adc-core.o
>>>> obj-$(CONFIG_STM32_ADC) += stm32-adc.o
>>>> obj-$(CONFIG_STM32_DFSDM_ADC) += stm32-dfsdm-adc.o
>>>> +obj-$(CONFIG_STM32_DFSDM_AUDIO) += stm32-dfsdm-audio.o
>>>> obj-$(CONFIG_STM32_DFSDM_CORE) += stm32-dfsdm-core.o
>>>> obj-$(CONFIG_TI_ADC081C) += ti-adc081c.o
>>>> obj-$(CONFIG_TI_ADC0832) += ti-adc0832.o
>>>> diff --git a/drivers/iio/adc/stm32-dfsdm-audio.c b/drivers/iio/adc/stm32-dfsdm-audio.c
>>>> new file mode 100644
>>>> index 0000000..115ef8f
>>>> --- /dev/null
>>>> +++ b/drivers/iio/adc/stm32-dfsdm-audio.c
>>>> @@ -0,0 +1,720 @@
>>>> +/*
>>>> + * This file is the ADC part of of the STM32 DFSDM driver
>>>> + *
>>>> + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved
>>>> + * Author: Arnaud Pouliquen <arnaud.pouliquen@st.com>.
>>>> + *
>>>> + * License type: GPLv2
>>>> + *
>>>> + * 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.
>>>> + *
>>>> + * This program is distributed in the hope that it will be useful, but
>>>> + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
>>>> + * or FITNESS FOR A PARTICULAR PURPOSE.
>>>> + * See the GNU General Public License for more details.
>>>> + *
>>>> + * You should have received a copy of the GNU General Public License along with
>>>> + * this program. If not, see <http://www.gnu.org/licenses/>.
>>>> + */
>>>> +
>>>> +#include <linux/dmaengine.h>
>>>> +#include <linux/dma-mapping.h>
>>>> +#include <linux/interrupt.h>
>>>> +#include <linux/module.h>
>>>> +#include <linux/of.h>
>>>> +#include <linux/platform_device.h>
>>>> +#include <linux/regmap.h>
>>>> +#include <linux/slab.h>
>>>> +
>>>> +#include <linux/iio/buffer.h>
>>>> +#include <linux/iio/hw_consumer.h>
>>>> +#include <linux/iio/sysfs.h>
>>>> +#include <linux/iio/trigger.h>
>>>> +#include <linux/iio/trigger_consumer.h>
>>>> +#include <linux/iio/triggered_buffer.h>
>>>> +
>>>> +#include "stm32-dfsdm.h"
>>>> +
>>>> +#define DFSDM_DMA_BUFFER_SIZE (4 * PAGE_SIZE)
>>>> +
>>>> +struct stm32_dfsdm_audio {
>>>> + struct stm32_dfsdm *dfsdm;
>>>> + unsigned int fl_id;
>>>> + unsigned int ch_id;
>>>> + unsigned int spi_freq; /* SPI bus clock frequency */
>>>> + unsigned int sample_freq; /* Sample frequency after filter decimation */
>>>> +
>>>> + u8 *rx_buf;
>>>> + unsigned int bufi; /* Buffer current position */
>>>> + unsigned int buf_sz; /* Buffer size */
>>>> +
>>>> + struct dma_chan *dma_chan;
>>>> + dma_addr_t dma_buf;
>>>> +
>>>> + int (*cb)(const void *data, size_t size, void *cb_priv);
>>>> + void *cb_priv;
>>>> +};
>>>> +
>>>> +const char *stm32_dfsdm_spi_trigger = DFSDM_SPI_TRIGGER_NAME;
>>>> +
>>>> +static ssize_t dfsdm_audio_get_rate(struct iio_dev *indio_dev, uintptr_t priv,
>>>> + const struct iio_chan_spec *chan, char *buf)
>>>> +{
>>>> + struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
>>>> +
>>>> + return snprintf(buf, PAGE_SIZE, "%d\n", pdmc->sample_freq);
>>>> +}
>>>> +
>>>> +static ssize_t dfsdm_audio_set_rate(struct iio_dev *indio_dev, uintptr_t priv,
>>>> + const struct iio_chan_spec *chan,
>>>> + const char *buf, size_t len)
>>>> +{
>>>> + struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
>>>> + struct stm32_dfsdm_filter *fl = &pdmc->dfsdm->fl_list[pdmc->fl_id];
>>>> + struct stm32_dfsdm_channel *ch = &pdmc->dfsdm->ch_list[pdmc->ch_id];
>>>> + unsigned int spi_freq = pdmc->spi_freq;
>>>> + unsigned int sample_freq;
>>>> + int ret;
>>>> +
>>>> + ret = kstrtoint(buf, 0, &sample_freq);
>>>> + if (ret)
>>>> + return ret;
>>>> +
>>>> + dev_dbg(&indio_dev->dev, "Requested sample_freq :%d\n", sample_freq);
>>>> + if (!sample_freq)
>>>> + return -EINVAL;
>>>> +
>>>> + if (ch->src != DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL)
>>>> + spi_freq = pdmc->dfsdm->spi_master_freq;
>>>> +
>>>> + if (spi_freq % sample_freq)
>>>> + dev_warn(&indio_dev->dev, "Sampling rate not accurate (%d)\n",
>>>> + spi_freq / (spi_freq / sample_freq));
>>>> +
>>>> + ret = stm32_dfsdm_set_osrs(fl, 0, (spi_freq / sample_freq));
>>>> + if (ret < 0) {
>>>> + dev_err(&indio_dev->dev,
>>>> + "Not able to find filter parameter that match!\n");
>>>> + return ret;
>>>> + }
>>>> + pdmc->sample_freq = sample_freq;
>>>> +
>>>> + return len;
>>>> +}
>>>> +
>>>> +static ssize_t dfsdm_audio_get_spiclk(struct iio_dev *indio_dev, uintptr_t priv,
>>>> + const struct iio_chan_spec *chan,
>>>> + char *buf)
>>>> +{
>>>> + struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
>>>> +
>>>> + return snprintf(buf, PAGE_SIZE, "%d\n", pdmc->spi_freq);
>>>> +}
>>>> +
>>>> +static ssize_t dfsdm_audio_set_spiclk(struct iio_dev *indio_dev, uintptr_t priv,
>>>> + const struct iio_chan_spec *chan,
>>>> + const char *buf, size_t len)
>>>> +{
>>>> + struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
>>>> + struct stm32_dfsdm_filter *fl = &pdmc->dfsdm->fl_list[pdmc->fl_id];
>>>> + struct stm32_dfsdm_channel *ch = &pdmc->dfsdm->ch_list[pdmc->ch_id];
>>>> + unsigned int sample_freq = pdmc->sample_freq;
>>>> + unsigned int spi_freq;
>>>> + int ret;
>>>> +
>>>> + /* If DFSDM is master on SPI, SPI freq can not be updated */
>>>> + if (ch->src != DFSDM_CHANNEL_SPI_CLOCK_EXTERNAL)
>>>> + return -EPERM;
>>>> +
>>>> + ret = kstrtoint(buf, 0, &spi_freq);
>>>> + if (ret)
>>>> + return ret;
>>>> +
>>>> + dev_dbg(&indio_dev->dev, "Requested frequency :%d\n", spi_freq);
>>>> + if (!spi_freq)
>>>> + return -EINVAL;
>>>> +
>>>> + if (sample_freq) {
>>>> + if (spi_freq % sample_freq)
>>>> + dev_warn(&indio_dev->dev,
>>>> + "Sampling rate not accurate (%d)\n",
>>>> + spi_freq / (spi_freq / sample_freq));
>>>> +
>>>> + ret = stm32_dfsdm_set_osrs(fl, 0, (spi_freq / sample_freq));
>>>> + if (ret < 0) {
>>>> + dev_err(&indio_dev->dev,
>>>> + "No filter parameters that match!\n");
>>>> + return ret;
>>>> + }
>>>> + }
>>>> + pdmc->spi_freq = spi_freq;
>>>> +
>>>> + return len;
>>>> +}
>>>> +
>>>> +/*
>>>> + * Define external info for SPI Frequency and audio sampling rate that can be
>>>> + * configured by ASoC driver through consumer.h API
>>>> + */
>>>> +static const struct iio_chan_spec_ext_info dfsdm_adc_ext_info[] = {
>>>> + /* filter oversampling: Post filter oversampling ratio */
>>>> + {
>>>> + .name = "audio_sampling_rate",
>>>> + .shared = IIO_SHARED_BY_TYPE,
>>>> + .read = dfsdm_audio_get_rate,
>>>> + .write = dfsdm_audio_set_rate,
>>>> + },
>>>> + /* data_right_bit_shift : Filter output data shifting */
>>>> + {
>>>> + .name = "spi_clk_freq",
>>>> + .shared = IIO_SHARED_BY_TYPE,
>>>> + .read = dfsdm_audio_get_spiclk,
>>>> + .write = dfsdm_audio_set_spiclk,
>>>> + },
>>>> + {},
>>>> +};
>>>> +
>>>> +static int stm32_dfsdm_start_conv(struct stm32_dfsdm_audio *pdmc, bool single)
>>>> +{
>>>> + struct regmap *regmap = pdmc->dfsdm->regmap;
>>>> + int ret;
>>>> +
>>>> + ret = stm32_dfsdm_start_dfsdm(pdmc->dfsdm);
>>>> + if (ret < 0)
>>>> + return ret;
>>>> +
>>>> + ret = stm32_dfsdm_start_channel(pdmc->dfsdm, pdmc->ch_id);
>>>> + if (ret < 0)
>>>> + goto stop_dfsdm;
>>>> +
>>>> + ret = stm32_dfsdm_filter_configure(pdmc->dfsdm, pdmc->fl_id,
>>>> + pdmc->ch_id);
>>>> + if (ret < 0)
>>>> + goto stop_channels;
>>>> +
>>>> + /* Enable DMA transfer*/
>>>> + ret = regmap_update_bits(regmap, DFSDM_CR1(pdmc->fl_id),
>>>> + DFSDM_CR1_RDMAEN_MASK, DFSDM_CR1_RDMAEN(1));
>>>> + if (ret < 0)
>>>> + return ret;
>>>> +
>>>> + /* Enable conversion triggered by SPI clock*/
>>>> + ret = regmap_update_bits(regmap, DFSDM_CR1(pdmc->fl_id),
>>>> + DFSDM_CR1_RCONT_MASK, DFSDM_CR1_RCONT(1));
>>>> + if (ret < 0)
>>>> + return ret;
>>>> +
>>>> + ret = stm32_dfsdm_start_filter(pdmc->dfsdm, pdmc->fl_id);
>>>> + if (ret < 0)
>>>> + goto stop_channels;
>>>> +
>>>> + return 0;
>>>> +
>>>> +stop_channels:
>>>> + stm32_dfsdm_stop_channel(pdmc->dfsdm, pdmc->fl_id);
>>>> +stop_dfsdm:
>>>> + stm32_dfsdm_stop_dfsdm(pdmc->dfsdm);
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static void stm32_dfsdm_stop_conv(struct stm32_dfsdm_audio *pdmc)
>>>> +{
>>>> + stm32_dfsdm_stop_filter(pdmc->dfsdm, pdmc->fl_id);
>>>> +
>>>> + stm32_dfsdm_stop_channel(pdmc->dfsdm, pdmc->ch_id);
>>>> +
>>>> + stm32_dfsdm_stop_dfsdm(pdmc->dfsdm);
>>>> +}
>>>> +
>>>> +static int stm32_dfsdm_set_watermark(struct iio_dev *indio_dev,
>>>> + unsigned int val)
>>>> +{
>>>> + struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
>>>> + unsigned int watermark = DFSDM_DMA_BUFFER_SIZE / 2;
>>>> +
>>>> + /*
>>>> + * DMA cyclic transfers are used, buffer is split into two periods.
>>>> + * There should be :
>>>> + * - always one buffer (period) DMA is working on
>>>> + * - one buffer (period) driver pushed to ASoC side ().
>>>> + */
>>>> + watermark = min(watermark, val * (unsigned int)(sizeof(u32)));
>>>> + pdmc->buf_sz = watermark * 2;
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +int stm32_dfsdm_validate_trigger(struct iio_dev *indio_dev,
>>>> + struct iio_trigger *trig)
>>>> +{
>>>> + if (!strcmp(stm32_dfsdm_spi_trigger, trig->name))
>>>> + return 0;
>>>> +
>>>> + return -EINVAL;
>>>> +}
>>>> +
>>>> +static const struct iio_info stm32_dfsdm_info_pdmc = {
>>>> + .hwfifo_set_watermark = stm32_dfsdm_set_watermark,
>>>> + .driver_module = THIS_MODULE,
>>>> + .validate_trigger = stm32_dfsdm_validate_trigger,
>>>> +};
>>>> +
>>>> +static irqreturn_t stm32_dfsdm_irq(int irq, void *arg)
>>>> +{
>>>> + struct stm32_dfsdm_audio *pdmc = arg;
>>>> + struct iio_dev *indio_dev = iio_priv_to_dev(pdmc);
>>>> + struct regmap *regmap = pdmc->dfsdm->regmap;
>>>> + unsigned int status;
>>>> +
>>>> + regmap_read(regmap, DFSDM_ISR(pdmc->fl_id), &status);
>>>> +
>>>> + if (status & DFSDM_ISR_ROVRF_MASK) {
>>>> + dev_err(&indio_dev->dev, "Unexpected Conversion overflow\n");
>>>> + regmap_update_bits(regmap, DFSDM_ICR(pdmc->fl_id),
>>>> + DFSDM_ICR_CLRROVRF_MASK,
>>>> + DFSDM_ICR_CLRROVRF_MASK);
>>>> + }
>>>> +
>>>> + return IRQ_HANDLED;
>>>> +}
>>>> +
>>>> +static unsigned int stm32_dfsdm_audio_avail_data(struct stm32_dfsdm_audio *pdmc)
>>>> +{
>>>> + struct dma_tx_state state;
>>>> + enum dma_status status;
>>>> +
>>>> + status = dmaengine_tx_status(pdmc->dma_chan,
>>>> + pdmc->dma_chan->cookie,
>>>> + &state);
>>>> + if (status == DMA_IN_PROGRESS) {
>>>> + /* Residue is size in bytes from end of buffer */
>>>> + unsigned int i = pdmc->buf_sz - state.residue;
>>>> + unsigned int size;
>>>> +
>>>> + /* Return available bytes */
>>>> + if (i >= pdmc->bufi)
>>>> + size = i - pdmc->bufi;
>>>> + else
>>>> + size = pdmc->buf_sz + i - pdmc->bufi;
>>>> +
>>>> + return size;
>>>> + }
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static void stm32_dfsdm_audio_dma_buffer_done(void *data)
>>>> +{
>>>> + struct iio_dev *indio_dev = data;
>>>> +
>>>> + iio_trigger_poll_chained(indio_dev->trig);
>>> This shouldn't be going through the trigger infrastructure at all really.
>>> I'm not 100% sure what doing so is gaining you...
>> Yes i will clean trigger part and call directly the ASoC callback here.
>>>> +}
>>>> +
>>>> +static int stm32_dfsdm_audio_dma_start(struct iio_dev *indio_dev)
>>>> +{
>>>> + struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
>>>> + struct dma_async_tx_descriptor *desc;
>>>> + dma_cookie_t cookie;
>>>> + int ret;
>>>> +
>>>> + if (!pdmc->dma_chan)
>>>> + return -EINVAL;
>>>> +
>>>> + dev_dbg(&indio_dev->dev, "%s size=%d watermark=%d\n", __func__,
>>>> + pdmc->buf_sz, pdmc->buf_sz / 2);
>>>> +
>>>> + /* Prepare a DMA cyclic transaction */
>>>> + desc = dmaengine_prep_dma_cyclic(pdmc->dma_chan,
>>>> + pdmc->dma_buf,
>>>> + pdmc->buf_sz, pdmc->buf_sz / 2,
>>>> + DMA_DEV_TO_MEM,
>>>> + DMA_PREP_INTERRUPT);
>>>> + if (!desc)
>>>> + return -EBUSY;
>>>> +
>>>> + desc->callback = stm32_dfsdm_audio_dma_buffer_done;
>>>> + desc->callback_param = indio_dev;
>>>> +
>>>> + cookie = dmaengine_submit(desc);
>>>> + ret = dma_submit_error(cookie);
>>>> + if (ret) {
>>>> + dmaengine_terminate_all(pdmc->dma_chan);
>>>> + return ret;
>>>> + }
>>>> +
>>>> + /* Issue pending DMA requests */
>>>> + dma_async_issue_pending(pdmc->dma_chan);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int stm32_dfsdm_postenable(struct iio_dev *indio_dev)
>>>> +{
>>>> + struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
>>>> + int ret;
>>>> +
>>>> + dev_dbg(&indio_dev->dev, "%s\n", __func__);
>>>> + /* Reset pdmc buffer index */
>>>> + pdmc->bufi = 0;
>>>> +
>>>> + ret = stm32_dfsdm_start_conv(pdmc, false);
>>>> + if (ret) {
>>>> + dev_err(&indio_dev->dev, "Can't start conversion\n");
>>>> + return ret;
>>>> + }
>>>> +
>>>> + ret = stm32_dfsdm_audio_dma_start(indio_dev);
>>>> + if (ret) {
>>>> + dev_err(&indio_dev->dev, "Can't start DMA\n");
>>>> + goto err_stop_conv;
>>>> + }
>>>> +
>>>> + ret = iio_triggered_buffer_postenable(indio_dev);
>>>> + if (ret < 0) {
>>>> + dev_err(&indio_dev->dev, "%s :%d\n", __func__, __LINE__);
>>>> + goto err_stop_dma;
>>>> + }
>>>> +
>>>> + return 0;
>>>> +
>>>> +err_stop_dma:
>>>> + if (pdmc->dma_chan)
>>>> + dmaengine_terminate_all(pdmc->dma_chan);
>>>> +err_stop_conv:
>>>> + stm32_dfsdm_stop_conv(pdmc);
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int stm32_dfsdm_predisable(struct iio_dev *indio_dev)
>>>> +{
>>>> + struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
>>>> + int ret;
>>>> +
>>>> + dev_dbg(&indio_dev->dev, "%s\n", __func__);
>>>> + ret = iio_triggered_buffer_predisable(indio_dev);
>>>> + if (ret < 0)
>>>> + dev_err(&indio_dev->dev, "Predisable failed\n");
>>>> +
>>>> + if (pdmc->dma_chan)
>>>> + dmaengine_terminate_all(pdmc->dma_chan);
>>>> +
>>>> + stm32_dfsdm_stop_conv(pdmc);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static const struct iio_buffer_setup_ops stm32_dfsdm_buffer_setup_ops = {
>>>> + .postenable = &stm32_dfsdm_postenable,
>>>> + .predisable = &stm32_dfsdm_predisable,
>>>> +};
>>>> +
>>>> +static irqreturn_t stm32_dfsdm_audio_trigger_handler(int irq, void *p)
>>>> +{
>>> OK. So now I kind of understand what the trigger provided in the adc driver
>>> was about. hmm. Triggers are supposed to be per sample so this is indeed
>>> a hack. We need fullblown DMA buffer consumers to do this right.
>> Yes exactly.
>>>
>>> Probably best person to comment on that is Lars.
>>>> + struct iio_poll_func *pf = p;
>>>> + struct iio_dev *indio_dev = pf->indio_dev;
>>>> + struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
>>>> + size_t old_pos;
>>>> + int available = stm32_dfsdm_audio_avail_data(pdmc);
>>>> +
>>>> + /*
>>>> + * Buffer interface is not support cyclic DMA buffer,and offer only
>>>> + * an interface to push data samples per samples.
>>>> + * For this reason iio_push_to_buffers_with_timestamp in not used
>>>> + * and interface is hacked using a private callback registered by ASoC.
>>>> + * This should be a temporary solution waiting a cyclic DMA engine
>>>> + * support in IIO.
>>>> + */
>>>> +
>>>> + dev_dbg(&indio_dev->dev, "%s: pos = %d, available = %d\n", __func__,
>>>> + pdmc->bufi, available);
>>>> + old_pos = pdmc->bufi;
>>>> + while (available >= indio_dev->scan_bytes) {
>>>> + u32 *buffer = (u32 *)&pdmc->rx_buf[pdmc->bufi];
>>>> +
>>>> + /* Mask 8 LSB that contains the channel ID */
>>>> + *buffer &= 0xFFFFFF00;
>>>> + available -= indio_dev->scan_bytes;
>>>> + pdmc->bufi += indio_dev->scan_bytes;
>>>> + if (pdmc->bufi >= pdmc->buf_sz) {
>>>> + if (pdmc->cb)
>>>> + pdmc->cb(&pdmc->rx_buf[old_pos],
>>>> + pdmc->buf_sz - old_pos, pdmc->cb_priv);
>>>> + pdmc->bufi = 0;
>>>> + old_pos = 0;
>>>> + }
>>>> + }
>>>> + if (pdmc->cb)
>>>> + pdmc->cb(&pdmc->rx_buf[old_pos], pdmc->bufi - old_pos,
>>>> + pdmc->cb_priv);
>>>> +
>>>> + iio_trigger_notify_done(indio_dev->trig);
>>>> +
>>>> + return IRQ_HANDLED;
>>>> +}
>>>> +
>>>> +/**
>>>> + * stm32_dfsdm_get_buff_cb - register a callback
>>>> + * that will be called when DMA transfer period is achieved.
>>>> + *
>>>> + * @iio_dev: Handle to IIO device.
>>>> + * @cb: pointer to callback function.
>>>> + * @data: pointer to data buffer
>>>> + * @size: size in byte of the data buffer
>>>> + * @private: pointer to consumer private structure
>>>> + * @private: pointer to consumer private structure
>>>> + */
>>>> +int stm32_dfsdm_get_buff_cb(struct iio_dev *iio_dev,
>>>> + int (*cb)(const void *data, size_t size,
>>>> + void *private),
>>>> + void *private)
>>>> +{
>>>> + struct stm32_dfsdm_audio *pdmc;
>>>> +
>>>> + if (!iio_dev)
>>>> + return -EINVAL;
>>>> + pdmc = iio_priv(iio_dev);
>>>> +
>>>> + if (iio_dev != iio_priv_to_dev(pdmc))
>>>> + return -EINVAL;
>>>> +
>>>> + pdmc->cb = cb;
>>>> + pdmc->cb_priv = private;
>>>> +
>>>> + return 0;
>>>> +}
>>>> +EXPORT_SYMBOL_GPL(stm32_dfsdm_get_buff_cb);
>>>> +
>>>> +/**
>>>> + * stm32_dfsdm_release_buff_cb - unregister buffer callback
>>>> + *
>>>> + * @iio_dev: Handle to IIO device.
>>>> + */
>>>> +int stm32_dfsdm_release_buff_cb(struct iio_dev *iio_dev)
>>>> +{
>>>> + struct stm32_dfsdm_audio *pdmc;
>>>> +
>>>> + if (!iio_dev)
>>>> + return -EINVAL;
>>>> + pdmc = iio_priv(iio_dev);
>>>> +
>>>> + if (iio_dev != iio_priv_to_dev(pdmc))
>>>> + return -EINVAL;
>>>> + pdmc->cb = NULL;
>>>> + pdmc->cb_priv = NULL;
>>>> +
>>>> + return 0;
>>>> +}
>>>> +EXPORT_SYMBOL_GPL(stm32_dfsdm_release_buff_cb);
>>>> +
>>>> +static int stm32_dfsdm_audio_chan_init(struct iio_dev *indio_dev)
>>>> +{
>>>> + struct iio_chan_spec *ch;
>>>> + struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
>>>> + int ret;
>>>> +
>>>> + ch = devm_kzalloc(&indio_dev->dev, sizeof(*ch), GFP_KERNEL);
>>>> + if (!ch)
>>>> + return -ENOMEM;
>>>> +
>>>> + ret = stm32_dfsdm_channel_parse_of(pdmc->dfsdm, indio_dev, ch, 0);
>>>> + if (ret < 0)
>>>> + return ret;
>>>> +
>>>> + ch->type = IIO_VOLTAGE;
>>>> + ch->indexed = 1;
>>>> + ch->scan_index = 0;
>>>> + ch->ext_info = dfsdm_adc_ext_info;
>>>> +
>>>> + ch->scan_type.sign = 's';
>>>> + ch->scan_type.realbits = 24;
>>>> + ch->scan_type.storagebits = 32;
>>>> +
>>>> + pdmc->ch_id = ch->channel;
>>>> + ret = stm32_dfsdm_chan_configure(pdmc->dfsdm,
>>>> + &pdmc->dfsdm->ch_list[ch->channel]);
>>>> +
>>>> + indio_dev->num_channels = 1;
>>>> + indio_dev->channels = ch;
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static const struct of_device_id stm32_dfsdm_audio_match[] = {
>>>> + { .compatible = "st,stm32-dfsdm-audio"},
>>>> + {}
>>>> +};
>>>> +
>>>> +static int stm32_dfsdm_audio_dma_request(struct iio_dev *indio_dev)
>>>> +{
>>>> + struct stm32_dfsdm_audio *pdmc = iio_priv(indio_dev);
>>>> + struct dma_slave_config config;
>>>> + int ret;
>>>> +
>>>> + pdmc->dma_chan = dma_request_slave_channel(&indio_dev->dev, "rx");
>>>> + if (!pdmc->dma_chan)
>>>> + return -EINVAL;
>>>> +
>>>> + pdmc->rx_buf = dma_alloc_coherent(pdmc->dma_chan->device->dev,
>>>> + DFSDM_DMA_BUFFER_SIZE,
>>>> + &pdmc->dma_buf, GFP_KERNEL);
>>>> + if (!pdmc->rx_buf) {
>>>> + ret = -ENOMEM;
>>>> + goto err_release;
>>>> + }
>>>> +
>>>> + /* Configure DMA channel to read data register */
>>>> + memset(&config, 0, sizeof(config));
>>>> + config.src_addr = (dma_addr_t)pdmc->dfsdm->phys_base;
>>>> + config.src_addr += DFSDM_RDATAR(pdmc->fl_id);
>>>> + config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
>>>> +
>>>> + ret = dmaengine_slave_config(pdmc->dma_chan, &config);
>>>> + if (ret)
>>>> + goto err_free;
>>>> +
>>>> + return 0;
>>>> +
>>>> +err_free:
>>>> + dma_free_coherent(pdmc->dma_chan->device->dev, DFSDM_DMA_BUFFER_SIZE,
>>>> + pdmc->rx_buf, pdmc->dma_buf);
>>>> +err_release:
>>>> + dma_release_channel(pdmc->dma_chan);
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int stm32_dfsdm_audio_probe(struct platform_device *pdev)
>>>> +{
>>>> + struct device *dev = &pdev->dev;
>>>> + struct stm32_dfsdm_audio *pdmc;
>>>> + struct device_node *np = dev->of_node;
>>>> + struct iio_dev *iio;
>>>> + char *name;
>>>> + int ret, irq, val;
>>>> +
>>>> + iio = devm_iio_device_alloc(dev, sizeof(*pdmc));
>>>> + if (IS_ERR(iio)) {
>>>> + dev_err(dev, "%s: Failed to allocate IIO\n", __func__);
>>>> + return PTR_ERR(iio);
>>>> + }
>>>> +
>>>> + pdmc = iio_priv(iio);
>>>> + if (IS_ERR(pdmc)) {
>>>> + dev_err(dev, "%s: Failed to allocate ADC\n", __func__);
>>>> + return PTR_ERR(pdmc);
>>>> + }
>>>> + pdmc->dfsdm = dev_get_drvdata(dev->parent);
>>>> +
>>>> + iio->name = np->name;
>>>> + iio->dev.parent = dev;
>>>> + iio->dev.of_node = np;
>>>> + iio->info = &stm32_dfsdm_info_pdmc;
>>>> + iio->modes = INDIO_DIRECT_MODE;
>>>> +
>>>> + platform_set_drvdata(pdev, pdmc);
>>>> +
>>>> + ret = of_property_read_u32(dev->of_node, "reg", &pdmc->fl_id);
>>>> + if (ret != 0) {
>>>> + dev_err(dev, "Missing reg property\n");
>>>> + return -EINVAL;
>>>> + }
>>>> +
>>>> + name = kzalloc(sizeof("dfsdm-pdm0"), GFP_KERNEL);
>>>> + if (!name)
>>>> + return -ENOMEM;
>>>> + snprintf(name, sizeof("dfsdm-pdm0"), "dfsdm-pdm%d", pdmc->fl_id);
>>>> + iio->name = name;
>>>> +
>>>> + /*
>>>> + * In a first step IRQs generated for channels are not treated.
>>>> + * So IRQ associated to filter instance 0 is dedicated to the Filter 0.
>>>> + */
>>>> + irq = platform_get_irq(pdev, 0);
>>>> + ret = devm_request_irq(dev, irq, stm32_dfsdm_irq,
>>>> + 0, pdev->name, pdmc);
>>>> + if (ret < 0) {
>>>> + dev_err(dev, "Failed to request IRQ\n");
>>>> + return ret;
>>>> + }
>>>> +
>>>> + ret = of_property_read_u32(dev->of_node, "st,filter-order", &val);
>>>> + if (ret < 0) {
>>>> + dev_err(dev, "Failed to set filter order\n");
>>>> + return ret;
>>>> + }
>>>> + pdmc->dfsdm->fl_list[pdmc->fl_id].ford = val;
>>>> +
>>>> + ret = of_property_read_u32(dev->of_node, "st,filter0-sync", &val);
>>>> + if (!ret)
>>>> + pdmc->dfsdm->fl_list[pdmc->fl_id].sync_mode = val;
>>>> +
>>>> + ret = stm32_dfsdm_audio_chan_init(iio);
>>>> + if (ret < 0)
>>>> + return ret;
>>>> +
>>>> + ret = stm32_dfsdm_audio_dma_request(iio);
>>>> + if (ret) {
>>>> + dev_err(&pdev->dev, "DMA request failed\n");
>>>> + return ret;
>>>> + }
>>>> +
>>>> + iio->modes |= INDIO_BUFFER_SOFTWARE;
>>>> +
>>>> + ret = iio_triggered_buffer_setup(iio,
>>>> + &iio_pollfunc_store_time,
>>>> + &stm32_dfsdm_audio_trigger_handler,
>>>> + &stm32_dfsdm_buffer_setup_ops);
>>>> + if (ret) {
>>>> + dev_err(&pdev->dev, "Buffer setup failed\n");
>>>> + goto err_dma_disable;
>>>> + }
>>>> +
>>>> + ret = iio_device_register(iio);
>>>> + if (ret) {
>>>> + dev_err(&pdev->dev, "IIO dev register failed\n");
>>>> + goto err_buffer_cleanup;
>>>> + }
>>>> +
>>>> + return 0;
>>>> +
>>>> +err_buffer_cleanup:
>>>> + iio_triggered_buffer_cleanup(iio);
>>>> +
>>>> +err_dma_disable:
>>>> + if (pdmc->dma_chan) {
>>>> + dma_free_coherent(pdmc->dma_chan->device->dev,
>>>> + DFSDM_DMA_BUFFER_SIZE,
>>>> + pdmc->rx_buf, pdmc->dma_buf);
>>>> + dma_release_channel(pdmc->dma_chan);
>>>> + }
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int stm32_dfsdm_audio_remove(struct platform_device *pdev)
>>>> +{
>>>> + struct stm32_dfsdm_audio *pdmc = platform_get_drvdata(pdev);
>>>> + struct iio_dev *iio = iio_priv_to_dev(pdmc);
>>>> +
>>>> + iio_device_unregister(iio);
>>> Same as in the other driver. Can just use the devm_iio_device_register
>>> version and drop remove.
>> oops missing the previous comment... sorry.
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static struct platform_driver stm32_dfsdm_audio_driver = {
>>>> + .driver = {
>>>> + .name = "stm32-dfsdm-audio",
>>>> + .of_match_table = stm32_dfsdm_audio_match,
>>>> + },
>>>> + .probe = stm32_dfsdm_audio_probe,
>>>> + .remove = stm32_dfsdm_audio_remove,
>>>> +};
>>>> +module_platform_driver(stm32_dfsdm_audio_driver);
>>>> +
>>>> +MODULE_DESCRIPTION("STM32 sigma delta converter for PDM microphone");
>>>> +MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@st.com>");
>>>> +MODULE_LICENSE("GPL v2");
>>>> diff --git a/include/linux/iio/adc/stm32-dfsdm-audio.h b/include/linux/iio/adc/stm32-dfsdm-audio.h
>>>> new file mode 100644
>>>> index 0000000..9b41b28
>>>> --- /dev/null
>>>> +++ b/include/linux/iio/adc/stm32-dfsdm-audio.h
>>>> @@ -0,0 +1,41 @@
>>>> +/*
>>>> + * This file discribe the STM32 DFSDM IIO driver API for audio part
>>>> + *
>>>> + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved
>>>> + * Author(s): Arnaud Pouliquen <arnaud.pouliquen@st.com>.
>>>> + *
>>>> + * License terms: GPL V2.0.
>>>> + *
>>>> + * 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.
>>>> + *
>>>> + * This program is distributed in the hope that it will be useful, but
>>>> + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
>>>> + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
>>>> + * details.
>>>> + */
>>>> +#ifndef STM32_DFSDM_AUDIO_H
>>>> +#define STM32_DFSDM_AUDIO_H
>>>> +
>>>> +/**
>>>> + * stm32_dfsdm_get_buff_cb() - register callback for capture buffer period.
>>>> + * @dev: Pointer to client device.
>>>> + * @indio_dev: Device on which the channel exists.
>>>> + * @cb: Callback function.
>>>> + * @data: pointer to data buffer
>>>> + * @size: size of the data buffer in bytes
>>>> + * @private: Private data passed to callback.
>>>> + *
>>>> + */
>>>> +int stm32_dfsdm_get_buff_cb(struct iio_dev *iio_dev,
>>>> + int (*cb)(const void *data, size_t size,
>>>> + void *private),
>>>> + void *private);
>>>> +/**
>>>> + * stm32_dfsdm_get_buff_cb() - release callback for capture buffer period.
>>>> + * @indio_dev: Device on which the channel exists.
>>>> + */
>>>> +int stm32_dfsdm_release_buff_cb(struct iio_dev *iio_dev);
>>>> +
>>>> +#endif
>>>>
>>>
>> --
>> To unsubscribe from this list: send the line "unsubscribe linux-iio" in
>> the body of a message to majordomo@vger.kernel.org
>> More majordomo info at http://vger.kernel.org/majordomo-info.html
>>
>
^ permalink raw reply
* Re: [PATCH] arm64: dts: ls1012a: add crypto node
From: Horia Geantă @ 2017-03-28 7:19 UTC (permalink / raw)
To: Shawn Guo
Cc: Mark Rutland, devicetree@vger.kernel.org, Herbert Xu,
Harninder Rai, Catalin Marinas, Bhaskar U, Will Deacon,
Dan Douglass, Rob Herring, linux-crypto@vger.kernel.org,
David S. Miller, linux-arm-kernel@lists.infradead.org
In-Reply-To: <20170324140320.GP30608@dragon>
On 3/24/2017 4:04 PM, Shawn Guo wrote:
> On Fri, Mar 24, 2017 at 08:29:17AM +0000, Horia Geantă wrote:
>> On 3/24/2017 9:35 AM, Shawn Guo wrote:
>>> On Fri, Mar 24, 2017 at 07:17:50AM +0000, Horia Geantă wrote:
>>>>>> + sec_mon: sec_mon@1e90000 {
>>>>>
>>>>> Hyphen is more preferred to be used in node name than underscore.
>>>>>
>>>> This would imply changing the
>>>> Documentation/devicetree/bindings/crypto/fsl-sec4.txt binding and
>>>> dealing with all the consequences, which IIUC is probably not worth.
>>>
>>> I do not care the bindings doc that much, since I'm not the maintainer
>>> of it. What are the consequences specifically, if we use a better node
>>> name in dts than bindings example?
>>>
>> Users relying on finding the sec_mon node will obviously stop working.
>> I don't see any in-kernel users, however there could be others I am not
>> aware of and DT bindings should provide for backwards compatibility.
>
> Okay, point taken. You can keep the node name as it is.
>
>> I could deprecate "sec_mon" in the bindings and suggest "sec-mon"
>> instead, while leaving all existing dts files as-is.
>> The risk is breaking LS1012A users relying on "sec_mon".
>
> For existing bindings, I do not care that much. But for new ones, I do
> hope that we recommend to use hyphen, as that's more idiomatic at least
> for Linux kernel.
>
>> I see that ePAPR:
>> -allows both for hyphen and underline in case of node names
>> -allows only for hyphen (i.e. forbids underline) in case of alias nodes
>>
>> In the first case, I understand there's an (undocumented?) agreement to
>> prefer hyphen over underline.
>
> Both are valid, but hyphen is more idiomatic for Linux kernel.
>
>> For the 2nd one, does this mean I should change alias names?
>
> This is something I see difference between specification and DTC.
>
> aliases {
> alias-name = &label_name;
> };
>
> label_name: node-name {
> ...
> };
>
> The spec says that only hyphen is valid for alias name, but DTC works
> happily with underscore too. From my experience with DTC playing, both
> hyphen and underscore are valid for alias and node name. But for label
> name, only underscore is valid. Using hyphen in label name will cause
> DTC to report syntax error.
>
Yes indeed, thanks for pointing it out.
For the sake of current patch, please clarify whether a v2 is needed.
IIUC:
-sec_mon node name could stay the same (existing binding)
-label names are ok, since underline is the only option allowed by DTC
-alias names are out-of-spec but accepted by DTC; if changing underline
to hyphen is requested, I will push out v2
Thanks,
Horia
^ permalink raw reply
* [PATCH v34 13/14] Documentation: dt: chosen properties for arm64 kdump
From: AKASHI Takahiro @ 2017-03-28 6:52 UTC (permalink / raw)
To: catalin.marinas-5wv7dgnIgG8, will.deacon-5wv7dgnIgG8,
robh+dt-DgEjT+Ai2ygdnm+yROfE0A, mark.rutland-5wv7dgnIgG8
Cc: james.morse-5wv7dgnIgG8, geoff-wEGCiKHe2LqWVfeAwA7xHQ,
bauerman-23VcF4HTsmIX0ybBhKVfKdBPR1lH4CV8,
dyoung-H+wXaHxf7aLQT0dZR+AlfA,
ard.biesheuvel-QSEj5FYQhm4dnm+yROfE0A,
panand-H+wXaHxf7aLQT0dZR+AlfA, sgoel-sgV2jX0FEOL9JmXXK+q4OQ,
dwmw2-wEGCiKHe2LqWVfeAwA7xHQ,
kexec-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
devicetree-u79uwXL29TY76Z2rM5mHXA, AKASHI Takahiro
In-Reply-To: <20170328064831.15894-1-takahiro.akashi-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
From: James Morse <james.morse-5wv7dgnIgG8@public.gmane.org>
Add documentation for DT properties:
linux,usable-memory-range
linux,elfcorehdr
used by arm64 kdump. Those are, respectively, a usable memory range
allocated to crash dump kernel and the elfcorehdr's location within it.
Signed-off-by: James Morse <james.morse-5wv7dgnIgG8@public.gmane.org>
[takahiro.akashi-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org: update the text due to recent changes ]
Signed-off-by: AKASHI Takahiro <takahiro.akashi-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org>
Acked-by: Mark Rutland <mark.rutland-5wv7dgnIgG8@public.gmane.org>
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
Cc: Rob Herring <robh+dt-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
---
Documentation/devicetree/bindings/chosen.txt | 45 ++++++++++++++++++++++++++++
1 file changed, 45 insertions(+)
diff --git a/Documentation/devicetree/bindings/chosen.txt b/Documentation/devicetree/bindings/chosen.txt
index 6ae9d82d4c37..b5e39af4ddc0 100644
--- a/Documentation/devicetree/bindings/chosen.txt
+++ b/Documentation/devicetree/bindings/chosen.txt
@@ -52,3 +52,48 @@ This property is set (currently only on PowerPC, and only needed on
book3e) by some versions of kexec-tools to tell the new kernel that it
is being booted by kexec, as the booting environment may differ (e.g.
a different secondary CPU release mechanism)
+
+linux,usable-memory-range
+-------------------------
+
+This property (arm64 only) holds a base address and size, describing a
+limited region in which memory may be considered available for use by
+the kernel. Memory outside of this range is not available for use.
+
+This property describes a limitation: memory within this range is only
+valid when also described through another mechanism that the kernel
+would otherwise use to determine available memory (e.g. memory nodes
+or the EFI memory map). Valid memory may be sparse within the range.
+e.g.
+
+/ {
+ chosen {
+ linux,usable-memory-range = <0x9 0xf0000000 0x0 0x10000000>;
+ };
+};
+
+The main usage is for crash dump kernel to identify its own usable
+memory and exclude, at its boot time, any other memory areas that are
+part of the panicked kernel's memory.
+
+While this property does not represent a real hardware, the address
+and the size are expressed in #address-cells and #size-cells,
+respectively, of the root node.
+
+linux,elfcorehdr
+----------------
+
+This property (currently used only on arm64) holds the memory range,
+the address and the size, of the elf core header which mainly describes
+the panicked kernel's memory layout as PT_LOAD segments of elf format.
+e.g.
+
+/ {
+ chosen {
+ linux,elfcorehdr = <0x9 0xfffff000 0x0 0x800>;
+ };
+};
+
+While this property does not represent a real hardware, the address
+and the size are expressed in #address-cells and #size-cells,
+respectively, of the root node.
--
2.11.1
--
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 net-next v2 5/5] net-next: dsa: add dsa support for Mediatek MT7530 switch
From: Sean Wang @ 2017-03-28 6:50 UTC (permalink / raw)
To: Andrew Lunn
Cc: f.fainelli-Re5JQEeQqe8AvxtiuMwx3w,
vivien.didelot-4ysUXcep3aM1wj+D4I0NRVaTQe2KTcn/,
matthias.bgg-Re5JQEeQqe8AvxtiuMwx3w,
robh+dt-DgEjT+Ai2ygdnm+yROfE0A, mark.rutland-5wv7dgnIgG8,
devicetree-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
linux-mediatek-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
davem-fT/PcQaiUtIeIZ0/mPfg9Q, Landen.Chao-NuS5LvNUpcJWk0Htik3J/w,
keyhaede-Re5JQEeQqe8AvxtiuMwx3w, objelf-Re5JQEeQqe8AvxtiuMwx3w
In-Reply-To: <20170324141953.GH28518-g2DYL2Zd6BY@public.gmane.org>
Hi Andrew
On Fri, 2017-03-24 at 15:19 +0100, Andrew Lunn wrote:
> On Tue, Mar 21, 2017 at 05:35:10PM +0800, sean.wang-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org wrote:
>
> Hi Sean
>
> > + /* Lower Tx Driving */
> > + for (i = 0 ; i < 6 ; i++)
>
> Could MT7530_CPU_PORT be used here?
>
I should create meaningful definition instead of avoiding hard coding
where 6 should be corrected into 5 which is meant for the number for
control register to configure TRGMII path
> > + mt7530_write(priv, MT7530_TRGMII_TD_ODT(i),
> > + TD_DM_DRVP(8) | TD_DM_DRVN(8));
> > +
> > + /* Setup MT7530 core clock */
> > + if (!trgint) {
> > + /* Disable MT7530 core clock */
> > + core_clear(priv, CORE_TRGMII_GSW_CLK_CG, REG_GSWCK_EN);
> > +
> > + /* Disable MT7530 PLL, since phy_device has not yet been
> > + * created when this function is called. So we provide
> > + * core_write_mmd_indirect to complete this function
> > + */
> > + core_write_mmd_indirect(priv,
> > + CORE_GSWPLL_GRP1,
> > + MDIO_MMD_VEND2,
> > + 0);
> > +
> > + /* Setup MT7530 core clock into 500Mhz */
> > + core_write(priv, CORE_GSWPLL_GRP2,
> > + RG_GSWPLL_POSDIV_500M(1) |
> > + RG_GSWPLL_FBKDIV_500M(25));
> > +
> > + /* Enable MT7530 PLL */
> > + core_write(priv, CORE_GSWPLL_GRP1,
> > + RG_GSWPLL_EN_PRE |
> > + RG_GSWPLL_POSDIV_200M(2) |
> > + RG_GSWPLL_FBKDIV_200M(32));
> > +
> > + /* Enable MT7530 core clock */
> > + core_set(priv, CORE_TRGMII_GSW_CLK_CG, REG_GSWCK_EN);
> > + }
> > +
> > + /* Setup the MT7530 TRGMII Tx Clock */
> > + core_set(priv, CORE_TRGMII_GSW_CLK_CG, REG_GSWCK_EN);
> > + core_write(priv, CORE_PLL_GROUP5, RG_LCDDS_PCW_NCPO1(ncpo1));
> > + core_write(priv, CORE_PLL_GROUP6, RG_LCDDS_PCW_NCPO0(0));
> > + core_write(priv, CORE_PLL_GROUP10, RG_LCDDS_SSC_DELTA(ssc_delta));
> > + core_write(priv, CORE_PLL_GROUP11, RG_LCDDS_SSC_DELTA1(ssc_delta));
> > + core_write(priv, CORE_PLL_GROUP4,
> > + RG_SYSPLL_DDSFBK_EN | RG_SYSPLL_BIAS_EN |
> > + RG_SYSPLL_BIAS_LPF_EN);
> > + core_write(priv, CORE_PLL_GROUP2,
> > + RG_SYSPLL_EN_NORMAL | RG_SYSPLL_VODEN |
> > + RG_SYSPLL_POSDIV(1));
> > + core_write(priv, CORE_PLL_GROUP7,
> > + RG_LCDDS_PCW_NCPO_CHG | RG_LCCDS_C(3) |
> > + RG_LCDDS_PWDB | RG_LCDDS_ISO_EN);
> > + core_set(priv, CORE_TRGMII_GSW_CLK_CG,
> > + REG_GSWCK_EN | REG_TRGMIICK_EN);
> > +
> > + if (!trgint)
> > + for (i = 0 ; i < 5 ; i++)
>
> Why only 5 here? All other similar loops are to 6. Replacing 5 with a
> #define might help make this more readable.
the same as above, it should be corrected
> > + mt7530_rmw(priv, MT7530_TRGMII_RD(i),
> > + RD_TAP_MASK, RD_TAP(16));
> > + else
> > + mt7623_trgmii_set(priv, GSW_INTF_MODE, INTF_MODE_TRGMII);
> > +
> > + return 0;
> > +}
> > +
> > +static int
> > +mt7623_pad_clk_setup(struct dsa_switch *ds)
> > +{
> > + struct mt7530_priv *priv = ds->priv;
> > + int i;
> > +
> > + for (i = 0 ; i < 6; i++)
>
> MT7530_CPU_PORT?
the same as above , it should be corrected
> > +
> > +/* Registers to mac forward conrol for unknown frames */
>
> /conrol/control
>
will fix it up
> > +
> > +/* Registor for port control */
>
> Register
>
will fix it up
> > +/* Regiser for TOP signal control */
>
> Register
>
will fix it up
> > +/* struct mt7530_priv - This is the main datasructure for holding the state
>
> data structure
>
> > + * of the driver
> > + * @dev: The device pointer
> > + * @ds: The pointer to the dsa core structure
> > + * @bus: The bus used for the device and built-in PHY
> > + * @ethsys: The regmap used for enabling the necessary PLL
> > + * @ethernet: The regmap used for access TRGMII-based registers
> > + * @core_pwr: The power supplied into the core
> > + * @io_pwr: The power supplied into the I/O
> > + * @mcm: Flag for distinguishing if standalone IC or module
> > + * coupling
> > + * @reset: The descriptor for GPIO line tied to its reset pin
> > + * @phy_mode: The xMII for cpu port used
> > + * @ports: Holding the state amongs ports
>
> among
will fix it up
thank for your careful reviewing again!!
> Andrew
--
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
* Re: [PATCH] clk: stm32h7: Add stm32h743 clock driver
From: Gabriel Fernandez @ 2017-03-28 6:20 UTC (permalink / raw)
To: Rob Herring
Cc: Mark Rutland, devicetree@vger.kernel.org, Daniel Thompson,
Radosław Pietrzyk, Alexandre Torgue, Arnd Bergmann,
Nicolas Pitre, Andrea Merello, Michael Turquette, Olivier Bideau,
Stephen Boyd, Russell King, linux-kernel@vger.kernel.org,
Ludovic Barre, Maxime Coquelin, Amelie Delaunay, Lee Jones,
linux-clk, linux-arm-kernel@lists.infradead.org
In-Reply-To: <CAL_Jsq++rdPqNZk54S7gQaeLBdzSptogp3dp9P1HmNTV67Sk9w@mail.gmail.com>
On 03/27/2017 09:04 PM, Rob Herring wrote:
> On Fri, Mar 24, 2017 at 4:41 AM, Gabriel Fernandez
> <gabriel.fernandez@st.com> wrote:
>> Hi Rob,
>>
>> Thanks for reviewing
>>
>>
>>
>> On 03/24/2017 03:06 AM, Rob Herring wrote:
>>> On Wed, Mar 15, 2017 at 10:23:30AM +0100, gabriel.fernandez@st.com wrote:
>>>> From: Gabriel Fernandez <gabriel.fernandez@st.com>
>>>>
>>>> This patch enables clocks for STM32H743 boards.
>>>>
>>>> Signed-off-by: Gabriel Fernandez <gabriel.fernandez@st.com>
>>>> ---
>>>> .../devicetree/bindings/clock/st,stm32h7-rcc.txt | 152 ++
>>>> drivers/clk/Makefile | 1 +
>>>> drivers/clk/clk-stm32h7.c | 1586
>>>> ++++++++++++++++++++
>>>> include/dt-bindings/clock/stm32h7-clks.h | 165 ++
>>>> include/dt-bindings/mfd/stm32h7-rcc.h | 138 ++
>>>> 5 files changed, 2042 insertions(+)
>>>> create mode 100644
>>>> Documentation/devicetree/bindings/clock/st,stm32h7-rcc.txt
>>>> create mode 100644 drivers/clk/clk-stm32h7.c
>>>> create mode 100644 include/dt-bindings/clock/stm32h7-clks.h
>>>> create mode 100644 include/dt-bindings/mfd/stm32h7-rcc.h
>>>>
>>>> diff --git a/Documentation/devicetree/bindings/clock/st,stm32h7-rcc.txt
>>>> b/Documentation/devicetree/bindings/clock/st,stm32h7-rcc.txt
>>>> new file mode 100644
>>>> index 0000000..9d4b587
>>>> --- /dev/null
>>>> +++ b/Documentation/devicetree/bindings/clock/st,stm32h7-rcc.txt
>>>> @@ -0,0 +1,152 @@
>>>> +STMicroelectronics STM32H7 Reset and Clock Controller
>>>> +=====================================================
>>>> +
>>>> +The RCC IP is both a reset and a clock controller.
>>>> +
>>>> +Please refer to clock-bindings.txt for common clock controller binding
>>>> usage.
>>>> +Please also refer to reset.txt for common reset controller binding
>>>> usage.
>>>> +
>>>> +Required properties:
>>>> +- compatible: Should be:
>>>> + "st,stm32h743-rcc"
>>>> +
>>>> +- reg: should be register base and length as documented in the
>>>> + datasheet
>>>> +
>>>> +- #reset-cells: 1, see below
>>>> +
>>>> +- #clock-cells : from common clock binding; shall be set to 1
>>>> +
>>>> +- clocks: External oscillator clock phandle
>>>> + - high speed external clock signal (HSE)
>>>> + - low speed external clock signal (LSE)
>>>> + - external I2S clock (I2S_CKIN)
>>>> +
>>>> +- st,syscfg: phandle for pwrcfg, mandatory to disable/enable backup
>>>> domain
>>>> + write protection (RTC clock).
>>>> +
>>>> +- pll x node: Allow to register a pll with specific parameters.
>>>> + Please see PLL section below.
>>>> +
>>>> +Example:
>>>> +
>>>> + rcc: rcc@58024400 {
>>>> + #reset-cells = <1>;
>>>> + #clock-cells = <2>
>>>> + compatible = "st,stm32h743-rcc", "st,stm32-rcc";
>>>> + reg = <0x58024400 0x400>;
>>>> + clocks = <&clk_hse>, <&clk_lse>, <&clk_i2s_ckin>;
>>>> +
>>>> + st,syscfg = <&pwrcfg>;
>>>> +
>>>> + #address-cells = <1>;
>>>> + #size-cells = <0>;
>>>> +
>>>> + vco1@58024430 {
>>>> + #clock-cells = <0>;
>>>> + compatible = "stm32,pll";
>>>> + reg = <0>;
>>> The reg addr value and unit address don't match.
>> ok il will change into
>>
>> vco1@0 {
>> reg = <0>;
>>
>>
>>>> + };
>>>> +
>>>> + vco2@58024438 {
>>>> + #clock-cells = <0>;
>>>> + compatible = "stm32,pll";
>>>> + reg = <1>;
>>>> + st,clock-div = <2>;
>>>> + st,clock-mult = <40>;
>>>> + st,frac-status = <0>;
>>>> + st,frac = <0>;
>>>> + st,vcosel = <1>;
>>>> + st,pllrge = <2>;
>>>> + };
>>>> + };
>>>> +
>>>> +
>>>> +STM32H7 PLL
>>>> +-----------
>>>> +
>>>> +The VCO of STM32 PLL could be reprensented like this:
>>>> +
>>>> + Vref --------- --------
>>>> + ---->| / DIVM |---->| x DIVN | ------> VCO
>>>> + --------- --------
>>>> + ^
>>>> + |
>>>> + -------
>>>> + | FRACN |
>>>> + -------
>>>> +
>>>> +When the PLL is configured in integer mode:
>>>> +- VCO = ( Vref / DIVM ) * DIVN
>>>> +
>>>> +When the PLL is configured in fractional mode:
>>>> +- VCO = ( Vref / DIVM ) * ( DIVN + FRACN / 2^13)
>>>> +
>>>> +
>>>> +Required properties for pll node:
>>>> +- compatible: Should be:
>>>> + "stm32,pll"
>>> Only 1 single PLL design for all STM32 chips ever?
>> no, i can change into "stm32h7,pll"
> Actually, should be "st,stm32h7-pll".
ok
>>>> +
>>>> +- #clock-cells: from common clock binding; shall be set to 0
>>>> +- reg: Should be the pll number.
>>>> +
>>>> +Optional properties:
>>>> +- st,clock-div: DIVM division factor : <1..63>
>>>> +- st,clock-mult: DIVN multiplication factor : <4..512>
>>>> +
>>>> +- st,frac-status:
>>>> + - 0 Pll is configured in integer mode
>>>> + - 1 Pll is configure in fractional mode
>>> Isn't this implied by the presence of the next property?
>> do you prefer this ?
>>
>> - st,frac :
>> 0 : pll is configured in integer mode
>> 1..8191 : Fractional part of the multiplication factor and pll is configured
>> in fractional mode
> Why not no st,frac property means integer mode?
No
no st,frac property means no change (keep default values from bootloader)
Best Regards
Gabriel
> Rob
^ permalink raw reply
* Re: [PATCH net-next v2 5/5] net-next: dsa: add dsa support for Mediatek MT7530 switch
From: Sean Wang @ 2017-03-28 6:05 UTC (permalink / raw)
To: Andrew Lunn
Cc: f.fainelli, vivien.didelot, matthias.bgg, robh+dt, mark.rutland,
devicetree, netdev, linux-kernel, linux-mediatek, davem,
Landen.Chao, keyhaede, objelf
In-Reply-To: <20170324140215.GG28518@lunn.ch>
Hi Andrew,
Add comment as below inline
On Fri, 2017-03-24 at 15:02 +0100, Andrew Lunn wrote:
> Hi Sean
>
> > + regmap = devm_regmap_init(ds->dev, NULL, priv,
> > + &mt7530_regmap_config);
> > + if (IS_ERR(regmap))
> > + dev_warn(priv->dev, "phy regmap initialization failed");
> > +
>
> Shouldn't this be a fatal error? If you keep going when there is an
> error, what happens when you actually try to use the regmap?
>
Initial thought is that it is just for debugging purpose so if it fails,
it should break any functionality of switch and only have implication as
a warning message.
Anyway, i will remove it in the next one since it seems better being
kept in private place as you suggested in the previous mail.
> > + phy_mode = of_get_phy_mode(ds->ports[ds->dst->cpu_port].dn);
> > + if (phy_mode < 0) {
> > + dev_err(priv->dev, "Can't find phy-mode for master device\n");
> > + return phy_mode;
> > + }
> > + dev_info(priv->dev, "phy-mode for master device = %x\n", phy_mode);
>
> dev_dbg?
>
Sorry. i forgot turned it into dev_dbg
> > +
> > + id = mt7530_read(priv, MT7530_CREV);
> > + id >>= CHIP_NAME_SHIFT;
> > + if (id != MT7530_ID)
> > + return -ENODEV;
>
> It might be helpful to say what ID has been found, if it is not the
> supported ID.
I will fix it up to make users know what was going on
thanks for taking your time on those reviewing again!
> Andrew
^ permalink raw reply
* Re: [PATCH net-next v2 5/5] net-next: dsa: add dsa support for Mediatek MT7530 switch
From: Sean Wang @ 2017-03-28 5:51 UTC (permalink / raw)
To: Florian Fainelli
Cc: mark.rutland-5wv7dgnIgG8, andrew-g2DYL2Zd6BY,
Landen.Chao-NuS5LvNUpcJWk0Htik3J/w,
keyhaede-Re5JQEeQqe8AvxtiuMwx3w,
devicetree-u79uwXL29TY76Z2rM5mHXA, netdev-u79uwXL29TY76Z2rM5mHXA,
vivien.didelot-4ysUXcep3aM1wj+D4I0NRVaTQe2KTcn/,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
robh+dt-DgEjT+Ai2ygdnm+yROfE0A,
linux-mediatek-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
objelf-Re5JQEeQqe8AvxtiuMwx3w,
matthias.bgg-Re5JQEeQqe8AvxtiuMwx3w, davem-fT/PcQaiUtIeIZ0/mPfg9Q
In-Reply-To: <842616ad-1e04-9e56-3137-dd8aed1dd896-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Hi Florian,
Thank for taking your time on reviewing. Add comment as inline.
On Wed, 2017-03-22 at 11:39 -0700, Florian Fainelli wrote:
> On 03/21/2017 02:35 AM, sean.wang-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org wrote:
> > From: Sean Wang <sean.wang-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
> >
> > MT7530 is a 7-ports Gigabit Ethernet Switch that could be found on
> > Mediatek router platforms such as MT7623A or MT7623N platform which
> > includes 7-port Gigabit Ethernet MAC and 5-port Gigabit Ethernet PHY.
> > Among these ports, The port from 0 to 4 are the user ports connecting
> > with the remote devices while the port 5 and 6 are the CPU ports
> > connecting into Mediatek Ethernet GMAC.
> >
> > For port 6, it can communicate with the CPU via Mediatek Ethernet GMAC
> > through either the TRGMII or RGMII which could be controlled by phy-mode
> > in the dt-bindings to specify which mode is preferred to use. And for
> > port 5, only RGMII can be specified. However, currently, only port 6 is
> > being supported in this DSA driver.
> >
> > The driver is made with the reference to qca8k and other existing DSA
> > driver. The most of the essential callbacks of the DSA are already
> > support in the driver, including tag insert for user port distinguishing,
> > port control, bridge offloading, STP setup and ethtool operation to allow
> > DSA to model each user port into a standalone netdevice as the other DSA
> > driver had done.
>
> Overall, this looks pretty nice and clean, a few comments below
>
> >
> > Signed-off-by: Sean Wang <sean.wang-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
> > Signed-off-by: Landen Chao <Landen.Chao-NuS5LvNUpcJWk0Htik3J/w@public.gmane.org>
> > ---
>
> > +static void
> > +mt7530_fdb_read(struct mt7530_priv *priv, struct mt7530_fdb *fdb)
> > +{
> > + u32 reg[3];
> > + int i;
> > +
> > + /* Read from ARL table into an array */
> > + for (i = 0; i < 3; i++) {
> > + reg[i] = mt7530_read(priv, MT7530_TSRA1 + (i * 4));
> > +
> > + dev_dbg(priv->dev, "%s(%d) reg[%d]=0x%x\n",
> > + __func__, __LINE__, i, reg[i]);
> > + }
> > +
> > + /* vid - 11:0 on reg[1] */
> > + fdb->vid = (reg[1] >> 0) & 0xfff;
> > + /* aging - 31:24 on reg[2] */
> > + fdb->aging = (reg[2] >> 24) & 0xff;
> > + /* portmask - 11:4 on reg[2] */
> > + fdb->port_mask = (reg[2] >> 4) & 0xff;
> > + /* mac - 31:0 on reg[0] and 31:16 on reg[1] */
> > + fdb->mac[0] = (reg[0] >> 24) & 0xff;
> > + fdb->mac[1] = (reg[0] >> 16) & 0xff;
> > + fdb->mac[2] = (reg[0] >> 8) & 0xff;
> > + fdb->mac[3] = (reg[0] >> 0) & 0xff;
> > + fdb->mac[4] = (reg[1] >> 24) & 0xff;
> > + fdb->mac[5] = (reg[1] >> 16) & 0xff;
> > + /* noarp - 3:2 on reg[2] */
> > + fdb->noarp = ((reg[2] >> 2) & 0x3) == STATIC_ENT;
>
> Could you add some definitions for the bits and masks that you are
> shifting here?
>
Okay, I'll make into proper macro for readability
> > +}
> > +
> > +static void
> > +mt7530_fdb_write(struct mt7530_priv *priv, u16 vid,
> > + u8 port_mask, const u8 *mac,
> > + u8 aging, u8 type)
> > +{
> > + u32 reg[3] = { 0 };
> > + int i;
> > +
> > + /* vid - 11:0 on reg[1] */
> > + reg[1] |= (vid & 0xfff) << 0;
> > + /* aging - 31:25 on reg[2] */
> > + reg[2] |= (aging & 0xff) << 24;
> > + /* portmask - 11:4 on reg[2] */
> > + reg[2] |= (port_mask & 0xff) << 4;
> > + /* type - 3 indicate that entry is static wouldn't
> > + * be aged out and 0 specified as erasing an entry
> > + */
> > + reg[2] |= (type & 0x3) << 2;
> > + /* mac - 31:0 on reg[0] and 31:16 on reg[1] */
> > + reg[1] |= mac[5] << 16;
> > + reg[1] |= mac[4] << 24;
> > + reg[0] |= mac[3] << 0;
> > + reg[0] |= mac[2] << 8;
> > + reg[0] |= mac[1] << 16;
> > + reg[0] |= mac[0] << 24;
> > +
> > + /* Wrirte array into the ARL table */
> > + for (i = 0; i < 3; i++)
> > + mt7530_write(priv, MT7530_ATA1 + (i * 4), reg[i]);
> > +}
>
> Same here.
>
As above. I will improve them.
> > +
> > +static int
> > +mt7530_pad_clk_setup(struct dsa_switch *ds, int mode)
> > +{
> > + struct mt7530_priv *priv = ds->priv;
> > + u32 ncpo1, ssc_delta, trgint, i;
> > +
> > + switch (mode) {
> > + case PHY_INTERFACE_MODE_RGMII:
> > + trgint = 0;
> > + ncpo1 = 0x0c80;
> > + ssc_delta = 0x87;
> > + break;
> > + case PHY_INTERFACE_MODE_TRGMII:
> > + trgint = 1;
> > + ncpo1 = 0x1400;
> > + ssc_delta = 0x57;
> > + break;
> > + default:
> > + pr_err("xMII mode %d not supported\n", mode);
> > + return -EINVAL;
> > + }
>
> You may be able to move this to an adjust_link callback that the PHY
> library would call when the PHY gets setup and the port is finally used,
> as opposed to doing this upfront during driver initialization.
>
Good point. i will follow up
>
> > +mt7530_setup(struct dsa_switch *ds)
> > +{
> > + struct mt7530_priv *priv = ds->priv;
> > + int ret, i, phy_mode;
> > + u8 cpup_mask = 0;
> > + u32 id, val;
> > + struct regmap *regmap;
> > + struct device_node *dn;
> > +
> > + /* Make sure that cpu port specfied on the dt is appropriate */
> > + if (!dsa_is_cpu_port(ds, MT7530_CPU_PORT)) {
> > + dev_err(priv->dev, "port not matched with the CPU port\n");
> > + return -EINVAL;
> > + }
>
> This is kind of a hard error, in that case, a sensible thing to do could
> be issue a warning to the user telling that the configuration does not
> permit the use of Mediatek tags. Your get_tag_protocol() function could
> then return DSA_TAG_PROTO_NONE here instead of DSA_TAG_PROTO_MTK. It
> would still allow an user to utilize the switch, and we would know what
> is wrong with the configuration/board setup though.
>
I seems making a hard requirement.
I will improve it with showing warning message if the number of cpu port
is not right.
It at least allows the other user ports can work in case cpu port
doesn't work.
> > +
> > + /* The parent node of master_netdev which holds the common system
> > + * controller also is the container for two GMACs nodes representing
> > + * as two netdev instances.
> > + */
> > + dn = ds->master_netdev->dev.of_node->parent;
> > + priv->ethernet = syscon_node_to_regmap(dn);
> > + if (IS_ERR(priv->ethernet))
> > + return PTR_ERR(priv->ethernet);
> > +
> > + regmap = devm_regmap_init(ds->dev, NULL, priv,
> > + &mt7530_regmap_config);
> > + if (IS_ERR(regmap))
> > + dev_warn(priv->dev, "phy regmap initialization failed");
> > +
> > + phy_mode = of_get_phy_mode(ds->ports[ds->dst->cpu_port].dn);
> > + if (phy_mode < 0) {
> > + dev_err(priv->dev, "Can't find phy-mode for master device\n");
> > + return phy_mode;
> > + }
> > + dev_info(priv->dev, "phy-mode for master device = %x\n", phy_mode);
>
> An adjust_link() callback which has a proper PHY device structure would
> be more appropriate to look up the phy_mode rather than doing this here.
>
As above. I will follow up
> [snip]
>
> > + mt7530_clear(priv, MT7530_MFC, UNU_FFP_MASK);
> > +
> > + /* Fabric setup for the cpu port */
> > + for (i = 0; i < MT7530_NUM_PORTS; i++)
>
> Are not you missing an opening parenthesis here in the for() statement?
>
It is missing because the following is single statement.
I admits it is better to add the parenthesis for readability, so I will
add it.
> > + if (dsa_is_cpu_port(ds, i)) {
> > + /* Enable Mediatek header mode on the cpu port */
> > + mt7530_write(priv, MT7530_PVC_P(i),
> > + PORT_SPEC_TAG);
> > +
> > + /* Setup the MAC by default for the cpu port */
> > + mt7530_write(priv, MT7530_PMCR_P(i), PMCR_CPUP_LINK);
> > +
> > + /* Disable auto learning on the cpu port */
> > + mt7530_set(priv, MT7530_PSC_P(i), SA_DIS);
> > +
> > + /* Unknown unicast frame fordwarding to the cpu port */
> > + mt7530_set(priv, MT7530_MFC, UNU_FFP(BIT(i)));
> > +
> > + /* CPU port gets connected to all user ports of
> > + * the switch
> > + */
> > + mt7530_write(priv, MT7530_PCR_P(i),
> > + PCR_MATRIX(ds->enabled_port_mask));
> > +
> > + cpup_mask |= BIT(i);
> > + }
> > +
> > + /* Fabric setup for the all user ports */
> > + for (i = 0; i < MT7530_NUM_PORTS; i++)
> > + if (ds->enabled_port_mask & BIT(i)) {
> > + /* Setup the MAC by default for all user ports */
> > + mt7530_write(priv, MT7530_PMCR_P(i),
> > + PMCR_USERP_LINK);
> > +
> > + /* The user port gets connected to the cpu port only */
> > + mt7530_write(priv, MT7530_PCR_P(i),
> > + PCR_MATRIX(cpup_mask));
> > + }
>
> This should be moved to the port_enable() function.
>
okay. i will follow up.
> [snip]
*_poll_timeout
> > +#define wait_condition_timeout(condition, timeout) \
> > +({ \
> > + long __ret = 0; \
> > + unsigned long toj; \
> > + toj = jiffies + msecs_to_jiffies(timeout); \
> > + \
> > + for ( ; !(condition) && !time_after_eq(jiffies, toj) ; ) \
> > + cond_resched(); \
> > + \
> > + if (time_after_eq(jiffies, toj)) \
> > + __ret = -ETIMEDOUT; \
> > + __ret; \
> > +})
>
> Can you use read*_poll_timeout() instead of this?
>
I will try to reuse them
the function pointer for op in *_poll_timeout seems only accepting
only single argument for address. which implies I need creating another
function for mt7530_readX() used by *_poll_timeout and keeping another
static pointer to mt7530 internal for mt7530_readX() to work.
> > +/* struct mt7530_priv - This is the main datasructure for holding the state
> > + * of the driver
> > + * @dev: The device pointer
> > + * @ds: The pointer to the dsa core structure
> > + * @bus: The bus used for the device and built-in PHY
> > + * @ethsys: The regmap used for enabling the necessary PLL
> > + * @ethernet: The regmap used for access TRGMII-based registers
> > + * @core_pwr: The power supplied into the core
> > + * @io_pwr: The power supplied into the I/O
> > + * @mcm: Flag for distinguishing if standalone IC or module
> > + * coupling
> > + * @reset: The descriptor for GPIO line tied to its reset pin
> > + * @phy_mode: The xMII for cpu port used
> > + * @ports: Holding the state amongs ports
> > + * @reg_mutex: The lock for protecting among process accessing
> > + * registers
> > + */
>
> Kudos for using kernel doc here.
^ permalink raw reply
* [PATCH 2/2] drivers/serial: Add driver for Aspeed virtual UART
From: Joel Stanley @ 2017-03-28 5:44 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby, Mark Rutland, Rob Herring
Cc: Jeremy Kerr, linux-serial, linux-kernel, openbmc, devicetree,
Benjamin Herrenschmidt
In-Reply-To: <20170328054458.29341-1-joel@jms.id.au>
From: Jeremy Kerr <jk@ozlabs.org>
This change adds a driver for the 16550-based Aspeed virtual UART
device. We use a similar process to the of_serial driver for device
probe, but expose some VUART-specific functions through sysfs too.
OpenPOWER host firmware doesn't like it when the host-side of the
VUART's FIFO is not drained. This driver only disables host TX discard
mode when the port is in use. We set the VUART enabled bit when we bind
to the device, and clear it on unbind.
We don't want to do this on open/release, as the host may be using this
bit to configure serial output modes, which is independent of whether
the devices has been opened by BMC userspace.
Signed-off-by: Jeremy Kerr <jk@ozlabs.org>
Signed-off-by: Joel Stanley <joel@jms.id.au>
---
Documentation/devicetree/bindings/serial/8250.txt | 2 +
drivers/tty/serial/Kconfig | 10 +
drivers/tty/serial/Makefile | 1 +
drivers/tty/serial/aspeed-vuart.c | 335 ++++++++++++++++++++++
4 files changed, 348 insertions(+)
create mode 100644 drivers/tty/serial/aspeed-vuart.c
diff --git a/Documentation/devicetree/bindings/serial/8250.txt b/Documentation/devicetree/bindings/serial/8250.txt
index f86bb06c39e9..a12e9277ac5d 100644
--- a/Documentation/devicetree/bindings/serial/8250.txt
+++ b/Documentation/devicetree/bindings/serial/8250.txt
@@ -19,6 +19,8 @@ Required properties:
- "altr,16550-FIFO128"
- "fsl,16550-FIFO64"
- "fsl,ns16550"
+ - "aspeed,ast2400-vuart"
+ - "aspeed,ast2500-vuart"
- "serial" if the port type is unknown.
- reg : offset and length of the register set for the device.
- interrupts : should contain uart interrupt.
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index e9cf5b67f1b7..758b69a51078 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -1129,6 +1129,16 @@ config SERIAL_NETX_CONSOLE
If you have enabled the serial port on the Hilscher NetX SoC
you can make it the console by answering Y to this option.
+config SERIAL_ASPEED_VUART
+ tristate "Aspeed Virtual UART"
+ depends on OF
+ depends on SERIAL_8250
+ help
+ If you want to use the virtual UART (VUART) device on Aspeed
+ BMC platforms, enable this option. This enables the 16550A-
+ compatible device on the local LPC bus, giving a UART device
+ with no physical RS232 connections.
+
config SERIAL_OMAP
tristate "OMAP serial port support"
depends on ARCH_OMAP2PLUS
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index 2d6288bc4554..5b97b0fa29e2 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_SERIAL_8250) += 8250/
obj-$(CONFIG_SERIAL_AMBA_PL010) += amba-pl010.o
obj-$(CONFIG_SERIAL_AMBA_PL011) += amba-pl011.o
+obj-$(CONFIG_SERIAL_ASPEED_VUART) += aspeed-vuart.o
obj-$(CONFIG_SERIAL_CLPS711X) += clps711x.o
obj-$(CONFIG_SERIAL_PXA_NON8250) += pxa.o
obj-$(CONFIG_SERIAL_PNX8XXX) += pnx8xxx_uart.o
diff --git a/drivers/tty/serial/aspeed-vuart.c b/drivers/tty/serial/aspeed-vuart.c
new file mode 100644
index 000000000000..fc6fa6d243c8
--- /dev/null
+++ b/drivers/tty/serial/aspeed-vuart.c
@@ -0,0 +1,335 @@
+/*
+ * Serial Port driver for Aspeed VUART device
+ *
+ * Copyright (C) 2016 Jeremy Kerr <jk@ozlabs.org>, IBM Corp.
+ * Copyright (C) 2006 Arnd Bergmann <arnd@arndb.de>, IBM Corp.
+ *
+ * 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; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ */
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/clk.h>
+
+#include "8250/8250.h"
+
+#define AST_VUART_GCRA 0x20
+#define AST_VUART_GCRA_VUART_EN 0x01
+#define AST_VUART_GCRA_HOST_TX_DISCARD 0x20
+#define AST_VUART_GCRB 0x24
+#define AST_VUART_GCRB_HOST_SIRQ_MASK 0xf0
+#define AST_VUART_GCRB_HOST_SIRQ_SHIFT 4
+#define AST_VUART_ADDRL 0x28
+#define AST_VUART_ADDRH 0x2c
+
+struct ast_vuart {
+ struct platform_device *pdev;
+ void __iomem *regs;
+ struct clk *clk;
+ int line;
+};
+
+static ssize_t ast_vuart_show_addr(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ast_vuart *vuart = dev_get_drvdata(dev);
+ u16 addr;
+
+ addr = (readb(vuart->regs + AST_VUART_ADDRH) << 8) |
+ (readb(vuart->regs + AST_VUART_ADDRL));
+
+ return snprintf(buf, PAGE_SIZE - 1, "0x%x\n", addr);
+}
+
+static ssize_t ast_vuart_set_addr(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ast_vuart *vuart = dev_get_drvdata(dev);
+ unsigned long val;
+ int err;
+
+ err = kstrtoul(buf, 0, &val);
+ if (err)
+ return err;
+
+ writeb((val >> 8) & 0xff, vuart->regs + AST_VUART_ADDRH);
+ writeb((val >> 0) & 0xff, vuart->regs + AST_VUART_ADDRL);
+
+ return count;
+}
+
+static DEVICE_ATTR(lpc_address, 0664,
+ ast_vuart_show_addr, ast_vuart_set_addr);
+
+static ssize_t ast_vuart_show_sirq(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ast_vuart *vuart = dev_get_drvdata(dev);
+ u8 reg;
+
+ reg = readb(vuart->regs + AST_VUART_GCRB);
+ reg &= AST_VUART_GCRB_HOST_SIRQ_MASK;
+ reg >>= AST_VUART_GCRB_HOST_SIRQ_SHIFT;
+
+ return snprintf(buf, PAGE_SIZE - 1, "%u\n", reg);
+}
+
+static ssize_t ast_vuart_set_sirq(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ast_vuart *vuart = dev_get_drvdata(dev);
+ unsigned long val;
+ int err;
+ u8 reg;
+
+ err = kstrtoul(buf, 0, &val);
+ if (err)
+ return err;
+
+ val <<= AST_VUART_GCRB_HOST_SIRQ_SHIFT;
+ val &= AST_VUART_GCRB_HOST_SIRQ_MASK;
+
+ reg = readb(vuart->regs + AST_VUART_GCRB);
+ reg &= ~AST_VUART_GCRB_HOST_SIRQ_MASK;
+ reg |= val;
+ writeb(reg, vuart->regs + AST_VUART_GCRB);
+
+ return count;
+}
+
+static DEVICE_ATTR(sirq, 0664,
+ ast_vuart_show_sirq, ast_vuart_set_sirq);
+
+static void ast_vuart_set_enabled(struct ast_vuart *vuart, bool enabled)
+{
+ u8 reg;
+
+ reg = readb(vuart->regs + AST_VUART_GCRA);
+ reg &= ~AST_VUART_GCRA_VUART_EN;
+ if (enabled)
+ reg |= AST_VUART_GCRA_VUART_EN;
+ writeb(reg, vuart->regs + AST_VUART_GCRA);
+}
+
+static void ast_vuart_set_host_tx_discard(struct ast_vuart *vuart, bool discard)
+{
+ u8 reg;
+
+ reg = readb(vuart->regs + AST_VUART_GCRA);
+
+ /* if the HOST_TX_DISCARD bit is set, discard is *disabled* */
+ reg &= ~AST_VUART_GCRA_HOST_TX_DISCARD;
+ if (!discard)
+ reg |= AST_VUART_GCRA_HOST_TX_DISCARD;
+
+ writeb(reg, vuart->regs + AST_VUART_GCRA);
+}
+
+static int ast_vuart_startup(struct uart_port *uart_port)
+{
+ struct uart_8250_port *uart_8250_port = up_to_u8250p(uart_port);
+ struct ast_vuart *vuart = uart_8250_port->port.private_data;
+ int rc;
+
+ rc = serial8250_do_startup(uart_port);
+ if (rc)
+ return rc;
+
+ ast_vuart_set_host_tx_discard(vuart, false);
+
+ return 0;
+}
+
+static void ast_vuart_shutdown(struct uart_port *uart_port)
+{
+ struct uart_8250_port *uart_8250_port = up_to_u8250p(uart_port);
+ struct ast_vuart *vuart = uart_8250_port->port.private_data;
+
+ ast_vuart_set_host_tx_discard(vuart, true);
+
+ serial8250_do_shutdown(uart_port);
+}
+
+
+/**
+ * The device tree parsing code here is heavily based on that of the of_serial
+ * driver, but we have a few core differences, as we need to use our own
+ * ioremapping for extra register support
+ */
+static int ast_vuart_probe(struct platform_device *pdev)
+{
+ struct uart_8250_port port;
+ struct resource resource;
+ struct ast_vuart *vuart;
+ struct device_node *np;
+ u32 clk, prop;
+ int rc;
+
+ np = pdev->dev.of_node;
+
+ vuart = devm_kzalloc(&pdev->dev, sizeof(*vuart), GFP_KERNEL);
+ if (!vuart)
+ return -ENOMEM;
+
+ vuart->pdev = pdev;
+ rc = of_address_to_resource(np, 0, &resource);
+ if (rc) {
+ dev_warn(&pdev->dev, "invalid address\n");
+ return rc;
+ }
+
+ /* create our own mapping for VUART-specific registers */
+ vuart->regs = devm_ioremap_resource(&pdev->dev, &resource);
+ if (IS_ERR(vuart->regs)) {
+ dev_warn(&pdev->dev, "failed to map registers\n");
+ return PTR_ERR(vuart->regs);
+ }
+
+ memset(&port, 0, sizeof(port));
+ port.port.private_data = vuart;
+ port.port.membase = vuart->regs;
+ port.port.mapbase = resource.start;
+ port.port.mapsize = resource_size(&resource);
+ port.port.startup = ast_vuart_startup;
+ port.port.shutdown = ast_vuart_shutdown;
+
+ if (of_property_read_u32(np, "clock-frequency", &clk)) {
+ vuart->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(vuart->clk)) {
+ dev_warn(&pdev->dev,
+ "clk or clock-frequency not defined\n");
+ return PTR_ERR(vuart->clk);
+ }
+
+ rc = clk_prepare_enable(vuart->clk);
+ if (rc < 0)
+ return rc;
+
+ clk = clk_get_rate(vuart->clk);
+ }
+
+ /* If current-speed was set, then try not to change it. */
+ if (of_property_read_u32(np, "current-speed", &prop) == 0)
+ port.port.custom_divisor = clk / (16 * prop);
+
+ /* Check for shifted address mapping */
+ if (of_property_read_u32(np, "reg-offset", &prop) == 0)
+ port.port.mapbase += prop;
+
+ /* Check for registers offset within the devices address range */
+ if (of_property_read_u32(np, "reg-shift", &prop) == 0)
+ port.port.regshift = prop;
+
+ /* Check for fifo size */
+ if (of_property_read_u32(np, "fifo-size", &prop) == 0)
+ port.port.fifosize = prop;
+
+ /* Check for a fixed line number */
+ rc = of_alias_get_id(np, "serial");
+ if (rc >= 0)
+ port.port.line = rc;
+
+ port.port.irq = irq_of_parse_and_map(np, 0);
+ port.port.irqflags = IRQF_SHARED;
+ port.port.iotype = UPIO_MEM;
+ if (of_property_read_u32(np, "reg-io-width", &prop) == 0) {
+ switch (prop) {
+ case 1:
+ port.port.iotype = UPIO_MEM;
+ break;
+ case 4:
+ port.port.iotype = of_device_is_big_endian(np) ?
+ UPIO_MEM32BE : UPIO_MEM32;
+ break;
+ default:
+ dev_warn(&pdev->dev, "unsupported reg-io-width (%d)\n",
+ prop);
+ rc = -EINVAL;
+ goto err_clk_disable;
+ }
+ }
+
+ port.port.type = PORT_16550A;
+ port.port.uartclk = clk;
+ port.port.flags = UPF_SHARE_IRQ | UPF_BOOT_AUTOCONF
+ | UPF_FIXED_PORT | UPF_FIXED_TYPE | UPF_NO_THRE_TEST;
+
+ if (of_find_property(np, "no-loopback-test", NULL))
+ port.port.flags |= UPF_SKIP_TEST;
+
+ port.port.dev = &pdev->dev;
+
+ if (port.port.fifosize)
+ port.capabilities = UART_CAP_FIFO;
+
+ if (of_property_read_bool(pdev->dev.of_node,
+ "auto-flow-control"))
+ port.capabilities |= UART_CAP_AFE;
+
+ rc = serial8250_register_8250_port(&port);
+ if (rc < 0)
+ goto err_clk_disable;
+
+
+ vuart->line = rc;
+ ast_vuart_set_enabled(vuart, true);
+ ast_vuart_set_host_tx_discard(vuart, true);
+ platform_set_drvdata(pdev, vuart);
+
+ /* extra sysfs control */
+ rc = device_create_file(&pdev->dev, &dev_attr_lpc_address);
+ if (rc)
+ dev_warn(&pdev->dev, "can't create lpc_address file\n");
+ rc = device_create_file(&pdev->dev, &dev_attr_sirq);
+ if (rc)
+ dev_warn(&pdev->dev, "can't create sirq file\n");
+
+ return 0;
+
+err_clk_disable:
+ if (vuart->clk)
+ clk_disable_unprepare(vuart->clk);
+
+ irq_dispose_mapping(port.port.irq);
+ return rc;
+}
+
+static int ast_vuart_remove(struct platform_device *pdev)
+{
+ struct ast_vuart *vuart = platform_get_drvdata(pdev);
+
+ ast_vuart_set_enabled(vuart, false);
+
+ if (vuart->clk)
+ clk_disable_unprepare(vuart->clk);
+ return 0;
+}
+
+static const struct of_device_id ast_vuart_table[] = {
+ { .compatible = "aspeed,ast2400-vuart" },
+ { .compatible = "aspeed,ast2500-vuart" },
+ { },
+};
+
+static struct platform_driver ast_vuart_driver = {
+ .driver = {
+ .name = "aspeed-vuart",
+ .of_match_table = ast_vuart_table,
+ },
+ .probe = ast_vuart_probe,
+ .remove = ast_vuart_remove,
+};
+
+module_platform_driver(ast_vuart_driver);
+
+MODULE_AUTHOR("Jeremy Kerr <jk@ozlabs.org>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Driver for Aspeed VUART device");
--
2.11.0
^ permalink raw reply related
* [PATCH 1/2] serial: 8250: Add flag so drivers can avoid THRE probe
From: Joel Stanley @ 2017-03-28 5:44 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby, Mark Rutland, Rob Herring
Cc: linux-serial, linux-kernel, openbmc, devicetree,
Benjamin Herrenschmidt, Jeremy Kerr
In-Reply-To: <20170328054458.29341-1-joel@jms.id.au>
The probing of THRE irq behaviour assumes the other end will be reading
bytes out of the buffer in order to probe the port at driver init. In
some cases the other end cannot be relied upon to read these bytes, so
provide a flag for them to skip this step.
Bit 16 was chosen as the flags are a int and the top bits are taken.
Acked-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Signed-off-by: Joel Stanley <joel@jms.id.au>
---
drivers/tty/serial/8250/8250_port.c | 2 +-
include/linux/serial_core.h | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
index fe4399b41df6..f4c6da107dec 100644
--- a/drivers/tty/serial/8250/8250_port.c
+++ b/drivers/tty/serial/8250/8250_port.c
@@ -2205,7 +2205,7 @@ int serial8250_do_startup(struct uart_port *port)
}
}
- if (port->irq) {
+ if (port->irq && !(up->port.flags & UPF_NO_THRE_TEST)) {
unsigned char iir1;
/*
* Test for UARTs that do not reassert THRE when the
diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h
index 5def8e830fb0..f9e1fa39f553 100644
--- a/include/linux/serial_core.h
+++ b/include/linux/serial_core.h
@@ -195,6 +195,7 @@ struct uart_port {
#define UPF_NO_TXEN_TEST ((__force upf_t) (1 << 15))
#define UPF_MAGIC_MULTIPLIER ((__force upf_t) ASYNC_MAGIC_MULTIPLIER /* 16 */ )
+#define UPF_NO_THRE_TEST ((__force upf_t) (1 << 19))
/* Port has hardware-assisted h/w flow control */
#define UPF_AUTO_CTS ((__force upf_t) (1 << 20))
#define UPF_AUTO_RTS ((__force upf_t) (1 << 21))
--
2.11.0
^ permalink raw reply related
* [PATCH 0/2] drivers: serial: Aspeed VUART driver
From: Joel Stanley @ 2017-03-28 5:44 UTC (permalink / raw)
To: Greg Kroah-Hartman, Jiri Slaby, Mark Rutland, Rob Herring
Cc: linux-serial-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
openbmc-uLR06cmDAlY/bJ5BZ2RsiQ, devicetree-u79uwXL29TY76Z2rM5mHXA,
Benjamin Herrenschmidt, Jeremy Kerr
This is a driver for the Aspeed VUART. The VUART is a serial device on the BMC
side of the LPC bus that connects a BMC to it's host processor.
We add a flag to the serial core to allow the driver to skip probing of the
THRE irq behaviour, which could hang due to the host not reading bytes out of
the buffer.
We've been using this on systems for over a year, so it has seen a good amount
of testing.
Cheers,
Joel
Jeremy Kerr (1):
drivers/serial: Add driver for Aspeed virtual UART
Joel Stanley (1):
serial: 8250: Add flag so drivers can avoid THRE probe
Documentation/devicetree/bindings/serial/8250.txt | 2 +
drivers/tty/serial/8250/8250_port.c | 2 +-
drivers/tty/serial/Kconfig | 10 +
drivers/tty/serial/Makefile | 1 +
drivers/tty/serial/aspeed-vuart.c | 335 ++++++++++++++++++++++
include/linux/serial_core.h | 1 +
6 files changed, 350 insertions(+), 1 deletion(-)
create mode 100644 drivers/tty/serial/aspeed-vuart.c
--
2.11.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
* Re: [PATCH] Make EN2 pin optional in the TRF7970A driver
From: Heiko Schocher @ 2017-03-28 5:37 UTC (permalink / raw)
To: Rob Herring
Cc: netdev, Guan Ben, Mark Jonas,
devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
linux-wireless,
linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
Samuel Ortiz, Lauro Ramos Venancio, Aloisio Almeida Jr,
Mark Rutland
In-Reply-To: <CAL_JsqKo_Y6i5otiEfQASqa4YRdqnr=+Got5XJp+ZfPCGZoxAg-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
Hello all,
Am 21.02.2017 um 17:43 schrieb Rob Herring:
> On Sun, Feb 19, 2017 at 11:19 PM, Heiko Schocher <hs-ynQEQJNshbs@public.gmane.org> wrote:
>> Hello all,
>>
>> Am 13.02.2017 um 22:31 schrieb Rob Herring:
>>>
>>> On Mon, Feb 13, 2017 at 12:38 AM, Heiko Schocher <hs-ynQEQJNshbs@public.gmane.org> wrote:
>>>>
>>>> Hello Rob,
>>>>
>>>>
>>>> Am 10.02.2017 um 16:51 schrieb Rob Herring:
>>>>>
>>>>>
>>>>> On Tue, Feb 07, 2017 at 06:22:04AM +0100, Heiko Schocher wrote:
>>>>>>
>>>>>>
>>>>>> From: Guan Ben <ben.guan-dUNdQdTMQaZWk0Htik3J/w@public.gmane.org>
>>>>>>
>>>>>> Make the EN2 pin optional. This is useful for boards,
>>>>>> which have this pin fix wired, for example to ground.
>>>>>>
>>>>>> Signed-off-by: Guan Ben <ben.guan-dUNdQdTMQaZWk0Htik3J/w@public.gmane.org>
>>>>>> Signed-off-by: Mark Jonas <mark.jonas-V5te9oGctAVWk0Htik3J/w@public.gmane.org>
>>>>>> Signed-off-by: Heiko Schocher <hs-ynQEQJNshbs@public.gmane.org>
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> .../devicetree/bindings/net/nfc/trf7970a.txt | 4 ++--
>>>>>> drivers/nfc/trf7970a.c | 26
>>>>>> ++++++++++++----------
>>>>>> 2 files changed, 16 insertions(+), 14 deletions(-)
>>>>>>
>>>>>> diff --git a/Documentation/devicetree/bindings/net/nfc/trf7970a.txt
>>>>>> b/Documentation/devicetree/bindings/net/nfc/trf7970a.txt
>>>>>> index 32b35a0..5889a3d 100644
>>>>>> --- a/Documentation/devicetree/bindings/net/nfc/trf7970a.txt
>>>>>> +++ b/Documentation/devicetree/bindings/net/nfc/trf7970a.txt
>>>>>> @@ -5,8 +5,8 @@ Required properties:
>>>>>> - spi-max-frequency: Maximum SPI frequency (<= 2000000).
>>>>>> - interrupt-parent: phandle of parent interrupt handler.
>>>>>> - interrupts: A single interrupt specifier.
>>>>>> -- ti,enable-gpios: Two GPIO entries used for 'EN' and 'EN2' pins on
>>>>>> the
>>>>>> - TRF7970A.
>>>>>> +- ti,enable-gpios: One or two GPIO entries used for 'EN' and 'EN2'
>>>>>> pins
>>>>>> on the
>>>>>> + TRF7970A. EN2 is optional.
>>>>>
>>>>>
>>>>>
>>>>> Could EN ever be optional/fixed? If so, perhaps deprecate this property
>>>>> and do 2 properties, one for each pin.
>>>>
>>>>
>>>>
>>>> The hardware I have has the EN2 pin fix connected to ground. Looking
>>>> into http://www.ti.com/lit/ds/slos743k/slos743k.pdf page 19 table 6-3
>>>> and 6-4 the EN2 pin is a don;t core if EN = 1. If EN = 0 EN2 pin
>>>> selects between Power Down and Sleep Mode ... I see no reason why
>>>> this is not possible/allowed ...
>>>>
>>>> Hmm.. I do not like the idea of deprecating the "ti,enable-gpios"
>>>> property into 2 seperate properties ... but if this would be a reason
>>>> for not accepting this patch, I can do this ... How should I name
>>>> the 2 new properties?
>>>
>>>
>>> I guess if this ever happens, then we just add "ti,enable2-gpios" and
>>> ti,enable-gpios continues to point to EN. We don't need to deprecate
>>> anything (or maybe just deprecate having both GPIOs on single
>>> property).
>>>
>>> In that case,
>>>
>>> Acked-by: Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org>
>>
>>
>> gentle ping.
>>
>> Are there any more comments to this patch? Is it acceptable as it
>> is?
>
> I acked it, so yes, it is fine.
Gentle ping. Any more issues or can this patch go into mainline?
bye,
Heiko
--
DENX Software Engineering GmbH, Managing Director: Wolfgang Denk
HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany
--
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
* Re: [RFC PATCH 1/3] of/pci: dma-ranges to account highest possible host bridge dma_mask
From: Oza Oza via iommu @ 2017-03-28 5:27 UTC (permalink / raw)
To: Rob Herring
Cc: devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
linux-pci-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, Linux IOMMU,
bcm-kernel-feedback-list-dY08KVG/lbpWk0Htik3J/w@public.gmane.org,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org
In-Reply-To: <CAL_JsqLtTiH7DhqKf_x7xy1KZYQyiOreK=M90HFr=8BDW9542w-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
On Mon, Mar 27, 2017 at 8:16 PM, Rob Herring <robh-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> wrote:
> On Sat, Mar 25, 2017 at 12:31 AM, Oza Pawandeep <oza.oza-dY08KVG/lbpWk0Htik3J/w@public.gmane.org> wrote:
>> it is possible that PCI device supports 64-bit DMA addressing,
>> and thus it's driver sets device's dma_mask to DMA_BIT_MASK(64),
>> however PCI host bridge may have limitations on the inbound
>> transaction addressing. As an example, consider NVME SSD device
>> connected to iproc-PCIe controller.
>>
>> Currently, the IOMMU DMA ops only considers PCI device dma_mask
>> when allocating an IOVA. This is particularly problematic on
>> ARM/ARM64 SOCs where the IOMMU (i.e. SMMU) translates IOVA to
>> PA for in-bound transactions only after PCI Host has forwarded
>> these transactions on SOC IO bus. This means on such ARM/ARM64
>> SOCs the IOVA of in-bound transactions has to honor the addressing
>> restrictions of the PCI Host.
>>
>> current pcie frmework and of framework integration assumes dma-ranges
>> in a way where memory-mapped devices define their dma-ranges.
>> dma-ranges: (child-bus-address, parent-bus-address, length).
>>
>> but iproc based SOCs and even Rcar based SOCs has PCI world dma-ranges.
>> dma-ranges = <0x43000000 0x00 0x00 0x00 0x00 0x80 0x00>;
>
> If you implement a common function, then I expect to see other users
> converted to use it. There's also PCI hosts in arch/powerpc that parse
> dma-ranges.
the common function should be similar to what
of_pci_get_host_bridge_resources is doing right now.
it parses ranges property right now.
the new function would look look following.
of_pci_get_dma_ranges(struct device_node *dev, struct list_head *resources)
where resources would return the dma-ranges.
but right now if you see the patch, of_dma_configure calls the new
function, which actually returns the largest possible size.
so this new function has to be generic in a way where other PCI hosts
can use it. but certainly iproc(Broadcom SOC) , rcar based SOCs can
use it for sure.
although having powerpc using it; is a separate exercise, since I do
not have any access to other PCI hosts such as powerpc. but we can
workout with them on thsi forum if required.
so overall, of_pci_get_dma_ranges has to serve following 2 purposes.
1) it has to return largest possible size to of_dma_configure to
generate largest possible dma_mask.
2) it also has to return resources (dma-ranges) parsed, to the users.
so to address above needs
of_pci_get_dma_ranges(struct device_node *dev, struct list_head
*resources, u64 *size)
dev -> device node.
resources -> dma-ranges in allocated list.
size -> highest possible size to generate possible dma_mask for
of_dma_configure.
let em know how this sounds.
Regards,
Oza.
^ permalink raw reply
* Re: [PATCH v5 1/2] i2c: aspeed: added driver for Aspeed I2C
From: Brendan Higgins @ 2017-03-28 5:23 UTC (permalink / raw)
To: Wolfram Sang
Cc: Kachalov Anton, vz@mleia.com, clg@kaod.org, robh+dt@kernel.org,
mark.rutland@arm.com, linux-i2c@vger.kernel.org,
devicetree@vger.kernel.org, joel@jms.id.au,
openbmc@lists.ozlabs.org
In-Reply-To: <20161212111005.GA1450@katana>
>> >> + /* Switch from master mode to slave mode. */
>> >> + func_ctrl_reg_val = aspeed_i2c_read(bus, ASPEED_I2C_FUN_CTRL_REG);
>> >> + func_ctrl_reg_val &= ~ASPEED_I2CD_MASTER_EN;
>> >> + func_ctrl_reg_val |= ASPEED_I2CD_SLAVE_EN;
>> >> + aspeed_i2c_write(bus, func_ctrl_reg_val, ASPEED_I2C_FUN_CTRL_REG);
>> >
>> > Can't the hardware work both as master and slave on the same bus?
>>
>> The hardware can work as master and slave on the same bus. This is how IPMB over i2c works on Aspeed.
I no longer disable master when a slave is registered. I was concerned
about adding multimaster support because I don't think it will play
nicely with bus recovery; should we maybe provide a device tree option
to turn multimaster support on which will disable bus recovery, since
someone else might be using it?
>
> Thanks! Then the driver should support this. Maybe it is an idea to
> first upstream the master support and add the slave support
> incrementally?
Yeah, probably a good idea. Slave support is now added in
https://lkml.org/lkml/2017/3/28/22.
>
> Regards,
>
> Wolfram
>
^ permalink raw reply
* Re: [PATCH v5 1/2] i2c: aspeed: added driver for Aspeed I2C
From: Brendan Higgins @ 2017-03-28 5:22 UTC (permalink / raw)
To: Vladimir Zapolskiy
Cc: Wolfram Sang, Cédric Le Goater, Kachalov Anton, robh+dt,
mark.rutland, linux-i2c, devicetree, Joel Stanley,
OpenBMC Maillist
In-Reply-To: <3236642a-3907-d54f-a091-6a952d8ff1c9@mleia.com>
I think most of these comments were pretty straightforward except for
pulling out the "struct aspeed_i2c_controller" into a interrupt
controller, which is now known as "struct aspeed_i2c_ic". See
https://lkml.org/lkml/2017/3/28/20 for details.
^ permalink raw reply
* [PATCH v6 5/5] i2c: aspeed: added slave support for Aspeed I2C driver
From: Brendan Higgins @ 2017-03-28 5:12 UTC (permalink / raw)
To: wsa, robh+dt, mark.rutland, tglx, jason, marc.zyngier, joel, vz,
mouse, clg
Cc: linux-i2c, devicetree, linux-kernel, openbmc, benh,
Brendan Higgins
In-Reply-To: <20170328051226.21677-1-brendanhiggins@google.com>
Added slave support for Aspeed I2C controller. Supports fourteen busses
present in AST24XX and AST25XX BMC SoCs by Aspeed.
Signed-off-by: Brendan Higgins <brendanhiggins@google.com>
---
Added in v6:
- Pulled slave support out of initial driver commit into its own commit.
- No longer arbitrarily restrict bus to be slave xor master.
---
drivers/i2c/busses/i2c-aspeed.c | 186 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 186 insertions(+)
diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c
index 04266acc6c46..a9ee58a2c4e2 100644
--- a/drivers/i2c/busses/i2c-aspeed.c
+++ b/drivers/i2c/busses/i2c-aspeed.c
@@ -49,6 +49,7 @@
#define ASPEED_I2CD_SDA_DRIVE_1T_EN BIT(8)
#define ASPEED_I2CD_M_SDA_DRIVE_1T_EN BIT(7)
#define ASPEED_I2CD_M_HIGH_SPEED_EN BIT(6)
+#define ASPEED_I2CD_SLAVE_EN BIT(1)
#define ASPEED_I2CD_MASTER_EN BIT(0)
/* 0x04 : I2CD Clock and AC Timing Control Register #1 */
@@ -69,6 +70,7 @@
*/
#define ASPEED_I2CD_INTR_SDA_DL_TIMEOUT BIT(14)
#define ASPEED_I2CD_INTR_BUS_RECOVER_DONE BIT(13)
+#define ASPEED_I2CD_INTR_SLAVE_MATCH BIT(7)
#define ASPEED_I2CD_INTR_SCL_TIMEOUT BIT(6)
#define ASPEED_I2CD_INTR_ABNORMAL BIT(5)
#define ASPEED_I2CD_INTR_NORMAL_STOP BIT(4)
@@ -106,6 +108,9 @@
#define ASPEED_I2CD_M_TX_CMD BIT(1)
#define ASPEED_I2CD_M_START_CMD BIT(0)
+/* 0x18 : I2CD Slave Device Address Register */
+#define ASPEED_I2CD_DEV_ADDR_MASK GENMASK(6, 0)
+
enum aspeed_i2c_master_state {
ASPEED_I2C_MASTER_START,
ASPEED_I2C_MASTER_TX_FIRST,
@@ -115,6 +120,15 @@ enum aspeed_i2c_master_state {
ASPEED_I2C_MASTER_INACTIVE,
};
+enum aspeed_i2c_slave_state {
+ ASPEED_I2C_SLAVE_START,
+ ASPEED_I2C_SLAVE_READ_REQUESTED,
+ ASPEED_I2C_SLAVE_READ_PROCESSED,
+ ASPEED_I2C_SLAVE_WRITE_REQUESTED,
+ ASPEED_I2C_SLAVE_WRITE_RECEIVED,
+ ASPEED_I2C_SLAVE_STOP,
+};
+
struct aspeed_i2c_bus {
struct i2c_adapter adap;
struct device *dev;
@@ -207,6 +221,110 @@ static int aspeed_i2c_recover_bus(struct aspeed_i2c_bus *bus)
return ret;
}
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+static bool aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus)
+{
+ u32 command, irq_status, status_ack = 0;
+ struct i2c_client *slave = bus->slave;
+ bool irq_handled = true;
+ u8 value;
+
+ spin_lock(&bus->lock);
+ if (!slave) {
+ irq_handled = false;
+ goto out;
+ }
+
+ command = aspeed_i2c_read(bus, ASPEED_I2C_CMD_REG);
+ irq_status = aspeed_i2c_read(bus, ASPEED_I2C_INTR_STS_REG);
+
+ /* Slave was requested, restart state machine. */
+ if (irq_status & ASPEED_I2CD_INTR_SLAVE_MATCH) {
+ status_ack |= ASPEED_I2CD_INTR_SLAVE_MATCH;
+ bus->slave_state = ASPEED_I2C_SLAVE_START;
+ }
+
+ /* Slave is not currently active, irq was for someone else. */
+ if (bus->slave_state == ASPEED_I2C_SLAVE_STOP) {
+ irq_handled = false;
+ goto out;
+ }
+
+ dev_dbg(bus->dev, "slave irq status 0x%08x, cmd 0x%08x\n",
+ irq_status, command);
+
+ /* Slave was sent something. */
+ if (irq_status & ASPEED_I2CD_INTR_RX_DONE) {
+ value = aspeed_i2c_read(bus, ASPEED_I2C_BYTE_BUF_REG) >> 8;
+ /* Handle address frame. */
+ if (bus->slave_state == ASPEED_I2C_SLAVE_START) {
+ if (value & 0x1)
+ bus->slave_state =
+ ASPEED_I2C_SLAVE_READ_REQUESTED;
+ else
+ bus->slave_state =
+ ASPEED_I2C_SLAVE_WRITE_REQUESTED;
+ }
+ status_ack |= ASPEED_I2CD_INTR_RX_DONE;
+ }
+
+ /* Slave was asked to stop. */
+ if (irq_status & ASPEED_I2CD_INTR_NORMAL_STOP) {
+ status_ack |= ASPEED_I2CD_INTR_NORMAL_STOP;
+ bus->slave_state = ASPEED_I2C_SLAVE_STOP;
+ }
+ if (irq_status & ASPEED_I2CD_INTR_TX_NAK) {
+ status_ack |= ASPEED_I2CD_INTR_TX_NAK;
+ bus->slave_state = ASPEED_I2C_SLAVE_STOP;
+ }
+
+ switch (bus->slave_state) {
+ case ASPEED_I2C_SLAVE_READ_REQUESTED:
+ if (irq_status & ASPEED_I2CD_INTR_TX_ACK)
+ dev_err(bus->dev, "Unexpected ACK on read request.\n");
+ bus->slave_state = ASPEED_I2C_SLAVE_READ_PROCESSED;
+
+ i2c_slave_event(slave, I2C_SLAVE_READ_REQUESTED, &value);
+ aspeed_i2c_write(bus, value, ASPEED_I2C_BYTE_BUF_REG);
+ aspeed_i2c_write(bus, ASPEED_I2CD_S_TX_CMD, ASPEED_I2C_CMD_REG);
+ break;
+ case ASPEED_I2C_SLAVE_READ_PROCESSED:
+ status_ack |= ASPEED_I2CD_INTR_TX_ACK;
+ if (!(irq_status & ASPEED_I2CD_INTR_TX_ACK))
+ dev_err(bus->dev,
+ "Expected ACK after processed read.\n");
+ i2c_slave_event(slave, I2C_SLAVE_READ_PROCESSED, &value);
+ aspeed_i2c_write(bus, value, ASPEED_I2C_BYTE_BUF_REG);
+ aspeed_i2c_write(bus, ASPEED_I2CD_S_TX_CMD, ASPEED_I2C_CMD_REG);
+ break;
+ case ASPEED_I2C_SLAVE_WRITE_REQUESTED:
+ bus->slave_state = ASPEED_I2C_SLAVE_WRITE_RECEIVED;
+ i2c_slave_event(slave, I2C_SLAVE_WRITE_REQUESTED, &value);
+ break;
+ case ASPEED_I2C_SLAVE_WRITE_RECEIVED:
+ i2c_slave_event(slave, I2C_SLAVE_WRITE_RECEIVED, &value);
+ break;
+ case ASPEED_I2C_SLAVE_STOP:
+ i2c_slave_event(slave, I2C_SLAVE_STOP, &value);
+ break;
+ default:
+ dev_err(bus->dev, "unhandled slave_state: %d\n",
+ bus->slave_state);
+ break;
+ }
+
+ if (status_ack != irq_status)
+ dev_err(bus->dev,
+ "irq handled != irq. expected %x, but was %x\n",
+ irq_status, status_ack);
+ aspeed_i2c_write(bus, status_ack, ASPEED_I2C_INTR_STS_REG);
+
+out:
+ spin_unlock(&bus->lock);
+ return irq_handled;
+}
+#endif
+
static void do_start(struct aspeed_i2c_bus *bus)
{
u32 command = ASPEED_I2CD_M_START_CMD | ASPEED_I2CD_M_TX_CMD;
@@ -371,6 +489,14 @@ static irqreturn_t aspeed_i2c_bus_irq(int irq, void *dev_id)
{
struct aspeed_i2c_bus *bus = dev_id;
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+ if (aspeed_i2c_slave_irq(bus)) {
+ dev_dbg(bus->dev, "irq handled by slave.\n");
+ return IRQ_HANDLED;
+ }
+#endif
+
+ dev_dbg(bus->dev, "irq handled by master.\n");
aspeed_i2c_master_irq(bus);
return IRQ_HANDLED;
}
@@ -426,9 +552,69 @@ static u32 aspeed_i2c_functionality(struct i2c_adapter *adap)
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_BLOCK_DATA;
}
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+static int aspeed_i2c_reg_slave(struct i2c_client *client)
+{
+ u32 addr_reg_val, func_ctrl_reg_val;
+ struct aspeed_i2c_bus *bus;
+ unsigned long flags;
+
+ bus = client->adapter->algo_data;
+ spin_lock_irqsave(&bus->lock, flags);
+ if (bus->slave) {
+ spin_unlock_irqrestore(&bus->lock, flags);
+ return -EINVAL;
+ }
+
+ /* Set slave addr. */
+ addr_reg_val = aspeed_i2c_read(bus, ASPEED_I2C_DEV_ADDR_REG);
+ addr_reg_val &= ~ASPEED_I2CD_DEV_ADDR_MASK;
+ addr_reg_val |= client->addr & ASPEED_I2CD_DEV_ADDR_MASK;
+ aspeed_i2c_write(bus, addr_reg_val, ASPEED_I2C_DEV_ADDR_REG);
+
+ /* Turn on slave mode. */
+ func_ctrl_reg_val = aspeed_i2c_read(bus, ASPEED_I2C_FUN_CTRL_REG);
+ func_ctrl_reg_val |= ASPEED_I2CD_SLAVE_EN;
+ aspeed_i2c_write(bus, func_ctrl_reg_val, ASPEED_I2C_FUN_CTRL_REG);
+
+ bus->slave = client;
+ bus->slave_state = ASPEED_I2C_SLAVE_STOP;
+ spin_unlock_irqrestore(&bus->lock, flags);
+
+ return 0;
+}
+
+static int aspeed_i2c_unreg_slave(struct i2c_client *client)
+{
+ struct aspeed_i2c_bus *bus = client->adapter->algo_data;
+ u32 func_ctrl_reg_val;
+ unsigned long flags;
+
+ spin_lock_irqsave(&bus->lock, flags);
+ if (!bus->slave) {
+ spin_unlock_irqrestore(&bus->lock, flags);
+ return -EINVAL;
+ }
+
+ /* Turn off slave mode. */
+ func_ctrl_reg_val = aspeed_i2c_read(bus, ASPEED_I2C_FUN_CTRL_REG);
+ func_ctrl_reg_val &= ~ASPEED_I2CD_SLAVE_EN;
+ aspeed_i2c_write(bus, func_ctrl_reg_val, ASPEED_I2C_FUN_CTRL_REG);
+
+ bus->slave = NULL;
+ spin_unlock_irqrestore(&bus->lock, flags);
+
+ return 0;
+}
+#endif
+
static const struct i2c_algorithm aspeed_i2c_algo = {
.master_xfer = aspeed_i2c_master_xfer,
.functionality = aspeed_i2c_functionality,
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+ .reg_slave = aspeed_i2c_reg_slave,
+ .unreg_slave = aspeed_i2c_unreg_slave,
+#endif
};
static u32 aspeed_i2c_get_clk_reg_val(u32 divisor)
--
2.12.2.564.g063fe858b8-goog
^ permalink raw reply related
* [PATCH v6 4/5] i2c: aspeed: added driver for Aspeed I2C
From: Brendan Higgins @ 2017-03-28 5:12 UTC (permalink / raw)
To: wsa-z923LK4zBo2bacvFa/9K2g, robh+dt-DgEjT+Ai2ygdnm+yROfE0A,
mark.rutland-5wv7dgnIgG8, tglx-hfZtesqFncYOwBW4kG4KsQ,
jason-NLaQJdtUoK4Be96aLqz0jA, marc.zyngier-5wv7dgnIgG8,
joel-U3u1mxZcP9KHXe+LvDLADg, vz-ChpfBGZJDbMAvxtiuMwx3w,
mouse-Pma6HLj0uuo, clg-Bxea+6Xhats
Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
openbmc-uLR06cmDAlY/bJ5BZ2RsiQ,
benh-XVmvHMARGAS8U2dJNN8I7kB+6BGkLq7r, Brendan Higgins
In-Reply-To: <20170328051226.21677-1-brendanhiggins-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
Added initial master support for Aspeed I2C controller. Supports
fourteen busses present in AST24XX and AST25XX BMC SoCs by Aspeed.
Signed-off-by: Brendan Higgins <brendanhiggins-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
---
Changes for v2:
- Added single module_init (multiple was breaking some builds).
Changes for v3:
- Removed "bus" device tree param; now extracted from bus address offset
Changes for v4:
- I2C adapter number is now generated dynamically unless specified in alias.
Changes for v5:
- Removed irq_chip used to multiplex IRQ and replaced it with dummy_irq_chip
along with some other IRQ cleanup.
- Addressed comments from Cedric, and Vladimir, mostly stylistic things and
using devm managed resources.
- Increased max clock frequency before the bus is put in HighSpeed mode, as
per Kachalov's comment.
Changes for v6:
- No longer arbitrarily restrict bus to be slave xor master.
- Pulled out "struct aspeed_i2c_controller" as a interrupt controller.
- Pulled out slave support into its own commit.
- Rewrote code that sets clock divider register because the original version
set it incorrectly.
- Rewrote the aspeed_i2c_master_irq handler because the old method of
completing a completion in between restarts was too slow causing devices to
misbehave.
- Added support for I2C_M_RECV_LEN which I had incorrectly said was supported
before.
- Addressed other comments from Vladimir.
---
drivers/i2c/busses/Kconfig | 10 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-aspeed.c | 610 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 621 insertions(+)
create mode 100644 drivers/i2c/busses/i2c-aspeed.c
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 8adc0f1d7ad0..e5ea5641a874 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -326,6 +326,16 @@ config I2C_POWERMAC
comment "I2C system bus drivers (mostly embedded / system-on-chip)"
+config I2C_ASPEED
+ tristate "Aspeed AST2xxx SoC I2C Controller"
+ depends on ARCH_ASPEED
+ help
+ If you say yes to this option, support will be included for the
+ Aspeed AST2xxx SoC I2C controller.
+
+ This driver can also be built as a module. If so, the module
+ will be called i2c-aspeed.
+
config I2C_AT91
tristate "Atmel AT91 I2C Two-Wire interface (TWI)"
depends on ARCH_AT91
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 30b60855fbcd..e84604b9bf3b 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -29,6 +29,7 @@ obj-$(CONFIG_I2C_HYDRA) += i2c-hydra.o
obj-$(CONFIG_I2C_POWERMAC) += i2c-powermac.o
# Embedded system I2C/SMBus host controller drivers
+obj-$(CONFIG_I2C_ASPEED) += i2c-aspeed.o
obj-$(CONFIG_I2C_AT91) += i2c-at91.o
obj-$(CONFIG_I2C_AU1550) += i2c-au1550.o
obj-$(CONFIG_I2C_AXXIA) += i2c-axxia.o
diff --git a/drivers/i2c/busses/i2c-aspeed.c b/drivers/i2c/busses/i2c-aspeed.c
new file mode 100644
index 000000000000..04266acc6c46
--- /dev/null
+++ b/drivers/i2c/busses/i2c-aspeed.c
@@ -0,0 +1,610 @@
+/*
+ * Aspeed 24XX/25XX I2C Interrupt Controller.
+ *
+ * Copyright (C) 2012-2017 ASPEED Technology Inc.
+ * Copyright 2017 IBM Corporation
+ * Copyright 2017 Google, Inc.
+ *
+ * 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/clk.h>
+#include <linux/completion.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+/* I2C Register */
+#define ASPEED_I2C_FUN_CTRL_REG 0x00
+#define ASPEED_I2C_AC_TIMING_REG1 0x04
+#define ASPEED_I2C_AC_TIMING_REG2 0x08
+#define ASPEED_I2C_INTR_CTRL_REG 0x0c
+#define ASPEED_I2C_INTR_STS_REG 0x10
+#define ASPEED_I2C_CMD_REG 0x14
+#define ASPEED_I2C_DEV_ADDR_REG 0x18
+#define ASPEED_I2C_BYTE_BUF_REG 0x20
+
+/* Global Register Definition */
+/* 0x00 : I2C Interrupt Status Register */
+/* 0x08 : I2C Interrupt Target Assignment */
+
+/* Device Register Definition */
+/* 0x00 : I2CD Function Control Register */
+#define ASPEED_I2CD_MULTI_MASTER_DIS BIT(15)
+#define ASPEED_I2CD_SDA_DRIVE_1T_EN BIT(8)
+#define ASPEED_I2CD_M_SDA_DRIVE_1T_EN BIT(7)
+#define ASPEED_I2CD_M_HIGH_SPEED_EN BIT(6)
+#define ASPEED_I2CD_MASTER_EN BIT(0)
+
+/* 0x04 : I2CD Clock and AC Timing Control Register #1 */
+#define ASPEED_I2CD_TIME_SCL_HIGH_SHIFT 16
+#define ASPEED_I2CD_TIME_SCL_HIGH_MASK GENMASK(19, 16)
+#define ASPEED_I2CD_TIME_SCL_LOW_SHIFT 12
+#define ASPEED_I2CD_TIME_SCL_LOW_MASK GENMASK(15, 12)
+#define ASPEED_I2CD_TIME_BASE_DIVISOR_MASK GENMASK(3, 0)
+#define ASPEED_I2CD_TIME_SCL_REG_MAX GENMASK(3, 0)
+/* 0x08 : I2CD Clock and AC Timing Control Register #2 */
+#define ASPEED_NO_TIMEOUT_CTRL 0
+
+/* 0x0c : I2CD Interrupt Control Register &
+ * 0x10 : I2CD Interrupt Status Register
+ *
+ * These share bit definitions, so use the same values for the enable &
+ * status bits.
+ */
+#define ASPEED_I2CD_INTR_SDA_DL_TIMEOUT BIT(14)
+#define ASPEED_I2CD_INTR_BUS_RECOVER_DONE BIT(13)
+#define ASPEED_I2CD_INTR_SCL_TIMEOUT BIT(6)
+#define ASPEED_I2CD_INTR_ABNORMAL BIT(5)
+#define ASPEED_I2CD_INTR_NORMAL_STOP BIT(4)
+#define ASPEED_I2CD_INTR_ARBIT_LOSS BIT(3)
+#define ASPEED_I2CD_INTR_RX_DONE BIT(2)
+#define ASPEED_I2CD_INTR_TX_NAK BIT(1)
+#define ASPEED_I2CD_INTR_TX_ACK BIT(0)
+#define ASPEED_I2CD_INTR_ERROR \
+ (ASPEED_I2CD_INTR_ARBIT_LOSS | \
+ ASPEED_I2CD_INTR_ABNORMAL | \
+ ASPEED_I2CD_INTR_SCL_TIMEOUT | \
+ ASPEED_I2CD_INTR_SDA_DL_TIMEOUT)
+#define ASPEED_I2CD_INTR_ALL \
+ (ASPEED_I2CD_INTR_SDA_DL_TIMEOUT | \
+ ASPEED_I2CD_INTR_BUS_RECOVER_DONE | \
+ ASPEED_I2CD_INTR_SCL_TIMEOUT | \
+ ASPEED_I2CD_INTR_ABNORMAL | \
+ ASPEED_I2CD_INTR_NORMAL_STOP | \
+ ASPEED_I2CD_INTR_ARBIT_LOSS | \
+ ASPEED_I2CD_INTR_RX_DONE | \
+ ASPEED_I2CD_INTR_TX_NAK | \
+ ASPEED_I2CD_INTR_TX_ACK)
+
+/* 0x14 : I2CD Command/Status Register */
+#define ASPEED_I2CD_SCL_LINE_STS BIT(18)
+#define ASPEED_I2CD_SDA_LINE_STS BIT(17)
+#define ASPEED_I2CD_BUS_BUSY_STS BIT(16)
+#define ASPEED_I2CD_BUS_RECOVER_CMD BIT(11)
+
+/* Command Bit */
+#define ASPEED_I2CD_M_STOP_CMD BIT(5)
+#define ASPEED_I2CD_M_S_RX_CMD_LAST BIT(4)
+#define ASPEED_I2CD_M_RX_CMD BIT(3)
+#define ASPEED_I2CD_S_TX_CMD BIT(2)
+#define ASPEED_I2CD_M_TX_CMD BIT(1)
+#define ASPEED_I2CD_M_START_CMD BIT(0)
+
+enum aspeed_i2c_master_state {
+ ASPEED_I2C_MASTER_START,
+ ASPEED_I2C_MASTER_TX_FIRST,
+ ASPEED_I2C_MASTER_TX,
+ ASPEED_I2C_MASTER_RX,
+ ASPEED_I2C_MASTER_STOP,
+ ASPEED_I2C_MASTER_INACTIVE,
+};
+
+struct aspeed_i2c_bus {
+ struct i2c_adapter adap;
+ struct device *dev;
+ void __iomem *base;
+ /* Synchronizes I/O mem access to base. */
+ spinlock_t lock;
+ struct completion cmd_complete;
+ int irq;
+ /* Transaction state. */
+ enum aspeed_i2c_master_state master_state;
+ struct i2c_msg *msgs;
+ size_t buf_index;
+ size_t msgs_index;
+ size_t msgs_size;
+ bool send_stop;
+ int cmd_err;
+#if IS_ENABLED(CONFIG_I2C_SLAVE)
+ struct i2c_client *slave;
+ enum aspeed_i2c_slave_state slave_state;
+#endif
+};
+
+static inline void aspeed_i2c_write(struct aspeed_i2c_bus *bus, u32 val,
+ u32 reg)
+{
+ writel(val, bus->base + reg);
+}
+
+static inline u32 aspeed_i2c_read(struct aspeed_i2c_bus *bus, u32 reg)
+{
+ return readl(bus->base + reg);
+}
+
+static int aspeed_i2c_recover_bus(struct aspeed_i2c_bus *bus)
+{
+ unsigned long time_left, flags;
+ int ret = 0;
+ u32 command;
+
+ spin_lock_irqsave(&bus->lock, flags);
+ command = aspeed_i2c_read(bus, ASPEED_I2C_CMD_REG);
+
+ if (command & ASPEED_I2CD_SDA_LINE_STS) {
+ /* Bus is idle: no recovery needed. */
+ if (command & ASPEED_I2CD_SCL_LINE_STS)
+ goto out;
+ dev_dbg(bus->dev, "bus hung (state %x), attempting recovery\n",
+ command);
+
+ aspeed_i2c_write(bus, ASPEED_I2CD_M_STOP_CMD,
+ ASPEED_I2C_CMD_REG);
+ reinit_completion(&bus->cmd_complete);
+ spin_unlock_irqrestore(&bus->lock, flags);
+
+ time_left = wait_for_completion_timeout(
+ &bus->cmd_complete, bus->adap.timeout);
+
+ spin_lock_irqsave(&bus->lock, flags);
+ if (time_left == 0)
+ ret = -ETIMEDOUT;
+ else if (bus->cmd_err)
+ ret = -EIO;
+ /* Bus error. */
+ } else {
+ dev_dbg(bus->dev, "bus hung (state %x), attempting recovery\n",
+ command);
+
+ aspeed_i2c_write(bus, ASPEED_I2CD_BUS_RECOVER_CMD,
+ ASPEED_I2C_CMD_REG);
+ reinit_completion(&bus->cmd_complete);
+ spin_unlock_irqrestore(&bus->lock, flags);
+
+ time_left = wait_for_completion_timeout(
+ &bus->cmd_complete, bus->adap.timeout);
+
+ spin_lock_irqsave(&bus->lock, flags);
+ if (time_left == 0)
+ ret = -ETIMEDOUT;
+ else if (bus->cmd_err)
+ ret = -EIO;
+ /* Recovery failed. */
+ else if (!(aspeed_i2c_read(bus, ASPEED_I2C_CMD_REG) &
+ ASPEED_I2CD_SDA_LINE_STS))
+ ret = -EIO;
+ }
+
+out:
+ spin_unlock_irqrestore(&bus->lock, flags);
+
+ return ret;
+}
+
+static void do_start(struct aspeed_i2c_bus *bus)
+{
+ u32 command = ASPEED_I2CD_M_START_CMD | ASPEED_I2CD_M_TX_CMD;
+ struct i2c_msg *msg = &bus->msgs[bus->msgs_index];
+ u8 slave_addr = msg->addr << 1;
+
+ bus->master_state = ASPEED_I2C_MASTER_START;
+ bus->buf_index = 0;
+
+ if (msg->flags & I2C_M_RD) {
+ slave_addr |= 1;
+ command |= ASPEED_I2CD_M_RX_CMD;
+ /* Need to let the hardware know to NACK after RX. */
+ if (msg->len == 1 && !(msg->flags & I2C_M_RECV_LEN))
+ command |= ASPEED_I2CD_M_S_RX_CMD_LAST;
+ }
+
+ aspeed_i2c_write(bus, slave_addr, ASPEED_I2C_BYTE_BUF_REG);
+ aspeed_i2c_write(bus, command, ASPEED_I2C_CMD_REG);
+}
+
+static void do_stop(struct aspeed_i2c_bus *bus)
+{
+ bus->master_state = ASPEED_I2C_MASTER_STOP;
+ aspeed_i2c_write(bus, ASPEED_I2CD_M_STOP_CMD,
+ ASPEED_I2C_CMD_REG);
+}
+
+static void aspeed_i2c_master_irq(struct aspeed_i2c_bus *bus)
+{
+ struct i2c_msg *msg = &bus->msgs[bus->msgs_index];
+ u32 irq_status, status_ack = 0, command = 0;
+ u8 recv_byte;
+
+ spin_lock(&bus->lock);
+ irq_status = aspeed_i2c_read(bus, ASPEED_I2C_INTR_STS_REG);
+
+ if (irq_status & ASPEED_I2CD_INTR_ERROR ||
+ (!bus->msgs && bus->master_state != ASPEED_I2C_MASTER_STOP)) {
+ dev_dbg(bus->dev, "received error interrupt: 0x%08x",
+ irq_status);
+ bus->cmd_err = -EIO;
+ do_stop(bus);
+ goto out_no_complete;
+ }
+
+ if (irq_status & ASPEED_I2CD_INTR_BUS_RECOVER_DONE) {
+ bus->master_state = ASPEED_I2C_MASTER_INACTIVE;
+ status_ack |= ASPEED_I2CD_INTR_BUS_RECOVER_DONE;
+ goto out_complete;
+ }
+
+ if (bus->master_state == ASPEED_I2C_MASTER_START) {
+ if (!(irq_status & ASPEED_I2CD_INTR_TX_ACK)) {
+ dev_dbg(bus->dev,
+ "no slave present at %02x", msg->addr);
+ status_ack |= ASPEED_I2CD_INTR_TX_NAK;
+ bus->cmd_err = -EIO;
+ do_stop(bus);
+ goto out_no_complete;
+ } else {
+ status_ack |= ASPEED_I2CD_INTR_TX_ACK;
+ if (msg->flags & I2C_M_RD)
+ bus->master_state = ASPEED_I2C_MASTER_RX;
+ else
+ bus->master_state = ASPEED_I2C_MASTER_TX_FIRST;
+ }
+ }
+
+ switch (bus->master_state) {
+ case ASPEED_I2C_MASTER_TX:
+ if (irq_status & ASPEED_I2CD_INTR_TX_NAK) {
+ dev_dbg(bus->dev, "slave NACKed TX");
+ status_ack |= ASPEED_I2CD_INTR_TX_NAK;
+ bus->cmd_err = -EIO;
+ do_stop(bus);
+ goto out_no_complete;
+ } else if (!(irq_status & ASPEED_I2CD_INTR_TX_ACK)) {
+ dev_err(bus->dev, "slave failed to ACK TX");
+ goto out_complete;
+ }
+ status_ack |= ASPEED_I2CD_INTR_TX_ACK;
+ /* fallthrough intended */
+ case ASPEED_I2C_MASTER_TX_FIRST:
+ if (bus->buf_index < msg->len) {
+ bus->master_state = ASPEED_I2C_MASTER_TX;
+ aspeed_i2c_write(bus, msg->buf[bus->buf_index++],
+ ASPEED_I2C_BYTE_BUF_REG);
+ aspeed_i2c_write(bus, ASPEED_I2CD_M_TX_CMD,
+ ASPEED_I2C_CMD_REG);
+ } else if (bus->msgs_index + 1 < bus->msgs_size) {
+ bus->msgs_index++;
+ do_start(bus);
+ } else {
+ do_stop(bus);
+ }
+ goto out_no_complete;
+ case ASPEED_I2C_MASTER_RX:
+ if (!(irq_status & ASPEED_I2CD_INTR_RX_DONE)) {
+ dev_err(bus->dev, "master failed to RX");
+ goto out_complete;
+ }
+ status_ack |= ASPEED_I2CD_INTR_RX_DONE;
+
+ recv_byte = aspeed_i2c_read(bus, ASPEED_I2C_BYTE_BUF_REG) >> 8;
+ msg->buf[bus->buf_index++] = recv_byte;
+
+ if (msg->flags & I2C_M_RECV_LEN &&
+ recv_byte <= I2C_SMBUS_BLOCK_MAX) {
+ msg->len = recv_byte +
+ ((msg->flags & I2C_CLIENT_PEC) ? 2 : 1);
+ msg->flags &= ~I2C_M_RECV_LEN;
+ }
+
+ if (bus->buf_index < msg->len) {
+ bus->master_state = ASPEED_I2C_MASTER_RX;
+ command = ASPEED_I2CD_M_RX_CMD;
+ if (bus->buf_index + 1 == msg->len)
+ command |= ASPEED_I2CD_M_S_RX_CMD_LAST;
+ aspeed_i2c_write(bus, command, ASPEED_I2C_CMD_REG);
+ } else if (bus->msgs_index + 1 < bus->msgs_size) {
+ bus->msgs_index++;
+ do_start(bus);
+ } else {
+ do_stop(bus);
+ }
+ goto out_no_complete;
+ case ASPEED_I2C_MASTER_STOP:
+ if (!(irq_status & ASPEED_I2CD_INTR_NORMAL_STOP)) {
+ dev_err(bus->dev, "master failed to STOP");
+ bus->cmd_err = -EIO;
+ }
+ status_ack |= ASPEED_I2CD_INTR_NORMAL_STOP;
+
+ bus->master_state = ASPEED_I2C_MASTER_INACTIVE;
+ goto out_complete;
+ case ASPEED_I2C_MASTER_INACTIVE:
+ dev_err(bus->dev,
+ "master received interrupt 0x%08x, but is inactive",
+ irq_status);
+ bus->cmd_err = -EIO;
+ goto out_complete;
+ default:
+ WARN(1, "unknown master state\n");
+ bus->master_state = ASPEED_I2C_MASTER_INACTIVE;
+ bus->cmd_err = -EIO;
+ goto out_complete;
+ }
+
+out_complete:
+ complete(&bus->cmd_complete);
+out_no_complete:
+ if (irq_status != status_ack)
+ dev_err(bus->dev,
+ "irq handled != irq. expected 0x%08x, but was 0x%08x\n",
+ irq_status, status_ack);
+ aspeed_i2c_write(bus, irq_status, ASPEED_I2C_INTR_STS_REG);
+ spin_unlock(&bus->lock);
+}
+
+static irqreturn_t aspeed_i2c_bus_irq(int irq, void *dev_id)
+{
+ struct aspeed_i2c_bus *bus = dev_id;
+
+ aspeed_i2c_master_irq(bus);
+ return IRQ_HANDLED;
+}
+
+static int aspeed_i2c_master_xfer(struct i2c_adapter *adap,
+ struct i2c_msg *msgs, int num)
+{
+ struct aspeed_i2c_bus *bus = adap->algo_data;
+ unsigned long time_left, flags;
+ int ret = 0;
+
+ bus->cmd_err = 0;
+
+ /* If bus is busy, attempt recovery. We assume a single master
+ * environment.
+ */
+ if (aspeed_i2c_read(bus, ASPEED_I2C_CMD_REG) &
+ ASPEED_I2CD_BUS_BUSY_STS) {
+ ret = aspeed_i2c_recover_bus(bus);
+ if (ret)
+ return ret;
+ }
+
+ spin_lock_irqsave(&bus->lock, flags);
+ bus->msgs = msgs;
+ bus->msgs_index = 0;
+ bus->msgs_size = num;
+
+ do_start(bus);
+ reinit_completion(&bus->cmd_complete);
+ spin_unlock_irqrestore(&bus->lock, flags);
+
+ time_left = wait_for_completion_timeout(&bus->cmd_complete,
+ bus->adap.timeout);
+
+ spin_lock_irqsave(&bus->lock, flags);
+ bus->msgs = NULL;
+ if (time_left == 0)
+ ret = -ETIMEDOUT;
+ else
+ ret = bus->cmd_err;
+ spin_unlock_irqrestore(&bus->lock, flags);
+
+ /* If nothing went wrong, return number of messages transferred. */
+ if (ret >= 0)
+ return bus->msgs_index + 1;
+ else
+ return ret;
+}
+
+static u32 aspeed_i2c_functionality(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_BLOCK_DATA;
+}
+
+static const struct i2c_algorithm aspeed_i2c_algo = {
+ .master_xfer = aspeed_i2c_master_xfer,
+ .functionality = aspeed_i2c_functionality,
+};
+
+static u32 aspeed_i2c_get_clk_reg_val(u32 divisor)
+{
+ u32 base_clk, clk_high, clk_low, tmp;
+
+ /*
+ * The actual clock frequency of SCL is:
+ * SCL_freq = base_freq * (SCL_high + SCL_low)
+ * = APB_freq / divisor
+ * where base_freq is a programmable clock divider; its value is
+ * base_freq = 1 << base_clk
+ * SCL_high is the number of base_freq clock cycles that SCL stays high
+ * and SCL_low is the number of base_freq clock cycles that SCL stays
+ * low for a period of SCL.
+ * The actual register has a minimum SCL_high and SCL_low minimum of 1;
+ * thus, they start counting at zero. So
+ * SCL_high = clk_high + 1
+ * SCL_low = clk_low + 1
+ * Thus,
+ * SCL_freq = (1 << base_clk) * (clk_high + 1 + clk_low + 1)
+ * The documentation recommends clk_high >= 8 and clk_low >= 7 when
+ * possible; this last constraint gives us the following solution:
+ */
+ base_clk = divisor > 32 ? ilog2(divisor / 16 - 1) : 0;
+ tmp = divisor / (1 << base_clk);
+ clk_high = tmp / 2 + tmp % 2;
+ clk_low = tmp - clk_high;
+
+ clk_high -= 1;
+ clk_low -= 1;
+
+ return ((clk_high << ASPEED_I2CD_TIME_SCL_HIGH_SHIFT)
+ & ASPEED_I2CD_TIME_SCL_HIGH_MASK)
+ | ((clk_low << ASPEED_I2CD_TIME_SCL_LOW_SHIFT)
+ & ASPEED_I2CD_TIME_SCL_LOW_MASK)
+ | (base_clk & ASPEED_I2CD_TIME_BASE_DIVISOR_MASK);
+}
+
+static int aspeed_i2c_init_clk(struct aspeed_i2c_bus *bus,
+ struct platform_device *pdev)
+{
+ u32 clk_freq, divisor;
+ struct clk *pclk;
+ int ret;
+
+ pclk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(pclk)) {
+ dev_err(&pdev->dev, "clk_get failed\n");
+ return PTR_ERR(pclk);
+ }
+ ret = of_property_read_u32(pdev->dev.of_node,
+ "clock-frequency", &clk_freq);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "Could not read clock-frequency property\n");
+ clk_freq = 100000;
+ }
+ divisor = clk_get_rate(pclk) / clk_freq;
+ /* We just need the clock rate, we don't actually use the clk object. */
+ devm_clk_put(&pdev->dev, pclk);
+
+ /* Set AC Timing */
+ if (clk_freq / 1000 > 1000) {
+ aspeed_i2c_write(bus, aspeed_i2c_read(bus,
+ ASPEED_I2C_FUN_CTRL_REG) |
+ ASPEED_I2CD_M_HIGH_SPEED_EN |
+ ASPEED_I2CD_M_SDA_DRIVE_1T_EN |
+ ASPEED_I2CD_SDA_DRIVE_1T_EN,
+ ASPEED_I2C_FUN_CTRL_REG);
+
+ aspeed_i2c_write(bus, 0x3, ASPEED_I2C_AC_TIMING_REG2);
+ aspeed_i2c_write(bus, aspeed_i2c_get_clk_reg_val(divisor),
+ ASPEED_I2C_AC_TIMING_REG1);
+ } else {
+ aspeed_i2c_write(bus, aspeed_i2c_get_clk_reg_val(divisor),
+ ASPEED_I2C_AC_TIMING_REG1);
+ aspeed_i2c_write(bus, ASPEED_NO_TIMEOUT_CTRL,
+ ASPEED_I2C_AC_TIMING_REG2);
+ }
+
+ return 0;
+}
+
+static int aspeed_i2c_probe_bus(struct platform_device *pdev)
+{
+ struct aspeed_i2c_bus *bus;
+ struct resource *res;
+ int ret;
+
+ bus = devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL);
+ if (!bus)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ bus->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(bus->base)) {
+ dev_err(&pdev->dev, "failed to devm_ioremap_resource\n");
+ return PTR_ERR(bus->base);
+ }
+
+ bus->irq = irq_of_parse_and_map(pdev->dev.of_node, 0);
+ ret = devm_request_irq(&pdev->dev, bus->irq, aspeed_i2c_bus_irq,
+ IRQF_SHARED, dev_name(&pdev->dev), bus);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to request interrupt\n");
+ return ret;
+ }
+
+ /* Initialize the I2C adapter */
+ spin_lock_init(&bus->lock);
+ init_completion(&bus->cmd_complete);
+ bus->adap.owner = THIS_MODULE;
+ bus->adap.retries = 0;
+ bus->adap.timeout = 5 * HZ;
+ bus->adap.algo = &aspeed_i2c_algo;
+ bus->adap.algo_data = bus;
+ bus->adap.dev.parent = &pdev->dev;
+ bus->adap.dev.of_node = pdev->dev.of_node;
+ snprintf(bus->adap.name, sizeof(bus->adap.name), "Aspeed i2c");
+
+ bus->dev = &pdev->dev;
+
+ /* reset device: disable master & slave functions */
+ aspeed_i2c_write(bus, 0, ASPEED_I2C_FUN_CTRL_REG);
+
+ ret = aspeed_i2c_init_clk(bus, pdev);
+ if (ret < 0)
+ return ret;
+
+ /* Enable Master Mode */
+ aspeed_i2c_write(bus, aspeed_i2c_read(bus, ASPEED_I2C_FUN_CTRL_REG) |
+ ASPEED_I2CD_MASTER_EN |
+ ASPEED_I2CD_MULTI_MASTER_DIS, ASPEED_I2C_FUN_CTRL_REG);
+
+ /* Set interrupt generation of I2C controller */
+ aspeed_i2c_write(bus, ASPEED_I2CD_INTR_ALL, ASPEED_I2C_INTR_CTRL_REG);
+
+ ret = i2c_add_adapter(&bus->adap);
+ if (ret < 0)
+ return ret;
+
+ platform_set_drvdata(pdev, bus);
+
+ dev_info(bus->dev, "i2c bus %d registered, irq %d\n",
+ bus->adap.nr, bus->irq);
+
+ return 0;
+}
+
+static int aspeed_i2c_remove_bus(struct platform_device *pdev)
+{
+ struct aspeed_i2c_bus *bus = platform_get_drvdata(pdev);
+
+ i2c_del_adapter(&bus->adap);
+
+ return 0;
+}
+
+static const struct of_device_id aspeed_i2c_bus_of_table[] = {
+ { .compatible = "aspeed,ast2400-i2c-bus", },
+ { .compatible = "aspeed,ast2500-i2c-bus", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, aspeed_i2c_bus_of_table);
+
+static struct platform_driver aspeed_i2c_bus_driver = {
+ .probe = aspeed_i2c_probe_bus,
+ .remove = aspeed_i2c_remove_bus,
+ .driver = {
+ .name = "ast-i2c-bus",
+ .of_match_table = aspeed_i2c_bus_of_table,
+ },
+};
+module_platform_driver(aspeed_i2c_bus_driver);
+
+MODULE_AUTHOR("Brendan Higgins <brendanhiggins-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>");
+MODULE_DESCRIPTION("Aspeed I2C Bus Driver");
+MODULE_LICENSE("GPL v2");
--
2.12.2.564.g063fe858b8-goog
--
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 3/5] i2c: aspeed: added documentation for Aspeed I2C driver
From: Brendan Higgins @ 2017-03-28 5:12 UTC (permalink / raw)
To: wsa, robh+dt, mark.rutland, tglx, jason, marc.zyngier, joel, vz,
mouse, clg
Cc: linux-i2c, devicetree, linux-kernel, openbmc, benh,
Brendan Higgins
In-Reply-To: <20170328051226.21677-1-brendanhiggins@google.com>
Added device tree binding documentation for Aspeed I2C busses.
Signed-off-by: Brendan Higgins <brendanhiggins@google.com>
---
Changes for v2:
- None
Changes for v3:
- Removed reference to "bus" device tree param
Changes for v4:
- None
Changes for v5:
- None
Changes for v6:
- Replaced the controller property with and interrupt controller, leaving only
the busses in the I2C documentation.
---
.../devicetree/bindings/i2c/i2c-aspeed.txt | 49 ++++++++++++++++++++++
1 file changed, 49 insertions(+)
create mode 100644 Documentation/devicetree/bindings/i2c/i2c-aspeed.txt
diff --git a/Documentation/devicetree/bindings/i2c/i2c-aspeed.txt b/Documentation/devicetree/bindings/i2c/i2c-aspeed.txt
new file mode 100644
index 000000000000..fbcc501706b1
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/i2c-aspeed.txt
@@ -0,0 +1,49 @@
+Device tree configuration for the I2C busses on the AST24XX and AST25XX SoCs.
+
+Required Properties:
+- #address-cells : should be 1
+- #size-cells : should be 0
+- reg : address offset and range of bus
+- compatible : should be "aspeed,ast2400-i2c-bus"
+ or "aspeed,ast2500-i2c-bus"
+- clocks : root clock of bus, should reference the APB
+ clock
+- interrupts : interrupt number
+- interrupt-parent : interrupt controller for bus, should reference a
+ aspeed,ast2400-i2c-ic or aspeed,ast2500-i2c-ic
+ interrupt controller
+
+Optional Properties:
+- clock-frequency : frequency of the bus clock in Hz
+ defaults to 100 kHz when not specified
+
+Example:
+
+i2c {
+ compatible = "simple-bus";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ ranges = <0 0x1e78a000 0x1000>;
+
+ i2c_ic: interrupt-controller@0 {
+ #interrupt-cells = <1>;
+ compatible = "aspeed,ast2400-i2c-ic";
+ reg = <0x0 0x40>;
+ interrupts = <12>;
+ interrupt-controller;
+ };
+
+ i2c0: i2c-bus@40 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ #interrupt-cells = <1>;
+ reg = <0x40 0x40>;
+ compatible = "aspeed,ast2400-i2c-bus";
+ bus = <0>;
+ clocks = <&clk_apb>;
+ clock-frequency = <100000>;
+ status = "disabled";
+ interrupts = <0>;
+ interrupt-parent = <&i2c_ic>;
+ };
+};
--
2.12.2.564.g063fe858b8-goog
^ permalink raw reply related
* [PATCH v6 2/5] irqchip/aspeed-i2c-ic: Add I2C IRQ controller for Aspeed
From: Brendan Higgins @ 2017-03-28 5:12 UTC (permalink / raw)
To: wsa-z923LK4zBo2bacvFa/9K2g, robh+dt-DgEjT+Ai2ygdnm+yROfE0A,
mark.rutland-5wv7dgnIgG8, tglx-hfZtesqFncYOwBW4kG4KsQ,
jason-NLaQJdtUoK4Be96aLqz0jA, marc.zyngier-5wv7dgnIgG8,
joel-U3u1mxZcP9KHXe+LvDLADg, vz-ChpfBGZJDbMAvxtiuMwx3w,
mouse-Pma6HLj0uuo, clg-Bxea+6Xhats
Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
openbmc-uLR06cmDAlY/bJ5BZ2RsiQ,
benh-XVmvHMARGAS8U2dJNN8I7kB+6BGkLq7r, Brendan Higgins
In-Reply-To: <20170328051226.21677-1-brendanhiggins-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
The Aspeed 24XX/25XX chips share a single hardware interrupt across 14
separate I2C busses. This adds a dummy irqchip which maps the single
hardware interrupt to software interrupts for each of the busses.
Signed-off-by: Brendan Higgins <brendanhiggins-hpIqsD4AKlfQT0dZR+AlfA@public.gmane.org>
---
Added in v6:
- Pulled "aspeed_i2c_controller" out into a interrupt controller since that is
what it actually does.
---
drivers/irqchip/Makefile | 2 +-
drivers/irqchip/irq-aspeed-i2c-ic.c | 102 ++++++++++++++++++++++++++++++++++++
2 files changed, 103 insertions(+), 1 deletion(-)
create mode 100644 drivers/irqchip/irq-aspeed-i2c-ic.c
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 152bc40b6762..c136c2bd1761 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -74,6 +74,6 @@ obj-$(CONFIG_MVEBU_ODMI) += irq-mvebu-odmi.o
obj-$(CONFIG_MVEBU_PIC) += irq-mvebu-pic.o
obj-$(CONFIG_LS_SCFG_MSI) += irq-ls-scfg-msi.o
obj-$(CONFIG_EZNPS_GIC) += irq-eznps.o
-obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o
+obj-$(CONFIG_ARCH_ASPEED) += irq-aspeed-vic.o irq-aspeed-i2c-ic.o
obj-$(CONFIG_STM32_EXTI) += irq-stm32-exti.o
obj-$(CONFIG_QCOM_IRQ_COMBINER) += qcom-irq-combiner.o
diff --git a/drivers/irqchip/irq-aspeed-i2c-ic.c b/drivers/irqchip/irq-aspeed-i2c-ic.c
new file mode 100644
index 000000000000..59c50b28dec0
--- /dev/null
+++ b/drivers/irqchip/irq-aspeed-i2c-ic.c
@@ -0,0 +1,102 @@
+/*
+ * Aspeed 24XX/25XX I2C Interrupt Controller.
+ *
+ * Copyright (C) 2012-2017 ASPEED Technology Inc.
+ * Copyright 2017 IBM Corporation
+ * Copyright 2017 Google, Inc.
+ *
+ * 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/irq.h>
+#include <linux/irqchip.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqdomain.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/io.h>
+
+
+#define ASPEED_I2C_IC_NUM_BUS 14
+
+struct aspeed_i2c_ic {
+ void __iomem *base;
+ int parent_irq;
+ struct irq_domain *irq_domain;
+};
+
+/*
+ * The aspeed chip provides a single hardware interrupt for all of the I2C
+ * busses, so we use a dummy interrupt chip to translate this single interrupt
+ * into multiple interrupts, each associated with a single I2C bus.
+ */
+static void aspeed_i2c_ic_irq_handler(struct irq_desc *desc)
+{
+ struct aspeed_i2c_ic *i2c_ic = irq_desc_get_handler_data(desc);
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ unsigned long bit, status;
+ unsigned int bus_irq;
+
+ chained_irq_enter(chip, desc);
+ status = readl(i2c_ic->base);
+ for_each_set_bit(bit, &status, ASPEED_I2C_IC_NUM_BUS) {
+ bus_irq = irq_find_mapping(i2c_ic->irq_domain, bit);
+ generic_handle_irq(bus_irq);
+ }
+ chained_irq_exit(chip, desc);
+}
+
+/*
+ * Set simple handler and mark IRQ as valid. Nothing interesting to do here
+ * since we are using a dummy interrupt chip.
+ */
+static int aspeed_i2c_ic_map_irq_domain(struct irq_domain *domain,
+ unsigned int irq, irq_hw_number_t hwirq)
+{
+ irq_set_chip_and_handler(irq, &dummy_irq_chip, handle_simple_irq);
+ irq_set_chip_data(irq, domain->host_data);
+
+ return 0;
+}
+
+static const struct irq_domain_ops aspeed_i2c_ic_irq_domain_ops = {
+ .map = aspeed_i2c_ic_map_irq_domain,
+};
+
+static int __init aspeed_i2c_ic_of_init(struct device_node *node,
+ struct device_node *parent)
+{
+ struct aspeed_i2c_ic *i2c_ic;
+
+ i2c_ic = kzalloc(sizeof(*i2c_ic), GFP_KERNEL);
+ if (!i2c_ic)
+ return -ENOMEM;
+
+ i2c_ic->base = of_iomap(node, 0);
+ if (IS_ERR(i2c_ic->base))
+ return PTR_ERR(i2c_ic->base);
+
+ i2c_ic->parent_irq = irq_of_parse_and_map(node, 0);
+ if (i2c_ic->parent_irq < 0)
+ return i2c_ic->parent_irq;
+
+ i2c_ic->irq_domain = irq_domain_add_linear(
+ node, ASPEED_I2C_IC_NUM_BUS,
+ &aspeed_i2c_ic_irq_domain_ops, NULL);
+ if (!i2c_ic->irq_domain)
+ return -ENOMEM;
+
+ i2c_ic->irq_domain->name = "ast-i2c-domain";
+
+ irq_set_chained_handler_and_data(i2c_ic->parent_irq,
+ aspeed_i2c_ic_irq_handler, i2c_ic);
+
+ pr_info("i2c controller registered, irq %d\n", i2c_ic->parent_irq);
+
+ return 0;
+}
+
+IRQCHIP_DECLARE(ast2400_i2c_ic, "aspeed,ast2400-i2c-ic", aspeed_i2c_ic_of_init);
+IRQCHIP_DECLARE(ast2500_i2c_ic, "aspeed,ast2500-i2c-ic", aspeed_i2c_ic_of_init);
--
2.12.2.564.g063fe858b8-goog
--
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 1/5] irqchip/aspeed-i2c-ic: binding docs for Aspeed I2C Interrupt Controller
From: Brendan Higgins @ 2017-03-28 5:12 UTC (permalink / raw)
To: wsa, robh+dt, mark.rutland, tglx, jason, marc.zyngier, joel, vz,
mouse, clg
Cc: linux-i2c, devicetree, linux-kernel, openbmc, benh,
Brendan Higgins
In-Reply-To: <20170328051226.21677-1-brendanhiggins@google.com>
Added device tree binding documentation for Aspeed I2C Interrupt
Controller.
Signed-off-by: Brendan Higgins <brendanhiggins@google.com>
---
Added in v6:
- Pulled "aspeed_i2c_controller" out into a interrupt controller since that is
what it actually does.
---
.../interrupt-controller/aspeed,ast2400-i2c-ic.txt | 25 ++++++++++++++++++++++
1 file changed, 25 insertions(+)
create mode 100644 Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2400-i2c-ic.txt
diff --git a/Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2400-i2c-ic.txt b/Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2400-i2c-ic.txt
new file mode 100644
index 000000000000..033cc82e5684
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/aspeed,ast2400-i2c-ic.txt
@@ -0,0 +1,25 @@
+Device tree configuration for the I2C Interrupt Controller on the AST24XX and
+AST25XX SoCs.
+
+Required Properties:
+- #address-cells : should be 1
+- #size-cells : should be 1
+- #interrupt-cells : should be 1
+- compatible : should be "aspeed,ast2400-i2c-ic"
+ or "aspeed,ast2500-i2c-ic"
+- reg : address start and range of controller
+- interrupts : interrupt number
+- interrupt-controller : denotes that the controller receives and fires
+ new interrupts for child busses
+
+Example:
+
+i2c_ic: interrupt-controller@0 {
+ #address-cells = <1>;
+ #size-cells = <1>;
+ #interrupt-cells = <1>;
+ compatible = "aspeed,ast2400-i2c-ic";
+ reg = <0x0 0x40>;
+ interrupts = <12>;
+ interrupt-controller;
+};
--
2.12.2.564.g063fe858b8-goog
^ permalink raw reply related
* [PATCH v6 0/5] i2c: aspeed: added driver for Aspeed I2C
From: Brendan Higgins @ 2017-03-28 5:12 UTC (permalink / raw)
To: wsa-z923LK4zBo2bacvFa/9K2g, robh+dt-DgEjT+Ai2ygdnm+yROfE0A,
mark.rutland-5wv7dgnIgG8, tglx-hfZtesqFncYOwBW4kG4KsQ,
jason-NLaQJdtUoK4Be96aLqz0jA, marc.zyngier-5wv7dgnIgG8,
joel-U3u1mxZcP9KHXe+LvDLADg, vz-ChpfBGZJDbMAvxtiuMwx3w,
mouse-Pma6HLj0uuo, clg-Bxea+6Xhats
Cc: linux-i2c-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA,
linux-kernel-u79uwXL29TY76Z2rM5mHXA,
openbmc-uLR06cmDAlY/bJ5BZ2RsiQ,
benh-XVmvHMARGAS8U2dJNN8I7kB+6BGkLq7r
Sorry for the delay, I went on a long vacation prior to receiving feedback and
got back in the middle of a hardware bring up that consumed all of my attention
for an extended period of time. I will try to plan upstream submissions around
my other responsibilities better in the future.
Addressed comments from:
- Vladimir in: https://www.spinics.net/lists/linux-i2c/msg27387.html
and: https://www.spinics.net/lists/linux-i2c/msg27386.html
- Wolfram in: https://www.spinics.net/lists/linux-i2c/msg27476.html
and: https://www.spinics.net/lists/linux-i2c/msg27483.html
Changes since previous update:
- No longer arbitrarily restrict bus to be slave xor master.
- Pulled out "struct aspeed_i2c_controller" as a interrupt controller.
- Pulled out slave support into its own commit.
- Rewrote code that sets clock divider register because the original version
set it incorrectly.
- Discovered and fixed issue in implementation that caused certain slave
devices to misbehave; the cause was that the master IRQ handler would return
control to the requesting thread after the last RX or TX command was handled
such that the requesting thread would issue either a repeated start or stop.
This was incorrect because the time taken to complete the completion was too
great. I fixed this by rewriting the master IRQ handler so that it now
manages the entire transaction only returning control to the requesting
thread once the entire transaction is complete.
- Rewrote the aspeed_i2c_master_irq handler because the old method of
completing a completion in between restarts was too slow causing devices to
misbehave.
- Added support for I2C_M_RECV_LEN which I had incorrectly said was supported
before.
- Addressed other comments from Vladimir.
Changes have been tested on the Aspeed 2500 evaluation board, as before, and now
on a real platform with an Aspeed 2520.
--
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
* Re: [RFC PATCH 3/3] of: fix node traversing in of_dma_get_range
From: Oza Oza via iommu @ 2017-03-28 4:50 UTC (permalink / raw)
To: Robin Murphy
Cc: Rob Herring, devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
linux-pci-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org, Linux IOMMU,
bcm-kernel-feedback-list-dY08KVG/lbpWk0Htik3J/w@public.gmane.org,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r@public.gmane.org
In-Reply-To: <3aec823d-d3c7-03d3-68f7-963d3649c184-5wv7dgnIgG8@public.gmane.org>
please find my comments inline.
On Mon, Mar 27, 2017 at 8:15 PM, Robin Murphy <robin.murphy-5wv7dgnIgG8@public.gmane.org> wrote:
> Hi Rob,
>
> On 27/03/17 15:34, Rob Herring wrote:
>> On Sat, Mar 25, 2017 at 12:31 AM, Oza Pawandeep <oza.oza-dY08KVG/lbpWk0Htik3J/w@public.gmane.org> wrote:
>>> it jumps to the parent node without examining the child node.
>>> also with that, it throws "no dma-ranges found for node"
>>> for pci dma-ranges.
>>>
>>> this patch fixes device node traversing for dma-ranges.
>>
>> What's the DT look like that doesn't work?
>
> The problem is the bodge in pci_dma_configure() where we don't have an
> OF node for the actual device itself, so pass in the host bridge's OF
> node instead. This happens to work well enough for dma-coherent, but I
> don't think dma-ranges was even considered at the time.
>
> As it happens I'm currently halfway through writing an experiment
> wherein pci_dma_configure() creates a temporary child node for the
> of_dma_configure() call if no other suitable alternative (e.g. some
> intermediate bridge node) exists. How hard are you likely to NAK that
> approach? ;)
>
>> dma-ranges is supposed to be a bus property, not a device's property.
>> So looking at the parent is correct behavior generally.
>
> Indeed, this patch as-is will break currently correct DTs (because we
> won't find dma-ranges on the device, so will bail before even looking at
> the parent as we should).
current parsing of dma-ranges assume that dma-ranges always to be
found in parent node.
based on that, my thinking is following:
couple of options
1)
instead while(1) some meaningful condition such as while(!node)
the following bail out is not required.
if (!ranges)
break;
2)
have check based on dt-property to distinguish between pci and handle
dma-ranges root bridge
but again these changes do not solve the entire problem for choosing
right dma_mask.
neither it actually correctly address root bridge pci dma-ranges.
and hence I have written
https://lkml.org/lkml/2017/3/27/540
my final take is: this function does not need to change, let it parse
memory mapped dma-ranges as it is doing.
I am more inclined to have generic pci dma-ranges and parsing.
which following already addresses.
https://lkml.org/lkml/2017/3/27/540
Regards,
Oza.
^ permalink raw reply
* [PATCH] base: export symbol of_phandle_iterator_init/next
From: Kuninori Morimoto @ 2017-03-28 2:30 UTC (permalink / raw)
To: Mark Brown, Rob Herring; +Cc: Linux-ALSA, Simon, Linux-DT
From: Kuninori Morimoto <kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org>
of_for_each_phandle() uses of_phandle_iterator_init/next
but these aren't exported. So kernel module complile will say
ERROR: "of_phandle_iterator_init" [xxx.ko] undefined!
ERROR: "of_phandle_iterator_next" [xxx.ko] undefined!
This patch solves this issue
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx-zM6kxYcvzFBBDgjK7y7TUQ@public.gmane.org>
---
This patch is related to this patch-set
Subject: [PATCH v5 0/9] ASoC: add OF-graph base simple-card
Date: Tue, 21 Mar 2017 14:17:03 +0900
drivers/of/base.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/of/base.c b/drivers/of/base.c
index 812edb9..bc42f91 100644
--- a/drivers/of/base.c
+++ b/drivers/of/base.c
@@ -1570,6 +1570,7 @@ int of_phandle_iterator_init(struct of_phandle_iterator *it,
return 0;
}
+EXPORT_SYMBOL_GPL(of_phandle_iterator_init);
int of_phandle_iterator_next(struct of_phandle_iterator *it)
{
@@ -1639,6 +1640,7 @@ int of_phandle_iterator_next(struct of_phandle_iterator *it)
return -EINVAL;
}
+EXPORT_SYMBOL_GPL(of_phandle_iterator_next);
int of_phandle_iterator_args(struct of_phandle_iterator *it,
uint32_t *args,
--
1.9.1
--
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] ARM: dts: da850: move spi0_cs3_pin pinconf node
From: David Lechner @ 2017-03-28 1:12 UTC (permalink / raw)
To: linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
devicetree-u79uwXL29TY76Z2rM5mHXA
Cc: David Lechner, Sekhar Nori, Kevin Hilman, Rob Herring,
Mark Rutland, linux-kernel-u79uwXL29TY76Z2rM5mHXA
This moves the spi0_cs3_pin pinconf node from the LEGO EV3 file to the
common DA850 include file. This node is applicable to any board, and
therefore belongs in the common file.
Signed-off-by: David Lechner <david-nq/r/kbU++upp/zk7JDF2g@public.gmane.org>
---
arch/arm/boot/dts/da850-lego-ev3.dts | 7 -------
arch/arm/boot/dts/da850.dtsi | 6 ++++++
2 files changed, 6 insertions(+), 7 deletions(-)
diff --git a/arch/arm/boot/dts/da850-lego-ev3.dts b/arch/arm/boot/dts/da850-lego-ev3.dts
index c20580a..512604a 100644
--- a/arch/arm/boot/dts/da850-lego-ev3.dts
+++ b/arch/arm/boot/dts/da850-lego-ev3.dts
@@ -177,13 +177,6 @@
&pmx_core {
status = "okay";
- spi0_cs3_pin: pinmux_spi0_cs3_pin {
- pinctrl-single,bits = <
- /* CS3 */
- 0xc 0x01000000 0x0f000000
- >;
- };
-
mmc0_cd_pin: pinmux_mmc0_cd {
pinctrl-single,bits = <
/* GP5[14] */
diff --git a/arch/arm/boot/dts/da850.dtsi b/arch/arm/boot/dts/da850.dtsi
index c708155..941d455 100644
--- a/arch/arm/boot/dts/da850.dtsi
+++ b/arch/arm/boot/dts/da850.dtsi
@@ -153,6 +153,12 @@
0x10 0x00000010 0x000000f0
>;
};
+ spi0_cs3_pin: pinmux_spi0_cs3_pin {
+ pinctrl-single,bits = <
+ /* CS3 */
+ 0xc 0x01000000 0x0f000000
+ >;
+ };
spi1_pins: pinmux_spi1_pins {
pinctrl-single,bits = <
/* SIMO, SOMI, CLK */
--
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
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