* [PATCH v5 09/17] iommu/amd: Declare MSI and HT regions as reserved IOVA regions
From: Eric Auger @ 2017-01-04 13:32 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1483536746-2725-1-git-send-email-eric.auger@redhat.com>
This patch registers the MSI and HT regions as non mappable
reserved regions. They will be exposed in the iommu-group sysfs.
For direct-mapped regions let's also use iommu_alloc_resv_region().
Signed-off-by: Eric Auger <eric.auger@redhat.com>
---
v5: creation
---
drivers/iommu/amd_iommu.c | 37 ++++++++++++++++++++++++++-----------
1 file changed, 26 insertions(+), 11 deletions(-)
diff --git a/drivers/iommu/amd_iommu.c b/drivers/iommu/amd_iommu.c
index c6c3f1e..24c752d 100644
--- a/drivers/iommu/amd_iommu.c
+++ b/drivers/iommu/amd_iommu.c
@@ -3164,6 +3164,7 @@ static bool amd_iommu_capable(enum iommu_cap cap)
static void amd_iommu_get_resv_regions(struct device *dev,
struct list_head *head)
{
+ struct iommu_resv_region *region;
struct unity_map_entry *entry;
int devid;
@@ -3172,28 +3173,42 @@ static void amd_iommu_get_resv_regions(struct device *dev,
return;
list_for_each_entry(entry, &amd_iommu_unity_map, list) {
- struct iommu_resv_region *region;
+ size_t length;
+ int prot = 0;
if (devid < entry->devid_start || devid > entry->devid_end)
continue;
- region = kzalloc(sizeof(*region), GFP_KERNEL);
+ length = entry->address_end - entry->address_start;
+ if (entry->prot & IOMMU_PROT_IR)
+ prot |= IOMMU_READ;
+ if (entry->prot & IOMMU_PROT_IW)
+ prot |= IOMMU_WRITE;
+
+ region = iommu_alloc_resv_region(entry->address_start,
+ length, prot,
+ IOMMU_RESV_DIRECT);
if (!region) {
pr_err("Out of memory allocating dm-regions for %s\n",
dev_name(dev));
return;
}
-
- region->start = entry->address_start;
- region->length = entry->address_end - entry->address_start;
- region->type = IOMMU_RESV_DIRECT;
- if (entry->prot & IOMMU_PROT_IR)
- region->prot |= IOMMU_READ;
- if (entry->prot & IOMMU_PROT_IW)
- region->prot |= IOMMU_WRITE;
-
list_add_tail(®ion->list, head);
}
+
+ region = iommu_alloc_resv_region(MSI_RANGE_START,
+ MSI_RANGE_END - MSI_RANGE_START + 1,
+ 0, IOMMU_RESV_NOMAP);
+ if (!region)
+ return;
+ list_add_tail(®ion->list, head);
+
+ region = iommu_alloc_resv_region(HT_RANGE_START,
+ HT_RANGE_END - HT_RANGE_START + 1,
+ 0, IOMMU_RESV_NOMAP);
+ if (!region)
+ return;
+ list_add_tail(®ion->list, head);
}
static void amd_iommu_put_resv_regions(struct device *dev,
--
1.9.1
^ permalink raw reply related
* [PATCH v5 08/17] iommu/vt-d: Implement reserved region get/put callbacks
From: Eric Auger @ 2017-01-04 13:32 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1483536746-2725-1-git-send-email-eric.auger@redhat.com>
This patch registers the [FEE0_0000h - FEF0_000h] 1MB MSI range
as a reserved region. This will allow to report that range
in the iommu-group sysfs.
Signed-off-by: Eric Auger <eric.auger@redhat.com>
---
RFCv2 -> RFCv3:
- use get/put_resv_region callbacks.
RFC v1 -> RFC v2:
- fix intel_iommu_add_reserved_regions name
- use IOAPIC_RANGE_START and IOAPIC_RANGE_END defines
- return if the MSI region is already registered;
---
drivers/iommu/intel-iommu.c | 50 +++++++++++++++++++++++++++++++++------------
1 file changed, 37 insertions(+), 13 deletions(-)
diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c
index c66c273..4080207 100644
--- a/drivers/iommu/intel-iommu.c
+++ b/drivers/iommu/intel-iommu.c
@@ -5184,6 +5184,28 @@ static void intel_iommu_remove_device(struct device *dev)
iommu_device_unlink(iommu->iommu_dev, dev);
}
+static void intel_iommu_get_resv_regions(struct device *device,
+ struct list_head *head)
+{
+ struct iommu_resv_region *reg;
+
+ reg = iommu_alloc_resv_region(IOAPIC_RANGE_START,
+ IOAPIC_RANGE_END - IOAPIC_RANGE_START + 1,
+ 0, IOMMU_RESV_NOMAP);
+ if (!reg)
+ return;
+ list_add_tail(®->list, head);
+}
+
+static void intel_iommu_put_resv_regions(struct device *dev,
+ struct list_head *head)
+{
+ struct iommu_resv_region *entry, *next;
+
+ list_for_each_entry_safe(entry, next, head, list)
+ kfree(entry);
+}
+
#ifdef CONFIG_INTEL_IOMMU_SVM
int intel_iommu_enable_pasid(struct intel_iommu *iommu, struct intel_svm_dev *sdev)
{
@@ -5293,19 +5315,21 @@ struct intel_iommu *intel_svm_device_to_iommu(struct device *dev)
#endif /* CONFIG_INTEL_IOMMU_SVM */
static const struct iommu_ops intel_iommu_ops = {
- .capable = intel_iommu_capable,
- .domain_alloc = intel_iommu_domain_alloc,
- .domain_free = intel_iommu_domain_free,
- .attach_dev = intel_iommu_attach_device,
- .detach_dev = intel_iommu_detach_device,
- .map = intel_iommu_map,
- .unmap = intel_iommu_unmap,
- .map_sg = default_iommu_map_sg,
- .iova_to_phys = intel_iommu_iova_to_phys,
- .add_device = intel_iommu_add_device,
- .remove_device = intel_iommu_remove_device,
- .device_group = pci_device_group,
- .pgsize_bitmap = INTEL_IOMMU_PGSIZES,
+ .capable = intel_iommu_capable,
+ .domain_alloc = intel_iommu_domain_alloc,
+ .domain_free = intel_iommu_domain_free,
+ .attach_dev = intel_iommu_attach_device,
+ .detach_dev = intel_iommu_detach_device,
+ .map = intel_iommu_map,
+ .unmap = intel_iommu_unmap,
+ .map_sg = default_iommu_map_sg,
+ .iova_to_phys = intel_iommu_iova_to_phys,
+ .add_device = intel_iommu_add_device,
+ .remove_device = intel_iommu_remove_device,
+ .get_resv_regions = intel_iommu_get_resv_regions,
+ .put_resv_regions = intel_iommu_put_resv_regions,
+ .device_group = pci_device_group,
+ .pgsize_bitmap = INTEL_IOMMU_PGSIZES,
};
static void quirk_iommu_g4x_gfx(struct pci_dev *dev)
--
1.9.1
^ permalink raw reply related
* [PATCH v5 07/17] iommu: Implement reserved_regions iommu-group sysfs file
From: Eric Auger @ 2017-01-04 13:32 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1483536746-2725-1-git-send-email-eric.auger@redhat.com>
A new iommu-group sysfs attribute file is introduced. It contains
the list of reserved regions for the iommu-group. Each reserved
region is described on a separate line:
- first field is the start IOVA address,
- second is the end IOVA address,
Signed-off-by: Eric Auger <eric.auger@redhat.com>
---
v3 -> v4:
- add cast to long long int when printing to avoid warning on
i386
- change S_IRUGO into 0444
- remove sort. The list is natively sorted now.
The file layout is inspired of /sys/bus/pci/devices/BDF/resource.
I also read Documentation/filesystems/sysfs.txt so I expect this
to be frowned upon.
---
drivers/iommu/iommu.c | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index a2d51b3..15756d8 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -211,8 +211,32 @@ int iommu_get_group_resv_regions(struct iommu_group *group,
}
EXPORT_SYMBOL_GPL(iommu_get_group_resv_regions);
+static ssize_t iommu_group_show_resv_regions(struct iommu_group *group,
+ char *buf)
+{
+ struct iommu_resv_region *region, *next;
+ struct list_head group_resv_regions;
+ char *str = buf;
+
+ INIT_LIST_HEAD(&group_resv_regions);
+ iommu_get_group_resv_regions(group, &group_resv_regions);
+
+ list_for_each_entry_safe(region, next, &group_resv_regions, list) {
+ str += sprintf(str, "0x%016llx 0x%016llx\n",
+ (long long int)region->start,
+ (long long int)(region->start +
+ region->length - 1));
+ kfree(region);
+ }
+
+ return (str - buf);
+}
+
static IOMMU_GROUP_ATTR(name, S_IRUGO, iommu_group_show_name, NULL);
+static IOMMU_GROUP_ATTR(reserved_regions, 0444,
+ iommu_group_show_resv_regions, NULL);
+
static void iommu_group_release(struct kobject *kobj)
{
struct iommu_group *group = to_iommu_group(kobj);
@@ -227,6 +251,8 @@ static void iommu_group_release(struct kobject *kobj)
if (group->default_domain)
iommu_domain_free(group->default_domain);
+ iommu_group_remove_file(group, &iommu_group_attr_reserved_regions);
+
kfree(group->name);
kfree(group);
}
@@ -290,6 +316,11 @@ struct iommu_group *iommu_group_alloc(void)
*/
kobject_put(&group->kobj);
+ ret = iommu_group_create_file(group,
+ &iommu_group_attr_reserved_regions);
+ if (ret)
+ return ERR_PTR(ret);
+
pr_debug("Allocated group %d\n", group->id);
return group;
--
1.9.1
^ permalink raw reply related
* [PATCH v5 06/17] iommu: iommu_get_group_resv_regions
From: Eric Auger @ 2017-01-04 13:32 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1483536746-2725-1-git-send-email-eric.auger@redhat.com>
Introduce iommu_get_group_resv_regions whose role consists in
enumerating all devices from the group and collecting their
reserved regions. The list is sorted and overlaps are checked.
Signed-off-by: Eric Auger <eric.auger@redhat.com>
---
v3 -> v4:
- take the iommu_group lock in iommu_get_group_resv_regions
- the list now is sorted and overlaps are checked
NOTE:
- we do not move list elements from device to group list since
the iommu_put_resv_regions() could not be called.
- at the moment I did not introduce any iommu_put_group_resv_regions
since it simply consists in voiding/freeing the list
---
drivers/iommu/iommu.c | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/iommu.h | 8 ++++++
2 files changed, 86 insertions(+)
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index 41c1906..a2d51b3 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -133,6 +133,84 @@ static ssize_t iommu_group_show_name(struct iommu_group *group, char *buf)
return sprintf(buf, "%s\n", group->name);
}
+static int iommu_insert_resv_region(struct iommu_resv_region *new,
+ struct list_head *regions)
+{
+ struct iommu_resv_region *region;
+ phys_addr_t start = new->start;
+ phys_addr_t end = new->start + new->length - 1;
+ struct list_head *pos = regions->next;
+
+ while (pos != regions) {
+ struct iommu_resv_region *entry =
+ list_entry(pos, struct iommu_resv_region, list);
+ phys_addr_t a = entry->start;
+ phys_addr_t b = entry->start + entry->length - 1;
+
+ if (end < a) {
+ goto insert;
+ } else if (start > b) {
+ pos = pos->next;
+ } else if ((start >= a) && (end <= b)) {
+ goto done;
+ } else {
+ phys_addr_t new_start = min(a, start);
+ phys_addr_t new_end = max(b, end);
+
+ list_del(&entry->list);
+ entry->start = new_start;
+ entry->length = new_end - new_start + 1;
+ iommu_insert_resv_region(entry, regions);
+ }
+ }
+insert:
+ region = iommu_alloc_resv_region(new->start, new->length,
+ new->prot, new->type);
+ if (!region)
+ return -ENOMEM;
+
+ list_add_tail(®ion->list, pos);
+done:
+ return 0;
+}
+
+static int
+iommu_insert_device_resv_regions(struct list_head *dev_resv_regions,
+ struct list_head *group_resv_regions)
+{
+ struct iommu_resv_region *entry;
+ int ret;
+
+ list_for_each_entry(entry, dev_resv_regions, list) {
+ ret = iommu_insert_resv_region(entry, group_resv_regions);
+ if (ret)
+ break;
+ }
+ return ret;
+}
+
+int iommu_get_group_resv_regions(struct iommu_group *group,
+ struct list_head *head)
+{
+ struct iommu_device *device;
+ int ret = 0;
+
+ mutex_lock(&group->mutex);
+ list_for_each_entry(device, &group->devices, list) {
+ struct list_head dev_resv_regions;
+
+ INIT_LIST_HEAD(&dev_resv_regions);
+ iommu_get_resv_regions(device->dev, &dev_resv_regions);
+ ret = iommu_insert_device_resv_regions(&dev_resv_regions, head);
+ iommu_put_resv_regions(device->dev, &dev_resv_regions);
+ if (ret)
+ break;
+ }
+ mutex_unlock(&group->mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(iommu_get_group_resv_regions);
+
static IOMMU_GROUP_ATTR(name, S_IRUGO, iommu_group_show_name, NULL);
static void iommu_group_release(struct kobject *kobj)
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index e97a877..b5f8492 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -246,6 +246,8 @@ extern void iommu_set_fault_handler(struct iommu_domain *domain,
extern int iommu_request_dm_for_dev(struct device *dev);
extern struct iommu_resv_region *
iommu_alloc_resv_region(phys_addr_t start, size_t length, int prot, int type);
+extern int iommu_get_group_resv_regions(struct iommu_group *group,
+ struct list_head *head);
extern int iommu_attach_group(struct iommu_domain *domain,
struct iommu_group *group);
@@ -463,6 +465,12 @@ static inline void iommu_put_resv_regions(struct device *dev,
{
}
+static inline int iommu_get_group_resv_regions(struct iommu_group *group,
+ struct list_head *head)
+{
+ return -ENODEV;
+}
+
static inline int iommu_request_dm_for_dev(struct device *dev)
{
return -ENODEV;
--
1.9.1
^ permalink raw reply related
* [PATCH v5 05/17] iommu: Only map direct mapped regions
From: Eric Auger @ 2017-01-04 13:32 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1483536746-2725-1-git-send-email-eric.auger@redhat.com>
As we introduced new reserved region types which do not require
mapping, let's make sure we only map direct mapped regions.
Signed-off-by: Eric Auger <eric.auger@redhat.com>
---
v3 -> v4:
- use region's type and reword commit message and title
---
drivers/iommu/iommu.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index 927878d..41c1906 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -343,6 +343,9 @@ static int iommu_group_create_direct_mappings(struct iommu_group *group,
start = ALIGN(entry->start, pg_size);
end = ALIGN(entry->start + entry->length, pg_size);
+ if (entry->type != IOMMU_RESV_DIRECT)
+ continue;
+
for (addr = start; addr < end; addr += pg_size) {
phys_addr_t phys_addr;
--
1.9.1
^ permalink raw reply related
* [PATCH v5 04/17] iommu: iommu_alloc_resv_region
From: Eric Auger @ 2017-01-04 13:32 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1483536746-2725-1-git-send-email-eric.auger@redhat.com>
Introduce a new helper serving the purpose to allocate a reserved
region. This will be used in iommu driver implementing reserved
region callbacks.
Signed-off-by: Eric Auger <eric.auger@redhat.com>
---
v3 -> v4:
- add INIT_LIST_HEAD(®ion->list)
- use int for prot param and add int type param
- remove implementation outside of CONFIG_IOMMU_API
---
drivers/iommu/iommu.c | 18 ++++++++++++++++++
include/linux/iommu.h | 2 ++
2 files changed, 20 insertions(+)
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index 1cee5c3..927878d 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -1575,6 +1575,24 @@ void iommu_put_resv_regions(struct device *dev, struct list_head *list)
ops->put_resv_regions(dev, list);
}
+struct iommu_resv_region *iommu_alloc_resv_region(phys_addr_t start,
+ size_t length,
+ int prot, int type)
+{
+ struct iommu_resv_region *region;
+
+ region = kzalloc(sizeof(*region), GFP_KERNEL);
+ if (!region)
+ return NULL;
+
+ INIT_LIST_HEAD(®ion->list);
+ region->start = start;
+ region->length = length;
+ region->prot = prot;
+ region->type = type;
+ return region;
+}
+
/* Request that a device is direct mapped by the IOMMU */
int iommu_request_dm_for_dev(struct device *dev)
{
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index 9128a8d..e97a877 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -244,6 +244,8 @@ extern void iommu_set_fault_handler(struct iommu_domain *domain,
extern void iommu_get_resv_regions(struct device *dev, struct list_head *list);
extern void iommu_put_resv_regions(struct device *dev, struct list_head *list);
extern int iommu_request_dm_for_dev(struct device *dev);
+extern struct iommu_resv_region *
+iommu_alloc_resv_region(phys_addr_t start, size_t length, int prot, int type);
extern int iommu_attach_group(struct iommu_domain *domain,
struct iommu_group *group);
--
1.9.1
^ permalink raw reply related
* [PATCH v5 03/17] iommu: Add a new type field in iommu_resv_region
From: Eric Auger @ 2017-01-04 13:32 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1483536746-2725-1-git-send-email-eric.auger@redhat.com>
We introduce a new field to differentiate the reserved region
types and specialize the apply_resv_region implementation.
Legacy direct mapped regions have IOMMU_RESV_DIRECT type.
We introduce 2 new reserved memory types:
- IOMMU_RESV_MSI will characterize MSI regions
- IOMMU_RESV_NOMAP characterize regions that cannot by mapped.
Signed-off-by: Eric Auger <eric.auger@redhat.com>
---
drivers/iommu/amd_iommu.c | 1 +
include/linux/iommu.h | 7 +++++++
2 files changed, 8 insertions(+)
diff --git a/drivers/iommu/amd_iommu.c b/drivers/iommu/amd_iommu.c
index 04334eb..c6c3f1e 100644
--- a/drivers/iommu/amd_iommu.c
+++ b/drivers/iommu/amd_iommu.c
@@ -3186,6 +3186,7 @@ static void amd_iommu_get_resv_regions(struct device *dev,
region->start = entry->address_start;
region->length = entry->address_end - entry->address_start;
+ region->type = IOMMU_RESV_DIRECT;
if (entry->prot & IOMMU_PROT_IR)
region->prot |= IOMMU_READ;
if (entry->prot & IOMMU_PROT_IW)
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index bfecb8b..9128a8d 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -117,18 +117,25 @@ enum iommu_attr {
DOMAIN_ATTR_MAX,
};
+/* These are the possible reserved region types */
+#define IOMMU_RESV_DIRECT (1 << 0)
+#define IOMMU_RESV_NOMAP (1 << 1)
+#define IOMMU_RESV_MSI (1 << 2)
+
/**
* struct iommu_resv_region - descriptor for a reserved memory region
* @list: Linked list pointers
* @start: System physical start address of the region
* @length: Length of the region in bytes
* @prot: IOMMU Protection flags (READ/WRITE/...)
+ * @type: Type of the reserved region
*/
struct iommu_resv_region {
struct list_head list;
phys_addr_t start;
size_t length;
int prot;
+ int type;
};
#ifdef CONFIG_IOMMU_API
--
1.9.1
^ permalink raw reply related
* [PATCH v5 02/17] iommu: Rename iommu_dm_regions into iommu_resv_regions
From: Eric Auger @ 2017-01-04 13:32 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1483536746-2725-1-git-send-email-eric.auger@redhat.com>
We want to extend the callbacks used for dm regions and
use them for reserved regions. Reserved regions can be
- directly mapped regions
- regions that cannot be iommu mapped (PCI host bridge windows, ...)
- MSI regions (because they belong to another address space or because
they are not translated by the IOMMU and need special handling)
So let's rename the struct and also the callbacks.
Signed-off-by: Eric Auger <eric.auger@redhat.com>
Acked-by: Robin Murphy <robin.murphy@arm.com>
---
v3 -> v4:
- add Robin's A-b
---
drivers/iommu/amd_iommu.c | 20 ++++++++++----------
drivers/iommu/iommu.c | 22 +++++++++++-----------
include/linux/iommu.h | 29 +++++++++++++++--------------
3 files changed, 36 insertions(+), 35 deletions(-)
diff --git a/drivers/iommu/amd_iommu.c b/drivers/iommu/amd_iommu.c
index 019e027..04334eb 100644
--- a/drivers/iommu/amd_iommu.c
+++ b/drivers/iommu/amd_iommu.c
@@ -3161,8 +3161,8 @@ static bool amd_iommu_capable(enum iommu_cap cap)
return false;
}
-static void amd_iommu_get_dm_regions(struct device *dev,
- struct list_head *head)
+static void amd_iommu_get_resv_regions(struct device *dev,
+ struct list_head *head)
{
struct unity_map_entry *entry;
int devid;
@@ -3172,7 +3172,7 @@ static void amd_iommu_get_dm_regions(struct device *dev,
return;
list_for_each_entry(entry, &amd_iommu_unity_map, list) {
- struct iommu_dm_region *region;
+ struct iommu_resv_region *region;
if (devid < entry->devid_start || devid > entry->devid_end)
continue;
@@ -3195,18 +3195,18 @@ static void amd_iommu_get_dm_regions(struct device *dev,
}
}
-static void amd_iommu_put_dm_regions(struct device *dev,
+static void amd_iommu_put_resv_regions(struct device *dev,
struct list_head *head)
{
- struct iommu_dm_region *entry, *next;
+ struct iommu_resv_region *entry, *next;
list_for_each_entry_safe(entry, next, head, list)
kfree(entry);
}
-static void amd_iommu_apply_dm_region(struct device *dev,
+static void amd_iommu_apply_resv_region(struct device *dev,
struct iommu_domain *domain,
- struct iommu_dm_region *region)
+ struct iommu_resv_region *region)
{
struct dma_ops_domain *dma_dom = to_dma_ops_domain(to_pdomain(domain));
unsigned long start, end;
@@ -3230,9 +3230,9 @@ static void amd_iommu_apply_dm_region(struct device *dev,
.add_device = amd_iommu_add_device,
.remove_device = amd_iommu_remove_device,
.device_group = amd_iommu_device_group,
- .get_dm_regions = amd_iommu_get_dm_regions,
- .put_dm_regions = amd_iommu_put_dm_regions,
- .apply_dm_region = amd_iommu_apply_dm_region,
+ .get_resv_regions = amd_iommu_get_resv_regions,
+ .put_resv_regions = amd_iommu_put_resv_regions,
+ .apply_resv_region = amd_iommu_apply_resv_region,
.pgsize_bitmap = AMD_IOMMU_PGSIZES,
};
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index dbe7f65..1cee5c3 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -318,7 +318,7 @@ static int iommu_group_create_direct_mappings(struct iommu_group *group,
struct device *dev)
{
struct iommu_domain *domain = group->default_domain;
- struct iommu_dm_region *entry;
+ struct iommu_resv_region *entry;
struct list_head mappings;
unsigned long pg_size;
int ret = 0;
@@ -331,14 +331,14 @@ static int iommu_group_create_direct_mappings(struct iommu_group *group,
pg_size = 1UL << __ffs(domain->pgsize_bitmap);
INIT_LIST_HEAD(&mappings);
- iommu_get_dm_regions(dev, &mappings);
+ iommu_get_resv_regions(dev, &mappings);
/* We need to consider overlapping regions for different devices */
list_for_each_entry(entry, &mappings, list) {
dma_addr_t start, end, addr;
- if (domain->ops->apply_dm_region)
- domain->ops->apply_dm_region(dev, domain, entry);
+ if (domain->ops->apply_resv_region)
+ domain->ops->apply_resv_region(dev, domain, entry);
start = ALIGN(entry->start, pg_size);
end = ALIGN(entry->start + entry->length, pg_size);
@@ -358,7 +358,7 @@ static int iommu_group_create_direct_mappings(struct iommu_group *group,
}
out:
- iommu_put_dm_regions(dev, &mappings);
+ iommu_put_resv_regions(dev, &mappings);
return ret;
}
@@ -1559,20 +1559,20 @@ int iommu_domain_set_attr(struct iommu_domain *domain,
}
EXPORT_SYMBOL_GPL(iommu_domain_set_attr);
-void iommu_get_dm_regions(struct device *dev, struct list_head *list)
+void iommu_get_resv_regions(struct device *dev, struct list_head *list)
{
const struct iommu_ops *ops = dev->bus->iommu_ops;
- if (ops && ops->get_dm_regions)
- ops->get_dm_regions(dev, list);
+ if (ops && ops->get_resv_regions)
+ ops->get_resv_regions(dev, list);
}
-void iommu_put_dm_regions(struct device *dev, struct list_head *list)
+void iommu_put_resv_regions(struct device *dev, struct list_head *list)
{
const struct iommu_ops *ops = dev->bus->iommu_ops;
- if (ops && ops->put_dm_regions)
- ops->put_dm_regions(dev, list);
+ if (ops && ops->put_resv_regions)
+ ops->put_resv_regions(dev, list);
}
/* Request that a device is direct mapped by the IOMMU */
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index 0ff5111..bfecb8b 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -118,13 +118,13 @@ enum iommu_attr {
};
/**
- * struct iommu_dm_region - descriptor for a direct mapped memory region
+ * struct iommu_resv_region - descriptor for a reserved memory region
* @list: Linked list pointers
* @start: System physical start address of the region
* @length: Length of the region in bytes
* @prot: IOMMU Protection flags (READ/WRITE/...)
*/
-struct iommu_dm_region {
+struct iommu_resv_region {
struct list_head list;
phys_addr_t start;
size_t length;
@@ -150,9 +150,9 @@ struct iommu_dm_region {
* @device_group: find iommu group for a particular device
* @domain_get_attr: Query domain attributes
* @domain_set_attr: Change domain attributes
- * @get_dm_regions: Request list of direct mapping requirements for a device
- * @put_dm_regions: Free list of direct mapping requirements for a device
- * @apply_dm_region: Temporary helper call-back for iova reserved ranges
+ * @get_resv_regions: Request list of reserved regions for a device
+ * @put_resv_regions: Free list of reserved regions for a device
+ * @apply_resv_region: Temporary helper call-back for iova reserved ranges
* @domain_window_enable: Configure and enable a particular window for a domain
* @domain_window_disable: Disable a particular window for a domain
* @domain_set_windows: Set the number of windows for a domain
@@ -184,11 +184,12 @@ struct iommu_ops {
int (*domain_set_attr)(struct iommu_domain *domain,
enum iommu_attr attr, void *data);
- /* Request/Free a list of direct mapping requirements for a device */
- void (*get_dm_regions)(struct device *dev, struct list_head *list);
- void (*put_dm_regions)(struct device *dev, struct list_head *list);
- void (*apply_dm_region)(struct device *dev, struct iommu_domain *domain,
- struct iommu_dm_region *region);
+ /* Request/Free a list of reserved regions for a device */
+ void (*get_resv_regions)(struct device *dev, struct list_head *list);
+ void (*put_resv_regions)(struct device *dev, struct list_head *list);
+ void (*apply_resv_region)(struct device *dev,
+ struct iommu_domain *domain,
+ struct iommu_resv_region *region);
/* Window handling functions */
int (*domain_window_enable)(struct iommu_domain *domain, u32 wnd_nr,
@@ -233,8 +234,8 @@ extern size_t default_iommu_map_sg(struct iommu_domain *domain, unsigned long io
extern void iommu_set_fault_handler(struct iommu_domain *domain,
iommu_fault_handler_t handler, void *token);
-extern void iommu_get_dm_regions(struct device *dev, struct list_head *list);
-extern void iommu_put_dm_regions(struct device *dev, struct list_head *list);
+extern void iommu_get_resv_regions(struct device *dev, struct list_head *list);
+extern void iommu_put_resv_regions(struct device *dev, struct list_head *list);
extern int iommu_request_dm_for_dev(struct device *dev);
extern int iommu_attach_group(struct iommu_domain *domain,
@@ -443,12 +444,12 @@ static inline void iommu_set_fault_handler(struct iommu_domain *domain,
{
}
-static inline void iommu_get_dm_regions(struct device *dev,
+static inline void iommu_get_resv_regions(struct device *dev,
struct list_head *list)
{
}
-static inline void iommu_put_dm_regions(struct device *dev,
+static inline void iommu_put_resv_regions(struct device *dev,
struct list_head *list)
{
}
--
1.9.1
^ permalink raw reply related
* [PATCH v5 01/17] iommu/dma: Allow MSI-only cookies
From: Eric Auger @ 2017-01-04 13:32 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1483536746-2725-1-git-send-email-eric.auger@redhat.com>
IOMMU domain users such as VFIO face a similar problem to DMA API ops
with regard to mapping MSI messages in systems where the MSI write is
subject to IOMMU translation. With the relevant infrastructure now in
place for managed DMA domains, it's actually really simple for other
users to piggyback off that and reap the benefits without giving up
their own IOVA management, and without having to reinvent their own
wheel in the MSI layer.
Allow such users to opt into automatic MSI remapping by dedicating a
region of their IOVA space to a managed cookie, and extend the mapping
routine to implement a trivial linear allocator in such cases, to avoid
the needless overhead of a full-blown IOVA domain.
Signed-off-by: Robin Murphy <robin.murphy@arm.com>
Signed-off-by: Eric Auger <eric.auger@redhat.com>
---
[Eric] fixed 2 issues on MSI path
---
drivers/iommu/dma-iommu.c | 116 +++++++++++++++++++++++++++++++++++++---------
include/linux/dma-iommu.h | 7 +++
2 files changed, 101 insertions(+), 22 deletions(-)
diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c
index 2db0d64..f2c47ad 100644
--- a/drivers/iommu/dma-iommu.c
+++ b/drivers/iommu/dma-iommu.c
@@ -37,10 +37,19 @@ struct iommu_dma_msi_page {
phys_addr_t phys;
};
+enum iommu_dma_cookie_type {
+ IOMMU_DMA_IOVA_COOKIE,
+ IOMMU_DMA_MSI_COOKIE,
+};
+
struct iommu_dma_cookie {
- struct iova_domain iovad;
- struct list_head msi_page_list;
- spinlock_t msi_lock;
+ union {
+ struct iova_domain iovad;
+ dma_addr_t msi_iova;
+ };
+ struct list_head msi_page_list;
+ spinlock_t msi_lock;
+ enum iommu_dma_cookie_type type;
};
static inline struct iova_domain *cookie_iovad(struct iommu_domain *domain)
@@ -53,6 +62,19 @@ int iommu_dma_init(void)
return iova_cache_get();
}
+static struct iommu_dma_cookie *__cookie_alloc(enum iommu_dma_cookie_type type)
+{
+ struct iommu_dma_cookie *cookie;
+
+ cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
+ if (cookie) {
+ spin_lock_init(&cookie->msi_lock);
+ INIT_LIST_HEAD(&cookie->msi_page_list);
+ cookie->type = type;
+ }
+ return cookie;
+}
+
/**
* iommu_get_dma_cookie - Acquire DMA-API resources for a domain
* @domain: IOMMU domain to prepare for DMA-API usage
@@ -62,25 +84,53 @@ int iommu_dma_init(void)
*/
int iommu_get_dma_cookie(struct iommu_domain *domain)
{
+ if (domain->iova_cookie)
+ return -EEXIST;
+
+ domain->iova_cookie = __cookie_alloc(IOMMU_DMA_IOVA_COOKIE);
+ if (!domain->iova_cookie)
+ return -ENOMEM;
+
+ return 0;
+}
+EXPORT_SYMBOL(iommu_get_dma_cookie);
+
+/**
+ * iommu_get_msi_cookie - Acquire just MSI remapping resources
+ * @domain: IOMMU domain to prepare
+ * @base: Start address of IOVA region for MSI mappings
+ *
+ * Users who manage their own IOVA allocation and do not want DMA API support,
+ * but would still like to take advantage of automatic MSI remapping, can use
+ * this to initialise their own domain appropriately. Users should reserve a
+ * contiguous IOVA region, starting at @base, large enough to accommodate the
+ * number of PAGE_SIZE mappings necessary to cover every MSI doorbell address
+ * used by the devices attached to @domain.
+ */
+int iommu_get_msi_cookie(struct iommu_domain *domain, dma_addr_t base)
+{
struct iommu_dma_cookie *cookie;
+ if (domain->type != IOMMU_DOMAIN_UNMANAGED)
+ return -EINVAL;
+
if (domain->iova_cookie)
return -EEXIST;
- cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
+ cookie = __cookie_alloc(IOMMU_DMA_MSI_COOKIE);
if (!cookie)
return -ENOMEM;
- spin_lock_init(&cookie->msi_lock);
- INIT_LIST_HEAD(&cookie->msi_page_list);
+ cookie->msi_iova = base;
domain->iova_cookie = cookie;
return 0;
}
-EXPORT_SYMBOL(iommu_get_dma_cookie);
+EXPORT_SYMBOL(iommu_get_msi_cookie);
/**
* iommu_put_dma_cookie - Release a domain's DMA mapping resources
- * @domain: IOMMU domain previously prepared by iommu_get_dma_cookie()
+ * @domain: IOMMU domain previously prepared by iommu_get_dma_cookie() or
+ * iommu_get_msi_cookie()
*
* IOMMU drivers should normally call this from their domain_free callback.
*/
@@ -92,7 +142,7 @@ void iommu_put_dma_cookie(struct iommu_domain *domain)
if (!cookie)
return;
- if (cookie->iovad.granule)
+ if (cookie->type == IOMMU_DMA_IOVA_COOKIE && cookie->iovad.granule)
put_iova_domain(&cookie->iovad);
list_for_each_entry_safe(msi, tmp, &cookie->msi_page_list, list) {
@@ -137,11 +187,12 @@ static void iova_reserve_pci_windows(struct pci_dev *dev,
int iommu_dma_init_domain(struct iommu_domain *domain, dma_addr_t base,
u64 size, struct device *dev)
{
- struct iova_domain *iovad = cookie_iovad(domain);
+ struct iommu_dma_cookie *cookie = domain->iova_cookie;
+ struct iova_domain *iovad = &cookie->iovad;
unsigned long order, base_pfn, end_pfn;
- if (!iovad)
- return -ENODEV;
+ if (!cookie || cookie->type != IOMMU_DMA_IOVA_COOKIE)
+ return -EINVAL;
/* Use the smallest supported page size for IOVA granularity */
order = __ffs(domain->pgsize_bitmap);
@@ -662,11 +713,21 @@ static struct iommu_dma_msi_page *iommu_dma_get_msi_page(struct device *dev,
{
struct iommu_dma_cookie *cookie = domain->iova_cookie;
struct iommu_dma_msi_page *msi_page;
- struct iova_domain *iovad = &cookie->iovad;
+ struct iova_domain *iovad;
struct iova *iova;
int prot = IOMMU_WRITE | IOMMU_NOEXEC | IOMMU_MMIO;
+ size_t size;
+
+ if (cookie->type == IOMMU_DMA_IOVA_COOKIE) {
+ iovad = &cookie->iovad;
+ size = iovad->granule;
+ } else {
+ iovad = NULL;
+ size = PAGE_SIZE;
+ }
+
+ msi_addr &= ~(phys_addr_t)(size - 1);
- msi_addr &= ~(phys_addr_t)iova_mask(iovad);
list_for_each_entry(msi_page, &cookie->msi_page_list, list)
if (msi_page->phys == msi_addr)
return msi_page;
@@ -675,13 +736,18 @@ static struct iommu_dma_msi_page *iommu_dma_get_msi_page(struct device *dev,
if (!msi_page)
return NULL;
- iova = __alloc_iova(domain, iovad->granule, dma_get_mask(dev));
- if (!iova)
- goto out_free_page;
-
msi_page->phys = msi_addr;
- msi_page->iova = iova_dma_addr(iovad, iova);
- if (iommu_map(domain, msi_page->iova, msi_addr, iovad->granule, prot))
+ if (iovad) {
+ iova = __alloc_iova(domain, size, dma_get_mask(dev));
+ if (!iova)
+ goto out_free_page;
+ msi_page->iova = iova_dma_addr(iovad, iova);
+ } else {
+ msi_page->iova = cookie->msi_iova;
+ cookie->msi_iova += size;
+ }
+
+ if (iommu_map(domain, msi_page->iova, msi_addr, size, prot))
goto out_free_iova;
INIT_LIST_HEAD(&msi_page->list);
@@ -689,7 +755,10 @@ static struct iommu_dma_msi_page *iommu_dma_get_msi_page(struct device *dev,
return msi_page;
out_free_iova:
- __free_iova(iovad, iova);
+ if (iovad)
+ __free_iova(iovad, iova);
+ else
+ cookie->msi_iova -= size;
out_free_page:
kfree(msi_page);
return NULL;
@@ -730,7 +799,10 @@ void iommu_dma_map_msi_msg(int irq, struct msi_msg *msg)
msg->data = ~0U;
} else {
msg->address_hi = upper_32_bits(msi_page->iova);
- msg->address_lo &= iova_mask(&cookie->iovad);
+ if (cookie->type == IOMMU_DMA_IOVA_COOKIE)
+ msg->address_lo &= iova_mask(&cookie->iovad);
+ else
+ msg->address_lo &= (PAGE_SIZE - 1);
msg->address_lo += lower_32_bits(msi_page->iova);
}
}
diff --git a/include/linux/dma-iommu.h b/include/linux/dma-iommu.h
index 7f7e9a7..c843461 100644
--- a/include/linux/dma-iommu.h
+++ b/include/linux/dma-iommu.h
@@ -27,6 +27,7 @@
/* Domain management interface for IOMMU drivers */
int iommu_get_dma_cookie(struct iommu_domain *domain);
+int iommu_get_msi_cookie(struct iommu_domain *domain, dma_addr_t base);
void iommu_put_dma_cookie(struct iommu_domain *domain);
/* Setup call for arch DMA mapping code */
@@ -86,6 +87,12 @@ static inline int iommu_get_dma_cookie(struct iommu_domain *domain)
return -ENODEV;
}
+static inline int iommu_get_msi_cookie(struct iommu_domain *domain,
+ dma_addr_t base)
+{
+ return -ENODEV;
+}
+
static inline void iommu_put_dma_cookie(struct iommu_domain *domain)
{
}
--
1.9.1
^ permalink raw reply related
* [PATCH v5 00/17] KVM PCIe/MSI passthrough on ARM/ARM64 and IOVA reserved regions
From: Eric Auger @ 2017-01-04 13:32 UTC (permalink / raw)
To: linux-arm-kernel
Following LPC discussions, we now report reserved regions through
iommu-group sysfs reserved_regions attribute file.
Reserved regions are populated through the IOMMU get_resv_region
callback (former get_dm_regions), now implemented by amd-iommu,
intel-iommu and arm-smmu:
- the intel-iommu reports the [0xfee00000 - 0xfeefffff] MSI window
as an IOMMU_RESV_NOMAP reserved region.
- the amd-iommu reports device direct mapped regions, the MSI region
and HT regions.
- the arm-smmu reports the MSI window (arbitrarily located at
0x8000000 and 1MB large).
Unsafe interrupt assignment is tested by enumerating all MSI irq
domains and checking MSI remapping is supported in the above hierarchy.
This check is done in case we detect the iommu translates MSI
(an IOMMU_RESV_MSI window exists). Otherwise the IRQ remapping
capability is checked at IOMMU level. Obviously this is a defensive
IRQ safety assessment: Assuming there are several MSI controllers
in the system and at least one does not implement IRQ remapping,
the assignment will be considered as unsafe (even if this controller
is not acessible from the assigned devices).
The series integrates a not officially posted patch from Robin:
"iommu/dma: Allow MSI-only cookies".
Best Regards
Eric
Git: complete series available at
https://github.com/eauger/linux/tree/v4.10-rc2-reserved-v5
History:
RFCv4 -> PATCHv5
- fix IRQ security assessment by looking at irq domain parents
- check DOMAIN_BUS_FSL_MC_MSI irq domains
- AMD MSI and HT regions are exposed in iommu group sysfs
RFCv3 -> RFCv4:
- arm-smmu driver does not register PCI host bridge windows as
reserved regions anymore
- Implement reserved region get/put callbacks also in arm-smmuv3
- take the iommu_group lock on iommu_get_group_resv_regions
- add a type field in iommu_resv_region instead of using prot
- init the region list_head in iommu_alloc_resv_region, also
add type parameter
- iommu_insert_resv_region manage overlaps and sort reserved
windows
- address IRQ safety assessment by enumerating all the MSI irq
domains and checking the MSI_REMAP flag
- update Documentation/ABI/testing/sysfs-kernel-iommu_groups
RFC v2 -> v3:
- switch to an iommu-group sysfs API
- use new dummy allocator provided by Robin
- dummy allocator initialized by vfio-iommu-type1 after enumerating
the reserved regions
- at the moment ARM MSI base address/size is left unchanged compared
to v2
- we currently report reserved regions and not usable IOVA regions as
requested by Alex
RFC v1 -> v2:
- fix intel_add_reserved_regions
- add mutex lock/unlock in vfio_iommu_type1
Eric Auger (17):
iommu/dma: Allow MSI-only cookies
iommu: Rename iommu_dm_regions into iommu_resv_regions
iommu: Add a new type field in iommu_resv_region
iommu: iommu_alloc_resv_region
iommu: Only map direct mapped regions
iommu: iommu_get_group_resv_regions
iommu: Implement reserved_regions iommu-group sysfs file
iommu/vt-d: Implement reserved region get/put callbacks
iommu/amd: Declare MSI and HT regions as reserved IOVA regions
iommu/arm-smmu: Implement reserved region get/put callbacks
iommu/arm-smmu-v3: Implement reserved region get/put callbacks
irqdomain: Add IRQ_DOMAIN_FLAG_MSI_REMAP value
irqdomain: irq_domain_check_msi_remap
irqchip/gicv3-its: Sets IRQ_DOMAIN_FLAG_MSI_REMAP
vfio/type1: Allow transparent MSI IOVA allocation
vfio/type1: Check MSI remapping at irq domain level
iommu/arm-smmu: Do not advertise IOMMU_CAP_INTR_REMAP anymore
drivers/iommu/amd_iommu.c | 54 +++++++++-----
drivers/iommu/arm-smmu-v3.c | 30 +++++++-
drivers/iommu/arm-smmu.c | 30 +++++++-
drivers/iommu/dma-iommu.c | 116 ++++++++++++++++++++++++------
drivers/iommu/intel-iommu.c | 50 +++++++++----
drivers/iommu/iommu.c | 152 ++++++++++++++++++++++++++++++++++++---
drivers/irqchip/irq-gic-v3-its.c | 1 +
drivers/vfio/vfio_iommu_type1.c | 34 ++++++++-
include/linux/dma-iommu.h | 7 ++
include/linux/iommu.h | 46 ++++++++----
include/linux/irqdomain.h | 4 ++
kernel/irq/irqdomain.c | 41 +++++++++++
12 files changed, 481 insertions(+), 84 deletions(-)
--
1.9.1
^ permalink raw reply
* [RFC PATCH] usb: dwc3: add support for OTG driver compilation
From: Felipe Balbi @ 2017-01-04 13:31 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1483536181-22356-2-git-send-email-mnarani@xilinx.com>
Hi,
Manish Narani <manish.narani@xilinx.com> writes:
> This patch adds support for OTG driver compilation and object
> file creation
>
> Signed-off-by: Manish Narani <mnarani@xilinx.com>
> ---
> drivers/usb/dwc3/Makefile | 4 ++++
> 1 file changed, 4 insertions(+)
>
> diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile
> index ffca340..2d269ad 100644
> --- a/drivers/usb/dwc3/Makefile
> +++ b/drivers/usb/dwc3/Makefile
> @@ -17,6 +17,10 @@ ifneq ($(filter y,$(CONFIG_USB_DWC3_GADGET) $(CONFIG_USB_DWC3_DUAL_ROLE)),)
> dwc3-y += gadget.o ep0.o
> endif
>
> +ifneq ($(CONFIG_USB_DWC3_DUAL_ROLE),)
> + dwc3-y += otg.o
> +endif
you just broke compilation
--
balbi
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 832 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20170104/5e6aebb4/attachment.sig>
^ permalink raw reply
* [RFC PATCH] arch: arm64: dts: add USB OTG interrupts support in ZynqMP device tree
From: Felipe Balbi @ 2017-01-04 13:30 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1483536181-22356-1-git-send-email-mnarani@xilinx.com>
Hi,
Manish Narani <manish.narani@xilinx.com> writes:
> This patch adds OTG interrupt support in device tree. It will add
> an extra interrupt line number dedicated to OTG events. This will
> enable OTG interrupts to serve in DWC3 OTG driver.
>
> Signed-off-by: Manish Narani <mnarani@xilinx.com>
> ---
> arch/arm64/boot/dts/xilinx/zynqmp.dtsi | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/arch/arm64/boot/dts/xilinx/zynqmp.dtsi b/arch/arm64/boot/dts/xilinx/zynqmp.dtsi
> index 68a90833..ce9ad02 100644
> --- a/arch/arm64/boot/dts/xilinx/zynqmp.dtsi
> +++ b/arch/arm64/boot/dts/xilinx/zynqmp.dtsi
> @@ -351,7 +351,7 @@
> compatible = "snps,dwc3";
> status = "disabled";
> interrupt-parent = <&gic>;
> - interrupts = <0 65 4>;
> + interrupts = <0 65 4>, <0 69 4>;
help the driver by passing names to these IRQ lines.
--
balbi
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 832 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-arm-kernel/attachments/20170104/aae7b52a/attachment-0001.sig>
^ permalink raw reply
* [PATCH v3 2/3] dmaeninge: xilinx_dma: Fix bug in multiple frame stores scenario in vdma
From: Appana Durga Kedareswara Rao @ 2017-01-04 13:30 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <df3662de-fa3b-b33e-ebf5-88802aea5c49@synopsys.com>
Hi
Thanks for the review...
>
> Hi Kedar,
>
>
> On 04-01-2017 06:54, Kedareswara rao Appana wrote:
> > When VDMA is configured for more than one frame in the h/w for example
> > h/w is configured for n number of frames and user Submits n number of
> > frames and triggered the DMA using issue_pending API.
> > In the current driver flow we are submitting one frame at a time but
> > we should submit all the n number of frames at one time as the h/w Is
> > configured for n number of frames.
> >
> > This patch fixes this issue.
> >
> > Signed-off-by: Kedareswara rao Appana <appanad@xilinx.com>
>
> Looks fine. I have a couple of minor comments, if you address them you can add
> "Reviewed-by: Jose Abreu <joabreu@synopsys.com>"
> in next version.
Thanks...
Sure will fix the comments and will add your' s Reviewed-by....
[snip]
> > + /*
> > + * Note: When VDMA is built with default h/w configuration
> > + * On the S2MM(recv) side user should submit frames upto
> > + * H/W configured. If users submits less than h/w configured
> > + * VDMA engine tries to write to a invalid location
> > + * Results undefined behaviour/memory corruption.
> > + *
> > + * If user would like to submit frames less than h/w capable
> > + * On S2MM side please enable debug info 13 at the h/w level
> > + * It will allows the frame buffers numbers to be modified at runtime.
> > + */
> > + if (!chan->has_fstoreconfig && chan->direction == DMA_DEV_TO_MEM
> &&
> > + chan->desc_pendingcount < chan->num_frms) {
> > + dev_dbg(chan->dev, "Frame Store Configuration is not enabled
> at the");
> > + dev_dbg(chan->dev, " H/w level enable Debug info 13 at the
> h/w level");
> > + dev_dbg(chan->dev, " OR Submit the frames upto h/w
> Capable\n\r");
> > +
> > + return;
> > + }
>
> Hmm, may dev_warn would be more suitable because with dev_dbg and no
> dynamic debug enabled user will not know what happened. Also, I am aware
> that in direction DMA_MEM_TO_DEV there will be no corruption in PC side but it
> will be corruption in VDMA side because it will read from invalid memory
> locations. Maybe drop the check for channel direction.
Sure will fix it in the next version....
>
> I am also not fancy about dropping prints that are not grep'able (you do not
> break line in each print so a user searching for the whole string will not find it).
> Try to do a line break in each print or change the string to be smaller.
>
Sure will add a line break in each print in the next version...
Regards,
Kedar.
> Best regards,
> Jose Miguel Abreu
>
^ permalink raw reply
* [PATCH 1/2] arm64: dma_mapping: allow PCI host driver to limit DMA mask
From: Arnd Bergmann @ 2017-01-04 13:29 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <4bbc3494-bf95-9765-ccaf-cc3b3e1cd297@cogentembedded.com>
On Wednesday, January 4, 2017 9:24:09 AM CET Nikita Yushchenko wrote:
> > commit 9a57d58d116800a535510053136c6dd7a9c26e25
> > Author: Arnd Bergmann <arnd@arndb.de>
> > Date: Tue Nov 17 14:06:55 2015 +0100
> >
> > [EXPERIMENTAL] ARM64: check implement dma_set_mask
> >
> > Needs work for coherent mask
> >
> > Signed-off-by: Arnd Bergmann <arnd@arndb.de>
>
> Unfortunately this is far incomplete
>
> > @@ -957,6 +983,18 @@ void arch_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,
> > if (!dev->archdata.dma_ops)
> > dev->archdata.dma_ops = &swiotlb_dma_ops;
> >
> > + /*
> > + * we don't yet support buses that have a non-zero mapping.
> > + * Let's hope we won't need it
> > + */
> > + WARN_ON(dma_base != 0);
> > +
> > + /*
> > + * Whatever the parent bus can set. A device must not set
> > + * a DMA mask larger than this.
> > + */
> > + dev->archdata.parent_dma_mask = size;
> > +
>
> ... because size/mask passed here for PCI devices are meaningless.
>
> For OF platforms, this is called via of_dma_configure(), that checks
> dma-ranges of node that is *parent* for host bridge. Host bridge
> currently does not control this at all.
We need to think about this a bit. Is it actually the PCI host
bridge that limits the ranges here, or the bus that it is connected
to. In the latter case, the caller needs to be adapted to handle
both.
> In current device trees no dma-ranges is defined for nodes that are
> parents to pci host bridges. This will make of_dma_configure() to fall
> back to 32-bit size for all devices on all current platforms. Thus
> applying this patch will immediately break 64-bit dma masks on all
> hardware that supports it.
No, it won't break it, it will just fall back to swiotlb for all the
ones that are lacking the dma-ranges property. I think this is correct
behavior.
> Also related: dma-ranges property used by several pci host bridges is
> *not* compatible with "legacy" dma-ranges parsed by of_get_dma_range() -
> former uses additional flags word at beginning.
Can you elaborate? Do we have PCI host bridges that use wrongly formatted
dma-ranges properties?
Arnd
^ permalink raw reply
* [PATCH 2/2] arm64: mm: enable CONFIG_HOLES_IN_ZONE for NUMA
From: Will Deacon @ 2017-01-04 13:28 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1481706707-6211-3-git-send-email-ard.biesheuvel@linaro.org>
On Wed, Dec 14, 2016 at 09:11:47AM +0000, Ard Biesheuvel wrote:
> The NUMA code may get confused by the presence of NOMAP regions within
> zones, resulting in spurious BUG() checks where the node id deviates
> from the containing zone's node id.
>
> Since the kernel has no business reasoning about node ids of pages it
> does not own in the first place, enable CONFIG_HOLES_IN_ZONE to ensure
> that such pages are disregarded.
>
> Signed-off-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>
> ---
> arch/arm64/Kconfig | 4 ++++
> 1 file changed, 4 insertions(+)
>
> diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
> index 111742126897..0472afe64d55 100644
> --- a/arch/arm64/Kconfig
> +++ b/arch/arm64/Kconfig
> @@ -614,6 +614,10 @@ config NEED_PER_CPU_EMBED_FIRST_CHUNK
> def_bool y
> depends on NUMA
>
> +config HOLES_IN_ZONE
> + def_bool y
> + depends on NUMA
> +
> source kernel/Kconfig.preempt
> source kernel/Kconfig.hz
I'm happy to apply this, but I'll hold off until the first patch is queued
somewhere, since this doesn't help without the VM_BUG_ON being moved.
Alternatively, I can queue both if somebody from the mm camp acks the
first patch.
Will
^ permalink raw reply
* [PATCH] drm/meson: Fix plane atomic check when no crtc for the plane
From: Ville Syrjälä @ 2017-01-04 13:27 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1483435254-27955-1-git-send-email-narmstrong@baylibre.com>
On Tue, Jan 03, 2017 at 10:20:54AM +0100, Neil Armstrong wrote:
> When no CRTC is associated with the plane, the meson_plane_atomic_check()
> call breaks the kernel with an Oops.
>
> Fixes: bbbe775ec5b5 ("drm: Add support for Amlogic Meson Graphic Controller")
> Signed-off-by: Neil Armstrong <narmstrong@baylibre.com>
> ---
> drivers/gpu/drm/meson/meson_plane.c | 3 +++
> 1 file changed, 3 insertions(+)
>
> diff --git a/drivers/gpu/drm/meson/meson_plane.c b/drivers/gpu/drm/meson/meson_plane.c
> index 4942ca0..7890e30 100644
> --- a/drivers/gpu/drm/meson/meson_plane.c
> +++ b/drivers/gpu/drm/meson/meson_plane.c
> @@ -51,6 +51,9 @@ static int meson_plane_atomic_check(struct drm_plane *plane,
> struct drm_crtc_state *crtc_state;
> struct drm_rect clip = { 0, };
>
> + if (!state->crtc)
> + return 0;
> +
Since you're not going to call drm_plane_helper_check_state() you will
fail to update plane_state->visible.
What i915 does in this case is look for state->crtc first, and if that's
NULL it'll pick the crtc from the old state (plane->state->crtc). It
looks a bit ugly, but maybe we should hide it in a small helper function
that could be used by all drivers?
> crtc_state = drm_atomic_get_crtc_state(state->state, state->crtc);
> if (IS_ERR(crtc_state))
> return PTR_ERR(crtc_state);
> --
> 1.9.1
>
> _______________________________________________
> dri-devel mailing list
> dri-devel at lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel
--
Ville Syrj?l?
Intel OTC
^ permalink raw reply
* arm64: virt_to_page() does not return right page for a kernel image address
From: Ard Biesheuvel @ 2017-01-04 13:24 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <e83640e2-61ed-4c29-b047-23965e670c08@redhat.com>
On 4 January 2017 at 12:23, Pratyush Anand <panand@redhat.com> wrote:
>
>
> On Wednesday 04 January 2017 05:36 PM, Mark Rutland wrote:
>>
>> So it seems we need to fix the crypto test.
>>
>> Looking at crypto/testmgr.c, I can't spot the kmap_atomic() or the
>> page_address()/virt_to_page(). I guess that's hidden behind helpers,
>> which might also be used elsewhere?
>>
>> Could you elaborate on where exactly the problem is in the crypto test?
>
>
> We have in test_acomp() -> crypto_acomp_decompress() -> tfm->decompress() ->
> scomp_acomp_decompress() -> scomp_acomp_comp_decomp() ->
> scatterwalk_map_and_copy() -> scatterwalk_copychunks()
>
> 41 if (out != 2) {
> 42 vaddr = scatterwalk_map(walk);
> 43 memcpy_dir(buf, vaddr, len_this_page, out);
> 44 scatterwalk_unmap(vaddr);
> 45 }
>
>
> scatterwalk_map() gets vaddr from kmap_atomic().
>
> test_acomp() initializes sg:
> sg_init_one(&src, ctemplate[i].input, ilen);
>
This is the bug right here, and it is fixed already upstream. The
crypto test vectors are part of the kernel image, and should not be
used in scatterlists.
> ctemplate is a kernel address (like lzo_comp_tv_template), which was
> assigned to walk->sg latter and passed to kmap_atomic().
>
>
> ~Pratyush
^ permalink raw reply
* [RFC PATCH] usb: host: xhci: plat: add support for otg_set_host() call
From: Manish Narani @ 2017-01-04 13:23 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1483536181-22356-1-git-send-email-mnarani@xilinx.com>
This patch will add support for OTG host initialization. This will
help OTG drivers to populate their host subsystem.
Signed-off-by: Manish Narani <mnarani@xilinx.com>
---
drivers/usb/host/xhci-plat.c | 39 +++++++++++++++++++++++++++++++++++++++
1 file changed, 39 insertions(+)
diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c
index ddfab30..aa08bdd 100644
--- a/drivers/usb/host/xhci-plat.c
+++ b/drivers/usb/host/xhci-plat.c
@@ -19,6 +19,7 @@
#include <linux/usb/phy.h>
#include <linux/slab.h>
#include <linux/acpi.h>
+#include <linux/usb/otg.h>
#include "xhci.h"
#include "xhci-plat.h"
@@ -144,6 +145,37 @@ static const struct of_device_id usb_xhci_of_match[] = {
MODULE_DEVICE_TABLE(of, usb_xhci_of_match);
#endif
+static int usb_otg_set_host(struct device *dev, struct usb_hcd *hcd, bool yes)
+{
+ int ret = 0;
+
+ hcd->usb_phy = usb_get_phy(USB_PHY_TYPE_USB3);
+ if (!IS_ERR_OR_NULL(hcd->usb_phy) && hcd->usb_phy->otg) {
+ dev_dbg(dev, "%s otg support available\n", __func__);
+ if (yes) {
+ if (otg_set_host(hcd->usb_phy->otg, &hcd->self)) {
+ dev_err(dev, "%s otg_set_host failed\n",
+ __func__);
+ usb_put_phy(hcd->usb_phy);
+ goto disable_phy;
+ }
+ } else {
+ ret = otg_set_host(hcd->usb_phy->otg, NULL);
+ usb_put_phy(hcd->usb_phy);
+ goto disable_phy;
+ }
+
+ } else
+ goto disable_phy;
+
+ return 0;
+
+disable_phy:
+ hcd->usb_phy = NULL;
+
+ return ret;
+}
+
static int xhci_plat_probe(struct platform_device *pdev)
{
const struct of_device_id *match;
@@ -255,6 +287,11 @@ static int xhci_plat_probe(struct platform_device *pdev)
if (ret)
goto dealloc_usb2_hcd;
+ ret = usb_otg_set_host(&pdev->dev, hcd, 1);
+ if (ret)
+ goto dealloc_usb2_hcd;
+
+
return 0;
@@ -283,6 +320,8 @@ static int xhci_plat_remove(struct platform_device *dev)
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
struct clk *clk = xhci->clk;
+ usb_otg_set_host(&dev->dev, hcd, 0);
+
usb_remove_hcd(xhci->shared_hcd);
usb_phy_shutdown(hcd->usb_phy);
--
2.1.1
^ permalink raw reply related
* [RFC PATCH] usb: dwc3: otg: Adding OTG driver for DWC3 controller
From: Manish Narani @ 2017-01-04 13:23 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1483536181-22356-1-git-send-email-mnarani@xilinx.com>
This driver will add support for USB 2.0 OTG and USB 3.0 DRD in
DWC3 framework.
This OTG driver supports host/peripheral modes on run time. This
driver will enable HNP and SRP support in High-Speed mode.
Signed-off-by: Manish Narani <mnarani@xilinx.com>
---
drivers/usb/dwc3/otg.c | 2064 ++++++++++++++++++++++++++++++++++++++++++++++++
drivers/usb/dwc3/otg.h | 247 ++++++
2 files changed, 2311 insertions(+)
create mode 100644 drivers/usb/dwc3/otg.c
create mode 100644 drivers/usb/dwc3/otg.h
diff --git a/drivers/usb/dwc3/otg.c b/drivers/usb/dwc3/otg.c
new file mode 100644
index 0000000..4f2aa891
--- /dev/null
+++ b/drivers/usb/dwc3/otg.c
@@ -0,0 +1,2064 @@
+/**
+ * otg.c - DesignWare USB3 DRD Controller OTG file
+ *
+ * Copyright (C) 2016 Xilinx, Inc. All rights reserved.
+ *
+ * Author: Manish Narani <mnarani@xilinx.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 of
+ * the License 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.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/freezer.h>
+#include <linux/kthread.h>
+#include <linux/version.h>
+#include <linux/sysfs.h>
+
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/otg.h>
+#include <linux/usb/phy.h>
+
+#include <../drivers/usb/host/xhci.h>
+#include "core.h"
+#include "gadget.h"
+#include "io.h"
+#include "otg.h"
+
+#include <linux/ulpi/regs.h>
+#include <linux/ulpi/driver.h>
+#include "debug.h"
+
+/* Print the hardware registers' value for debugging purpose */
+static void print_debug_regs(struct dwc3_otg *otg)
+{
+ u32 gctl = otg_read(otg, DWC3_GCTL);
+ u32 gsts = otg_read(otg, DWC3_GSTS);
+ u32 gdbgltssm = otg_read(otg, DWC3_GDBGLTSSM);
+ u32 gusb2phycfg0 = otg_read(otg, DWC3_GUSB2PHYCFG(0));
+ u32 gusb3pipectl0 = otg_read(otg, DWC3_GUSB3PIPECTL(0));
+ u32 dcfg = otg_read(otg, DWC3_DCFG);
+ u32 dctl = otg_read(otg, DWC3_DCTL);
+ u32 dsts = otg_read(otg, DWC3_DSTS);
+ u32 ocfg = otg_read(otg, OCFG);
+ u32 octl = otg_read(otg, OCTL);
+ u32 oevt = otg_read(otg, OEVT);
+ u32 oevten = otg_read(otg, OEVTEN);
+ u32 osts = otg_read(otg, OSTS);
+
+ otg_info(otg, "gctl = %08x\n", gctl);
+ otg_info(otg, "gsts = %08x\n", gsts);
+ otg_info(otg, "gdbgltssm = %08x\n", gdbgltssm);
+ otg_info(otg, "gusb2phycfg0 = %08x\n", gusb2phycfg0);
+ otg_info(otg, "gusb3pipectl0 = %08x\n", gusb3pipectl0);
+ otg_info(otg, "dcfg = %08x\n", dcfg);
+ otg_info(otg, "dctl = %08x\n", dctl);
+ otg_info(otg, "dsts = %08x\n", dsts);
+ otg_info(otg, "ocfg = %08x\n", ocfg);
+ otg_info(otg, "octl = %08x\n", octl);
+ otg_info(otg, "oevt = %08x\n", oevt);
+ otg_info(otg, "oevten = %08x\n", oevten);
+ otg_info(otg, "osts = %08x\n", osts);
+}
+
+/* Check whether the hardware supports HNP or not */
+static int hnp_capable(struct dwc3_otg *otg)
+{
+ if (otg->hwparams6 & GHWPARAMS6_HNP_SUPPORT_ENABLED)
+ return 1;
+ return 0;
+}
+
+/* Check whether the hardware supports SRP or not */
+static int srp_capable(struct dwc3_otg *otg)
+{
+ if (otg->hwparams6 & GHWPARAMS6_SRP_SUPPORT_ENABLED)
+ return 1;
+ return 0;
+}
+
+/* Wakeup main thread to execute the OTG flow after an event */
+static void wakeup_main_thread(struct dwc3_otg *otg)
+{
+ if (!otg->main_thread)
+ return;
+
+ otg_vdbg(otg, "\n");
+ /* Tell the main thread that something has happened */
+ otg->main_wakeup_needed = 1;
+ wake_up_interruptible(&otg->main_wq);
+}
+
+/* Sleep main thread for 'msecs' to wait for an event to occur */
+static int sleep_main_thread_timeout(struct dwc3_otg *otg, int msecs)
+{
+ signed long jiffies;
+ int rc = msecs;
+
+ if (signal_pending(current)) {
+ otg_dbg(otg, "Main thread signal pending\n");
+ rc = -EINTR;
+ goto done;
+ }
+ if (otg->main_wakeup_needed) {
+ otg_dbg(otg, "Main thread wakeup needed\n");
+ rc = msecs;
+ goto done;
+ }
+
+ jiffies = msecs_to_jiffies(msecs);
+ rc = wait_event_freezable_timeout(otg->main_wq,
+ otg->main_wakeup_needed,
+ jiffies);
+
+ if (rc > 0)
+ rc = jiffies_to_msecs(rc);
+
+done:
+ otg->main_wakeup_needed = 0;
+ return rc;
+}
+
+/* Sleep main thread to wait for an event to occur */
+static int sleep_main_thread(struct dwc3_otg *otg)
+{
+ int rc;
+
+ do {
+ rc = sleep_main_thread_timeout(otg, 5000);
+ } while (rc == 0);
+
+ return rc;
+}
+
+static void get_events(struct dwc3_otg *otg, u32 *otg_events, u32 *user_events)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&otg->lock, flags);
+
+ if (otg_events)
+ *otg_events = otg->otg_events;
+
+ if (user_events)
+ *user_events = otg->user_events;
+
+ spin_unlock_irqrestore(&otg->lock, flags);
+}
+
+static void get_and_clear_events(struct dwc3_otg *otg, u32 *otg_events,
+ u32 *user_events)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&otg->lock, flags);
+
+ if (otg_events)
+ *otg_events = otg->otg_events;
+
+ if (user_events)
+ *user_events = otg->user_events;
+
+ otg->otg_events = 0;
+ otg->user_events = 0;
+
+ spin_unlock_irqrestore(&otg->lock, flags);
+}
+
+static int check_event(struct dwc3_otg *otg, u32 otg_mask, u32 user_mask)
+{
+ u32 otg_events;
+ u32 user_events;
+
+ get_events(otg, &otg_events, &user_events);
+ if ((otg_events & otg_mask) || (user_events & user_mask)) {
+ otg_dbg(otg, "Event occurred: otg_events=%x, otg_mask=%x, ",
+ otg_events, otg_mask);
+ otg_dbg(otg, "user_events=%x, user_mask=%x\n",
+ user_events, user_mask);
+ return 1;
+ }
+
+ return 0;
+}
+
+static int sleep_until_event(struct dwc3_otg *otg, u32 otg_mask, u32 user_mask,
+ u32 *otg_events, u32 *user_events, int timeout)
+{
+ int rc;
+
+ /* Enable the events */
+ if (otg_mask)
+ otg_write(otg, OEVTEN, otg_mask);
+
+ /* Wait until it occurs, or timeout, or interrupt. */
+ if (timeout) {
+ otg_vdbg(otg, "Waiting for event (timeout=%d)...\n", timeout);
+ rc = sleep_main_thread_until_condition_timeout(otg,
+ check_event(otg, otg_mask, user_mask), timeout);
+ } else {
+ otg_vdbg(otg, "Waiting for event (no timeout)...\n");
+ rc = sleep_main_thread_until_condition(otg,
+ check_event(otg, otg_mask, user_mask));
+ }
+
+ /* Disable the events */
+ otg_write(otg, OEVTEN, 0);
+
+ otg_vdbg(otg, "Woke up rc=%d\n", rc);
+ if (rc >= 0)
+ get_and_clear_events(otg, otg_events, user_events);
+
+ return rc;
+}
+
+static void set_capabilities(struct dwc3_otg *otg)
+{
+ u32 ocfg = 0;
+
+ otg_dbg(otg, "\n");
+ if (srp_capable(otg))
+ ocfg |= OCFG_SRP_CAP;
+
+ if (hnp_capable(otg))
+ ocfg |= OCFG_HNP_CAP;
+
+ otg_write(otg, OCFG, ocfg);
+
+ otg_dbg(otg, "Enabled SRP and HNP capabilities in OCFG\n");
+}
+
+static int otg3_handshake(struct dwc3_otg *otg, u32 reg, u32 mask, u32 done,
+ u32 msec)
+{
+ u32 result;
+ u32 usec = msec * 1000;
+
+ otg_vdbg(otg, "reg=%08x, mask=%08x, value=%08x\n", reg, mask, done);
+ do {
+ result = otg_read(otg, reg);
+ if ((result & mask) == done)
+ return 1;
+ udelay(1);
+ usec -= 1;
+ } while (usec > 0);
+
+ return 0;
+}
+
+static int reset_port(struct dwc3_otg *otg)
+{
+ otg_dbg(otg, "\n");
+ if (!otg->otg.host)
+ return -ENODEV;
+ return usb_bus_start_enum(otg->otg.host, 1);
+}
+
+static int set_peri_mode(struct dwc3_otg *otg, int mode)
+{
+ u32 octl;
+
+ /* Set peri_mode */
+ octl = otg_read(otg, OCTL);
+ if (mode)
+ octl |= OCTL_PERI_MODE;
+ else
+ octl &= ~OCTL_PERI_MODE;
+
+ otg_write(otg, OCTL, octl);
+ otg_dbg(otg, "set OCTL PERI_MODE = %d in OCTL\n", mode);
+
+ if (mode)
+ return otg3_handshake(otg, OSTS, OSTS_PERIP_MODE,
+ OSTS_PERIP_MODE, 100);
+ else
+ return otg3_handshake(otg, OSTS, OSTS_PERIP_MODE, 0, 100);
+
+ msleep(20);
+}
+
+static int start_host(struct dwc3_otg *otg)
+{
+ int ret = -ENODEV;
+ int flg;
+ u32 octl;
+ u32 osts;
+ u32 dctl;
+ u32 event_addr;
+ struct usb_hcd *hcd;
+ struct xhci_hcd *xhci;
+
+ otg_dbg(otg, "\n");
+
+ if (!otg->otg.host)
+ return -ENODEV;
+
+ dctl = otg_read(otg, DCTL);
+ if (dctl & DWC3_DCTL_RUN_STOP) {
+ otg_dbg(otg, "Disabling the RUN/STOP bit\n");
+ dctl &= ~DWC3_DCTL_RUN_STOP;
+ otg_write(otg, DCTL, dctl);
+ }
+
+ event_addr = dwc3_readl(otg->dwc->regs, DWC3_GEVNTADRLO(0));
+ if (event_addr != 0x0) {
+ otg_dbg(otg, "Freeing the device event buffers\n");
+ dwc3_free_event_buffers(otg->dwc);
+ }
+
+ if (!set_peri_mode(otg, PERI_MODE_HOST)) {
+ otg_err(otg, "Failed to start host\n");
+ return -EINVAL;
+ }
+
+ hcd = container_of(otg->otg.host, struct usb_hcd, self);
+ xhci = hcd_to_xhci(hcd);
+ otg_dbg(otg, "hcd=%p xhci=%p\n", hcd, xhci);
+
+ if (otg->host_started) {
+ otg_info(otg, "Host already started\n");
+ goto skip;
+ }
+
+ /* Start host driver */
+
+ *(struct xhci_hcd **)hcd->hcd_priv = xhci;
+ otg_dbg(otg, "1- calling usb_add_hcd() irq=%d\n", otg->hcd_irq);
+ ret = usb_add_hcd(hcd, otg->hcd_irq, IRQF_SHARED);
+ if (ret) {
+ otg_err(otg, "%s: failed to start primary hcd, ret=%d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ *(struct xhci_hcd **)xhci->shared_hcd->hcd_priv = xhci;
+ if (xhci->shared_hcd) {
+ otg_dbg(otg, "2- calling usb_add_hcd() irq=%d\n", otg->hcd_irq);
+ ret = usb_add_hcd(xhci->shared_hcd, otg->hcd_irq, IRQF_SHARED);
+ if (ret) {
+ otg_err(otg,
+ "%s: failed to start secondary hcd, ret=%d\n",
+ __func__, ret);
+ usb_remove_hcd(hcd);
+ return ret;
+ }
+ }
+
+ otg->host_started = 1;
+skip:
+ hcd->self.otg_port = 1;
+ if (xhci->shared_hcd)
+ xhci->shared_hcd->self.otg_port = 1;
+
+ set_capabilities(otg);
+
+ /* Power the port only for A-host */
+ if (otg->otg.state == OTG_STATE_A_WAIT_VRISE) {
+ /* Spin on xhciPrtPwr bit until it becomes 1 */
+ osts = otg_read(otg, OSTS);
+ flg = otg3_handshake(otg, OSTS,
+ OSTS_XHCI_PRT_PWR,
+ OSTS_XHCI_PRT_PWR,
+ 1000);
+ if (flg) {
+ otg_dbg(otg, "Port is powered by xhci-hcd\n");
+ /* Set port power control bit */
+ octl = otg_read(otg, OCTL);
+ octl |= OCTL_PRT_PWR_CTL;
+ otg_write(otg, OCTL, octl);
+ } else {
+ otg_dbg(otg, "Port is not powered by xhci-hcd\n");
+ }
+ }
+
+ return ret;
+}
+
+static int stop_host(struct dwc3_otg *otg)
+{
+ struct usb_hcd *hcd;
+ struct xhci_hcd *xhci;
+
+ otg_dbg(otg, "\n");
+
+ if (!otg->host_started) {
+ otg_info(otg, "Host already stopped\n");
+ return 1;
+ }
+
+ if (!otg->otg.host)
+ return -ENODEV;
+
+ otg_dbg(otg, "%s: turn off host %s\n",
+ __func__, otg->otg.host->bus_name);
+
+ hcd = container_of(otg->otg.host, struct usb_hcd, self);
+ xhci = hcd_to_xhci(hcd);
+
+ if (xhci->shared_hcd)
+ usb_remove_hcd(xhci->shared_hcd);
+ usb_remove_hcd(hcd);
+
+ otg->host_started = 0;
+ otg->dev_enum = 0;
+ return 0;
+}
+
+int dwc3_otg_host_release(struct usb_hcd *hcd)
+{
+ struct usb_bus *bus;
+ struct usb_device *rh;
+ struct usb_device *udev;
+
+ if (!hcd)
+ return -EINVAL;
+
+ bus = &hcd->self;
+ if (!bus->otg_port)
+ return 0;
+
+ rh = bus->root_hub;
+ udev = usb_hub_find_child(rh, bus->otg_port);
+ if (!udev)
+ return 0;
+
+ if (udev->config && udev->parent == udev->bus->root_hub) {
+ struct usb_otg20_descriptor *desc;
+
+ if (__usb_get_extra_descriptor(udev->rawdescriptors[0],
+ le16_to_cpu(udev->config[0].desc.wTotalLength),
+ USB_DT_OTG, (void **) &desc) == 0) {
+ int err;
+
+ dev_info(&udev->dev, "found OTG descriptor\n");
+ if ((desc->bcdOTG >= 0x0200) &&
+ (udev->speed == USB_SPEED_HIGH)) {
+ err = usb_control_msg(udev,
+ usb_sndctrlpipe(udev, 0),
+ USB_REQ_SET_FEATURE, 0,
+ USB_DEVICE_TEST_MODE,
+ 7 << 8,
+ NULL, 0, USB_CTRL_SET_TIMEOUT);
+ if (err < 0) {
+ dev_info(&udev->dev,
+ "can't initiate HNP from host: %d\n",
+ err);
+ return -1;
+ }
+ }
+ } else {
+ dev_info(&udev->dev, "didn't find OTG descriptor\n");
+ }
+ } else {
+ dev_info(&udev->dev,
+ "udev->config NULL or udev->parent != udev->bus->root_hub\n");
+ }
+
+ return 0;
+}
+
+/* Sends the host release set feature request */
+static void host_release(struct dwc3_otg *otg)
+{
+ struct usb_hcd *hcd;
+ struct xhci_hcd *xhci;
+
+ otg_dbg(otg, "\n");
+ if (!otg->otg.host)
+ return;
+ hcd = container_of(otg->otg.host, struct usb_hcd, self);
+ xhci = hcd_to_xhci(hcd);
+ dwc3_otg_host_release(hcd);
+ if (xhci->shared_hcd)
+ dwc3_otg_host_release(xhci->shared_hcd);
+}
+
+static void start_peripheral(struct dwc3_otg *otg)
+{
+ struct usb_gadget *gadget = otg->otg.gadget;
+ u32 event_addr;
+
+ otg_dbg(otg, "\n");
+ if (!gadget)
+ return;
+
+ if (!set_peri_mode(otg, PERI_MODE_PERIPHERAL))
+ otg_err(otg, "Failed to set peripheral mode\n");
+
+ if (otg->peripheral_started) {
+ otg_info(otg, "Peripheral already started\n");
+ return;
+ }
+
+ event_addr = dwc3_readl(otg->dwc->regs, DWC3_GEVNTADRLO(0));
+ if (event_addr == 0x0) {
+ int ret;
+
+ otg_dbg(otg, "allocating the event buffer\n");
+ ret = dwc3_alloc_event_buffers(otg->dwc,
+ DWC3_EVENT_BUFFERS_SIZE);
+ if (ret) {
+ dev_err(otg->dwc->dev,
+ "failed to allocate event buffers\n");
+ }
+ otg_dbg(otg, "setting up event buffers\n");
+ dwc3_event_buffers_setup(otg->dwc);
+ }
+
+ otg->peripheral_started = 1;
+
+ gadget->ops->udc_start(gadget, NULL);
+
+ gadget->b_hnp_enable = 0;
+ gadget->host_request_flag = 0;
+
+ otg_write(otg, DCTL, otg_read(otg, DCTL) | DCTL_RUN_STOP);
+ otg_dbg(otg, "Setting DCTL_RUN_STOP to 1 in DCTL\n");
+
+ msleep(20);
+}
+
+static void stop_peripheral(struct dwc3_otg *otg)
+{
+ struct usb_gadget *gadget = otg->otg.gadget;
+
+ otg_dbg(otg, "\n");
+
+ if (!otg->peripheral_started) {
+ otg_info(otg, "Peripheral already stopped\n");
+ return;
+ }
+
+ if (!gadget)
+ return;
+
+ gadget->ops->udc_stop(gadget);
+ otg->peripheral_started = 0;
+ msleep(20);
+}
+
+static void set_b_host(struct dwc3_otg *otg, int val)
+{
+ otg->otg.host->is_b_host = val;
+}
+
+static enum usb_otg_state do_b_idle(struct dwc3_otg *otg);
+
+static int init_b_device(struct dwc3_otg *otg)
+{
+ otg_dbg(otg, "\n");
+ set_capabilities(otg);
+
+ if (!set_peri_mode(otg, PERI_MODE_PERIPHERAL))
+ otg_err(otg, "Failed to start peripheral\n");
+
+ return do_b_idle(otg);
+}
+
+static int init_a_device(struct dwc3_otg *otg)
+{
+ otg_write(otg, OCFG, 0);
+ otg_write(otg, OCTL, 0);
+
+ otg_dbg(otg, "Write 0 to OCFG and OCTL\n");
+ return OTG_STATE_A_IDLE;
+}
+
+static enum usb_otg_state do_connector_id_status(struct dwc3_otg *otg)
+{
+ enum usb_otg_state state;
+ u32 osts;
+
+ otg_dbg(otg, "\n");
+
+ otg_write(otg, OCFG, 0);
+ otg_write(otg, OEVTEN, 0);
+ otg_write(otg, OEVT, 0xffffffff);
+ otg_write(otg, OEVTEN, OEVT_CONN_ID_STS_CHNG_EVNT);
+
+ msleep(60);
+
+ osts = otg_read(otg, OSTS);
+ if (!(osts & OSTS_CONN_ID_STS)) {
+ otg_dbg(otg, "Connector ID is A\n");
+ state = init_a_device(otg);
+ } else {
+ otg_dbg(otg, "Connector ID is B\n");
+ stop_host(otg);
+ state = init_b_device(otg);
+ }
+
+ /* TODO: This is a workaround for latest hibernation-enabled bitfiles
+ * which have problems before initializing SRP.
+ */
+ msleep(50);
+
+ return state;
+}
+
+static void reset_hw(struct dwc3_otg *otg)
+{
+ u32 temp;
+
+ otg_dbg(otg, "\n");
+
+ otg_write(otg, OEVTEN, 0);
+ temp = otg_read(otg, OCTL);
+ temp &= OCTL_PERI_MODE;
+ otg_write(otg, OCTL, temp);
+ temp = otg_read(otg, GCTL);
+ temp |= GCTL_PRT_CAP_DIR_OTG << GCTL_PRT_CAP_DIR_SHIFT;
+ otg_write(otg, GCTL, temp);
+}
+
+#define SRP_TIMEOUT 6000
+
+static void start_srp(struct dwc3_otg *otg)
+{
+ u32 octl;
+
+ octl = otg_read(otg, OCTL);
+ octl |= OCTL_SES_REQ;
+ otg_write(otg, OCTL, octl);
+ otg_dbg(otg, "set OCTL_SES_REQ in OCTL\n");
+}
+
+static void start_b_hnp(struct dwc3_otg *otg)
+{
+ u32 octl;
+
+ octl = otg_read(otg, OCTL);
+ octl |= OCTL_HNP_REQ | OCTL_DEV_SET_HNP_EN;
+ otg_write(otg, OCTL, octl);
+ otg_dbg(otg, "set (OCTL_HNP_REQ | OCTL_DEV_SET_HNP_EN) in OCTL\n");
+}
+
+static void stop_b_hnp(struct dwc3_otg *otg)
+{
+ u32 octl;
+
+ octl = otg_read(otg, OCTL);
+ octl &= ~(OCTL_HNP_REQ | OCTL_DEV_SET_HNP_EN);
+ otg_write(otg, OCTL, octl);
+ otg_dbg(otg, "Clear ~(OCTL_HNP_REQ | OCTL_DEV_SET_HNP_EN) in OCTL\n");
+}
+
+static void start_a_hnp(struct dwc3_otg *otg)
+{
+ u32 octl;
+
+ octl = otg_read(otg, OCTL);
+ octl |= OCTL_HST_SET_HNP_EN;
+ otg_write(otg, OCTL, octl);
+ otg_dbg(otg, "set OCTL_HST_SET_HNP_EN in OCTL\n");
+}
+
+static void stop_a_hnp(struct dwc3_otg *otg)
+{
+ u32 octl;
+
+ octl = otg_read(otg, OCTL);
+ octl &= ~OCTL_HST_SET_HNP_EN;
+ otg_write(otg, OCTL, octl);
+ otg_dbg(otg, "clear OCTL_HST_SET_HNP_EN in OCTL\n");
+}
+
+static enum usb_otg_state do_a_hnp_init(struct dwc3_otg *otg)
+{
+ int rc;
+ u32 otg_mask;
+ u32 otg_events = 0;
+
+ otg_dbg(otg, "");
+ otg_mask = OEVT_CONN_ID_STS_CHNG_EVNT |
+ OEVT_A_DEV_HNP_CHNG_EVNT;
+
+ start_a_hnp(otg);
+ rc = 3000;
+
+again:
+ rc = sleep_until_event(otg,
+ otg_mask, 0,
+ &otg_events, NULL, rc);
+ stop_a_hnp(otg);
+ if (rc < 0)
+ return OTG_STATE_UNDEFINED;
+
+ /* Higher priority first */
+ if (otg_events & OEVT_CONN_ID_STS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_CONN_ID_STS_CHNG_EVNT\n");
+ return OTG_STATE_UNDEFINED;
+
+ } else if (otg_events & OEVT_A_DEV_HNP_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_A_DEV_HNP_CHNG_EVNT\n");
+ if (otg_events & OEVT_HST_NEG_SCS) {
+ otg_dbg(otg, "A-HNP Success\n");
+ return OTG_STATE_A_PERIPHERAL;
+
+ } else {
+ otg_dbg(otg, "A-HNP Failed\n");
+ return OTG_STATE_A_WAIT_VFALL;
+ }
+
+ } else if (rc == 0) {
+ otg_dbg(otg, "A-HNP Failed (Timed out)\n");
+ return OTG_STATE_A_WAIT_VFALL;
+
+ } else {
+ goto again;
+ }
+
+ /* Invalid state */
+ return OTG_STATE_UNDEFINED;
+}
+
+static enum usb_otg_state do_a_host(struct dwc3_otg *otg)
+{
+ int rc;
+ u32 otg_mask;
+ u32 user_mask;
+ u32 otg_events = 0;
+ u32 user_events = 0;
+
+ otg_dbg(otg, "");
+
+ otg_mask = OEVT_CONN_ID_STS_CHNG_EVNT |
+ OEVT_A_DEV_SESS_END_DET_EVNT;
+ user_mask = USER_SRP_EVENT |
+ USER_HNP_EVENT;
+
+ rc = sleep_until_event(otg,
+ otg_mask, user_mask,
+ &otg_events, &user_events, 0);
+ if (rc < 0)
+ return OTG_STATE_UNDEFINED;
+
+ /* Higher priority first */
+ if (otg_events & OEVT_CONN_ID_STS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_CONN_ID_STS_CHNG_EVNT\n");
+ return OTG_STATE_UNDEFINED;
+
+ } else if (otg_events & OEVT_A_DEV_SESS_END_DET_EVNT) {
+ otg_dbg(otg, "OEVT_A_DEV_SESS_END_DET_EVNT\n");
+ return OTG_STATE_A_WAIT_VFALL;
+
+ } else if (user_events & USER_HNP_EVENT) {
+ otg_dbg(otg, "USER_HNP_EVENT\n");
+ return OTG_STATE_A_SUSPEND;
+ }
+
+ /* Invalid state */
+ return OTG_STATE_UNDEFINED;
+}
+
+#define A_WAIT_VFALL_TIMEOUT 1000
+
+static enum usb_otg_state do_a_wait_vfall(struct dwc3_otg *otg)
+{
+ int rc;
+ u32 otg_mask;
+ u32 otg_events = 0;
+
+ otg_dbg(otg, "");
+
+ otg_mask = OEVT_A_DEV_IDLE_EVNT;
+
+ rc = A_WAIT_VFALL_TIMEOUT;
+ rc = sleep_until_event(otg,
+ otg_mask, 0,
+ &otg_events, NULL, rc);
+ if (rc < 0)
+ return OTG_STATE_UNDEFINED;
+
+ if (otg_events & OEVT_A_DEV_IDLE_EVNT) {
+ otg_dbg(otg, "OEVT_A_DEV_IDLE_EVNT\n");
+ return OTG_STATE_A_IDLE;
+
+ } else if (rc == 0) {
+ otg_dbg(otg, "A_WAIT_VFALL_TIMEOUT\n");
+ return OTG_STATE_A_IDLE;
+ }
+
+ /* Invalid state */
+ return OTG_STATE_UNDEFINED;
+
+}
+
+#define A_WAIT_BCON_TIMEOUT 1000
+
+static enum usb_otg_state do_a_wait_bconn(struct dwc3_otg *otg)
+{
+ int rc;
+ u32 otg_mask;
+ u32 otg_events = 0;
+
+ otg_dbg(otg, "");
+
+ otg_mask = OEVT_CONN_ID_STS_CHNG_EVNT |
+ OEVT_A_DEV_SESS_END_DET_EVNT |
+ OEVT_A_DEV_HOST_EVNT;
+
+ rc = A_WAIT_BCON_TIMEOUT;
+ rc = sleep_until_event(otg,
+ otg_mask, 0,
+ &otg_events, NULL, rc);
+ if (rc < 0)
+ return OTG_STATE_UNDEFINED;
+
+ /* Higher priority first */
+ if (otg_events & OEVT_CONN_ID_STS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_CONN_ID_STS_CHNG_EVNT\n");
+ return OTG_STATE_UNDEFINED;
+
+ } else if (otg_events & OEVT_A_DEV_SESS_END_DET_EVNT) {
+ otg_dbg(otg, "OEVT_A_DEV_SESS_END_DET_EVNT\n");
+ return OTG_STATE_A_WAIT_VFALL;
+
+ } else if (otg_events & OEVT_A_DEV_HOST_EVNT) {
+ otg_dbg(otg, "OEVT_A_DEV_HOST_EVNT\n");
+ return OTG_STATE_A_HOST;
+
+ } else if (rc == 0) {
+ if (otg_read(otg, OCTL) & OCTL_PRT_PWR_CTL)
+ return OTG_STATE_A_HOST;
+ else
+ return OTG_STATE_A_WAIT_VFALL;
+ }
+
+ /* Invalid state */
+ return OTG_STATE_UNDEFINED;
+}
+
+#define A_WAIT_VRISE_TIMEOUT 100
+
+static enum usb_otg_state do_a_wait_vrise(struct dwc3_otg *otg)
+{
+ int rc;
+ u32 otg_mask;
+ u32 otg_events = 0;
+ struct usb_hcd *hcd;
+ struct xhci_hcd *xhci;
+
+ otg_dbg(otg, "");
+ set_b_host(otg, 0);
+ start_host(otg);
+ hcd = container_of(otg->otg.host, struct usb_hcd, self);
+ xhci = hcd_to_xhci(hcd);
+ usb_kick_hub_wq(hcd->self.root_hub);
+ if (xhci->shared_hcd)
+ usb_kick_hub_wq(xhci->shared_hcd->self.root_hub);
+
+ otg_mask = OEVT_CONN_ID_STS_CHNG_EVNT |
+ OEVT_A_DEV_SESS_END_DET_EVNT;
+
+ rc = A_WAIT_VRISE_TIMEOUT;
+
+ rc = sleep_until_event(otg,
+ otg_mask, 0,
+ &otg_events, NULL, rc);
+ if (rc < 0)
+ return OTG_STATE_UNDEFINED;
+
+ /* Higher priority first */
+ if (otg_events & OEVT_CONN_ID_STS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_CONN_ID_STS_CHNG_EVNT\n");
+ return OTG_STATE_UNDEFINED;
+
+ } else if (otg_events & OEVT_A_DEV_SESS_END_DET_EVNT) {
+ otg_dbg(otg, "OEVT_A_DEV_SESS_END_DET_EVNT\n");
+ return OTG_STATE_A_WAIT_VFALL;
+
+ } else if (rc == 0) {
+ if (otg_read(otg, OCTL) & OCTL_PRT_PWR_CTL)
+ return OTG_STATE_A_WAIT_BCON;
+ else
+ return OTG_STATE_A_WAIT_VFALL;
+ }
+
+ /* Invalid state */
+ return OTG_STATE_UNDEFINED;
+}
+
+static enum usb_otg_state do_a_idle(struct dwc3_otg *otg)
+{
+ int rc;
+ u32 otg_mask;
+ u32 user_mask;
+ u32 otg_events = 0;
+ u32 user_events = 0;
+
+ otg_dbg(otg, "");
+
+ otg_mask = OEVT_CONN_ID_STS_CHNG_EVNT | OEVT_A_DEV_SRP_DET_EVNT;
+ user_mask = USER_SRP_EVENT;
+
+ rc = sleep_until_event(otg,
+ otg_mask, user_mask,
+ &otg_events, &user_events,
+ 0);
+
+ if (rc < 0)
+ return OTG_STATE_UNDEFINED;
+
+ if (otg_events & OEVT_CONN_ID_STS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_CONN_ID_STS_CHNG_EVNT\n");
+ return OTG_STATE_UNDEFINED;
+ } else if (otg_events & OEVT_A_DEV_SRP_DET_EVNT) {
+ otg_dbg(otg, "OEVT_A_DEV_SRP_DET_EVNT\n");
+ return OTG_STATE_A_WAIT_VRISE;
+ } else if (user_events & USER_SRP_EVENT) {
+ otg_dbg(otg, "User initiated VBUS\n");
+ return OTG_STATE_A_WAIT_VRISE;
+ }
+
+ return OTG_STATE_UNDEFINED;
+}
+
+static enum usb_otg_state do_a_peripheral(struct dwc3_otg *otg)
+{
+ int rc;
+ u32 otg_mask;
+ u32 otg_events = 0;
+
+ otg_dbg(otg, "");
+ otg_mask = OEVT_CONN_ID_STS_CHNG_EVNT |
+ OEVT_A_DEV_SESS_END_DET_EVNT |
+ OEVT_A_DEV_B_DEV_HOST_END_EVNT;
+
+ rc = sleep_until_event(otg,
+ otg_mask, 0,
+ &otg_events, NULL, 0);
+ if (rc < 0)
+ return OTG_STATE_UNDEFINED;
+
+ if (otg_events & OEVT_CONN_ID_STS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_CONN_ID_STS_CHNG_EVNT\n");
+ return OTG_STATE_UNDEFINED;
+
+ } else if (otg_events & OEVT_A_DEV_SESS_END_DET_EVNT) {
+ otg_dbg(otg, "OEVT_A_DEV_SESS_END_DET_EVNT\n");
+ return OTG_STATE_A_WAIT_VFALL;
+
+ } else if (otg_events & OEVT_A_DEV_B_DEV_HOST_END_EVNT) {
+ otg_dbg(otg, "OEVT_A_DEV_B_DEV_HOST_END_EVNT\n");
+ return OTG_STATE_A_WAIT_VRISE;
+ }
+
+ return OTG_STATE_UNDEFINED;
+}
+
+#define HNP_TIMEOUT 4000
+
+static enum usb_otg_state do_b_hnp_init(struct dwc3_otg *otg)
+{
+ int rc;
+ u32 otg_mask;
+ u32 events = 0;
+
+ otg_dbg(otg, "");
+ otg_mask = OEVT_CONN_ID_STS_CHNG_EVNT |
+ OEVT_B_DEV_HNP_CHNG_EVNT |
+ OEVT_B_DEV_VBUS_CHNG_EVNT;
+
+ start_b_hnp(otg);
+ rc = HNP_TIMEOUT;
+
+again:
+ rc = sleep_until_event(otg,
+ otg_mask, 0,
+ &events, NULL, rc);
+ stop_b_hnp(otg);
+
+ if (rc < 0)
+ return OTG_STATE_UNDEFINED;
+
+ if (events & OEVT_CONN_ID_STS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_CONN_ID_STS_CHNG_EVNT\n");
+ return OTG_STATE_UNDEFINED;
+ } else if (events & OEVT_B_DEV_VBUS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_B_DEV_VBUS_CHNG_EVNT\n");
+ return OTG_STATE_B_IDLE;
+ } else if (events & OEVT_B_DEV_HNP_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_B_DEV_HNP_CHNG_EVNT\n");
+ if (events & OEVT_HST_NEG_SCS) {
+ otg_dbg(otg, "B-HNP Success\n");
+ return OTG_STATE_B_WAIT_ACON;
+
+ } else {
+ otg_err(otg, "B-HNP Failed\n");
+ return OTG_STATE_B_PERIPHERAL;
+ }
+ } else if (rc == 0) {
+ /* Timeout */
+ otg_err(otg, "HNP timed out!\n");
+ return OTG_STATE_B_PERIPHERAL;
+
+ } else {
+ goto again;
+ }
+
+ return OTG_STATE_UNDEFINED;
+}
+
+static enum usb_otg_state do_b_peripheral(struct dwc3_otg *otg)
+{
+ int rc;
+ u32 otg_mask;
+ u32 user_mask;
+ u32 otg_events = 0;
+ u32 user_events = 0;
+
+ otg_dbg(otg, "");
+ otg_mask = OEVT_CONN_ID_STS_CHNG_EVNT | OEVT_B_DEV_VBUS_CHNG_EVNT;
+ user_mask = USER_HNP_EVENT | USER_END_SESSION |
+ USER_SRP_EVENT | INITIAL_SRP;
+
+again:
+ rc = sleep_until_event(otg,
+ otg_mask, user_mask,
+ &otg_events, &user_events, 0);
+ if (rc < 0)
+ return OTG_STATE_UNDEFINED;
+
+ if (otg_events & OEVT_CONN_ID_STS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_CONN_ID_STS_CHNG_EVNT\n");
+ return OTG_STATE_UNDEFINED;
+ } else if (otg_events & OEVT_B_DEV_VBUS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_B_DEV_VBUS_CHNG_EVNT\n");
+
+ if (otg_events & OEVT_B_SES_VLD_EVT) {
+ otg_dbg(otg, "Session valid\n");
+ goto again;
+ } else {
+ otg_dbg(otg, "Session not valid\n");
+ return OTG_STATE_B_IDLE;
+ }
+
+ } else if (user_events & USER_HNP_EVENT) {
+ otg_dbg(otg, "USER_HNP_EVENT\n");
+ return do_b_hnp_init(otg);
+ } else if (user_events & USER_END_SESSION) {
+ otg_dbg(otg, "USER_END_SESSION\n");
+ return OTG_STATE_B_IDLE;
+ }
+
+ return OTG_STATE_UNDEFINED;
+}
+
+static enum usb_otg_state do_b_wait_acon(struct dwc3_otg *otg)
+{
+ int rc;
+ u32 otg_mask;
+ u32 user_mask = 0;
+ u32 otg_events = 0;
+ u32 user_events = 0;
+ struct usb_hcd *hcd;
+ struct xhci_hcd *xhci;
+
+ otg_dbg(otg, "");
+ set_b_host(otg, 1);
+ start_host(otg);
+ otg_mask = OEVT_B_DEV_B_HOST_END_EVNT;
+ otg_write(otg, OEVTEN, otg_mask);
+ reset_port(otg);
+
+ hcd = container_of(otg->otg.host, struct usb_hcd, self);
+ xhci = hcd_to_xhci(hcd);
+ usb_kick_hub_wq(hcd->self.root_hub);
+ if (xhci->shared_hcd)
+ usb_kick_hub_wq(xhci->shared_hcd->self.root_hub);
+
+ otg_mask = OEVT_CONN_ID_STS_CHNG_EVNT |
+ OEVT_B_DEV_B_HOST_END_EVNT |
+ OEVT_B_DEV_VBUS_CHNG_EVNT |
+ OEVT_HOST_ROLE_REQ_INIT_EVNT;
+ user_mask = USER_A_CONN_EVENT;
+
+again:
+ rc = sleep_until_event(otg,
+ otg_mask, user_mask,
+ &otg_events, &user_events, 0);
+ if (rc < 0)
+ return OTG_STATE_UNDEFINED;
+
+ /* Higher priority first */
+ if (otg_events & OEVT_CONN_ID_STS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_CONN_ID_STS_CHNG_EVNT\n");
+ return OTG_STATE_UNDEFINED;
+ } else if (otg_events & OEVT_B_DEV_B_HOST_END_EVNT) {
+ otg_dbg(otg, "OEVT_B_DEV_B_HOST_END_EVNT\n");
+ return OTG_STATE_B_PERIPHERAL;
+ } else if (otg_events & OEVT_B_DEV_VBUS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_B_DEV_VBUS_CHNG_EVNT\n");
+ if (otg_events & OEVT_B_SES_VLD_EVT) {
+ otg_dbg(otg, "Session valid\n");
+ goto again;
+ } else {
+ otg_dbg(otg, "Session not valid\n");
+ return OTG_STATE_B_IDLE;
+ }
+ } else if (user_events & USER_A_CONN_EVENT) {
+ otg_dbg(otg, "A-device connected\n");
+ return OTG_STATE_B_HOST;
+ }
+
+ /* Invalid state */
+ return OTG_STATE_UNDEFINED;
+}
+
+static enum usb_otg_state do_b_host(struct dwc3_otg *otg)
+{
+ int rc;
+ u32 otg_mask;
+ u32 user_mask = 0;
+ u32 otg_events = 0;
+ u32 user_events = 0;
+
+ otg_dbg(otg, "");
+
+ otg_mask = OEVT_CONN_ID_STS_CHNG_EVNT |
+ OEVT_B_DEV_B_HOST_END_EVNT |
+ OEVT_B_DEV_VBUS_CHNG_EVNT |
+ OEVT_HOST_ROLE_REQ_INIT_EVNT;
+
+again:
+ rc = sleep_until_event(otg,
+ otg_mask, user_mask,
+ &otg_events, &user_events, 0);
+ if (rc < 0)
+ return OTG_STATE_UNDEFINED;
+
+ /* Higher priority first */
+ if (otg_events & OEVT_CONN_ID_STS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_CONN_ID_STS_CHNG_EVNT\n");
+ return OTG_STATE_UNDEFINED;
+ } else if (otg_events & OEVT_B_DEV_B_HOST_END_EVNT) {
+ otg_dbg(otg, "OEVT_B_DEV_B_HOST_END_EVNT\n");
+ return OTG_STATE_B_PERIPHERAL;
+ } else if (otg_events & OEVT_B_DEV_VBUS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_B_DEV_VBUS_CHNG_EVNT\n");
+ if (otg_events & OEVT_B_SES_VLD_EVT) {
+ otg_dbg(otg, "Session valid\n");
+ goto again;
+ } else {
+ otg_dbg(otg, "Session not valid\n");
+ return OTG_STATE_B_IDLE;
+ }
+ }
+
+ /* Invalid state */
+ return OTG_STATE_UNDEFINED;
+}
+
+static enum usb_otg_state do_b_idle(struct dwc3_otg *otg)
+{
+ int rc;
+ u32 otg_mask;
+ u32 user_mask;
+ u32 otg_events = 0;
+ u32 user_events = 0;
+
+ otg_dbg(otg, "");
+ otg_mask = OEVT_CONN_ID_STS_CHNG_EVNT |
+ OEVT_B_DEV_SES_VLD_DET_EVNT |
+ OEVT_B_DEV_VBUS_CHNG_EVNT;
+ user_mask = USER_SRP_EVENT;
+
+again:
+ rc = sleep_until_event(otg,
+ otg_mask, user_mask,
+ &otg_events, &user_events, 0);
+
+ if (rc < 0)
+ return OTG_STATE_UNDEFINED;
+
+ if (otg_events & OEVT_CONN_ID_STS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_CONN_ID_STS_CHNG_EVNT\n");
+ return OTG_STATE_UNDEFINED;
+ } else if ((otg_events & OEVT_B_DEV_VBUS_CHNG_EVNT) ||
+ (otg_events & OEVT_B_DEV_SES_VLD_DET_EVNT)) {
+ otg_dbg(otg, "OEVT_B_DEV_VBUS_CHNG_EVNT\n");
+ if (otg_events & OEVT_B_SES_VLD_EVT) {
+ otg_dbg(otg, "Session valid\n");
+ return OTG_STATE_B_PERIPHERAL;
+
+ } else {
+ otg_dbg(otg, "Session not valid\n");
+ goto again;
+ }
+ } else if (user_events & USER_SRP_EVENT) {
+ otg_dbg(otg, "USER_SRP_EVENT\n");
+ return OTG_STATE_B_SRP_INIT;
+ }
+
+ return OTG_STATE_UNDEFINED;
+}
+
+static enum usb_otg_state do_b_srp_init(struct dwc3_otg *otg)
+{
+ int rc;
+ u32 otg_mask;
+ u32 events = 0;
+
+ otg_dbg(otg, "");
+ otg_mask = OEVT_CONN_ID_STS_CHNG_EVNT |
+ OEVT_B_DEV_SES_VLD_DET_EVNT |
+ OEVT_B_DEV_VBUS_CHNG_EVNT;
+
+ otg_write(otg, OEVTEN, otg_mask);
+ start_srp(otg);
+
+ rc = SRP_TIMEOUT;
+
+again:
+ rc = sleep_until_event(otg,
+ otg_mask, 0,
+ &events, NULL, rc);
+ if (rc < 0)
+ return OTG_STATE_UNDEFINED;
+
+ if (events & OEVT_CONN_ID_STS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_CONN_ID_STS_CHNG_EVNT\n");
+ return OTG_STATE_UNDEFINED;
+ } else if (events & OEVT_B_DEV_SES_VLD_DET_EVNT) {
+ otg_dbg(otg, "OEVT_B_DEV_SES_VLD_DET_EVNT\n");
+ return OTG_STATE_B_PERIPHERAL;
+ } else if (rc == 0) {
+ otg_dbg(otg, "SRP Timeout (rc=%d)\n", rc);
+ otg_info(otg, "DEVICE NO RESPONSE FOR SRP\n");
+ return OTG_STATE_B_IDLE;
+
+ } else {
+ goto again;
+ }
+
+ return OTG_STATE_UNDEFINED;
+}
+
+int otg_main_thread(void *data)
+{
+ struct dwc3_otg *otg = (struct dwc3_otg *)data;
+ enum usb_otg_state prev = OTG_STATE_UNDEFINED;
+
+#ifdef VERBOSE_DEBUG
+ u32 snpsid = otg_read(otg, 0xc120);
+
+ otg_vdbg(otg, "io_priv=%p\n", otg->regs);
+ otg_vdbg(otg, "c120: %x\n", snpsid);
+#endif
+
+ /* Allow the thread to be killed by a signal, but set the signal mask
+ * to block everything but INT, TERM, KILL, and USR1.
+ */
+ allow_signal(SIGINT);
+ allow_signal(SIGTERM);
+ allow_signal(SIGKILL);
+ allow_signal(SIGUSR1);
+
+ /* Allow the thread to be frozen */
+ set_freezable();
+
+ /* Allow host/peripheral driver load to finish */
+ msleep(100);
+
+ reset_hw(otg);
+
+ stop_host(otg);
+ stop_peripheral(otg);
+
+ otg_dbg(otg, "Thread running\n");
+ while (1) {
+ enum usb_otg_state next = OTG_STATE_UNDEFINED;
+
+ otg_vdbg(otg, "\n\n\nMain thread entering state\n");
+
+ switch (otg->otg.state) {
+ case OTG_STATE_UNDEFINED:
+ otg_dbg(otg, "OTG_STATE_UNDEFINED\n");
+ next = do_connector_id_status(otg);
+ break;
+
+ case OTG_STATE_A_IDLE:
+ otg_dbg(otg, "OTG_STATE_A_IDLE\n");
+ stop_peripheral(otg);
+
+ if (prev == OTG_STATE_UNDEFINED)
+ next = OTG_STATE_A_WAIT_VRISE;
+ else
+ next = do_a_idle(otg);
+ break;
+
+ case OTG_STATE_A_WAIT_VRISE:
+ otg_dbg(otg, "OTG_STATE_A_WAIT_VRISE\n");
+ next = do_a_wait_vrise(otg);
+ break;
+
+ case OTG_STATE_A_WAIT_BCON:
+ otg_dbg(otg, "OTG_STATE_A_WAIT_BCON\n");
+ next = do_a_wait_bconn(otg);
+ break;
+
+ case OTG_STATE_A_HOST:
+ otg_dbg(otg, "OTG_STATE_A_HOST\n");
+ stop_peripheral(otg);
+ next = do_a_host(otg);
+ /* Don't stop the host here if we are going into
+ * A_SUSPEND. We need to delay that until later. It
+ * will be stopped when coming out of A_SUSPEND
+ * state.
+ */
+ if (next != OTG_STATE_A_SUSPEND)
+ stop_host(otg);
+ break;
+
+ case OTG_STATE_A_SUSPEND:
+ otg_dbg(otg, "OTG_STATE_A_SUSPEND\n");
+ next = do_a_hnp_init(otg);
+
+ /* Stop the host. */
+ stop_host(otg);
+ break;
+
+ case OTG_STATE_A_WAIT_VFALL:
+ otg_dbg(otg, "OTG_STATE_A_WAIT_VFALL\n");
+ next = do_a_wait_vfall(otg);
+ stop_host(otg);
+ break;
+
+ case OTG_STATE_A_PERIPHERAL:
+ otg_dbg(otg, "OTG_STATE_A_PERIPHERAL\n");
+ stop_host(otg);
+ start_peripheral(otg);
+ next = do_a_peripheral(otg);
+ stop_peripheral(otg);
+ break;
+
+ case OTG_STATE_B_IDLE:
+ otg_dbg(otg, "OTG_STATE_B_IDLE\n");
+ next = do_b_idle(otg);
+ break;
+
+ case OTG_STATE_B_PERIPHERAL:
+ otg_dbg(otg, "OTG_STATE_B_PERIPHERAL\n");
+ stop_host(otg);
+ start_peripheral(otg);
+ next = do_b_peripheral(otg);
+ stop_peripheral(otg);
+ break;
+
+ case OTG_STATE_B_SRP_INIT:
+ otg_dbg(otg, "OTG_STATE_B_SRP_INIT\n");
+ otg_read(otg, OSTS);
+ next = do_b_srp_init(otg);
+ break;
+
+ case OTG_STATE_B_WAIT_ACON:
+ otg_dbg(otg, "OTG_STATE_B_WAIT_ACON\n");
+ next = do_b_wait_acon(otg);
+ break;
+
+ case OTG_STATE_B_HOST:
+ otg_dbg(otg, "OTG_STATE_B_HOST\n");
+ next = do_b_host(otg);
+ stop_host(otg);
+ break;
+
+ default:
+ otg_err(otg, "Unknown state %d, sleeping...\n",
+ otg->state);
+ sleep_main_thread(otg);
+ break;
+ }
+
+ prev = otg->otg.state;
+ otg->otg.state = next;
+ if (kthread_should_stop())
+ break;
+ }
+
+ otg->main_thread = NULL;
+ otg_dbg(otg, "OTG main thread exiting....\n");
+
+ return 0;
+}
+
+static void start_main_thread(struct dwc3_otg *otg)
+{
+ if (!otg->main_thread && otg->otg.gadget && otg->otg.host) {
+ otg_dbg(otg, "Starting OTG main thread\n");
+ otg->main_thread = kthread_create(otg_main_thread, otg, "otg");
+ wake_up_process(otg->main_thread);
+ }
+}
+
+static inline struct dwc3_otg *otg_to_dwc3_otg(struct usb_otg *x)
+{
+ return container_of(x, struct dwc3_otg, otg);
+}
+
+static irqreturn_t dwc3_otg_irq(int irq, void *_otg)
+{
+ struct dwc3_otg *otg;
+ u32 oevt;
+ u32 osts;
+ u32 octl;
+ u32 ocfg;
+ u32 oevten;
+ u32 otg_mask = OEVT_ALL;
+
+ if (!_otg)
+ return 0;
+
+ otg = (struct dwc3_otg *)_otg;
+
+ oevt = otg_read(otg, OEVT);
+ osts = otg_read(otg, OSTS);
+ octl = otg_read(otg, OCTL);
+ ocfg = otg_read(otg, OCFG);
+ oevten = otg_read(otg, OEVTEN);
+
+ /* Clear handled events */
+ otg_write(otg, OEVT, oevt);
+
+ otg_vdbg(otg, "\n");
+ otg_vdbg(otg, " oevt = %08x\n", oevt);
+ otg_vdbg(otg, " osts = %08x\n", osts);
+ otg_vdbg(otg, " octl = %08x\n", octl);
+ otg_vdbg(otg, " ocfg = %08x\n", ocfg);
+ otg_vdbg(otg, " oevten = %08x\n", oevten);
+
+ otg_vdbg(otg, "oevt[DeviceMode] = %s\n",
+ oevt & OEVT_DEV_MOD_EVNT ? "Device" : "Host");
+
+ if (oevt & OEVT_CONN_ID_STS_CHNG_EVNT)
+ otg_dbg(otg, "Connector ID Status Change Event\n");
+ if (oevt & OEVT_HOST_ROLE_REQ_INIT_EVNT)
+ otg_dbg(otg, "Host Role Request Init Notification Event\n");
+ if (oevt & OEVT_HOST_ROLE_REQ_CONFIRM_EVNT)
+ otg_dbg(otg, "Host Role Request Confirm Notification Event\n");
+ if (oevt & OEVT_A_DEV_B_DEV_HOST_END_EVNT)
+ otg_dbg(otg, "A-Device B-Host End Event\n");
+ if (oevt & OEVT_A_DEV_HOST_EVNT)
+ otg_dbg(otg, "A-Device Host Event\n");
+ if (oevt & OEVT_A_DEV_HNP_CHNG_EVNT)
+ otg_dbg(otg, "A-Device HNP Change Event\n");
+ if (oevt & OEVT_A_DEV_SRP_DET_EVNT)
+ otg_dbg(otg, "A-Device SRP Detect Event\n");
+ if (oevt & OEVT_A_DEV_SESS_END_DET_EVNT)
+ otg_dbg(otg, "A-Device Session End Detected Event\n");
+ if (oevt & OEVT_B_DEV_B_HOST_END_EVNT)
+ otg_dbg(otg, "B-Device B-Host End Event\n");
+ if (oevt & OEVT_B_DEV_HNP_CHNG_EVNT)
+ otg_dbg(otg, "B-Device HNP Change Event\n");
+ if (oevt & OEVT_B_DEV_SES_VLD_DET_EVNT)
+ otg_dbg(otg, "B-Device Session Valid Detect Event\n");
+ if (oevt & OEVT_B_DEV_VBUS_CHNG_EVNT)
+ otg_dbg(otg, "B-Device VBUS Change Event\n");
+
+ if (oevt & otg_mask) {
+ /* Pass event to main thread */
+ spin_lock(&otg->lock);
+ otg->otg_events |= oevt;
+ wakeup_main_thread(otg);
+ spin_unlock(&otg->lock);
+ return 1;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void hnp_polling_work(struct work_struct *w)
+{
+ struct dwc3_otg *otg = container_of(w, struct dwc3_otg,
+ hp_work.work);
+ struct usb_bus *bus;
+ struct usb_device *udev;
+ struct usb_hcd *hcd;
+ u8 *otgstatus;
+ int ret;
+ int err;
+
+ hcd = container_of(otg->otg.host, struct usb_hcd, self);
+ if (!hcd)
+ return;
+
+ bus = &hcd->self;
+ if (!bus->otg_port)
+ return;
+
+ udev = usb_hub_find_child(bus->root_hub, bus->otg_port);
+ if (!udev)
+ return;
+
+ otgstatus = kmalloc(sizeof(*otgstatus), GFP_NOIO);
+ if (!otgstatus)
+ return;
+
+ ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+ USB_REQ_GET_STATUS, USB_DIR_IN | USB_RECIP_DEVICE,
+ 0, 0xf000, otgstatus, sizeof(*otgstatus),
+ USB_CTRL_GET_TIMEOUT);
+
+ if (ret == sizeof(*otgstatus) && (*otgstatus & 0x1)) {
+ /* enable HNP before suspend, it's simpler */
+
+ udev->bus->b_hnp_enable = 1;
+ err = usb_control_msg(udev,
+ usb_sndctrlpipe(udev, 0),
+ USB_REQ_SET_FEATURE, 0,
+ udev->bus->b_hnp_enable
+ ? USB_DEVICE_B_HNP_ENABLE
+ : USB_DEVICE_A_ALT_HNP_SUPPORT,
+ 0, NULL, 0, USB_CTRL_SET_TIMEOUT);
+
+ if (err < 0) {
+ /* OTG MESSAGE: report errors here,
+ * customize to match your product.
+ */
+ otg_info(otg, "ERROR : Device no response\n");
+ dev_info(&udev->dev, "can't set HNP mode: %d\n",
+ err);
+ udev->bus->b_hnp_enable = 0;
+ if (le16_to_cpu(udev->descriptor.idVendor) == 0x1a0a) {
+ if (usb_port_suspend(udev, PMSG_AUTO_SUSPEND)
+ < 0)
+ dev_dbg(&udev->dev, "HNP fail, %d\n",
+ err);
+ }
+ } else {
+ /* Device wants role-switch, suspend the bus. */
+ static struct usb_phy *phy;
+
+ phy = usb_get_phy(USB_PHY_TYPE_USB3);
+ otg_start_hnp(phy->otg);
+ usb_put_phy(phy);
+
+ if (usb_port_suspend(udev, PMSG_AUTO_SUSPEND) < 0)
+ dev_dbg(&udev->dev, "HNP fail, %d\n", err);
+ }
+ } else if (ret < 0) {
+ udev->bus->b_hnp_enable = 1;
+ err = usb_control_msg(udev,
+ usb_sndctrlpipe(udev, 0),
+ USB_REQ_SET_FEATURE, 0,
+ USB_DEVICE_B_HNP_ENABLE,
+ 0, NULL, 0, USB_CTRL_SET_TIMEOUT);
+ if (usb_port_suspend(udev, PMSG_AUTO_SUSPEND) < 0)
+ dev_dbg(&udev->dev, "HNP fail, %d\n", err);
+ } else {
+ schedule_delayed_work(&otg->hp_work, 1 * HZ);
+ }
+
+ kfree(otgstatus);
+}
+
+static int dwc3_otg_notify_connect(struct usb_phy *phy,
+ enum usb_device_speed speed)
+{
+ struct usb_bus *bus;
+ struct usb_device *udev;
+ struct usb_hcd *hcd;
+ struct dwc3_otg *otg;
+ int err = 0;
+
+ otg = otg_to_dwc3_otg(phy->otg);
+
+ hcd = container_of(phy->otg->host, struct usb_hcd, self);
+ if (!hcd)
+ return -EINVAL;
+
+ bus = &hcd->self;
+ if (!bus->otg_port)
+ return 0;
+
+ udev = usb_hub_find_child(bus->root_hub, bus->otg_port);
+ if (!udev)
+ return 0;
+
+ /*
+ * OTG-aware devices on OTG-capable root hubs may be able to use SRP,
+ * to wake us after we've powered off VBUS; and HNP, switching roles
+ * "host" to "peripheral". The OTG descriptor helps figure this out.
+ */
+ if (udev->config && udev->parent == udev->bus->root_hub) {
+ struct usb_otg20_descriptor *desc = NULL;
+
+ /* descriptor may appear anywhere in config */
+ err = __usb_get_extra_descriptor(udev->rawdescriptors[0],
+ le16_to_cpu(udev->config[0].desc.wTotalLength),
+ USB_DT_OTG, (void **) &desc);
+ if (err || !(desc->bmAttributes & USB_OTG_HNP))
+ return 0;
+
+ if (udev->portnum == udev->bus->otg_port) {
+ INIT_DELAYED_WORK(&otg->hp_work,
+ hnp_polling_work);
+ schedule_delayed_work(&otg->hp_work, HZ);
+ }
+
+ }
+
+ return err;
+}
+
+static int dwc3_otg_notify_disconnect(struct usb_phy *phy,
+ enum usb_device_speed speed)
+{
+ struct dwc3_otg *otg;
+
+ otg = otg_to_dwc3_otg(phy->otg);
+
+ if (work_pending(&otg->hp_work.work)) {
+ while (!cancel_delayed_work(&otg->hp_work))
+ msleep(20);
+ }
+ return 0;
+}
+
+static void dwc3_otg_set_peripheral(struct usb_otg *_otg, int yes)
+{
+ struct dwc3_otg *otg;
+
+ if (!_otg)
+ return;
+
+ otg = otg_to_dwc3_otg(_otg);
+ otg_dbg(otg, "\n");
+
+ if (yes) {
+ if (otg->hwparams6 == 0xdeadbeef)
+ otg->hwparams6 = otg_read(otg, GHWPARAMS6);
+ stop_host(otg);
+ } else {
+ stop_peripheral(otg);
+ }
+
+ set_peri_mode(otg, yes);
+}
+EXPORT_SYMBOL(dwc3_otg_set_peripheral);
+
+static int dwc3_otg_set_periph(struct usb_otg *_otg, struct usb_gadget *gadget)
+{
+ struct dwc3_otg *otg;
+
+ if (!_otg)
+ return -ENODEV;
+
+ otg = otg_to_dwc3_otg(_otg);
+ otg_dbg(otg, "\n");
+
+ if ((long)gadget == 1) {
+ dwc3_otg_set_peripheral(_otg, 1);
+ return 0;
+ }
+
+ if (!gadget) {
+ otg->otg.gadget = NULL;
+ return -ENODEV;
+ }
+
+ otg->otg.gadget = gadget;
+ otg->otg.gadget->hnp_polling_support = 1;
+ otg->otg.state = OTG_STATE_B_IDLE;
+
+ start_main_thread(otg);
+ return 0;
+}
+
+static int dwc3_otg_set_host(struct usb_otg *_otg, struct usb_bus *host)
+{
+ struct dwc3_otg *otg;
+ struct usb_hcd *hcd;
+ struct xhci_hcd *xhci;
+
+ if (!_otg)
+ return -ENODEV;
+
+ otg = otg_to_dwc3_otg(_otg);
+ otg_dbg(otg, "\n");
+
+ if ((long)host == 1) {
+ dwc3_otg_set_peripheral(_otg, 0);
+ return 0;
+ }
+
+ if (!host) {
+ otg->otg.host = NULL;
+ otg->hcd_irq = 0;
+ return -ENODEV;
+ }
+
+ hcd = container_of(host, struct usb_hcd, self);
+ xhci = hcd_to_xhci(hcd);
+ otg_dbg(otg, "hcd=%p xhci=%p\n", hcd, xhci);
+
+ hcd->self.otg_port = 1;
+ if (xhci->shared_hcd) {
+ xhci->shared_hcd->self.otg_port = 1;
+ otg_dbg(otg, "shared_hcd=%p\n", xhci->shared_hcd);
+ }
+
+ otg->otg.host = host;
+ otg->hcd_irq = hcd->irq;
+ otg_dbg(otg, "host=%p irq=%d\n", otg->otg.host, otg->hcd_irq);
+
+
+ otg->host_started = 1;
+ otg->dev_enum = 0;
+ start_main_thread(otg);
+ return 0;
+}
+
+static int dwc3_otg_start_srp(struct usb_otg *x)
+{
+ unsigned long flags;
+ struct dwc3_otg *otg;
+
+ if (!x)
+ return -ENODEV;
+
+ otg = otg_to_dwc3_otg(x);
+ otg_dbg(otg, "\n");
+
+ if (!otg->otg.host || !otg->otg.gadget)
+ return -ENODEV;
+
+ spin_lock_irqsave(&otg->lock, flags);
+ otg->user_events |= USER_SRP_EVENT;
+ wakeup_main_thread(otg);
+ spin_unlock_irqrestore(&otg->lock, flags);
+ return 0;
+}
+
+static int dwc3_otg_start_hnp(struct usb_otg *x)
+{
+ unsigned long flags;
+ struct dwc3_otg *otg;
+
+ if (!x)
+ return -ENODEV;
+
+ otg = otg_to_dwc3_otg(x);
+ otg_dbg(otg, "\n");
+
+ if (!otg->otg.host || !otg->otg.gadget)
+ return -ENODEV;
+
+ spin_lock_irqsave(&otg->lock, flags);
+ otg->user_events |= USER_HNP_EVENT;
+ wakeup_main_thread(otg);
+ spin_unlock_irqrestore(&otg->lock, flags);
+ return 0;
+}
+
+static int dwc3_otg_end_session(struct usb_otg *x)
+{
+ unsigned long flags;
+ struct dwc3_otg *otg;
+
+ if (!x)
+ return -ENODEV;
+
+ otg = otg_to_dwc3_otg(x);
+ otg_dbg(otg, "\n");
+
+ if (!otg->otg.host || !otg->otg.gadget)
+ return -ENODEV;
+
+ spin_lock_irqsave(&otg->lock, flags);
+ otg->user_events |= USER_END_SESSION;
+ wakeup_main_thread(otg);
+ spin_unlock_irqrestore(&otg->lock, flags);
+ return 0;
+}
+
+static int otg_end_session(struct usb_otg *otg)
+{
+ return dwc3_otg_end_session(otg);
+}
+EXPORT_SYMBOL(otg_end_session);
+
+static int dwc3_otg_received_host_release(struct usb_otg *x)
+{
+ struct dwc3_otg *otg;
+ unsigned long flags;
+
+ if (!x)
+ return -ENODEV;
+
+ otg = otg_to_dwc3_otg(x);
+ otg_dbg(otg, "\n");
+
+ if (!otg->otg.host || !otg->otg.gadget)
+ return -ENODEV;
+
+ spin_lock_irqsave(&otg->lock, flags);
+ otg->user_events |= PCD_RECEIVED_HOST_RELEASE_EVENT;
+ wakeup_main_thread(otg);
+ spin_unlock_irqrestore(&otg->lock, flags);
+ return 0;
+}
+
+int otg_host_release(struct usb_otg *otg)
+{
+ return dwc3_otg_received_host_release(otg);
+}
+EXPORT_SYMBOL(otg_host_release);
+
+static void dwc3_otg_enable_irq(struct dwc3_otg *otg)
+{
+ u32 reg;
+
+ /* Enable OTG IRQs */
+ reg = OEVT_ALL;
+
+ otg_write(otg, OEVTEN, reg);
+}
+
+static ssize_t store_srp(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct usb_phy *phy;
+ struct usb_otg *otg;
+
+ phy = usb_get_phy(USB_PHY_TYPE_USB3);
+ if (IS_ERR(phy) || !phy) {
+ if (!IS_ERR(phy))
+ usb_put_phy(phy);
+ return count;
+ }
+
+ otg = phy->otg;
+ if (!otg) {
+ usb_put_phy(phy);
+ return count;
+ }
+
+ otg_start_srp(otg);
+ usb_put_phy(phy);
+ return count;
+}
+static DEVICE_ATTR(srp, 0220, NULL, store_srp);
+
+static ssize_t store_end(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct usb_phy *phy;
+ struct usb_otg *otg;
+
+ phy = usb_get_phy(USB_PHY_TYPE_USB3);
+ if (IS_ERR(phy) || !phy) {
+ if (!IS_ERR(phy))
+ usb_put_phy(phy);
+ return count;
+ }
+
+ otg = phy->otg;
+ if (!otg) {
+ usb_put_phy(phy);
+ return count;
+ }
+
+ otg_end_session(otg);
+ usb_put_phy(phy);
+ return count;
+}
+static DEVICE_ATTR(end, 0220, NULL, store_end);
+
+static ssize_t store_hnp(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct dwc3 *dwc = dev_get_drvdata(dev);
+ struct usb_phy *phy = usb_get_phy(USB_PHY_TYPE_USB3);
+ struct usb_otg *otg;
+
+ dev_dbg(dwc->dev, "%s()\n", __func__);
+
+ if (IS_ERR(phy) || !phy) {
+ dev_info(dwc->dev, "NO PHY!!\n");
+ if (!IS_ERR(phy))
+ usb_put_phy(phy);
+ return count;
+ }
+
+ otg = phy->otg;
+ if (!otg) {
+ dev_info(dwc->dev, "NO OTG!!\n");
+ usb_put_phy(phy);
+ return count;
+ }
+
+ dev_info(dev, "b_hnp_enable is FALSE\n");
+ dwc->gadget.host_request_flag = 1;
+
+ usb_put_phy(phy);
+ return count;
+}
+static DEVICE_ATTR(hnp, 0220, NULL, store_hnp);
+
+static ssize_t store_a_hnp_reqd(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct dwc3 *dwc = dev_get_drvdata(dev);
+ struct dwc3_otg *otg;
+
+ otg = dwc->otg;
+ host_release(otg);
+ return count;
+}
+static DEVICE_ATTR(a_hnp_reqd, 0220, NULL, store_a_hnp_reqd);
+
+static ssize_t store_print_dbg(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct dwc3 *dwc = dev_get_drvdata(dev);
+ struct dwc3_otg *otg;
+
+ otg = dwc->otg;
+ print_debug_regs(otg);
+
+ return count;
+}
+static DEVICE_ATTR(print_dbg, 0220, NULL, store_print_dbg);
+
+void dwc_usb3_remove_dev_files(struct device *dev)
+{
+ device_remove_file(dev, &dev_attr_print_dbg);
+ device_remove_file(dev, &dev_attr_a_hnp_reqd);
+ device_remove_file(dev, &dev_attr_end);
+ device_remove_file(dev, &dev_attr_srp);
+ device_remove_file(dev, &dev_attr_hnp);
+}
+
+int dwc3_otg_create_dev_files(struct device *dev)
+{
+ int retval;
+
+ retval = device_create_file(dev, &dev_attr_hnp);
+ if (retval)
+ goto fail;
+
+ retval = device_create_file(dev, &dev_attr_srp);
+ if (retval)
+ goto fail;
+
+ retval = device_create_file(dev, &dev_attr_end);
+ if (retval)
+ goto fail;
+
+ retval = device_create_file(dev, &dev_attr_a_hnp_reqd);
+ if (retval)
+ goto fail;
+
+ retval = device_create_file(dev, &dev_attr_print_dbg);
+ if (retval)
+ goto fail;
+
+ return 0;
+
+fail:
+ dev_err(dev, "Failed to create one or more sysfs files!!\n");
+ return retval;
+}
+
+int dwc3_otg_init(struct dwc3 *dwc)
+{
+ struct dwc3_otg *otg;
+ int err;
+ u32 reg;
+
+ dev_dbg(dwc->dev, "dwc3_otg_init\n");
+
+ /*
+ * GHWPARAMS6[10] bit is SRPSupport.
+ * This bit also reflects DWC_USB3_EN_OTG
+ */
+ reg = dwc3_readl(dwc->regs, DWC3_GHWPARAMS6);
+ if (!(reg & GHWPARAMS6_SRP_SUPPORT_ENABLED)) {
+ /*
+ * No OTG support in the HW core.
+ * We return 0 to indicate no error, since this is acceptable
+ * situation, just continue probe the dwc3 driver without otg.
+ */
+ dev_dbg(dwc->dev, "dwc3_otg address space is not supported\n");
+ return 0;
+ }
+
+ otg = kzalloc(sizeof(*otg), GFP_KERNEL);
+ if (!otg)
+ return -ENOMEM;
+
+ dwc->otg = otg;
+ otg->dev = dwc->dev;
+ otg->dwc = dwc;
+
+ otg->regs = dwc->regs - DWC3_GLOBALS_REGS_START;
+ otg->otg.usb_phy = kzalloc(sizeof(struct usb_phy), GFP_KERNEL);
+ otg->otg.usb_phy->dev = otg->dev;
+ otg->otg.usb_phy->label = "dwc3_otg";
+ otg->otg.state = OTG_STATE_UNDEFINED;
+ otg->otg.usb_phy->otg = &otg->otg;
+ otg->otg.usb_phy->notify_connect = dwc3_otg_notify_connect;
+ otg->otg.usb_phy->notify_disconnect = dwc3_otg_notify_disconnect;
+
+ otg->otg.start_srp = dwc3_otg_start_srp;
+ otg->otg.start_hnp = dwc3_otg_start_hnp;
+ otg->otg.set_host = dwc3_otg_set_host;
+ otg->otg.set_peripheral = dwc3_otg_set_periph;
+
+ otg->hwparams6 = reg;
+ otg->state = OTG_STATE_UNDEFINED;
+
+ spin_lock_init(&otg->lock);
+ init_waitqueue_head(&otg->main_wq);
+
+ err = usb_add_phy(otg->otg.usb_phy, USB_PHY_TYPE_USB3);
+ if (err) {
+ dev_err(otg->dev, "can't register transceiver, err: %d\n",
+ err);
+ goto exit;
+ }
+
+ otg->irq = platform_get_irq(to_platform_device(otg->dev), 1);
+
+ dwc3_otg_create_dev_files(otg->dev);
+
+ /* Set irq handler */
+ err = request_irq(otg->irq, dwc3_otg_irq, IRQF_SHARED, "dwc3_otg", otg);
+ if (err) {
+ dev_err(otg->otg.usb_phy->dev, "failed to request irq #%d --> %d\n",
+ otg->irq, err);
+ goto exit;
+ }
+
+ dwc3_otg_enable_irq(otg);
+
+ return 0;
+exit:
+ kfree(otg->otg.usb_phy);
+ kfree(otg);
+ return err;
+}
+
+void dwc3_otg_exit(struct dwc3 *dwc)
+{
+ struct dwc3_otg *otg = dwc->otg;
+
+ otg_dbg(otg, "\n");
+ usb_remove_phy(otg->otg.usb_phy);
+ kfree(otg->otg.usb_phy);
+ kfree(otg);
+}
diff --git a/drivers/usb/dwc3/otg.h b/drivers/usb/dwc3/otg.h
new file mode 100644
index 0000000..e2eb4ca
--- /dev/null
+++ b/drivers/usb/dwc3/otg.h
@@ -0,0 +1,247 @@
+/**
+ * otg.h - DesignWare USB3 DRD OTG Header
+ *
+ * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com
+ *
+ * Authors: Felipe Balbi <balbi@ti.com>,
+ * Sebastian Andrzej Siewior <bigeasy@linutronix.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 of
+ * the License 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.
+ */
+
+#define otg_dbg(d, fmt, args...) dev_dbg((d)->dev, "%s(): " fmt,\
+ __func__, ## args)
+#define otg_vdbg(d, fmt, args...) dev_vdbg((d)->dev, "%s(): " fmt,\
+ __func__, ## args)
+#define otg_err(d, fmt, args...) dev_err((d)->dev, "%s(): ERROR: " fmt,\
+ __func__, ## args)
+#define otg_warn(d, fmt, args...) dev_warn((d)->dev, "%s(): WARN: " fmt,\
+ __func__, ## args)
+#define otg_info(d, fmt, args...) dev_info((d)->dev, "%s(): INFO: " fmt,\
+ __func__, ## args)
+
+#ifdef VERBOSE_DEBUG
+#define otg_write(o, reg, val) do { \
+ otg_vdbg(o, "OTG_WRITE: reg=0x%05x, val=0x%08x\n", reg, val); \
+ writel(val, ((void *)((o)->regs)) + reg); \
+ } while (0)
+
+#define otg_read(o, reg) ({ \
+ u32 __r = readl(((void *)((o)->regs)) + reg); \
+ otg_vdbg(o, "OTG_READ: reg=0x%05x, val=0x%08x\n", reg, __r); \
+ __r; \
+ })
+#else
+#define otg_write(o, reg, val) writel(val, ((void *)((o)->regs)) + reg)
+#define otg_read(o, reg) readl(((void *)((o)->regs)) + reg)
+#endif
+
+#define sleep_main_thread_until_condition_timeout(otg, condition, msecs) ({ \
+ int __timeout = msecs; \
+ while (!(condition)) { \
+ otg_dbg(otg, " ... sleeping for %d\n", __timeout); \
+ __timeout = sleep_main_thread_timeout(otg, __timeout); \
+ if (__timeout <= 0) { \
+ break; \
+ } \
+ } \
+ __timeout; \
+ })
+
+#define sleep_main_thread_until_condition(otg, condition) ({ \
+ int __rc; \
+ do { \
+ __rc = sleep_main_thread_until_condition_timeout(otg, \
+ condition, 50000); \
+ } while (__rc == 0); \
+ __rc; \
+ })
+
+#define GHWPARAMS6 0xc158
+#define GHWPARAMS6_SRP_SUPPORT_ENABLED 0x0400
+#define GHWPARAMS6_HNP_SUPPORT_ENABLED 0x0800
+
+#define GCTL 0xc110
+#define GCTL_PRT_CAP_DIR 0x3000
+#define GCTL_PRT_CAP_DIR_SHIFT 12
+#define GCTL_PRT_CAP_DIR_HOST 1
+#define GCTL_PRT_CAP_DIR_DEV 2
+#define GCTL_PRT_CAP_DIR_OTG 3
+#define GCTL_GBL_HIBERNATION_EN 0x2
+
+#define OCFG 0xcc00
+#define OCFG_SRP_CAP 0x01
+#define OCFG_SRP_CAP_SHIFT 0
+#define OCFG_HNP_CAP 0x02
+#define OCFG_HNP_CAP_SHIFT 1
+#define OCFG_OTG_VERSION 0x04
+#define OCFG_OTG_VERSION_SHIFT 2
+
+#define OCTL 0xcc04
+#define OCTL_HST_SET_HNP_EN 0x01
+#define OCTL_HST_SET_HNP_EN_SHIFT 0
+#define OCTL_DEV_SET_HNP_EN 0x02
+#define OCTL_DEV_SET_HNP_EN_SHIFT 1
+#define OCTL_TERM_SEL_DL_PULSE 0x04
+#define OCTL_TERM_SEL_DL_PULSE_SHIFT 2
+#define OCTL_SES_REQ 0x08
+#define OCTL_SES_REQ_SHIFT 3
+#define OCTL_HNP_REQ 0x10
+#define OCTL_HNP_REQ_SHIFT 4
+#define OCTL_PRT_PWR_CTL 0x20
+#define OCTL_PRT_PWR_CTL_SHIFT 5
+#define OCTL_PERI_MODE 0x40
+#define OCTL_PERI_MODE_SHIFT 6
+
+#define OEVT 0xcc08
+#define OEVT_ERR 0x00000001
+#define OEVT_ERR_SHIFT 0
+#define OEVT_SES_REQ_SCS 0x00000002
+#define OEVT_SES_REQ_SCS_SHIFT 1
+#define OEVT_HST_NEG_SCS 0x00000004
+#define OEVT_HST_NEG_SCS_SHIFT 2
+#define OEVT_B_SES_VLD_EVT 0x00000008
+#define OEVT_B_SES_VLD_EVT_SHIFT 3
+#define OEVT_B_DEV_VBUS_CHNG_EVNT 0x00000100
+#define OEVT_B_DEV_VBUS_CHNG_EVNT_SHIFT 8
+#define OEVT_B_DEV_SES_VLD_DET_EVNT 0x00000200
+#define OEVT_B_DEV_SES_VLD_DET_EVNT_SHIFT 9
+#define OEVT_B_DEV_HNP_CHNG_EVNT 0x00000400
+#define OEVT_B_DEV_HNP_CHNG_EVNT_SHIFT 10
+#define OEVT_B_DEV_B_HOST_END_EVNT 0x00000800
+#define OEVT_B_DEV_B_HOST_END_EVNT_SHIFT 11
+#define OEVT_A_DEV_SESS_END_DET_EVNT 0x00010000
+#define OEVT_A_DEV_SESS_END_DET_EVNT_SHIFT 16
+#define OEVT_A_DEV_SRP_DET_EVNT 0x00020000
+#define OEVT_A_DEV_SRP_DET_EVNT_SHIFT 17
+#define OEVT_A_DEV_HNP_CHNG_EVNT 0x00040000
+#define OEVT_A_DEV_HNP_CHNG_EVNT_SHIFT 18
+#define OEVT_A_DEV_HOST_EVNT 0x00080000
+#define OEVT_A_DEV_HOST_EVNT_SHIFT 19
+#define OEVT_A_DEV_B_DEV_HOST_END_EVNT 0x00100000
+#define OEVT_A_DEV_B_DEV_HOST_END_EVNT_SHIFT 20
+#define OEVT_A_DEV_IDLE_EVNT 0x00200000
+#define OEVT_A_DEV_IDLE_EVNT_SHIFT 21
+#define OEVT_HOST_ROLE_REQ_INIT_EVNT 0x00400000
+#define OEVT_HOST_ROLE_REQ_INIT_EVNT_SHIFT 22
+#define OEVT_HOST_ROLE_REQ_CONFIRM_EVNT 0x00800000
+#define OEVT_HOST_ROLE_REQ_CONFIRM_EVNT_SHIFT 23
+#define OEVT_CONN_ID_STS_CHNG_EVNT 0x01000000
+#define OEVT_CONN_ID_STS_CHNG_EVNT_SHIFT 24
+#define OEVT_DEV_MOD_EVNT 0x80000000
+#define OEVT_DEV_MOD_EVNT_SHIFT 31
+
+#define OEVTEN 0xcc0c
+
+#define OEVT_ALL (OEVT_CONN_ID_STS_CHNG_EVNT | \
+ OEVT_HOST_ROLE_REQ_INIT_EVNT | \
+ OEVT_HOST_ROLE_REQ_CONFIRM_EVNT | \
+ OEVT_A_DEV_B_DEV_HOST_END_EVNT | \
+ OEVT_A_DEV_HOST_EVNT | \
+ OEVT_A_DEV_HNP_CHNG_EVNT | \
+ OEVT_A_DEV_SRP_DET_EVNT | \
+ OEVT_A_DEV_SESS_END_DET_EVNT | \
+ OEVT_B_DEV_B_HOST_END_EVNT | \
+ OEVT_B_DEV_HNP_CHNG_EVNT | \
+ OEVT_B_DEV_SES_VLD_DET_EVNT | \
+ OEVT_B_DEV_VBUS_CHNG_EVNT)
+
+#define OSTS 0xcc10
+#define OSTS_CONN_ID_STS 0x0001
+#define OSTS_CONN_ID_STS_SHIFT 0
+#define OSTS_A_SES_VLD 0x0002
+#define OSTS_A_SES_VLD_SHIFT 1
+#define OSTS_B_SES_VLD 0x0004
+#define OSTS_B_SES_VLD_SHIFT 2
+#define OSTS_XHCI_PRT_PWR 0x0008
+#define OSTS_XHCI_PRT_PWR_SHIFT 3
+#define OSTS_PERIP_MODE 0x0010
+#define OSTS_PERIP_MODE_SHIFT 4
+#define OSTS_OTG_STATES 0x0f00
+#define OSTS_OTG_STATE_SHIFT 8
+
+#define DCTL 0xc704
+#define DCTL_RUN_STOP 0x80000000
+
+#define OTG_STATE_INVALID -1
+#define OTG_STATE_EXIT 14
+#define OTG_STATE_TERMINATED 15
+
+#define PERI_MODE_HOST 0
+#define PERI_MODE_PERIPHERAL 1
+
+/** The main structure to keep track of OTG driver state. */
+struct dwc3_otg {
+
+ /** OTG PHY */
+ struct usb_otg otg;
+ struct device *dev;
+ struct dwc3 *dwc;
+
+ void __iomem *regs;
+
+ int main_wakeup_needed;
+ struct task_struct *main_thread;
+ wait_queue_head_t main_wq;
+
+ spinlock_t lock;
+
+ int otg_srp_reqd;
+
+ /* Events */
+ u32 otg_events;
+
+ u32 user_events;
+
+ /** User initiated SRP.
+ *
+ * Valid in B-device during sensing/probing. Initiates SRP signalling
+ * across the bus.
+ *
+ * Also valid as an A-device during probing. This causes the A-device to
+ * apply V-bus manually and check for a device. Can be used if the
+ * device does not support SRP and the host does not support ADP.
+ */
+#define USER_SRP_EVENT 0x1
+ /** User initiated HNP (only valid in B-peripheral) */
+#define USER_HNP_EVENT 0x2
+ /** User has ended the session (only valid in B-peripheral) */
+#define USER_END_SESSION 0x4
+ /** User initiated VBUS. This will cause the A-device to turn on the
+ * VBUS and see if a device will connect (only valid in A-device during
+ * sensing/probing)
+ */
+#define USER_VBUS_ON 0x8
+ /** User has initiated RSP */
+#define USER_RSP_EVENT 0x10
+ /** Host release event */
+#define PCD_RECEIVED_HOST_RELEASE_EVENT 0x20
+ /** Initial SRP */
+#define INITIAL_SRP 0x40
+ /** A-device connected event*/
+#define USER_A_CONN_EVENT 0x80
+
+ /* States */
+ enum usb_otg_state prev;
+ enum usb_otg_state state;
+
+ u32 hwparams6;
+ int hcd_irq;
+ int irq;
+ int host_started;
+ int peripheral_started;
+ int dev_enum;
+
+ struct delayed_work hp_work; /* drives HNP polling */
+
+};
+
+extern int usb_port_suspend(struct usb_device *udev, pm_message_t msg);
+extern void usb_kick_hub_wq(struct usb_device *dev);
--
2.1.1
^ permalink raw reply related
* [RFC PATCH] usb: dwc3: host: add support for OTG in DWC3 host driver
From: Manish Narani @ 2017-01-04 13:22 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1483536181-22356-1-git-send-email-mnarani@xilinx.com>
This patch adds support for OTG host mode initialization in DWC3
host driver. Before the host initialization sequence begins. The
driver has to make sure the no OTG peripheral mode is enabled.
Signed-off-by: Manish Narani <mnarani@xilinx.com>
---
drivers/usb/dwc3/host.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c
index 487f0ff..4caa3fe 100644
--- a/drivers/usb/dwc3/host.c
+++ b/drivers/usb/dwc3/host.c
@@ -16,6 +16,8 @@
*/
#include <linux/platform_device.h>
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
#include "core.h"
@@ -111,6 +113,18 @@ int dwc3_host_init(struct dwc3 *dwc)
phy_create_lookup(dwc->usb3_generic_phy, "usb3-phy",
dev_name(dwc->dev));
+ if (dwc->dr_mode == USB_DR_MODE_OTG) {
+ struct usb_phy *phy;
+ /* Switch otg to host mode */
+ phy = usb_get_phy(USB_PHY_TYPE_USB3);
+ if (!IS_ERR(phy)) {
+ if (phy && phy->otg)
+ otg_set_host(phy->otg,
+ (struct usb_bus *)(long)1);
+ usb_put_phy(phy);
+ }
+ }
+
ret = platform_device_add(xhci);
if (ret) {
dev_err(dwc->dev, "failed to register xHCI device\n");
--
2.1.1
^ permalink raw reply related
* [RFC PATCH] usb: dwc3: gadget: add support for OTG in gadget framework
From: Manish Narani @ 2017-01-04 13:22 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1483536181-22356-1-git-send-email-mnarani@xilinx.com>
This patch adds support for OTG in DWC3 gadget framework. This
also adds support for HNP polling by host while in OTG mode.
Modifications in couple of functions to make it in sync with
OTG driver while keeping its original functionality intact.
Signed-off-by: Manish Narani <mnarani@xilinx.com>
---
drivers/usb/dwc3/ep0.c | 49 +++++++++++++++++++++++---
drivers/usb/dwc3/gadget.c | 87 +++++++++++++++++++++++++++++++++++++++--------
2 files changed, 116 insertions(+), 20 deletions(-)
diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c
index 4878d18..aa78c1b 100644
--- a/drivers/usb/dwc3/ep0.c
+++ b/drivers/usb/dwc3/ep0.c
@@ -334,6 +334,8 @@ static int dwc3_ep0_handle_status(struct dwc3 *dwc,
usb_status |= 1 << USB_DEV_STAT_U2_ENABLED;
}
+ usb_status |= dwc->remote_wakeup << USB_DEVICE_REMOTE_WAKEUP;
+
break;
case USB_RECIP_INTERFACE:
@@ -448,11 +450,45 @@ static int dwc3_ep0_handle_device(struct dwc3 *dwc,
switch (wValue) {
case USB_DEVICE_REMOTE_WAKEUP:
+ if (set)
+ dwc->remote_wakeup = 1;
+ else
+ dwc->remote_wakeup = 0;
break;
- /*
- * 9.4.1 says only only for SS, in AddressState only for
- * default control pipe
- */
+ case USB_DEVICE_B_HNP_ENABLE:
+ dev_dbg(dwc->dev,
+ "SET_FEATURE: USB_DEVICE_B_HNP_ENABLE\n");
+ if (set && dwc->gadget.is_otg) {
+ if (dwc->gadget.host_request_flag) {
+ struct usb_phy *phy =
+ usb_get_phy(USB_PHY_TYPE_USB3);
+
+ dwc->gadget.b_hnp_enable = 0;
+ dwc->gadget.host_request_flag = 0;
+ otg_start_hnp(phy->otg);
+ usb_put_phy(phy);
+ } else {
+ dwc->gadget.b_hnp_enable = 1;
+ }
+ } else
+ return -EINVAL;
+ break;
+
+ case USB_DEVICE_A_HNP_SUPPORT:
+ /* RH port supports HNP */
+ dev_dbg(dwc->dev,
+ "SET_FEATURE: USB_DEVICE_A_HNP_SUPPORT\n");
+ break;
+
+ case USB_DEVICE_A_ALT_HNP_SUPPORT:
+ /* other RH port does */
+ dev_dbg(dwc->dev,
+ "SET_FEATURE: USB_DEVICE_A_ALT_HNP_SUPPORT\n");
+ break;
+ /*
+ * 9.4.1 says only only for SS, in AddressState only for
+ * default control pipe
+ */
case USB_DEVICE_U1_ENABLE:
ret = dwc3_ep0_handle_u1(dwc, state, set);
break;
@@ -759,7 +795,10 @@ static int dwc3_ep0_std_request(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl)
switch (ctrl->bRequest) {
case USB_REQ_GET_STATUS:
- ret = dwc3_ep0_handle_status(dwc, ctrl);
+ if (le16_to_cpu(ctrl->wIndex) == OTG_STS_SELECTOR)
+ ret = dwc3_ep0_delegate_req(dwc, ctrl);
+ else
+ ret = dwc3_ep0_handle_status(dwc, ctrl);
break;
case USB_REQ_CLEAR_FEATURE:
ret = dwc3_ep0_handle_feature(dwc, ctrl, 0);
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
index 6785595..3b0b771 100644
--- a/drivers/usb/dwc3/gadget.c
+++ b/drivers/usb/dwc3/gadget.c
@@ -34,6 +34,7 @@
#include "core.h"
#include "gadget.h"
#include "io.h"
+#include "otg.h"
/**
* dwc3_gadget_set_test_mode - Enables USB2 Test Modes
@@ -1792,25 +1793,51 @@ static int dwc3_gadget_start(struct usb_gadget *g,
int ret = 0;
int irq;
- irq = dwc->irq_gadget;
- ret = request_threaded_irq(irq, dwc3_interrupt, dwc3_thread_interrupt,
- IRQF_SHARED, "dwc3", dwc->ev_buf);
- if (ret) {
- dev_err(dwc->dev, "failed to request irq #%d --> %d\n",
- irq, ret);
- goto err0;
- }
-
spin_lock_irqsave(&dwc->lock, flags);
if (dwc->gadget_driver) {
dev_err(dwc->dev, "%s is already bound to %s\n",
dwc->gadget.name,
dwc->gadget_driver->driver.name);
ret = -EBUSY;
- goto err1;
+ goto err0;
}
- dwc->gadget_driver = driver;
+ if (g->is_otg) {
+ static struct usb_gadget_driver *prev_driver;
+ /* There are two instances where OTG functionality is enabled :
+ * 1. This function will be called from OTG driver to start the
+ * gadget
+ * 2. This function will be called by the Linux Class Driver to
+ * start the gadget
+ * Below code will keep synchronization between these calls. The
+ * "driver" argument will be NULL when it is called from the OTG
+ * driver, so we are maintaning a global variable "prev_driver"
+ * to assign value of argument "driver" (from class driver) to
+ * dwc->gadget_driver when it is called from OTG.
+ */
+ if (driver) {
+ prev_driver = driver;
+ if (dwc->otg) {
+ struct dwc3_otg *otg = dwc->otg;
+
+ if ((otg->host_started ||
+ (!otg->peripheral_started)))
+ goto err0;
+ }
+ dwc->gadget_driver = driver;
+ } else
+ dwc->gadget_driver = prev_driver;
+ } else
+ dwc->gadget_driver = driver;
+
+ irq = dwc->irq_gadget;
+ ret = request_threaded_irq(irq, dwc3_interrupt, dwc3_thread_interrupt,
+ IRQF_SHARED, "dwc3", dwc->ev_buf);
+ if (ret) {
+ dev_err(dwc->dev, "failed to request irq #%d --> %d\n",
+ irq, ret);
+ goto err0;
+ }
if (pm_runtime_active(dwc->dev))
__dwc3_gadget_start(dwc);
@@ -1819,11 +1846,9 @@ static int dwc3_gadget_start(struct usb_gadget *g,
return 0;
-err1:
- spin_unlock_irqrestore(&dwc->lock, flags);
- free_irq(irq, dwc);
-
err0:
+ dwc->gadget_driver = NULL;
+ spin_unlock_irqrestore(&dwc->lock, flags);
return ret;
}
@@ -2977,6 +3002,18 @@ int dwc3_gadget_init(struct dwc3 *dwc)
dwc->irq_gadget = irq;
+ if (dwc->dr_mode == USB_DR_MODE_OTG) {
+ struct usb_phy *phy;
+ /* Switch otg to peripheral mode */
+ phy = usb_get_phy(USB_PHY_TYPE_USB3);
+ if (!IS_ERR(phy)) {
+ if (phy && phy->otg)
+ otg_set_peripheral(phy->otg,
+ (struct usb_gadget *)(long)1);
+ usb_put_phy(phy);
+ }
+ }
+
dwc->ctrl_req = dma_alloc_coherent(dwc->sysdev, sizeof(*dwc->ctrl_req),
&dwc->ctrl_req_addr, GFP_KERNEL);
if (!dwc->ctrl_req) {
@@ -3066,6 +3103,26 @@ int dwc3_gadget_init(struct dwc3 *dwc)
goto err5;
}
+ if (dwc->dr_mode == USB_DR_MODE_OTG) {
+ struct usb_phy *phy;
+
+ phy = usb_get_phy(USB_PHY_TYPE_USB3);
+ if (!IS_ERR(phy)) {
+ if (phy && phy->otg) {
+ ret = otg_set_peripheral(phy->otg,
+ &dwc->gadget);
+ if (ret) {
+ usb_put_phy(phy);
+ phy = NULL;
+ goto err5;
+ }
+ } else {
+ usb_put_phy(phy);
+ phy = NULL;
+ }
+ }
+ }
+
return 0;
err5:
--
2.1.1
^ permalink raw reply related
* [RFC PATCH] usb: dwc3: core: add OTG support function calls and modifications
From: Manish Narani @ 2017-01-04 13:22 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1483536181-22356-1-git-send-email-mnarani@xilinx.com>
This patch adds function call to initialize OTG driver. This patch
also adds support for OTG device structure in DWC3 device.
Modifications to event buffer related functions which are called
from OTG driver upon requirement.
Signed-off-by: Manish Narani <mnarani@xilinx.com>
---
drivers/usb/dwc3/core.c | 17 ++++++++++++-----
drivers/usb/dwc3/core.h | 14 ++++++++++++++
2 files changed, 26 insertions(+), 5 deletions(-)
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index 369bab1..9ab9c5b 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -240,7 +240,7 @@ static struct dwc3_event_buffer *dwc3_alloc_one_event_buffer(struct dwc3 *dwc,
* dwc3_free_event_buffers - frees all allocated event buffers
* @dwc: Pointer to our controller context structure
*/
-static void dwc3_free_event_buffers(struct dwc3 *dwc)
+void dwc3_free_event_buffers(struct dwc3 *dwc)
{
struct dwc3_event_buffer *evt;
@@ -257,7 +257,7 @@ static void dwc3_free_event_buffers(struct dwc3 *dwc)
* Returns 0 on success otherwise negative errno. In the error case, dwc
* may contain some buffers allocated but not all which were requested.
*/
-static int dwc3_alloc_event_buffers(struct dwc3 *dwc, unsigned length)
+int dwc3_alloc_event_buffers(struct dwc3 *dwc, unsigned length)
{
struct dwc3_event_buffer *evt;
@@ -277,7 +277,7 @@ static int dwc3_alloc_event_buffers(struct dwc3 *dwc, unsigned length)
*
* Returns 0 on success otherwise negative errno.
*/
-static int dwc3_event_buffers_setup(struct dwc3 *dwc)
+int dwc3_event_buffers_setup(struct dwc3 *dwc)
{
struct dwc3_event_buffer *evt;
@@ -862,10 +862,10 @@ static int dwc3_core_init_mode(struct dwc3 *dwc)
}
break;
case USB_DR_MODE_OTG:
- ret = dwc3_host_init(dwc);
+ ret = dwc3_otg_init(dwc);
if (ret) {
if (ret != -EPROBE_DEFER)
- dev_err(dev, "failed to initialize host\n");
+ dev_err(dev, "failed to initialize otg\n");
return ret;
}
@@ -875,6 +875,13 @@ static int dwc3_core_init_mode(struct dwc3 *dwc)
dev_err(dev, "failed to initialize gadget\n");
return ret;
}
+
+ ret = dwc3_host_init(dwc);
+ if (ret) {
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "failed to initialize host\n");
+ return ret;
+ }
break;
default:
dev_err(dev, "Unsupported mode of operation %d\n", dwc->dr_mode);
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index de5a857..6b92064 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -887,6 +887,8 @@ struct dwc3 {
struct usb_gadget gadget;
struct usb_gadget_driver *gadget_driver;
+ struct dwc3_otg *otg;
+
struct usb_phy *usb2_phy;
struct usb_phy *usb3_phy;
@@ -987,6 +989,7 @@ struct dwc3 {
unsigned setup_packet_pending:1;
unsigned three_stage_setup:1;
unsigned usb3_lpm_capable:1;
+ unsigned remote_wakeup:1;
unsigned disable_scramble_quirk:1;
unsigned u2exit_lfps_quirk:1;
@@ -1220,6 +1223,13 @@ static inline int dwc3_send_gadget_generic_command(struct dwc3 *dwc,
{ return 0; }
#endif
+#if IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)
+int dwc3_otg_init(struct dwc3 *dwc);
+#else
+static inline int dwc3_otg_init(struct dwc3 *dwc)
+{ return 0; }
+#endif
+
/* power management interface */
#if !IS_ENABLED(CONFIG_USB_DWC3_HOST)
int dwc3_gadget_suspend(struct dwc3 *dwc);
@@ -1251,4 +1261,8 @@ static inline void dwc3_ulpi_exit(struct dwc3 *dwc)
{ }
#endif
+int dwc3_alloc_event_buffers(struct dwc3 *dwc, unsigned length);
+void dwc3_free_event_buffers(struct dwc3 *dwc);
+int dwc3_event_buffers_setup(struct dwc3 *dwc);
+
#endif /* __DRIVERS_USB_DWC3_CORE_H */
--
2.1.1
^ permalink raw reply related
* [RFC PATCH] usb: dwc3: add support for OTG driver compilation
From: Manish Narani @ 2017-01-04 13:22 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <1483536181-22356-1-git-send-email-mnarani@xilinx.com>
This patch adds support for OTG driver compilation and object
file creation
Signed-off-by: Manish Narani <mnarani@xilinx.com>
---
drivers/usb/dwc3/Makefile | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile
index ffca340..2d269ad 100644
--- a/drivers/usb/dwc3/Makefile
+++ b/drivers/usb/dwc3/Makefile
@@ -17,6 +17,10 @@ ifneq ($(filter y,$(CONFIG_USB_DWC3_GADGET) $(CONFIG_USB_DWC3_DUAL_ROLE)),)
dwc3-y += gadget.o ep0.o
endif
+ifneq ($(CONFIG_USB_DWC3_DUAL_ROLE),)
+ dwc3-y += otg.o
+endif
+
ifneq ($(CONFIG_USB_DWC3_ULPI),)
dwc3-y += ulpi.o
endif
--
2.1.1
^ permalink raw reply related
* [RFC PATCH] arch: arm64: dts: add USB OTG interrupts support in ZynqMP device tree
From: Manish Narani @ 2017-01-04 13:22 UTC (permalink / raw)
To: linux-arm-kernel
This patch adds OTG interrupt support in device tree. It will add
an extra interrupt line number dedicated to OTG events. This will
enable OTG interrupts to serve in DWC3 OTG driver.
Signed-off-by: Manish Narani <mnarani@xilinx.com>
---
arch/arm64/boot/dts/xilinx/zynqmp.dtsi | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/arch/arm64/boot/dts/xilinx/zynqmp.dtsi b/arch/arm64/boot/dts/xilinx/zynqmp.dtsi
index 68a90833..ce9ad02 100644
--- a/arch/arm64/boot/dts/xilinx/zynqmp.dtsi
+++ b/arch/arm64/boot/dts/xilinx/zynqmp.dtsi
@@ -351,7 +351,7 @@
compatible = "snps,dwc3";
status = "disabled";
interrupt-parent = <&gic>;
- interrupts = <0 65 4>;
+ interrupts = <0 65 4>, <0 69 4>;
reg = <0x0 0xfe200000 0x0 0x40000>;
clock-names = "clk_xin", "clk_ahb";
};
--
2.1.1
^ permalink raw reply related
* [PATCH 14/22] dt-bindings: power: supply: add AXP20X/AXP22X battery DT binding
From: Rob Herring @ 2017-01-04 13:21 UTC (permalink / raw)
To: linux-arm-kernel
In-Reply-To: <20170102163723.7939-15-quentin.schulz@free-electrons.com>
On Mon, Jan 02, 2017 at 05:37:14PM +0100, Quentin Schulz wrote:
> The X-Powers AXP20X and AXP22X PMICs can have a battery as power supply.
>
> This patch adds the DT binding documentation for the battery power
> supply which gets various data from the PMIC, such as the battery status
> (charging, discharging, full, dead), current max limit, current current,
> battery capacity (in percentage), voltage max and min limits, current
> voltage and battery capacity (in Ah).
>
> Signed-off-by: Quentin Schulz <quentin.schulz@free-electrons.com>
> ---
> .../bindings/power/supply/axp20x_battery.txt | 27 ++++++++++++++++++++++
> 1 file changed, 27 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/power/supply/axp20x_battery.txt
>
> diff --git a/Documentation/devicetree/bindings/power/supply/axp20x_battery.txt b/Documentation/devicetree/bindings/power/supply/axp20x_battery.txt
> new file mode 100644
> index 0000000..5489d0d
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/power/supply/axp20x_battery.txt
> @@ -0,0 +1,27 @@
> +AXP20x and AXP22x battery power supply
> +
> +Required Properties:
> + - compatible, one of:
> + "x-powers,axp209-battery-power-supply"
> + "x-powers,axp221-battery-power-supply"
> + - io-channels: phandles to battery voltage, charge and discharge
> + currents ADC channels
> + - io-channel-names = "batt_v", "batt_chrg_i", "batt_dischrg_i";
> +
> +This node is a subnode of the axp20x/axp22x PMIC.
> +
> +The AXP20X and AXP22X can read the battery voltage, charge and discharge
> +currents of the battery by reading ADC channels from the AXP20X/AXP22X
> +ADC.
> +
> +Example:
> +
> +&axp209 {
> + battery_power_supply: battery_power_supply {
Humm, I guess you power-supply is not sufficient, so
'battery-power-supply' and similar for ac.
> + compatible = "x-powers,axp209-battery-power-supply";
> + io-channels = <&axp209_adc 7>, <&axp209_adc 8>,
> + <&axp209_adc 9>;
> + io-channel-names = "batt_v", "batt_chrg_i",
> + "batt_dischrg_i";
> + }
> +};
> --
> 2.9.3
>
^ permalink raw reply
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