* [RFC PATCH v5 05/19] memory-hotplug: check whether memory is present or not
From: Wen Congyang @ 2012-07-27 10:28 UTC (permalink / raw)
To: linux-mm, linux-kernel, linuxppc-dev, linux-acpi, linux-s390,
linux-sh, linux-ia64, cmetcalf
Cc: len.brown, Yasuaki ISIMATU, paulus, minchan.kim, kosaki.motohiro,
rientjes, cl, akpm, liuj97
In-Reply-To: <50126B83.3050201@cn.fujitsu.com>
From: Yasuaki Ishimatsu <isimatu.yasuaki@jp.fujitsu.com>
If system supports memory hot-remove, online_pages() may online removed pages.
So online_pages() need to check whether onlining pages are present or not.
CC: David Rientjes <rientjes@google.com>
CC: Jiang Liu <liuj97@gmail.com>
CC: Len Brown <len.brown@intel.com>
CC: Benjamin Herrenschmidt <benh@kernel.crashing.org>
CC: Paul Mackerras <paulus@samba.org>
CC: Christoph Lameter <cl@linux.com>
Cc: Minchan Kim <minchan.kim@gmail.com>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com>
CC: Wen Congyang <wency@cn.fujitsu.com>
Signed-off-by: Yasuaki Ishimatsu <isimatu.yasuaki@jp.fujitsu.com>
---
include/linux/mmzone.h | 19 +++++++++++++++++++
mm/memory_hotplug.c | 13 +++++++++++++
2 files changed, 32 insertions(+), 0 deletions(-)
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index 458988b..822f705 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -1168,6 +1168,25 @@ void sparse_init(void);
#define sparse_index_init(_sec, _nid) do {} while (0)
#endif /* CONFIG_SPARSEMEM */
+#ifdef CONFIG_SPARSEMEM
+static inline int pfns_present(unsigned long pfn, unsigned long nr_pages)
+{
+ int i;
+ for (i = 0; i < nr_pages; i++) {
+ if (pfn_present(pfn + 1))
+ continue;
+ else
+ return -EINVAL;
+ }
+ return 0;
+}
+#else
+static inline int pfns_present(unsigned long pfn, unsigned long nr_pages)
+{
+ return 0;
+}
+#endif /* CONFIG_SPARSEMEM*/
+
#ifdef CONFIG_NODES_SPAN_OTHER_NODES
bool early_pfn_in_nid(unsigned long pfn, int nid);
#else
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index 5af0a9f..d510be0 100644
--- a/mm/memory_hotplug.c
+++ b/mm/memory_hotplug.c
@@ -467,6 +467,19 @@ int __ref online_pages(unsigned long pfn, unsigned long nr_pages)
struct memory_notify arg;
lock_memory_hotplug();
+ /*
+ * If system supports memory hot-remove, the memory may have been
+ * removed. So we check whether the memory has been removed or not.
+ *
+ * Note: When CONFIG_SPARSEMEM is defined, pfns_present() become
+ * effective. If CONFIG_SPARSEMEM is not defined, pfns_present()
+ * always returns 0.
+ */
+ ret = pfns_present(pfn, nr_pages);
+ if (ret) {
+ unlock_memory_hotplug();
+ return ret;
+ }
arg.start_pfn = pfn;
arg.nr_pages = nr_pages;
arg.status_change_nid = -1;
--
1.7.1
^ permalink raw reply related
* [RFC PATCH v5 06/19] memory-hotplug: export the function acpi_bus_remove()
From: Wen Congyang @ 2012-07-27 10:28 UTC (permalink / raw)
To: linux-mm, linux-kernel, linuxppc-dev, linux-acpi, linux-s390,
linux-sh, linux-ia64, cmetcalf
Cc: len.brown, Yasuaki ISIMATU, paulus, minchan.kim, kosaki.motohiro,
rientjes, cl, akpm, liuj97
In-Reply-To: <50126B83.3050201@cn.fujitsu.com>
The function acpi_bus_remove() can remove a acpi device from acpi device.
When a acpi device is removed, we need to call this function to remove
the acpi device from acpi bus. So export this function.
CC: David Rientjes <rientjes@google.com>
CC: Jiang Liu <liuj97@gmail.com>
CC: Len Brown <len.brown@intel.com>
CC: Benjamin Herrenschmidt <benh@kernel.crashing.org>
CC: Paul Mackerras <paulus@samba.org>
CC: Christoph Lameter <cl@linux.com>
Cc: Minchan Kim <minchan.kim@gmail.com>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com>
CC: Yasuaki Ishimatsu <isimatu.yasuaki@jp.fujitsu.com>
Signed-off-by: Wen Congyang <wency@cn.fujitsu.com>
---
drivers/acpi/scan.c | 3 ++-
include/acpi/acpi_bus.h | 1 +
2 files changed, 3 insertions(+), 1 deletions(-)
diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c
index d1ecca2..1cefc34 100644
--- a/drivers/acpi/scan.c
+++ b/drivers/acpi/scan.c
@@ -1224,7 +1224,7 @@ static int acpi_device_set_context(struct acpi_device *device)
return -ENODEV;
}
-static int acpi_bus_remove(struct acpi_device *dev, int rmdevice)
+int acpi_bus_remove(struct acpi_device *dev, int rmdevice)
{
if (!dev)
return -EINVAL;
@@ -1246,6 +1246,7 @@ static int acpi_bus_remove(struct acpi_device *dev, int rmdevice)
return 0;
}
+EXPORT_SYMBOL(acpi_bus_remove);
static int acpi_add_single_object(struct acpi_device **child,
acpi_handle handle, int type,
diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h
index bde976e..2ccf109 100644
--- a/include/acpi/acpi_bus.h
+++ b/include/acpi/acpi_bus.h
@@ -360,6 +360,7 @@ bool acpi_bus_power_manageable(acpi_handle handle);
bool acpi_bus_can_wakeup(acpi_handle handle);
int acpi_power_resource_register_device(struct device *dev, acpi_handle handle);
void acpi_power_resource_unregister_device(struct device *dev, acpi_handle handle);
+int acpi_bus_remove(struct acpi_device *dev, int rmdevice);
#ifdef CONFIG_ACPI_PROC_EVENT
int acpi_bus_generate_proc_event(struct acpi_device *device, u8 type, int data);
int acpi_bus_generate_proc_event4(const char *class, const char *bid, u8 type, int data);
--
1.7.1
^ permalink raw reply related
* [RFC PATCH v5 07/19] memory-hotplug: call acpi_bus_remove() to remove memory device
From: Wen Congyang @ 2012-07-27 10:29 UTC (permalink / raw)
To: linux-mm, linux-kernel, linuxppc-dev, linux-acpi, linux-s390,
linux-sh, linux-ia64, cmetcalf
Cc: len.brown, Yasuaki ISIMATU, paulus, minchan.kim, kosaki.motohiro,
rientjes, cl, akpm, liuj97
In-Reply-To: <50126B83.3050201@cn.fujitsu.com>
The memory device has been ejected and powoffed, so we can call
acpi_bus_remove() to remove the memory device from acpi bus.
CC: David Rientjes <rientjes@google.com>
CC: Jiang Liu <liuj97@gmail.com>
CC: Len Brown <len.brown@intel.com>
CC: Benjamin Herrenschmidt <benh@kernel.crashing.org>
CC: Paul Mackerras <paulus@samba.org>
CC: Christoph Lameter <cl@linux.com>
Cc: Minchan Kim <minchan.kim@gmail.com>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com>
CC: Yasuaki Ishimatsu <isimatu.yasuaki@jp.fujitsu.com>
Signed-off-by: Wen Congyang <wency@cn.fujitsu.com>
---
drivers/acpi/acpi_memhotplug.c | 3 ++-
1 files changed, 2 insertions(+), 1 deletions(-)
diff --git a/drivers/acpi/acpi_memhotplug.c b/drivers/acpi/acpi_memhotplug.c
index ed37fc2..755cc31 100644
--- a/drivers/acpi/acpi_memhotplug.c
+++ b/drivers/acpi/acpi_memhotplug.c
@@ -423,8 +423,9 @@ static void acpi_memory_device_notify(acpi_handle handle, u32 event, void *data)
}
/*
- * TBD: Invoke acpi_bus_remove to cleanup data structures
+ * Invoke acpi_bus_remove() to remove memory device
*/
+ acpi_bus_remove(device, 1);
/* _EJ0 succeeded; _OST is not necessary */
return;
--
1.7.1
^ permalink raw reply related
* [RFC PATCH v5 08/19] memory-hotplug: remove /sys/firmware/memmap/X sysfs
From: Wen Congyang @ 2012-07-27 10:30 UTC (permalink / raw)
To: linux-mm, linux-kernel, linuxppc-dev, linux-acpi, linux-s390,
linux-sh, linux-ia64, cmetcalf
Cc: len.brown, Yasuaki ISIMATU, paulus, minchan.kim, kosaki.motohiro,
rientjes, cl, akpm, liuj97
In-Reply-To: <50126B83.3050201@cn.fujitsu.com>
From: Yasuaki Ishimatsu <isimatu.yasuaki@jp.fujitsu.com>
When (hot)adding memory into system, /sys/firmware/memmap/X/{end, start, type}
sysfs files are created. But there is no code to remove these files. The patch
implements the function to remove them.
Note : The code does not free firmware_map_entry since there is no way to free
memory which is allocated by bootmem.
CC: David Rientjes <rientjes@google.com>
CC: Jiang Liu <liuj97@gmail.com>
CC: Len Brown <len.brown@intel.com>
CC: Benjamin Herrenschmidt <benh@kernel.crashing.org>
CC: Paul Mackerras <paulus@samba.org>
CC: Christoph Lameter <cl@linux.com>
Cc: Minchan Kim <minchan.kim@gmail.com>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com>
CC: Wen Congyang <wency@cn.fujitsu.com>
Signed-off-by: Yasuaki Ishimatsu <isimatu.yasuaki@jp.fujitsu.com>
---
drivers/firmware/memmap.c | 78 +++++++++++++++++++++++++++++++++++++++++-
include/linux/firmware-map.h | 6 +++
mm/memory_hotplug.c | 9 ++++-
3 files changed, 90 insertions(+), 3 deletions(-)
diff --git a/drivers/firmware/memmap.c b/drivers/firmware/memmap.c
index 1296605..e03e84f 100644
--- a/drivers/firmware/memmap.c
+++ b/drivers/firmware/memmap.c
@@ -21,6 +21,7 @@
#include <linux/types.h>
#include <linux/bootmem.h>
#include <linux/slab.h>
+#include <linux/mm.h>
/*
* Data types ------------------------------------------------------------------
@@ -79,7 +80,22 @@ static const struct sysfs_ops memmap_attr_ops = {
.show = memmap_attr_show,
};
+#define to_memmap_entry(obj) container_of(obj, struct firmware_map_entry, kobj)
+
+static void release_firmware_map_entry(struct kobject *kobj)
+{
+ struct firmware_map_entry *entry = to_memmap_entry(kobj);
+ struct page *page;
+
+ page = virt_to_page(entry);
+ if (PageSlab(page) || PageCompound(page))
+ kfree(entry);
+
+ /* There is no way to free memory allocated from bootmem*/
+}
+
static struct kobj_type memmap_ktype = {
+ .release = release_firmware_map_entry,
.sysfs_ops = &memmap_attr_ops,
.default_attrs = def_attrs,
};
@@ -123,6 +139,16 @@ static int firmware_map_add_entry(u64 start, u64 end,
return 0;
}
+/**
+ * firmware_map_remove_entry() - Does the real work to remove a firmware
+ * memmap entry.
+ * @entry: removed entry.
+ **/
+static inline void firmware_map_remove_entry(struct firmware_map_entry *entry)
+{
+ list_del(&entry->list);
+}
+
/*
* Add memmap entry on sysfs
*/
@@ -144,6 +170,31 @@ static int add_sysfs_fw_map_entry(struct firmware_map_entry *entry)
return 0;
}
+/*
+ * Remove memmap entry on sysfs
+ */
+static inline void remove_sysfs_fw_map_entry(struct firmware_map_entry *entry)
+{
+ kobject_put(&entry->kobj);
+}
+
+/*
+ * Search memmap entry
+ */
+
+struct firmware_map_entry * __meminit
+find_firmware_map_entry(u64 start, u64 end, const char *type)
+{
+ struct firmware_map_entry *entry;
+
+ list_for_each_entry(entry, &map_entries, list)
+ if ((entry->start == start) && (entry->end == end) &&
+ (!strcmp(entry->type, type)))
+ return entry;
+
+ return NULL;
+}
+
/**
* firmware_map_add_hotplug() - Adds a firmware mapping entry when we do
* memory hotplug.
@@ -196,6 +247,32 @@ int __init firmware_map_add_early(u64 start, u64 end, const char *type)
return firmware_map_add_entry(start, end, type, entry);
}
+/**
+ * firmware_map_remove() - remove a firmware mapping entry
+ * @start: Start of the memory range.
+ * @end: End of the memory range.
+ * @type: Type of the memory range.
+ *
+ * removes a firmware mapping entry.
+ *
+ * Returns 0 on success, or -EINVAL if no entry.
+ **/
+int __meminit firmware_map_remove(u64 start, u64 end, const char *type)
+{
+ struct firmware_map_entry *entry;
+
+ entry = find_firmware_map_entry(start, end - 1, type);
+ if (!entry)
+ return -EINVAL;
+
+ firmware_map_remove_entry(entry);
+
+ /* remove the memmap entry */
+ remove_sysfs_fw_map_entry(entry);
+
+ return 0;
+}
+
/*
* Sysfs functions -------------------------------------------------------------
*/
@@ -218,7 +295,6 @@ static ssize_t type_show(struct firmware_map_entry *entry, char *buf)
}
#define to_memmap_attr(_attr) container_of(_attr, struct memmap_attribute, attr)
-#define to_memmap_entry(obj) container_of(obj, struct firmware_map_entry, kobj)
static ssize_t memmap_attr_show(struct kobject *kobj,
struct attribute *attr, char *buf)
diff --git a/include/linux/firmware-map.h b/include/linux/firmware-map.h
index 43fe52f..71d4fa7 100644
--- a/include/linux/firmware-map.h
+++ b/include/linux/firmware-map.h
@@ -25,6 +25,7 @@
int firmware_map_add_early(u64 start, u64 end, const char *type);
int firmware_map_add_hotplug(u64 start, u64 end, const char *type);
+int firmware_map_remove(u64 start, u64 end, const char *type);
#else /* CONFIG_FIRMWARE_MEMMAP */
@@ -38,6 +39,11 @@ static inline int firmware_map_add_hotplug(u64 start, u64 end, const char *type)
return 0;
}
+static inline int firmware_map_remove(u64 start, u64 end, const char *type)
+{
+ return 0;
+}
+
#endif /* CONFIG_FIRMWARE_MEMMAP */
#endif /* _LINUX_FIRMWARE_MAP_H */
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index d510be0..5237d49 100644
--- a/mm/memory_hotplug.c
+++ b/mm/memory_hotplug.c
@@ -1048,9 +1048,9 @@ int offline_memory(u64 start, u64 size)
return 0;
}
-int remove_memory(int nid, u64 start, u64 size)
+int __ref remove_memory(int nid, u64 start, u64 size)
{
- int ret = -EBUSY;
+ int ret = 0;
lock_memory_hotplug();
/*
* The memory might become online by other task, even if you offine it.
@@ -1061,8 +1061,13 @@ int remove_memory(int nid, u64 start, u64 size)
"because the memmory range is online\n",
start, start + size);
ret = -EAGAIN;
+ goto out;
}
+ /* remove memmap entry */
+ firmware_map_remove(start, start + size, "System RAM");
+
+out:
unlock_memory_hotplug();
return ret;
--
1.7.1
^ permalink raw reply related
* [RFC PATCH v5 09/19] memory-hotplug: does not release memory region in PAGES_PER_SECTION chunks
From: Wen Congyang @ 2012-07-27 10:30 UTC (permalink / raw)
To: linux-mm, linux-kernel, linuxppc-dev, linux-acpi, linux-s390,
linux-sh, linux-ia64, cmetcalf
Cc: len.brown, Yasuaki ISIMATU, paulus, minchan.kim, kosaki.motohiro,
rientjes, cl, akpm, liuj97
In-Reply-To: <50126B83.3050201@cn.fujitsu.com>
From: Yasuaki Ishimatsu <isimatu.yasuaki@jp.fujitsu.com>
Since applying a patch(de7f0cba96786c), release_mem_region() has been changed
as called in PAGES_PER_SECTION chunks because register_memory_resource() is
called in PAGES_PER_SECTION chunks by add_memory(). But it seems firmware
dependency. If CRS are written in the PAGES_PER_SECTION chunks in ACPI DSDT
Table, register_memory_resource() is called in PAGES_PER_SECTION chunks.
But if CRS are written in the DIMM unit in ACPI DSDT Table,
register_memory_resource() is called in DIMM unit. So release_mem_region()
should not be called in PAGES_PER_SECTION chunks. The patch fixes it.
CC: David Rientjes <rientjes@google.com>
CC: Jiang Liu <liuj97@gmail.com>
CC: Len Brown <len.brown@intel.com>
CC: Benjamin Herrenschmidt <benh@kernel.crashing.org>
CC: Paul Mackerras <paulus@samba.org>
CC: Christoph Lameter <cl@linux.com>
Cc: Minchan Kim <minchan.kim@gmail.com>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com>
CC: Wen Congyang <wency@cn.fujitsu.com>
Signed-off-by: Yasuaki Ishimatsu <isimatu.yasuaki@jp.fujitsu.com>
---
arch/powerpc/platforms/pseries/hotplug-memory.c | 13 +++++++++----
mm/memory_hotplug.c | 4 ++--
2 files changed, 11 insertions(+), 6 deletions(-)
diff --git a/arch/powerpc/platforms/pseries/hotplug-memory.c b/arch/powerpc/platforms/pseries/hotplug-memory.c
index 11d8e05..dc0a035 100644
--- a/arch/powerpc/platforms/pseries/hotplug-memory.c
+++ b/arch/powerpc/platforms/pseries/hotplug-memory.c
@@ -77,7 +77,8 @@ static int pseries_remove_memblock(unsigned long base, unsigned int memblock_siz
{
unsigned long start, start_pfn;
struct zone *zone;
- int ret;
+ int i, ret;
+ int sections_to_remove;
start_pfn = base >> PAGE_SHIFT;
@@ -97,9 +98,13 @@ static int pseries_remove_memblock(unsigned long base, unsigned int memblock_siz
* to sysfs "state" file and we can't remove sysfs entries
* while writing to it. So we have to defer it to here.
*/
- ret = __remove_pages(zone, start_pfn, memblock_size >> PAGE_SHIFT);
- if (ret)
- return ret;
+ sections_to_remove = (memblock_size >> PAGE_SHIFT) / PAGES_PER_SECTION;
+ for (i = 0; i < sections_to_remove; i++) {
+ unsigned long pfn = start_pfn + i * PAGES_PER_SECTION;
+ ret = __remove_pages(zone, start_pfn, PAGES_PER_SECTION);
+ if (ret)
+ return ret;
+ }
/*
* Update memory regions for memory remove
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index 5237d49..d360c5c 100644
--- a/mm/memory_hotplug.c
+++ b/mm/memory_hotplug.c
@@ -358,11 +358,11 @@ int __remove_pages(struct zone *zone, unsigned long phys_start_pfn,
BUG_ON(phys_start_pfn & ~PAGE_SECTION_MASK);
BUG_ON(nr_pages % PAGES_PER_SECTION);
+ release_mem_region(phys_start_pfn << PAGE_SHIFT, nr_pages * PAGE_SIZE);
+
sections_to_remove = nr_pages / PAGES_PER_SECTION;
for (i = 0; i < sections_to_remove; i++) {
unsigned long pfn = phys_start_pfn + i*PAGES_PER_SECTION;
- release_mem_region(pfn << PAGE_SHIFT,
- PAGES_PER_SECTION << PAGE_SHIFT);
ret = __remove_section(zone, __pfn_to_section(pfn));
if (ret)
break;
--
1.7.1
^ permalink raw reply related
* [RFC PATCH v5 10/19] memory-hotplug: add memory_block_release
From: Wen Congyang @ 2012-07-27 10:31 UTC (permalink / raw)
To: linux-mm, linux-kernel, linuxppc-dev, linux-acpi, linux-s390,
linux-sh, linux-ia64, cmetcalf
Cc: len.brown, Yasuaki ISIMATU, paulus, minchan.kim, kosaki.motohiro,
rientjes, cl, akpm, liuj97
In-Reply-To: <50126B83.3050201@cn.fujitsu.com>
From: Yasuaki Ishimatsu <isimatu.yasuaki@jp.fujitsu.com>
When calling remove_memory_block(), the function shows following message at
device_release().
Device 'memory528' does not have a release() function, it is broken and must
be fixed.
remove_memory_block() calls kfree(mem). I think it shouled be called from
device_release(). So the patch implements memory_block_release()
CC: David Rientjes <rientjes@google.com>
CC: Jiang Liu <liuj97@gmail.com>
CC: Len Brown <len.brown@intel.com>
CC: Benjamin Herrenschmidt <benh@kernel.crashing.org>
CC: Paul Mackerras <paulus@samba.org>
CC: Christoph Lameter <cl@linux.com>
Cc: Minchan Kim <minchan.kim@gmail.com>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com>
CC: Wen Congyang <wency@cn.fujitsu.com>
Signed-off-by: Yasuaki Ishimatsu <isimatu.yasuaki@jp.fujitsu.com>
---
drivers/base/memory.c | 11 ++++++++++-
1 files changed, 10 insertions(+), 1 deletions(-)
diff --git a/drivers/base/memory.c b/drivers/base/memory.c
index 038be73..1cd3ef3 100644
--- a/drivers/base/memory.c
+++ b/drivers/base/memory.c
@@ -109,6 +109,15 @@ bool is_memblk_offline(unsigned long start, unsigned long size)
}
EXPORT_SYMBOL(is_memblk_offline);
+#define to_memory_block(device) container_of(device, struct memory_block, dev)
+
+static void release_memory_block(struct device *dev)
+{
+ struct memory_block *mem = to_memory_block(dev);
+
+ kfree(mem);
+}
+
/*
* register_memory - Setup a sysfs device for a memory block
*/
@@ -119,6 +128,7 @@ int register_memory(struct memory_block *memory)
memory->dev.bus = &memory_subsys;
memory->dev.id = memory->start_section_nr / sections_per_block;
+ memory->dev.release = release_memory_block;
error = device_register(&memory->dev);
return error;
@@ -674,7 +684,6 @@ int remove_memory_block(unsigned long node_id, struct mem_section *section,
mem_remove_simple_file(mem, phys_device);
mem_remove_simple_file(mem, removable);
unregister_memory(mem);
- kfree(mem);
} else
kobject_put(&mem->dev.kobj);
--
1.7.1
^ permalink raw reply related
* [RFC PATCH v5 11/19] memory-hotplug: remove_memory calls __remove_pages
From: Wen Congyang @ 2012-07-27 10:31 UTC (permalink / raw)
To: linux-mm, linux-kernel, linuxppc-dev, linux-acpi, linux-s390,
linux-sh, linux-ia64, cmetcalf
Cc: len.brown, Yasuaki ISIMATU, paulus, minchan.kim, kosaki.motohiro,
rientjes, cl, akpm, liuj97
In-Reply-To: <50126B83.3050201@cn.fujitsu.com>
From: Yasuaki Ishimatsu <isimatu.yasuaki@jp.fujitsu.com>
The patch adds __remove_pages() to remove_memory(). Then the range of
phys_start_pfn argument and nr_pages argument in __remove_pagse() may
have different zone. So zone argument is removed from __remove_pages()
and __remove_pages() caluculates zone in each section.
When CONFIG_SPARSEMEM_VMEMMAP is defined, there is no way to remove a memmap.
So __remove_section only calls unregister_memory_section().
CC: David Rientjes <rientjes@google.com>
CC: Jiang Liu <liuj97@gmail.com>
CC: Len Brown <len.brown@intel.com>
CC: Benjamin Herrenschmidt <benh@kernel.crashing.org>
CC: Paul Mackerras <paulus@samba.org>
CC: Christoph Lameter <cl@linux.com>
Cc: Minchan Kim <minchan.kim@gmail.com>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com>
CC: Wen Congyang <wency@cn.fujitsu.com>
Signed-off-by: Yasuaki Ishimatsu <isimatu.yasuaki@jp.fujitsu.com>
---
arch/powerpc/platforms/pseries/hotplug-memory.c | 5 +----
include/linux/memory_hotplug.h | 3 +--
mm/memory_hotplug.c | 18 +++++++++++-------
3 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/arch/powerpc/platforms/pseries/hotplug-memory.c b/arch/powerpc/platforms/pseries/hotplug-memory.c
index dc0a035..cc14da4 100644
--- a/arch/powerpc/platforms/pseries/hotplug-memory.c
+++ b/arch/powerpc/platforms/pseries/hotplug-memory.c
@@ -76,7 +76,6 @@ unsigned long memory_block_size_bytes(void)
static int pseries_remove_memblock(unsigned long base, unsigned int memblock_size)
{
unsigned long start, start_pfn;
- struct zone *zone;
int i, ret;
int sections_to_remove;
@@ -87,8 +86,6 @@ static int pseries_remove_memblock(unsigned long base, unsigned int memblock_siz
return 0;
}
- zone = page_zone(pfn_to_page(start_pfn));
-
/*
* Remove section mappings and sysfs entries for the
* section of the memory we are removing.
@@ -101,7 +98,7 @@ static int pseries_remove_memblock(unsigned long base, unsigned int memblock_siz
sections_to_remove = (memblock_size >> PAGE_SHIFT) / PAGES_PER_SECTION;
for (i = 0; i < sections_to_remove; i++) {
unsigned long pfn = start_pfn + i * PAGES_PER_SECTION;
- ret = __remove_pages(zone, start_pfn, PAGES_PER_SECTION);
+ ret = __remove_pages(start_pfn, PAGES_PER_SECTION);
if (ret)
return ret;
}
diff --git a/include/linux/memory_hotplug.h b/include/linux/memory_hotplug.h
index fd84ea9..8bf820d 100644
--- a/include/linux/memory_hotplug.h
+++ b/include/linux/memory_hotplug.h
@@ -90,8 +90,7 @@ extern bool is_pageblock_removable_nolock(struct page *page);
/* reasonably generic interface to expand the physical pages in a zone */
extern int __add_pages(int nid, struct zone *zone, unsigned long start_pfn,
unsigned long nr_pages);
-extern int __remove_pages(struct zone *zone, unsigned long start_pfn,
- unsigned long nr_pages);
+extern int __remove_pages(unsigned long start_pfn, unsigned long nr_pages);
#ifdef CONFIG_NUMA
extern int memory_add_physaddr_to_nid(u64 start);
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index d360c5c..a9e1579 100644
--- a/mm/memory_hotplug.c
+++ b/mm/memory_hotplug.c
@@ -275,11 +275,14 @@ static int __meminit __add_section(int nid, struct zone *zone,
#ifdef CONFIG_SPARSEMEM_VMEMMAP
static int __remove_section(struct zone *zone, struct mem_section *ms)
{
- /*
- * XXX: Freeing memmap with vmemmap is not implement yet.
- * This should be removed later.
- */
- return -EBUSY;
+ int ret = -EINVAL;
+
+ if (!valid_section(ms))
+ return ret;
+
+ ret = unregister_memory_section(ms);
+
+ return ret;
}
#else
static int __remove_section(struct zone *zone, struct mem_section *ms)
@@ -346,11 +349,11 @@ EXPORT_SYMBOL_GPL(__add_pages);
* sure that pages are marked reserved and zones are adjust properly by
* calling offline_pages().
*/
-int __remove_pages(struct zone *zone, unsigned long phys_start_pfn,
- unsigned long nr_pages)
+int __remove_pages(unsigned long phys_start_pfn, unsigned long nr_pages)
{
unsigned long i, ret = 0;
int sections_to_remove;
+ struct zone *zone;
/*
* We can only remove entire sections
@@ -363,6 +366,7 @@ int __remove_pages(struct zone *zone, unsigned long phys_start_pfn,
sections_to_remove = nr_pages / PAGES_PER_SECTION;
for (i = 0; i < sections_to_remove; i++) {
unsigned long pfn = phys_start_pfn + i*PAGES_PER_SECTION;
+ zone = page_zone(pfn_to_page(pfn));
ret = __remove_section(zone, __pfn_to_section(pfn));
if (ret)
break;
--
1.7.1
^ permalink raw reply related
* [RFC PATCH v5 12/19] memory-hotplug: introduce new function arch_remove_memory()
From: Wen Congyang @ 2012-07-27 10:32 UTC (permalink / raw)
To: linux-mm, linux-kernel, linuxppc-dev, linux-acpi, linux-s390,
linux-sh, linux-ia64, cmetcalf
Cc: len.brown, Yasuaki ISIMATU, paulus, minchan.kim, kosaki.motohiro,
rientjes, cl, akpm, liuj97
In-Reply-To: <50126B83.3050201@cn.fujitsu.com>
We don't call __add_pages() directly in the function add_memory()
because some other architecture related things need to be done
before or after calling __add_pages(). So we should introduce
a new function arch_remove_memory() to revert the things
done in arch_add_memory().
Note: the function for s390 is not implemented(I don't know how to
implement it for s390).
CC: David Rientjes <rientjes@google.com>
CC: Jiang Liu <liuj97@gmail.com>
CC: Len Brown <len.brown@intel.com>
CC: Benjamin Herrenschmidt <benh@kernel.crashing.org>
CC: Paul Mackerras <paulus@samba.org>
CC: Christoph Lameter <cl@linux.com>
Cc: Minchan Kim <minchan.kim@gmail.com>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com>
CC: Yasuaki Ishimatsu <isimatu.yasuaki@jp.fujitsu.com>
Signed-off-by: Wen Congyang <wency@cn.fujitsu.com>
---
arch/ia64/mm/init.c | 16 ++++
arch/powerpc/mm/mem.c | 14 +++
arch/s390/mm/init.c | 8 ++
arch/sh/mm/init.c | 15 +++
arch/tile/mm/init.c | 8 ++
arch/x86/include/asm/pgtable_types.h | 1 +
arch/x86/mm/init_32.c | 10 ++
arch/x86/mm/init_64.c | 160 ++++++++++++++++++++++++++++++++++
arch/x86/mm/pageattr.c | 47 +++++-----
include/linux/memory_hotplug.h | 1 +
mm/memory_hotplug.c | 1 +
11 files changed, 259 insertions(+), 22 deletions(-)
diff --git a/arch/ia64/mm/init.c b/arch/ia64/mm/init.c
index 0eab454..1e345ed 100644
--- a/arch/ia64/mm/init.c
+++ b/arch/ia64/mm/init.c
@@ -688,6 +688,22 @@ int arch_add_memory(int nid, u64 start, u64 size)
return ret;
}
+
+#ifdef CONFIG_MEMORY_HOTREMOVE
+int arch_remove_memory(u64 start, u64 size)
+{
+ unsigned long start_pfn = start >> PAGE_SHIFT;
+ unsigned long nr_pages = size >> PAGE_SHIFT;
+ int ret;
+
+ ret = __remove_pages(start_pfn, nr_pages);
+ if (ret)
+ pr_warn("%s: Problem encountered in __remove_pages() as"
+ " ret=%d\n", __func__, ret);
+
+ return ret;
+}
+#endif
#endif
/*
diff --git a/arch/powerpc/mm/mem.c b/arch/powerpc/mm/mem.c
index baaafde..249cef4 100644
--- a/arch/powerpc/mm/mem.c
+++ b/arch/powerpc/mm/mem.c
@@ -133,6 +133,20 @@ int arch_add_memory(int nid, u64 start, u64 size)
return __add_pages(nid, zone, start_pfn, nr_pages);
}
+
+#ifdef CONFIG_MEMORY_HOTREMOVE
+int arch_remove_memory(u64 start, u64 size)
+{
+ unsigned long start_pfn = start >> PAGE_SHIFT;
+ unsigned long nr_pages = size >> PAGE_SHIFT;
+
+ start = (unsigned long)__va(start);
+ if (remove_section_mapping(start, start + size))
+ return -EINVAL;
+
+ return __remove_pages(start_pfn, nr_pages);
+}
+#endif
#endif /* CONFIG_MEMORY_HOTPLUG */
/*
diff --git a/arch/s390/mm/init.c b/arch/s390/mm/init.c
index 6adbc08..ca4bc46 100644
--- a/arch/s390/mm/init.c
+++ b/arch/s390/mm/init.c
@@ -257,4 +257,12 @@ int arch_add_memory(int nid, u64 start, u64 size)
vmem_remove_mapping(start, size);
return rc;
}
+
+#ifdef CONFIG_MEMORY_HOTREMOVE
+int arch_remove_memory(u64 start, u64 size)
+{
+ /* TODO */
+ return -EBUSY;
+}
+#endif
#endif /* CONFIG_MEMORY_HOTPLUG */
diff --git a/arch/sh/mm/init.c b/arch/sh/mm/init.c
index 82cc576..fc84491 100644
--- a/arch/sh/mm/init.c
+++ b/arch/sh/mm/init.c
@@ -558,4 +558,19 @@ int memory_add_physaddr_to_nid(u64 addr)
EXPORT_SYMBOL_GPL(memory_add_physaddr_to_nid);
#endif
+#ifdef CONFIG_MEMORY_HOTREMOVE
+int arch_remove_memory(u64 start, u64 size)
+{
+ unsigned long start_pfn = start >> PAGE_SHIFT;
+ unsigned long nr_pages = size >> PAGE_SHIFT;
+ int ret;
+
+ ret = __remove_pages(start_pfn, nr_pages);
+ if (unlikely(ret))
+ pr_warn("%s: Failed, __remove_pages() == %d\n", __func__,
+ ret);
+
+ return ret;
+}
+#endif
#endif /* CONFIG_MEMORY_HOTPLUG */
diff --git a/arch/tile/mm/init.c b/arch/tile/mm/init.c
index ef29d6c..2749515 100644
--- a/arch/tile/mm/init.c
+++ b/arch/tile/mm/init.c
@@ -935,6 +935,14 @@ int remove_memory(u64 start, u64 size)
{
return -EINVAL;
}
+
+#ifdef CONFIG_MEMORY_HOTREMOVE
+int arch_remove_memory(u64 start, u64 size)
+{
+ /* TODO */
+ return -EBUSY;
+}
+#endif
#endif
struct kmem_cache *pgd_cache;
diff --git a/arch/x86/include/asm/pgtable_types.h b/arch/x86/include/asm/pgtable_types.h
index 013286a..b725af2 100644
--- a/arch/x86/include/asm/pgtable_types.h
+++ b/arch/x86/include/asm/pgtable_types.h
@@ -334,6 +334,7 @@ static inline void update_page_count(int level, unsigned long pages) { }
* as a pte too.
*/
extern pte_t *lookup_address(unsigned long address, unsigned int *level);
+extern int __split_large_page(pte_t *kpte, unsigned long address, pte_t *pbase);
#endif /* !__ASSEMBLY__ */
diff --git a/arch/x86/mm/init_32.c b/arch/x86/mm/init_32.c
index 575d86f..a690153 100644
--- a/arch/x86/mm/init_32.c
+++ b/arch/x86/mm/init_32.c
@@ -842,6 +842,16 @@ int arch_add_memory(int nid, u64 start, u64 size)
return __add_pages(nid, zone, start_pfn, nr_pages);
}
+
+#ifdef CONFIG_MEMORY_HOTREMOVE
+int arch_remove_memory(unsigned long start, unsigned long size)
+{
+ unsigned long start_pfn = start >> PAGE_SHIFT;
+ unsigned long nr_pages = size >> PAGE_SHIFT;
+
+ return __remove_pages(start_pfn, nr_pages);
+}
+#endif
#endif
/*
diff --git a/arch/x86/mm/init_64.c b/arch/x86/mm/init_64.c
index 2b6b4a3..f1554a9 100644
--- a/arch/x86/mm/init_64.c
+++ b/arch/x86/mm/init_64.c
@@ -675,6 +675,166 @@ int arch_add_memory(int nid, u64 start, u64 size)
}
EXPORT_SYMBOL_GPL(arch_add_memory);
+static void __meminit
+phys_pte_remove(pte_t *pte_page, unsigned long addr, unsigned long end)
+{
+ unsigned pages = 0;
+ int i = pte_index(addr);
+
+ pte_t *pte = pte_page + pte_index(addr);
+
+ for (; i < PTRS_PER_PTE; i++, addr += PAGE_SIZE, pte++) {
+
+ if (addr >= end)
+ break;
+
+ if (!pte_present(*pte))
+ continue;
+
+ pages++;
+ set_pte(pte, __pte(0));
+ }
+
+ update_page_count(PG_LEVEL_4K, -pages);
+}
+
+static void __meminit
+phys_pmd_remove(pmd_t *pmd_page, unsigned long addr, unsigned long end)
+{
+ unsigned long pages = 0, next;
+ int i = pmd_index(addr);
+
+ for (; i < PTRS_PER_PMD; i++, addr = next) {
+ unsigned long pte_phys;
+ pmd_t *pmd = pmd_page + pmd_index(addr);
+ pte_t *pte;
+
+ if (addr >= end)
+ break;
+
+ next = (addr & PMD_MASK) + PMD_SIZE;
+
+ if (!pmd_present(*pmd))
+ continue;
+
+ if (pmd_large(*pmd)) {
+ if ((addr & ~PMD_MASK) == 0 && next <= end) {
+ set_pmd(pmd, __pmd(0));
+ pages++;
+ continue;
+ }
+
+ /*
+ * We use 2M page, but we need to remove part of them,
+ * so split 2M page to 4K page.
+ */
+ pte = alloc_low_page(&pte_phys);
+ __split_large_page((pte_t *)pmd, addr, pte);
+
+ spin_lock(&init_mm.page_table_lock);
+ pmd_populate_kernel(&init_mm, pmd, __va(pte_phys));
+ spin_unlock(&init_mm.page_table_lock);
+ }
+
+ spin_lock(&init_mm.page_table_lock);
+ pte = map_low_page((pte_t *)pmd_page_vaddr(*pmd));
+ phys_pte_remove(pte, addr, end);
+ unmap_low_page(pte);
+ spin_unlock(&init_mm.page_table_lock);
+ }
+ update_page_count(PG_LEVEL_2M, -pages);
+}
+
+static void __meminit
+phys_pud_remove(pud_t *pud_page, unsigned long addr, unsigned long end)
+{
+ unsigned long pages = 0, next;
+ int i = pud_index(addr);
+
+ for (; i < PTRS_PER_PUD; i++, addr = next) {
+ unsigned long pmd_phys;
+ pud_t *pud = pud_page + pud_index(addr);
+ pmd_t *pmd;
+
+ if (addr >= end)
+ break;
+
+ next = (addr & PUD_MASK) + PUD_SIZE;
+
+ if (!pud_present(*pud))
+ continue;
+
+ if (pud_large(*pud)) {
+ if ((addr & ~PUD_MASK) == 0 && next <= end) {
+ set_pud(pud, __pud(0));
+ pages++;
+ continue;
+ }
+
+ /*
+ * We use 1G page, but we need to remove part of them,
+ * so split 1G page to 2M page.
+ */
+ pmd = alloc_low_page(&pmd_phys);
+ __split_large_page((pte_t *)pud, addr, (pte_t *)pmd);
+
+ spin_lock(&init_mm.page_table_lock);
+ pud_populate(&init_mm, pud, __va(pmd_phys));
+ spin_unlock(&init_mm.page_table_lock);
+ }
+
+ pmd = map_low_page(pmd_offset(pud, 0));
+ phys_pmd_remove(pmd, addr, end);
+ unmap_low_page(pmd);
+ __flush_tlb_all();
+ }
+ __flush_tlb_all();
+
+ update_page_count(PG_LEVEL_1G, -pages);
+}
+
+void __meminit
+kernel_physical_mapping_remove(unsigned long start, unsigned long end)
+{
+ unsigned long next;
+
+ start = (unsigned long)__va(start);
+ end = (unsigned long)__va(end);
+
+ for (; start < end; start = next) {
+ pgd_t *pgd = pgd_offset_k(start);
+ pud_t *pud;
+
+ next = (start + PGDIR_SIZE) & PGDIR_MASK;
+ if (next > end)
+ next = end;
+
+ if (!pgd_present(*pgd))
+ continue;
+
+ pud = map_low_page((pud_t *)pgd_page_vaddr(*pgd));
+ phys_pud_remove(pud, __pa(start), __pa(end));
+ unmap_low_page(pud);
+ }
+
+ __flush_tlb_all();
+}
+
+#ifdef CONFIG_MEMORY_HOTREMOVE
+int __ref arch_remove_memory(unsigned long start, unsigned long size)
+{
+ unsigned long start_pfn = start >> PAGE_SHIFT;
+ unsigned long nr_pages = size >> PAGE_SHIFT;
+ int ret;
+
+ ret = __remove_pages(start_pfn, nr_pages);
+ WARN_ON_ONCE(ret);
+
+ kernel_physical_mapping_remove(start, start + size);
+
+ return ret;
+}
+#endif
#endif /* CONFIG_MEMORY_HOTPLUG */
static struct kcore_list kcore_vsyscall;
diff --git a/arch/x86/mm/pageattr.c b/arch/x86/mm/pageattr.c
index 931930a..c22963d 100644
--- a/arch/x86/mm/pageattr.c
+++ b/arch/x86/mm/pageattr.c
@@ -501,21 +501,13 @@ out_unlock:
return do_split;
}
-static int split_large_page(pte_t *kpte, unsigned long address)
+int __split_large_page(pte_t *kpte, unsigned long address, pte_t *pbase)
{
unsigned long pfn, pfninc = 1;
unsigned int i, level;
- pte_t *pbase, *tmp;
+ pte_t *tmp;
pgprot_t ref_prot;
- struct page *base;
-
- if (!debug_pagealloc)
- spin_unlock(&cpa_lock);
- base = alloc_pages(GFP_KERNEL | __GFP_NOTRACK, 0);
- if (!debug_pagealloc)
- spin_lock(&cpa_lock);
- if (!base)
- return -ENOMEM;
+ struct page *base = virt_to_page(pbase);
spin_lock(&pgd_lock);
/*
@@ -523,10 +515,11 @@ static int split_large_page(pte_t *kpte, unsigned long address)
* up for us already:
*/
tmp = lookup_address(address, &level);
- if (tmp != kpte)
- goto out_unlock;
+ if (tmp != kpte) {
+ spin_unlock(&pgd_lock);
+ return 1;
+ }
- pbase = (pte_t *)page_address(base);
paravirt_alloc_pte(&init_mm, page_to_pfn(base));
ref_prot = pte_pgprot(pte_clrhuge(*kpte));
/*
@@ -579,17 +572,27 @@ static int split_large_page(pte_t *kpte, unsigned long address)
* going on.
*/
__flush_tlb_all();
+ spin_unlock(&pgd_lock);
- base = NULL;
+ return 0;
+}
-out_unlock:
- /*
- * If we dropped out via the lookup_address check under
- * pgd_lock then stick the page back into the pool:
- */
- if (base)
+static int split_large_page(pte_t *kpte, unsigned long address)
+{
+ pte_t *pbase;
+ struct page *base;
+
+ if (!debug_pagealloc)
+ spin_unlock(&cpa_lock);
+ base = alloc_pages(GFP_KERNEL | __GFP_NOTRACK, 0);
+ if (!debug_pagealloc)
+ spin_lock(&cpa_lock);
+ if (!base)
+ return -ENOMEM;
+
+ pbase = (pte_t *)page_address(base);
+ if (__split_large_page(kpte, address, pbase))
__free_page(base);
- spin_unlock(&pgd_lock);
return 0;
}
diff --git a/include/linux/memory_hotplug.h b/include/linux/memory_hotplug.h
index 8bf820d..0d500be 100644
--- a/include/linux/memory_hotplug.h
+++ b/include/linux/memory_hotplug.h
@@ -85,6 +85,7 @@ extern void __online_page_free(struct page *page);
#ifdef CONFIG_MEMORY_HOTREMOVE
extern bool is_pageblock_removable_nolock(struct page *page);
+extern int arch_remove_memory(unsigned long start, unsigned long size);
#endif /* CONFIG_MEMORY_HOTREMOVE */
/* reasonably generic interface to expand the physical pages in a zone */
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index a9e1579..0c932e1 100644
--- a/mm/memory_hotplug.c
+++ b/mm/memory_hotplug.c
@@ -1071,6 +1071,7 @@ int __ref remove_memory(int nid, u64 start, u64 size)
/* remove memmap entry */
firmware_map_remove(start, start + size, "System RAM");
+ arch_remove_memory(start, size);
out:
unlock_memory_hotplug();
return ret;
--
1.7.1
^ permalink raw reply related
* [RFC PATCH v5 13/19] memory-hotplug: check page type in get_page_bootmem
From: Wen Congyang @ 2012-07-27 10:32 UTC (permalink / raw)
To: linux-mm, linux-kernel, linuxppc-dev, linux-acpi, linux-s390,
linux-sh, linux-ia64, cmetcalf
Cc: len.brown, Yasuaki ISIMATU, paulus, minchan.kim, kosaki.motohiro,
rientjes, cl, akpm, liuj97
In-Reply-To: <50126B83.3050201@cn.fujitsu.com>
From: Yasuaki Ishimatsu <isimatu.yasuaki@jp.fujitsu.com>
There is a possibility that get_page_bootmem() is called to the same page many
times. So when get_page_bootmem is called to the same page, the function only
increments page->_count.
CC: David Rientjes <rientjes@google.com>
CC: Jiang Liu <liuj97@gmail.com>
CC: Len Brown <len.brown@intel.com>
CC: Benjamin Herrenschmidt <benh@kernel.crashing.org>
CC: Paul Mackerras <paulus@samba.org>
CC: Christoph Lameter <cl@linux.com>
Cc: Minchan Kim <minchan.kim@gmail.com>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com>
CC: Wen Congyang <wency@cn.fujitsu.com>
Signed-off-by: Yasuaki Ishimatsu <isimatu.yasuaki@jp.fujitsu.com>
---
mm/memory_hotplug.c | 15 +++++++++++----
1 files changed, 11 insertions(+), 4 deletions(-)
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index 0c932e1..eae946b 100644
--- a/mm/memory_hotplug.c
+++ b/mm/memory_hotplug.c
@@ -95,10 +95,17 @@ static void release_memory_resource(struct resource *res)
static void get_page_bootmem(unsigned long info, struct page *page,
unsigned long type)
{
- page->lru.next = (struct list_head *) type;
- SetPagePrivate(page);
- set_page_private(page, info);
- atomic_inc(&page->_count);
+ unsigned long page_type;
+
+ page_type = (unsigned long) page->lru.next;
+ if (type < MEMORY_HOTPLUG_MIN_BOOTMEM_TYPE ||
+ type > MEMORY_HOTPLUG_MAX_BOOTMEM_TYPE){
+ page->lru.next = (struct list_head *) type;
+ SetPagePrivate(page);
+ set_page_private(page, info);
+ atomic_inc(&page->_count);
+ } else
+ atomic_inc(&page->_count);
}
/* reference to __meminit __free_pages_bootmem is valid
--
1.7.1
^ permalink raw reply related
* [RFC PATCH v5 14/19] memory-hotplug: move register_page_bootmem_info_node and put_page_bootmem for sparse-vmemmap
From: Wen Congyang @ 2012-07-27 10:33 UTC (permalink / raw)
To: linux-mm, linux-kernel, linuxppc-dev, linux-acpi, linux-s390,
linux-sh, linux-ia64, cmetcalf
Cc: len.brown, Yasuaki ISIMATU, paulus, minchan.kim, kosaki.motohiro,
rientjes, cl, akpm, liuj97
In-Reply-To: <50126B83.3050201@cn.fujitsu.com>
From: Yasuaki Ishimatsu <isimatu.yasuaki@jp.fujitsu.com>
For implementing register_page_bootmem_info_node of sparse-vmemmap,
register_page_bootmem_info_node and put_page_bootmem are moved to
memory_hotplug.c
CC: David Rientjes <rientjes@google.com>
CC: Jiang Liu <liuj97@gmail.com>
CC: Len Brown <len.brown@intel.com>
CC: Benjamin Herrenschmidt <benh@kernel.crashing.org>
CC: Paul Mackerras <paulus@samba.org>
CC: Christoph Lameter <cl@linux.com>
Cc: Minchan Kim <minchan.kim@gmail.com>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com>
CC: Wen Congyang <wency@cn.fujitsu.com>
Signed-off-by: Yasuaki Ishimatsu <isimatu.yasuaki@jp.fujitsu.com>
---
include/linux/memory_hotplug.h | 9 ---------
mm/memory_hotplug.c | 8 ++++++--
2 files changed, 6 insertions(+), 11 deletions(-)
diff --git a/include/linux/memory_hotplug.h b/include/linux/memory_hotplug.h
index 0d500be..fe50a9b 100644
--- a/include/linux/memory_hotplug.h
+++ b/include/linux/memory_hotplug.h
@@ -162,17 +162,8 @@ static inline void arch_refresh_nodedata(int nid, pg_data_t *pgdat)
#endif /* CONFIG_NUMA */
#endif /* CONFIG_HAVE_ARCH_NODEDATA_EXTENSION */
-#ifdef CONFIG_SPARSEMEM_VMEMMAP
-static inline void register_page_bootmem_info_node(struct pglist_data *pgdat)
-{
-}
-static inline void put_page_bootmem(struct page *page)
-{
-}
-#else
extern void register_page_bootmem_info_node(struct pglist_data *pgdat);
extern void put_page_bootmem(struct page *page);
-#endif
/*
* Lock for memory hotplug guarantees 1) all callbacks for memory hotplug
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index eae946b..180d555 100644
--- a/mm/memory_hotplug.c
+++ b/mm/memory_hotplug.c
@@ -91,7 +91,6 @@ static void release_memory_resource(struct resource *res)
}
#ifdef CONFIG_MEMORY_HOTPLUG_SPARSE
-#ifndef CONFIG_SPARSEMEM_VMEMMAP
static void get_page_bootmem(unsigned long info, struct page *page,
unsigned long type)
{
@@ -127,6 +126,7 @@ void __ref put_page_bootmem(struct page *page)
}
+#ifndef CONFIG_SPARSEMEM_VMEMMAP
static void register_page_bootmem_info_section(unsigned long start_pfn)
{
unsigned long *usemap, mapsize, section_nr, i;
@@ -163,6 +163,11 @@ static void register_page_bootmem_info_section(unsigned long start_pfn)
get_page_bootmem(section_nr, page, MIX_SECTION_INFO);
}
+#else
+static inline void register_page_bootmem_info_section(unsigned long start_pfn)
+{
+}
+#endif
void register_page_bootmem_info_node(struct pglist_data *pgdat)
{
@@ -198,7 +203,6 @@ void register_page_bootmem_info_node(struct pglist_data *pgdat)
register_page_bootmem_info_section(pfn);
}
-#endif /* !CONFIG_SPARSEMEM_VMEMMAP */
static void grow_zone_span(struct zone *zone, unsigned long start_pfn,
unsigned long end_pfn)
--
1.7.1
^ permalink raw reply related
* [RFC PATCH v5 15/19] memory-hotplug: implement register_page_bootmem_info_section of sparse-vmemmap
From: Wen Congyang @ 2012-07-27 10:34 UTC (permalink / raw)
To: linux-mm, linux-kernel, linuxppc-dev, linux-acpi, linux-s390,
linux-sh, linux-ia64, cmetcalf
Cc: len.brown, Yasuaki ISIMATU, paulus, minchan.kim, kosaki.motohiro,
rientjes, cl, akpm, liuj97
In-Reply-To: <50126B83.3050201@cn.fujitsu.com>
From: Yasuaki Ishimatsu <isimatu.yasuaki@jp.fujitsu.com>
For removing memmap region of sparse-vmemmap which is allocated bootmem,
memmap region of sparse-vmemmap needs to be registered by get_page_bootmem().
So the patch searches pages of virtual mapping and registers the pages by
get_page_bootmem().
CC: David Rientjes <rientjes@google.com>
CC: Jiang Liu <liuj97@gmail.com>
CC: Len Brown <len.brown@intel.com>
CC: Benjamin Herrenschmidt <benh@kernel.crashing.org>
CC: Paul Mackerras <paulus@samba.org>
CC: Christoph Lameter <cl@linux.com>
Cc: Minchan Kim <minchan.kim@gmail.com>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com>
CC: Wen Congyang <wency@cn.fujitsu.com>
Signed-off-by: Yasuaki Ishimatsu <isimatu.yasuaki@jp.fujitsu.com>
---
arch/x86/mm/init_64.c | 52 ++++++++++++++++++++++++++++++++++++++++
include/linux/memory_hotplug.h | 2 +
include/linux/mm.h | 3 +-
mm/memory_hotplug.c | 23 +++++++++++++++--
4 files changed, 76 insertions(+), 4 deletions(-)
diff --git a/arch/x86/mm/init_64.c b/arch/x86/mm/init_64.c
index f1554a9..a151145 100644
--- a/arch/x86/mm/init_64.c
+++ b/arch/x86/mm/init_64.c
@@ -1138,6 +1138,58 @@ vmemmap_populate(struct page *start_page, unsigned long size, int node)
return 0;
}
+void register_page_bootmem_memmap(unsigned long section_nr,
+ struct page *start_page, unsigned long size)
+{
+ unsigned long addr = (unsigned long)start_page;
+ unsigned long end = (unsigned long)(start_page + size);
+ unsigned long next;
+ pgd_t *pgd;
+ pud_t *pud;
+ pmd_t *pmd;
+
+ for (; addr < end; addr = next) {
+ pte_t *pte = NULL;
+
+ pgd = pgd_offset_k(addr);
+ if (pgd_none(*pgd)) {
+ next = (addr + PAGE_SIZE) & PAGE_MASK;
+ continue;
+ }
+ get_page_bootmem(section_nr, pgd_page(*pgd), MIX_SECTION_INFO);
+
+ pud = pud_offset(pgd, addr);
+ if (pud_none(*pud)) {
+ next = (addr + PAGE_SIZE) & PAGE_MASK;
+ continue;
+ }
+ get_page_bootmem(section_nr, pud_page(*pud), MIX_SECTION_INFO);
+
+ if (!cpu_has_pse) {
+ next = (addr + PAGE_SIZE) & PAGE_MASK;
+ pmd = pmd_offset(pud, addr);
+ if (pmd_none(*pmd))
+ continue;
+ get_page_bootmem(section_nr, pmd_page(*pmd),
+ MIX_SECTION_INFO);
+
+ pte = pte_offset_kernel(pmd, addr);
+ if (pte_none(*pte))
+ continue;
+ get_page_bootmem(section_nr, pte_page(*pte),
+ SECTION_INFO);
+ } else {
+ next = pmd_addr_end(addr, end);
+
+ pmd = pmd_offset(pud, addr);
+ if (pmd_none(*pmd))
+ continue;
+ get_page_bootmem(section_nr, pmd_page(*pmd),
+ SECTION_INFO);
+ }
+ }
+}
+
void __meminit vmemmap_populate_print_last(void)
{
if (p_start) {
diff --git a/include/linux/memory_hotplug.h b/include/linux/memory_hotplug.h
index fe50a9b..e79d744 100644
--- a/include/linux/memory_hotplug.h
+++ b/include/linux/memory_hotplug.h
@@ -164,6 +164,8 @@ static inline void arch_refresh_nodedata(int nid, pg_data_t *pgdat)
extern void register_page_bootmem_info_node(struct pglist_data *pgdat);
extern void put_page_bootmem(struct page *page);
+extern void get_page_bootmem(unsigned long ingo, struct page *page,
+ unsigned long type);
/*
* Lock for memory hotplug guarantees 1) all callbacks for memory hotplug
diff --git a/include/linux/mm.h b/include/linux/mm.h
index f9f279c..716f38b 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -1586,7 +1586,8 @@ int vmemmap_populate_basepages(struct page *start_page,
unsigned long pages, int node);
int vmemmap_populate(struct page *start_page, unsigned long pages, int node);
void vmemmap_populate_print_last(void);
-
+void register_page_bootmem_memmap(unsigned long section_nr, struct page *map,
+ unsigned long size);
enum mf_flags {
MF_COUNT_INCREASED = 1 << 0,
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index 180d555..adcc93d 100644
--- a/mm/memory_hotplug.c
+++ b/mm/memory_hotplug.c
@@ -91,8 +91,8 @@ static void release_memory_resource(struct resource *res)
}
#ifdef CONFIG_MEMORY_HOTPLUG_SPARSE
-static void get_page_bootmem(unsigned long info, struct page *page,
- unsigned long type)
+void get_page_bootmem(unsigned long info, struct page *page,
+ unsigned long type)
{
unsigned long page_type;
@@ -164,8 +164,25 @@ static void register_page_bootmem_info_section(unsigned long start_pfn)
}
#else
-static inline void register_page_bootmem_info_section(unsigned long start_pfn)
+static void register_page_bootmem_info_section(unsigned long start_pfn)
{
+ unsigned long mapsize, section_nr;
+ struct mem_section *ms;
+ struct page *page, *memmap;
+
+ if (!pfn_valid(start_pfn))
+ return;
+
+ section_nr = pfn_to_section_nr(start_pfn);
+ ms = __nr_to_section(section_nr);
+
+ memmap = sparse_decode_mem_map(ms->section_mem_map, section_nr);
+
+ page = virt_to_page(memmap);
+ mapsize = sizeof(struct page) * PAGES_PER_SECTION;
+ mapsize = PAGE_ALIGN(mapsize) >> PAGE_SHIFT;
+
+ register_page_bootmem_memmap(section_nr, memmap, PAGES_PER_SECTION);
}
#endif
--
1.7.1
^ permalink raw reply related
* [RFC PATCH v5 16/19] memory-hotplug: free memmap of sparse-vmemmap
From: Wen Congyang @ 2012-07-27 10:34 UTC (permalink / raw)
To: linux-mm, linux-kernel, linuxppc-dev, linux-acpi, linux-s390,
linux-sh, linux-ia64, cmetcalf
Cc: len.brown, Yasuaki ISIMATU, paulus, minchan.kim, kosaki.motohiro,
rientjes, cl, akpm, liuj97
In-Reply-To: <50126B83.3050201@cn.fujitsu.com>
From: Yasuaki Ishimatsu <isimatu.yasuaki@jp.fujitsu.com>
All pages of virtual mapping in removed memory cannot be freed, since some pages
used as PGD/PUD includes not only removed memory but also other memory. So the
patch checks whether page can be freed or not.
How to check whether page can be freed or not?
1. When removing memory, the page structs of the revmoved memory are filled
with 0FD.
2. All page structs are filled with 0xFD on PT/PMD, PT/PMD can be cleared.
In this case, the page used as PT/PMD can be freed.
Applying patch, __remove_section() of CONFIG_SPARSEMEM_VMEMMAP is integrated
into one. So __remove_section() of CONFIG_SPARSEMEM_VMEMMAP is deleted.
CC: David Rientjes <rientjes@google.com>
CC: Jiang Liu <liuj97@gmail.com>
CC: Len Brown <len.brown@intel.com>
CC: Benjamin Herrenschmidt <benh@kernel.crashing.org>
CC: Paul Mackerras <paulus@samba.org>
CC: Christoph Lameter <cl@linux.com>
Cc: Minchan Kim <minchan.kim@gmail.com>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com>
CC: Wen Congyang <wency@cn.fujitsu.com>
Signed-off-by: Yasuaki Ishimatsu <isimatu.yasuaki@jp.fujitsu.com>
---
arch/x86/mm/init_64.c | 121 +++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/mm.h | 2 +
mm/memory_hotplug.c | 17 +------
mm/sparse.c | 5 +-
4 files changed, 128 insertions(+), 17 deletions(-)
diff --git a/arch/x86/mm/init_64.c b/arch/x86/mm/init_64.c
index a151145..ef83955 100644
--- a/arch/x86/mm/init_64.c
+++ b/arch/x86/mm/init_64.c
@@ -1138,6 +1138,127 @@ vmemmap_populate(struct page *start_page, unsigned long size, int node)
return 0;
}
+#define PAGE_INUSE 0xFD
+
+unsigned long find_and_clear_pte_page(unsigned long addr, unsigned long end,
+ struct page **pp, int *page_size)
+{
+ pgd_t *pgd;
+ pud_t *pud;
+ pmd_t *pmd;
+ pte_t *pte;
+ void *page_addr;
+ unsigned long next;
+
+ *pp = NULL;
+
+ pgd = pgd_offset_k(addr);
+ if (pgd_none(*pgd))
+ return pgd_addr_end(addr, end);
+
+ pud = pud_offset(pgd, addr);
+ if (pud_none(*pud))
+ return pud_addr_end(addr, end);
+
+ if (!cpu_has_pse) {
+ next = (addr + PAGE_SIZE) & PAGE_MASK;
+ pmd = pmd_offset(pud, addr);
+ if (pmd_none(*pmd))
+ return next;
+
+ pte = pte_offset_kernel(pmd, addr);
+ if (pte_none(*pte))
+ return next;
+
+ *page_size = PAGE_SIZE;
+ *pp = pte_page(*pte);
+ } else {
+ next = pmd_addr_end(addr, end);
+
+ pmd = pmd_offset(pud, addr);
+ if (pmd_none(*pmd))
+ return next;
+
+ *page_size = PMD_SIZE;
+ *pp = pmd_page(*pmd);
+ }
+
+ /*
+ * Removed page structs are filled with 0xFD.
+ */
+ memset((void *)addr, PAGE_INUSE, next - addr);
+
+ page_addr = page_address(*pp);
+
+ /*
+ * Check the page is filled with 0xFD or not.
+ * memchr_inv() returns the address. In this case, we cannot
+ * clear PTE/PUD entry, since the page is used by other.
+ * So we cannot also free the page.
+ *
+ * memchr_inv() returns NULL. In this case, we can clear
+ * PTE/PUD entry, since the page is not used by other.
+ * So we can also free the page.
+ */
+ if (memchr_inv(page_addr, PAGE_INUSE, *page_size)) {
+ *pp = NULL;
+ return next;
+ }
+
+ if (!cpu_has_pse)
+ pte_clear(&init_mm, addr, pte);
+ else
+ pmd_clear(pmd);
+
+ return next;
+}
+
+void vmemmap_kfree(struct page *memmap, unsigned long nr_pages)
+{
+ unsigned long addr = (unsigned long)memmap;
+ unsigned long end = (unsigned long)(memmap + nr_pages);
+ unsigned long next;
+ struct page *page;
+ int page_size;
+
+ for (; addr < end; addr = next) {
+ page = NULL;
+ page_size = 0;
+ next = find_and_clear_pte_page(addr, end, &page, &page_size);
+ if (!page)
+ continue;
+
+ free_pages((unsigned long)page_address(page),
+ get_order(page_size));
+ __flush_tlb_one(addr);
+ }
+
+}
+
+void vmemmap_free_bootmem(struct page *memmap, unsigned long nr_pages)
+{
+ unsigned long addr = (unsigned long)memmap;
+ unsigned long end = (unsigned long)(memmap + nr_pages);
+ unsigned long next;
+ struct page *page;
+ int page_size;
+ unsigned long magic;
+
+ for (; addr < end; addr = next) {
+ page = NULL;
+ page_size = 0;
+ next = find_and_clear_pte_page(addr, end, &page, &page_size);
+ if (!page)
+ continue;
+
+ magic = (unsigned long) page->lru.next;
+ if (magic == SECTION_INFO)
+ put_page_bootmem(page);
+ flush_tlb_kernel_range(addr, end);
+ }
+
+}
+
void register_page_bootmem_memmap(unsigned long section_nr,
struct page *start_page, unsigned long size)
{
diff --git a/include/linux/mm.h b/include/linux/mm.h
index 716f38b..a5f23a9 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -1588,6 +1588,8 @@ int vmemmap_populate(struct page *start_page, unsigned long pages, int node);
void vmemmap_populate_print_last(void);
void register_page_bootmem_memmap(unsigned long section_nr, struct page *map,
unsigned long size);
+void vmemmap_kfree(struct page *memmpa, unsigned long nr_pages);
+void vmemmap_free_bootmem(struct page *memmpa, unsigned long nr_pages);
enum mf_flags {
MF_COUNT_INCREASED = 1 << 0,
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index adcc93d..859425c 100644
--- a/mm/memory_hotplug.c
+++ b/mm/memory_hotplug.c
@@ -300,19 +300,6 @@ static int __meminit __add_section(int nid, struct zone *zone,
return register_new_memory(nid, __pfn_to_section(phys_start_pfn));
}
-#ifdef CONFIG_SPARSEMEM_VMEMMAP
-static int __remove_section(struct zone *zone, struct mem_section *ms)
-{
- int ret = -EINVAL;
-
- if (!valid_section(ms))
- return ret;
-
- ret = unregister_memory_section(ms);
-
- return ret;
-}
-#else
static int __remove_section(struct zone *zone, struct mem_section *ms)
{
unsigned long flags;
@@ -329,9 +316,9 @@ static int __remove_section(struct zone *zone, struct mem_section *ms)
pgdat_resize_lock(pgdat, &flags);
sparse_remove_one_section(zone, ms);
pgdat_resize_unlock(pgdat, &flags);
- return 0;
+
+ return ret;
}
-#endif
/*
* Reasonably generic function for adding memory. It is
diff --git a/mm/sparse.c b/mm/sparse.c
index c7bb952..3b212a1 100644
--- a/mm/sparse.c
+++ b/mm/sparse.c
@@ -622,12 +622,13 @@ static inline struct page *kmalloc_section_memmap(unsigned long pnum, int nid,
/* This will make the necessary allocations eventually. */
return sparse_mem_map_populate(pnum, nid);
}
-static void __kfree_section_memmap(struct page *memmap, unsigned long nr_pages)
+static void __kfree_section_memmap(struct page *page, unsigned long nr_pages)
{
- return; /* XXX: Not implemented yet */
+ vmemmap_kfree(page, nr_pages);
}
static void free_map_bootmem(struct page *page, unsigned long nr_pages)
{
+ vmemmap_free_bootmem(page, nr_pages);
}
#else
static struct page *__kmalloc_section_memmap(unsigned long nr_pages)
--
1.7.1
^ permalink raw reply related
* [RFC PATCH v5 17/19] memory_hotplug: clear zone when the memory is removed
From: Wen Congyang @ 2012-07-27 10:35 UTC (permalink / raw)
To: linux-mm, linux-kernel, linuxppc-dev, linux-acpi, linux-s390,
linux-sh, linux-ia64, cmetcalf
Cc: len.brown, Yasuaki ISIMATU, paulus, minchan.kim, kosaki.motohiro,
rientjes, cl, akpm, liuj97
In-Reply-To: <50126B83.3050201@cn.fujitsu.com>
From: Yasuaki Ishimatsu <isimatu.yasuaki@jp.fujitsu.com>
When a memory is added, we update zone's and pgdat's start_pfn and spanned_pages
in the function __add_zone(). So we should revert these when the memory is
removed. Add a new function __remove_zone() to do this.
CC: David Rientjes <rientjes@google.com>
CC: Jiang Liu <liuj97@gmail.com>
CC: Len Brown <len.brown@intel.com>
CC: Benjamin Herrenschmidt <benh@kernel.crashing.org>
CC: Paul Mackerras <paulus@samba.org>
CC: Christoph Lameter <cl@linux.com>
Cc: Minchan Kim <minchan.kim@gmail.com>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com>
CC: Wen Congyang <wency@cn.fujitsu.com>
Signed-off-by: Yasuaki Ishimatsu <isimatu.yasuaki@jp.fujitsu.com>
---
mm/memory_hotplug.c | 181 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 181 insertions(+), 0 deletions(-)
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index 859425c..5ac035f 100644
--- a/mm/memory_hotplug.c
+++ b/mm/memory_hotplug.c
@@ -300,10 +300,187 @@ static int __meminit __add_section(int nid, struct zone *zone,
return register_new_memory(nid, __pfn_to_section(phys_start_pfn));
}
+/* find the smallest valid pfn in the range [start_pfn, end_pfn) */
+static int find_smallest_section_pfn(unsigned long start_pfn,
+ unsigned long end_pfn)
+{
+ struct mem_section *ms;
+
+ for (; start_pfn < end_pfn; start_pfn += PAGES_PER_SECTION) {
+ ms = __pfn_to_section(start_pfn);
+
+ if (unlikely(!valid_section(ms)))
+ continue;
+
+ return start_pfn;
+ }
+
+ return 0;
+}
+
+/* find the biggest valid pfn in the range [start_pfn, end_pfn). */
+static int find_biggest_section_pfn(unsigned long start_pfn,
+ unsigned long end_pfn)
+{
+ struct mem_section *ms;
+ unsigned long pfn;
+
+ /* pfn is the end pfn of a memory section. */
+ pfn = end_pfn - 1;
+ for (; pfn >= start_pfn; pfn -= PAGES_PER_SECTION) {
+ ms = __pfn_to_section(pfn);
+
+ if (unlikely(!valid_section(ms)))
+ continue;
+
+ return pfn;
+ }
+
+ return 0;
+}
+
+static void shrink_zone_span(struct zone *zone, unsigned long start_pfn,
+ unsigned long end_pfn)
+{
+ unsigned long zone_start_pfn = zone->zone_start_pfn;
+ unsigned long zone_end_pfn = zone->zone_start_pfn + zone->spanned_pages;
+ unsigned long pfn;
+ struct mem_section *ms;
+
+ zone_span_writelock(zone);
+ if (zone_start_pfn == start_pfn) {
+ /*
+ * If the section is smallest section in the zone, it need
+ * shrink zone->zone_start_pfn and zone->zone_spanned_pages.
+ * In this case, we find second smallest valid mem_section
+ * for shrinking zone.
+ */
+ pfn = find_smallest_section_pfn(end_pfn, zone_end_pfn);
+ if (pfn) {
+ zone->zone_start_pfn = pfn;
+ zone->spanned_pages = zone_end_pfn - pfn;
+ }
+ } else if (zone_end_pfn == end_pfn) {
+ /*
+ * If the section is biggest section in the zone, it need
+ * shrink zone->spanned_pages.
+ * In this case, we find second biggest valid mem_section for
+ * shrinking zone.
+ */
+ pfn = find_biggest_section_pfn(zone_start_pfn, start_pfn);
+ if (pfn)
+ zone->spanned_pages = pfn - zone_start_pfn + 1;
+ }
+
+ /*
+ * The section is not biggest or smallest mem_section in the zone, it
+ * only creates a hole in the zone. So in this case, we need not
+ * change the zone. But perhaps, the zone has only hole data. Thus
+ * it check the zone has only hole or not.
+ */
+ pfn = zone_start_pfn;
+ for (; pfn < zone_end_pfn; pfn += PAGES_PER_SECTION) {
+ ms = __pfn_to_section(pfn);
+
+ if (unlikely(!valid_section(ms)))
+ continue;
+
+ /* If the section is current section, it continues the loop */
+ if (start_pfn == pfn)
+ continue;
+
+ /* If we find valid section, we have nothing to do */
+ zone_span_writeunlock(zone);
+ return;
+ }
+
+ /* The zone has no valid section */
+ zone->zone_start_pfn = 0;
+ zone->spanned_pages = 0;
+ zone_span_writeunlock(zone);
+}
+
+static void shrink_pgdat_span(struct pglist_data *pgdat,
+ unsigned long start_pfn, unsigned long end_pfn)
+{
+ unsigned long pgdat_start_pfn = pgdat->node_start_pfn;
+ unsigned long pgdat_end_pfn =
+ pgdat->node_start_pfn + pgdat->node_spanned_pages;
+ unsigned long pfn;
+ struct mem_section *ms;
+
+ if (pgdat_start_pfn == start_pfn) {
+ /*
+ * If the section is smallest section in the pgdat, it need
+ * shrink pgdat->node_start_pfn and pgdat->node_spanned_pages.
+ * In this case, we find second smallest valid mem_section
+ * for shrinking zone.
+ */
+ pfn = find_smallest_section_pfn(end_pfn, pgdat_end_pfn);
+ if (pfn) {
+ pgdat->node_start_pfn = pfn;
+ pgdat->node_spanned_pages = pgdat_end_pfn - pfn;
+ }
+ } else if (pgdat_end_pfn == end_pfn) {
+ /*
+ * If the section is biggest section in the pgdat, it need
+ * shrink pgdat->node_spanned_pages.
+ * In this case, we find second biggest valid mem_section for
+ * shrinking zone.
+ */
+ pfn = find_biggest_section_pfn(pgdat_start_pfn, start_pfn);
+ if (pfn)
+ pgdat->node_spanned_pages = pfn - pgdat_start_pfn + 1;
+ }
+
+ /*
+ * If the section is not biggest or smallest mem_section in the pgdat,
+ * it only creates a hole in the pgdat. So in this case, we need not
+ * change the pgdat.
+ * But perhaps, the pgdat has only hole data. Thus it check the pgdat
+ * has only hole or not.
+ */
+ pfn = pgdat_start_pfn;
+ for (; pfn < pgdat_end_pfn; pfn += PAGES_PER_SECTION) {
+ ms = __pfn_to_section(pfn);
+
+ if (unlikely(!valid_section(ms)))
+ continue;
+
+ /* If the section is current section, it continues the loop */
+ if (start_pfn == pfn)
+ continue;
+
+ /* If we find valid section, we have nothing to do */
+ return;
+ }
+
+ /* The pgdat has no valid section */
+ pgdat->node_start_pfn = 0;
+ pgdat->node_spanned_pages = 0;
+}
+
+static void __remove_zone(struct zone *zone, unsigned long start_pfn)
+{
+ struct pglist_data *pgdat = zone->zone_pgdat;
+ int nr_pages = PAGES_PER_SECTION;
+ int zone_type;
+ unsigned long flags;
+
+ zone_type = zone - pgdat->node_zones;
+
+ pgdat_resize_lock(zone->zone_pgdat, &flags);
+ shrink_zone_span(zone, start_pfn, start_pfn + nr_pages);
+ shrink_pgdat_span(pgdat, start_pfn, start_pfn + nr_pages);
+ pgdat_resize_unlock(zone->zone_pgdat, &flags);
+}
+
static int __remove_section(struct zone *zone, struct mem_section *ms)
{
unsigned long flags;
struct pglist_data *pgdat = zone->zone_pgdat;
+ unsigned long start_pfn;
+ int scn_nr;
int ret = -EINVAL;
if (!valid_section(ms))
@@ -313,6 +490,10 @@ static int __remove_section(struct zone *zone, struct mem_section *ms)
if (ret)
return ret;
+ scn_nr = __section_nr(ms);
+ start_pfn = section_nr_to_pfn(scn_nr);
+ __remove_zone(zone, start_pfn);
+
pgdat_resize_lock(pgdat, &flags);
sparse_remove_one_section(zone, ms);
pgdat_resize_unlock(pgdat, &flags);
--
1.7.1
^ permalink raw reply related
* [RFC PATCH v5 18/19] memory-hotplug: add node_device_release
From: Wen Congyang @ 2012-07-27 10:35 UTC (permalink / raw)
To: linux-mm, linux-kernel, linuxppc-dev, linux-acpi, linux-s390,
linux-sh, linux-ia64, cmetcalf
Cc: len.brown, Yasuaki ISIMATU, paulus, minchan.kim, kosaki.motohiro,
rientjes, cl, akpm, liuj97
In-Reply-To: <50126B83.3050201@cn.fujitsu.com>
From: Yasuaki Ishimatsu <isimatu.yasuaki@jp.fujitsu.com>
When calling unregister_node(), the function shows following message at
device_release().
Device 'node2' does not have a release() function, it is broken and must be
fixed.
So the patch implements node_device_release()
CC: David Rientjes <rientjes@google.com>
CC: Jiang Liu <liuj97@gmail.com>
CC: Len Brown <len.brown@intel.com>
CC: Benjamin Herrenschmidt <benh@kernel.crashing.org>
CC: Paul Mackerras <paulus@samba.org>
CC: Christoph Lameter <cl@linux.com>
Cc: Minchan Kim <minchan.kim@gmail.com>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com>
Signed-off-by: Yasuaki Ishimatsu <isimatu.yasuaki@jp.fujitsu.com>
Signed-off-by: Wen Congyang <wency@cn.fujitsu.com>
---
drivers/base/node.c | 8 ++++++++
1 files changed, 8 insertions(+), 0 deletions(-)
diff --git a/drivers/base/node.c b/drivers/base/node.c
index af1a177..9bc2f57 100644
--- a/drivers/base/node.c
+++ b/drivers/base/node.c
@@ -252,6 +252,13 @@ static inline void hugetlb_register_node(struct node *node) {}
static inline void hugetlb_unregister_node(struct node *node) {}
#endif
+static void node_device_release(struct device *dev)
+{
+ struct node *node_dev = to_node(dev);
+
+ flush_work(&node_dev->node_work);
+ memset(node_dev, 0, sizeof(struct node));
+}
/*
* register_node - Setup a sysfs device for a node.
@@ -265,6 +272,7 @@ int register_node(struct node *node, int num, struct node *parent)
node->dev.id = num;
node->dev.bus = &node_subsys;
+ node->dev.release = node_device_release;
error = device_register(&node->dev);
if (!error){
--
1.7.1
^ permalink raw reply related
* [RFC PATCH v5 19/19] memory-hotplug: remove sysfs file of node
From: Wen Congyang @ 2012-07-27 10:36 UTC (permalink / raw)
To: linux-mm, linux-kernel, linuxppc-dev, linux-acpi, linux-s390,
linux-sh, linux-ia64, cmetcalf
Cc: len.brown, Yasuaki ISIMATU, paulus, minchan.kim, kosaki.motohiro,
rientjes, cl, akpm, liuj97
In-Reply-To: <50126B83.3050201@cn.fujitsu.com>
From: Yasuaki Ishimatsu <isimatu.yasuaki@jp.fujitsu.com>
The patch adds node_set_offline() and unregister_one_node() to remove_memory()
for removing sysfs file of node.
CC: David Rientjes <rientjes@google.com>
CC: Jiang Liu <liuj97@gmail.com>
CC: Len Brown <len.brown@intel.com>
CC: Benjamin Herrenschmidt <benh@kernel.crashing.org>
CC: Paul Mackerras <paulus@samba.org>
CC: Christoph Lameter <cl@linux.com>
Cc: Minchan Kim <minchan.kim@gmail.com>
CC: Andrew Morton <akpm@linux-foundation.org>
CC: KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com>
CC: Wen Congyang <wency@cn.fujitsu.com>
Signed-off-by: Yasuaki Ishimatsu <isimatu.yasuaki@jp.fujitsu.com>
---
mm/memory_hotplug.c | 5 +++++
1 files changed, 5 insertions(+), 0 deletions(-)
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index 5ac035f..5681968 100644
--- a/mm/memory_hotplug.c
+++ b/mm/memory_hotplug.c
@@ -1267,6 +1267,11 @@ int __ref remove_memory(int nid, u64 start, u64 size)
/* remove memmap entry */
firmware_map_remove(start, start + size, "System RAM");
+ if (!node_present_pages(nid)) {
+ node_set_offline(nid);
+ unregister_one_node(nid);
+ }
+
arch_remove_memory(start, size);
out:
unlock_memory_hotplug();
--
1.7.1
^ permalink raw reply related
* Re: [RFC PATCH v5 00/19] memory-hotplug: hot-remove physical memory
From: Yasuaki Ishimatsu @ 2012-07-27 10:35 UTC (permalink / raw)
To: Wen Congyang
Cc: linux-s390, linux-ia64, linux-acpi, len.brown, linux-sh,
linux-kernel, cmetcalf, linux-mm, paulus, minchan.kim,
kosaki.motohiro, rientjes, cl, linuxppc-dev, akpm, liuj97
In-Reply-To: <50126B83.3050201@cn.fujitsu.com>
Hi Wen,
2012/07/27 19:20, Wen Congyang wrote:
> This patch series aims to support physical memory hot-remove.
>
> The patches can free/remove following things:
>
> - acpi_memory_info : [RFC PATCH 4/19]
> - /sys/firmware/memmap/X/{end, start, type} : [RFC PATCH 8/19]
> - iomem_resource : [RFC PATCH 9/19]
> - mem_section and related sysfs files : [RFC PATCH 10-11, 13-16/19]
> - page table of removed memory : [RFC PATCH 12/19]
> - node and related sysfs files : [RFC PATCH 18-19/19]
>
> If you find lack of function for physical memory hot-remove, please let me
> know.
>
> change log of v5:
> * merge the patchset to clear page table and the patchset to hot remove
> memory(from ishimatsu) to one big patchset.
Thank you for merging patches. I'll review next Monday.
Thanks,
Yasuaki Ishimatsu
> [RFC PATCH v5 1/19]
> * rename remove_memory() to offline_memory()/offline_pages()
>
> [RFC PATCH v5 2/19]
> * new patch: implement offline_memory(). This function offlines pages,
> update memory block's state, and notify the userspace that the memory
> block's state is changed.
>
> [RFC PATCH v5 4/19]
> * offline and remove memory in acpi_memory_disable_device() too.
>
> [RFC PATCH v5 17/19]
> * new patch: add a new function __remove_zone() to revert the things done
> in the function __add_zone().
>
> [RFC PATCH v5 18/19]
> * flush work befor reseting node device.
>
> change log of v4:
> * remove "memory-hotplug : unify argument of firmware_map_add_early/hotplug"
> from the patch series, since the patch is a bugfix. It is being disccussed
> on other thread. But for testing the patch series, the patch is needed.
> So I added the patch as [PATCH 0/13].
>
> [RFC PATCH v4 2/13]
> * check memory is online or not at remove_memory()
> * add memory_add_physaddr_to_nid() to acpi_memory_device_remove() for
> getting node id
>
> [RFC PATCH v4 3/13]
> * create new patch : check memory is online or not at online_pages()
>
> [RFC PATCH v4 4/13]
> * add __ref section to remove_memory()
> * call firmware_map_remove_entry() before remove_sysfs_fw_map_entry()
>
> [RFC PATCH v4 11/13]
> * rewrite register_page_bootmem_memmap() for removing page used as PT/PMD
>
> change log of v3:
> * rebase to 3.5.0-rc6
>
> [RFC PATCH v2 2/13]
> * remove extra kobject_put()
>
> * The patch was commented by Wen. Wen's comment is
> "acpi_memory_device_remove() should ignore a return value of
> remove_memory() since caller does not care the return value".
> But I did not change it since I think caller should care the
> return value. And I am trying to fix it as follow:
>
> https://lkml.org/lkml/2012/7/5/624
>
> [RFC PATCH v2 4/13]
> * remove a firmware_memmap_entry allocated by kzmalloc()
>
> change log of v2:
> [RFC PATCH v2 2/13]
> * check whether memory block is offline or not before calling offline_memory()
> * check whether section is valid or not in is_memblk_offline()
> * call kobject_put() for each memory_block in is_memblk_offline()
>
> [RFC PATCH v2 3/13]
> * unify the end argument of firmware_map_add_early/hotplug
>
> [RFC PATCH v2 4/13]
> * add release_firmware_map_entry() for freeing firmware_map_entry
>
> [RFC PATCH v2 6/13]
> * add release_memory_block() for freeing memory_block
>
> [RFC PATCH v2 11/13]
> * fix wrong arguments of free_pages()
>
>
> Wen Congyang (5):
> memory-hotplug: implement offline_memory()
> memory-hotplug: store the node id in acpi_memory_device
> memory-hotplug: export the function acpi_bus_remove()
> memory-hotplug: call acpi_bus_remove() to remove memory device
> memory-hotplug: introduce new function arch_remove_memory()
>
> Yasuaki Ishimatsu (14):
> memory-hotplug: rename remove_memory() to
> offline_memory()/offline_pages()
> memory-hotplug: offline and remove memory when removing the memory
> device
> memory-hotplug: check whether memory is present or not
> memory-hotplug: remove /sys/firmware/memmap/X sysfs
> memory-hotplug: does not release memory region in PAGES_PER_SECTION
> chunks
> memory-hotplug: add memory_block_release
> memory-hotplug: remove_memory calls __remove_pages
> memory-hotplug: check page type in get_page_bootmem
> memory-hotplug: move register_page_bootmem_info_node and
> put_page_bootmem for sparse-vmemmap
> memory-hotplug: implement register_page_bootmem_info_section of
> sparse-vmemmap
> memory-hotplug: free memmap of sparse-vmemmap
> memory_hotplug: clear zone when the memory is removed
> memory-hotplug: add node_device_release
> memory-hotplug: remove sysfs file of node
>
> arch/ia64/mm/init.c | 16 +
> arch/powerpc/mm/mem.c | 14 +
> arch/powerpc/platforms/pseries/hotplug-memory.c | 16 +-
> arch/s390/mm/init.c | 8 +
> arch/sh/mm/init.c | 15 +
> arch/tile/mm/init.c | 8 +
> arch/x86/include/asm/pgtable_types.h | 1 +
> arch/x86/mm/init_32.c | 10 +
> arch/x86/mm/init_64.c | 333 ++++++++++++++++++++++
> arch/x86/mm/pageattr.c | 47 ++--
> drivers/acpi/acpi_memhotplug.c | 51 +++-
> drivers/acpi/scan.c | 3 +-
> drivers/base/memory.c | 90 ++++++-
> drivers/base/node.c | 8 +
> drivers/firmware/memmap.c | 78 +++++-
> include/acpi/acpi_bus.h | 1 +
> include/linux/firmware-map.h | 6 +
> include/linux/memory.h | 5 +
> include/linux/memory_hotplug.h | 25 +-
> include/linux/mm.h | 5 +-
> include/linux/mmzone.h | 19 ++
> mm/memory_hotplug.c | 337 +++++++++++++++++++++--
> mm/sparse.c | 5 +-
> 23 files changed, 1010 insertions(+), 91 deletions(-)
>
^ permalink raw reply
* Re: [RFC PATCH v5 19/19] memory-hotplug: remove sysfs file of node
From: Yasuaki Ishimatsu @ 2012-07-27 10:45 UTC (permalink / raw)
To: Wen Congyang
Cc: linux-s390, linux-ia64, linux-acpi, len.brown, linux-sh,
linux-kernel, cmetcalf, linux-mm, paulus, minchan.kim,
kosaki.motohiro, rientjes, cl, linuxppc-dev, akpm, liuj97
In-Reply-To: <50126F21.803@cn.fujitsu.com>
Hi Wen,
2012/07/27 19:36, Wen Congyang wrote:
> From: Yasuaki Ishimatsu <isimatu.yasuaki@jp.fujitsu.com>
>
> The patch adds node_set_offline() and unregister_one_node() to remove_memory()
> for removing sysfs file of node.
>
> CC: David Rientjes <rientjes@google.com>
> CC: Jiang Liu <liuj97@gmail.com>
> CC: Len Brown <len.brown@intel.com>
> CC: Benjamin Herrenschmidt <benh@kernel.crashing.org>
> CC: Paul Mackerras <paulus@samba.org>
> CC: Christoph Lameter <cl@linux.com>
> Cc: Minchan Kim <minchan.kim@gmail.com>
> CC: Andrew Morton <akpm@linux-foundation.org>
> CC: KOSAKI Motohiro <kosaki.motohiro@jp.fujitsu.com>
> CC: Wen Congyang <wency@cn.fujitsu.com>
> Signed-off-by: Yasuaki Ishimatsu <isimatu.yasuaki@jp.fujitsu.com>
> ---
> mm/memory_hotplug.c | 5 +++++
> 1 files changed, 5 insertions(+), 0 deletions(-)
>
> diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
> index 5ac035f..5681968 100644
> --- a/mm/memory_hotplug.c
> +++ b/mm/memory_hotplug.c
> @@ -1267,6 +1267,11 @@ int __ref remove_memory(int nid, u64 start, u64 size)
> /* remove memmap entry */
> firmware_map_remove(start, start + size, "System RAM");
>
> + if (!node_present_pages(nid)) {
Applying [PATCH v5 17/19], pgdat->node_spanned_pages can become 0 when
all memory of the pgdat is removed. When pgdat->node_spanned_pages is 0,
it means the pgdat has no memory. So I think node_spanned_pages() is
better.
Thanks,
Yasuaki Ishimatsu
> + node_set_offline(nid);
> + unregister_one_node(nid);
> + }
> +
> arch_remove_memory(start, size);
> out:
> unlock_memory_hotplug();
>
^ permalink raw reply
* [git pull] Please pull powerpc.git merge branch
From: Kumar Gala @ 2012-07-27 12:44 UTC (permalink / raw)
To: Benjamin Herrenschmidt; +Cc: linuxppc-dev
Ben,
A few patches that missed the initial 3.6 window. These are bug fixes at
this point.
- k
The following changes since commit 574ce79cea9d3fda109ffcc82f81733de4740e5c:
powerpc/mpic: Create a revmap with enough entries for IPIs and timers (2012-07-23 14:20:42 +1000)
are available in the git repository at:
git://git.kernel.org/pub/scm/linux/kernel/git/galak/powerpc.git merge
Claudiu Manoil (1):
powerpc/85xx: Fix sram_offset parameter type
Shaohui Xie (1):
powerpc/85xx: P3041DS - change espi input-clock from 40MHz to 35MHz
Tang Yuantian (1):
powerpc/85xx: Fix pci base address error for p2020rdb-pc in dts
Timur Tabi (2):
powerpc/85xx: p1022ds: disable the NAND flash node if video is enabled
powerpc/85xx: p1022ds: fix DIU/LBC switching with NAND enabled
arch/powerpc/boot/dts/p2020rdb-pc_32b.dts | 4 +-
arch/powerpc/boot/dts/p2020rdb-pc_36b.dts | 4 +-
arch/powerpc/boot/dts/p3041ds.dts | 2 +-
arch/powerpc/platforms/85xx/p1022_ds.c | 122 +++++++++++++++++++++++-----
arch/powerpc/sysdev/fsl_85xx_cache_ctlr.h | 4 +-
arch/powerpc/sysdev/fsl_85xx_l2ctlr.c | 39 ++++------
6 files changed, 121 insertions(+), 54 deletions(-)
^ permalink raw reply
* Re: [PATCH V3 1/5] powerpc/fsl-pci: Unify pci/pcie initialization code
From: Kumar Gala @ 2012-07-27 12:47 UTC (permalink / raw)
To: Jia Hongtao-B38951
Cc: Wood Scott-B07421, linuxppc-dev@lists.ozlabs.org, Li Yang-R58472
In-Reply-To: <412C8208B4A0464FA894C5F0C278CD5D01A2883B@039-SN1MPN1-002.039d.mgd.msft.net>
On Jul 27, 2012, at 3:35 AM, Jia Hongtao-B38951 wrote:
>=20
>=20
>> -----Original Message-----
>> From: Kumar Gala [mailto:galak@kernel.crashing.org]
>> Sent: Friday, July 27, 2012 2:15 AM
>> To: Jia Hongtao-B38951
>> Cc: linuxppc-dev@lists.ozlabs.org; Wood Scott-B07421; Li Yang-R58472
>> Subject: Re: [PATCH V3 1/5] powerpc/fsl-pci: Unify pci/pcie
>> initialization code
>>=20
>>=20
>> On Jul 26, 2012, at 7:30 AM, Jia Hongtao wrote:
>>=20
>>> We unified the Freescale pci/pcie initialization by changing the
>> fsl_pci
>>> to a platform driver. In previous PCI code architecture the
>> initialization
>>> routine is called at board_setup_arch stage. Now the initialization =
is
>> done
>>> in probe function which is architectural better. Also It's =
convenient
>> for
>>> adding PM support for PCI controller in later patch.
>>>=20
>>> One issue introduced by this architecture is the timing of =
swiotlb_init.
>>> During PCI initialization the need of swiotlb is determined and this
>> should
>>> be done before swiotlb_init. So a new function to determine swiotlb =
by
>>> parsing pci ranges is made. This function is called at =
board_setup_arch
>>> stage which is earlier than swiotlb_init.
>>>=20
>>> Signed-off-by: Jia Hongtao <B38951@freescale.com>
>>> Signed-off-by: Li Yang <leoli@freescale.com>
>>> ---
>>> Changed for V3:
>>> - Rebase the patch set on the latest tree
>>> - merge PCI unify and swiotlb patch into one
>>>=20
>>> arch/powerpc/sysdev/fsl_pci.c | 155 =
++++++++++++++++++++++++++++++++--
>> -------
>>> arch/powerpc/sysdev/fsl_pci.h | 9 +--
>>> 2 files changed, 125 insertions(+), 39 deletions(-)
>>=20
>> I'd like the SWIOTLB refactoring as a separate patch. Additionally, =
the
>> order of patches should be as follows:
>>=20
>> 1. refactor PCI node parsing code
>> 2. add pci_determine_swiotlb (should rename to =
fsl_pci_determine_swiotlb)
>> 3. Determine primary bus by looking for ISA node
>> 4. convert all boards over to fsl_pci_init
>> 5. convert fsl pci to platform driver (edac and other fixes should be
>> merged in here)
>> 6. PM support
>>=20
>> - k
>=20
> Should I convert all boards over to fsl_pci_init first and then =
convert them
> over to platform driver again or just convert them direct to platform =
driver?
Yes do the fsl_pci_init conversion first. The reason is we should NOT =
break functionality from one patch to another.
- k=
^ permalink raw reply
* RE: [PATCH 0/4] powerpc/booke: Modifications to fsl booke cpu setup code
From: Sethi Varun-B16395 @ 2012-07-27 12:57 UTC (permalink / raw)
To: galak@kernel.crashing.org
Cc: linuxppc-dev@lists.ozlabs.org, agraf@suse.de,
kvm-ppc@vger.kernel.org
In-Reply-To: <1341838357-16887-1-git-send-email-Varun.Sethi@freescale.com>
Hi Kumar,
Can you please review this patch set.
Regards
Varun
> -----Original Message-----
> From: Sethi Varun-B16395
> Sent: Monday, July 09, 2012 6:23 PM
> To: agraf@suse.de; galak@kernel.crashing.org; benh@kernel.crashing.org;
> linuxppc-dev@lists.ozlabs.org; kvm-ppc@vger.kernel.org
> Cc: Sethi Varun-B16395
> Subject: [PATCH 0/4] powerpc/booke: Modifications to fsl booke cpu setup
> code
>=20
> Modifications are aimed specifically at the e500mc/e5500 cpu setup code.
> Following modifications are introduced by this patchset:
> 1. Move the E.HV check to the cpu setup code. Based on this check we
> manipulte the
> CPU_FTR_EMB_HV flag (added as a part of e500mc KVM support) in the
> cpu_spec
> structure. We determine the presence of this feature based on the
> MMUCFG[LPIDSIZE]
> check and decide if we can setup the E.HV mode ivors.
> 2. Merge the 32 bit cpu setup code for e500mc/e5500 and define the
> "cpu_restore"
> routine (for e5500/e6500) only for the 64 bit case. The cpu_restore
> routine
> is used in the 64 bit case for setting up the secondary cores.
> 3. For the 64 bit case separate out e5500 cpu_setup and cpu_restore
> functions.
> The cpu_setup function (for the primary core) is passed the cpu_spec
> pointer,
> which is not there in case of the cpu_restore function. Also, in our
> case
> we will have to manipulate the CPU_FTR_EMB_HV flag on the the primary
> core.
> 4. Added CPU_FTR_EMB_HV check for e5500.
>=20
> Varun Sethi (4):
> Separate out E.HV check and ivor setup code.
> Merge the PPC 32 e5500 setup code with e500mc setup and don't define
> a restore routine for PPC 32.
> Separate out restore_e5500/setup_e5500 routines and check for E.HV
> mode before setting ivor setting code.
> Add CPU_FTR_EMB_HV check for e5500.
>=20
> arch/powerpc/kernel/cpu_setup_fsl_booke.S | 74
> +++++++++++++++++++++++++----
> arch/powerpc/kernel/cputable.c | 4 ++
> arch/powerpc/kernel/exceptions-64e.S | 18 +------
> arch/powerpc/kernel/head_fsl_booke.S | 18 ++-----
> 4 files changed, 75 insertions(+), 39 deletions(-)
>=20
> --
> 1.7.4.1
^ permalink raw reply
* Re: [PATCH] powerpc/fsl: mpic timer driver
From: Kumar Gala @ 2012-07-27 13:13 UTC (permalink / raw)
To: <Dongsheng.wang@freescale.com>; +Cc: scottwood, paulus, linuxppc-dev
In-Reply-To: <1343370058-2983-1-git-send-email-Dongsheng.wang@freescale.com>
On Jul 27, 2012, at 1:20 AM, <Dongsheng.wang@freescale.com> =
<Dongsheng.wang@freescale.com> wrote:
> From: Wang Dongsheng <Dongsheng.Wang@freescale.com>
>=20
> Global timers A and B internal to the PIC. The two independent groups
> of global timer, group A and group B, are identical in their =
functionality.
> The hardware timer generates an interrupt on every timer cycle.
> e.g
> Power management can use the hardware timer to wake up the machine.
>=20
> Signed-off-by: Wang Dongsheng <Dongsheng.Wang@freescale.com>
> Signed-off-by: Li Yang <leoli@freescale.com>
How much of this is FSL specific vs openpic? OpenPIC spec's timer =
support (only a single group).
> ---
> arch/powerpc/include/asm/mpic_timer.h | 15 +
> arch/powerpc/platforms/Kconfig | 5 +
> arch/powerpc/sysdev/Makefile | 1 +
> arch/powerpc/sysdev/mpic_timer.c | 459 =
+++++++++++++++++++++++++++++++++
> 4 files changed, 480 insertions(+), 0 deletions(-)
> create mode 100644 arch/powerpc/include/asm/mpic_timer.h
> create mode 100644 arch/powerpc/sysdev/mpic_timer.c
>=20
> diff --git a/arch/powerpc/include/asm/mpic_timer.h =
b/arch/powerpc/include/asm/mpic_timer.h
> new file mode 100644
> index 0000000..01d58a2
> --- /dev/null
> +++ b/arch/powerpc/include/asm/mpic_timer.h
> @@ -0,0 +1,15 @@
> +#ifndef __MPIC_TIMER__
> +#define __MPIC_TIMER__
> +
> +#include <linux/interrupt.h>
> +#include <linux/time.h>
> +
> +struct mpic_timer *mpic_request_timer(irq_handler_t fn, void *dev,
> + const struct timeval *time);
> +
> +void mpic_start_timer(struct mpic_timer *handle);
> +
> +void mpic_stop_timer(struct mpic_timer *handle);
> +
> +void mpic_free_timer(struct mpic_timer *handle);
> +#endif
> diff --git a/arch/powerpc/platforms/Kconfig =
b/arch/powerpc/platforms/Kconfig
> index f21af8d..3466690 100644
> --- a/arch/powerpc/platforms/Kconfig
> +++ b/arch/powerpc/platforms/Kconfig
> @@ -87,6 +87,11 @@ config MPIC
> bool
> default n
>=20
> +config MPIC_TIMER
> + bool "MPIC Global Timer"
> + depends on MPIC && FSL_SOC
> + default n
> +
> config PPC_EPAPR_HV_PIC
> bool
> default n
> diff --git a/arch/powerpc/sysdev/Makefile =
b/arch/powerpc/sysdev/Makefile
> index b0aff6c..3002f28 100644
> --- a/arch/powerpc/sysdev/Makefile
> +++ b/arch/powerpc/sysdev/Makefile
> @@ -4,6 +4,7 @@ ccflags-$(CONFIG_PPC64) :=3D =
-mno-minimal-toc
>=20
> mpic-msi-obj-$(CONFIG_PCI_MSI) +=3D mpic_msi.o mpic_u3msi.o =
mpic_pasemi_msi.o
> obj-$(CONFIG_MPIC) +=3D mpic.o $(mpic-msi-obj-y)
> +obj-$(CONFIG_MPIC_TIMER) +=3D mpic_timer.o
> obj-$(CONFIG_PPC_EPAPR_HV_PIC) +=3D ehv_pic.o
> fsl-msi-obj-$(CONFIG_PCI_MSI) +=3D fsl_msi.o
> obj-$(CONFIG_PPC_MSI_BITMAP) +=3D msi_bitmap.o
> diff --git a/arch/powerpc/sysdev/mpic_timer.c =
b/arch/powerpc/sysdev/mpic_timer.c
> new file mode 100644
> index 0000000..ef0db4d
> --- /dev/null
> +++ b/arch/powerpc/sysdev/mpic_timer.c
> @@ -0,0 +1,459 @@
> +/*
> + * Copyright (c) 2012 Freescale Semiconductor, Inc. All rights =
reserved.
> + *
> + * This program is free software; you can redistribute it and/or =
modify it
> + * under the terms of the GNU General Public License version 2 as =
published
> + * by the Free Software Foundation.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/init.h>
> +#include <linux/errno.h>
> +#include <asm/io.h>
> +#include <linux/mm.h>
> +#include <linux/interrupt.h>
> +#include <linux/slab.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +
> +#include <sysdev/fsl_soc.h>
> +#include <asm/mpic_timer.h>
> +
> +
> +#define MPIC_TIMER_TCR_ROVR_OFFSET 24
> +#define MPIC_TIMER_TCR_CLKDIV_64 0x00000300
> +
> +#define MPIC_TIMER_STOP 0x80000000
> +#define MPIC_ALL_TIMER 4
> +
> +#define MAX_TIME (~0U>>1)
> +#define MAX_TIME_CASCADE (~0U)
> +
> +#define TIMER_OFFSET(num) (1 << (MPIC_ALL_TIMER - 1 - =
num))
> +#define ONE_SECOND 1000000
> +
> +struct timer_regs {
> + u32 gtccr;
> + u32 res0[3];
> + u32 gtbcr;
> + u32 res1[3];
> + u32 gtvpr;
> + u32 res2[3];
> + u32 gtdr;
> + u32 res3[3];
> +};
> +
> +struct mpic_timer {
> + void *dev;
> + struct cascade_priv *cascade_handle;
> + unsigned int num;
> + int irq;
> +};
> +
> +struct cascade_priv {
> + u32 tcr_value; /* TCR register: CASC & ROVR =
value */
> + unsigned int cascade_map; /* cascade map */
> + unsigned int timer_num; /* cascade control timer */
> +};
> +
> +struct group_priv {
> + struct timer_regs __iomem *regs;
> + struct mpic_timer timer[MPIC_ALL_TIMER];
> + struct list_head node;
> + unsigned int idle;
> + spinlock_t lock;
> + void __iomem *group_tcr;
> +};
> +
> +static struct cascade_priv cascade_timer[] =3D {
> + /* cascade timer 0 and 1 */
> + {0x1, 0xc, 0x1},
> + /* cascade timer 1 and 2 */
> + {0x2, 0x6, 0x2},
> + /* cascade timer 2 and 3 */
> + {0x4, 0x3, 0x3}
> +};
> +
> +static u32 ccbfreq;
> +static u64 max_value; /* prevent u64 overflow */
> +static LIST_HEAD(group_list);
> +
> +/* the time set by the user is converted to "ticks" */
> +static int transform_time(const struct timeval *time, int clkdiv, u64 =
*ticks)
> +{
> + u64 tmp =3D 0;
> + u64 tmp_sec =3D 0;
> + u64 tmp_ms =3D 0;
> + u64 tmp_us =3D 0;
> + u32 div =3D 0;
> +
> + if ((time->tv_sec + time->tv_usec) =3D=3D 0 ||
> + time->tv_sec < 0 || time->tv_usec < 0)
> + return -EINVAL;
> +
> + if (time->tv_usec > ONE_SECOND)
> + return -EINVAL;
> +
> + if (time->tv_sec > max_value ||
> + (time->tv_sec =3D=3D max_value && time->tv_usec =
> 0))
> + return -EINVAL;
> +
> + div =3D (1 << (clkdiv >> 8)) * 8;
> +
> + tmp_sec =3D div_u64((u64)time->tv_sec * (u64)ccbfreq, div);
> + tmp +=3D tmp_sec;
> +
> + tmp_ms =3D time->tv_usec / 1000;
> + tmp_ms =3D div_u64((u64)tmp_ms * (u64)ccbfreq, div * 1000);
> + tmp +=3D tmp_ms;
> +
> + tmp_us =3D time->tv_usec % 1000;
> + tmp_us =3D div_u64((u64)tmp_us * (u64)ccbfreq, div * 1000000);
> + tmp +=3D tmp_us;
> +
> + *ticks =3D tmp;
> +
> + return 0;
> +}
> +
> +/* detect whether there is a cascade timer available */
> +struct mpic_timer *detect_idle_cascade_timer(void)
should this be static?
> +{
> + struct group_priv *priv;
> + struct cascade_priv *casc_priv;
> + unsigned int tmp;
> + unsigned int array_size =3D ARRAY_SIZE(cascade_timer);
> + unsigned int num;
> + unsigned int i;
> +
> + list_for_each_entry(priv, &group_list, node) {
> + casc_priv =3D cascade_timer;
> +
> + for (i =3D 0; i < array_size; i++) {
> + unsigned long flags;
> +
> + spin_lock_irqsave(&priv->lock, flags);
> + tmp =3D casc_priv->cascade_map & priv->idle;
> + if (tmp =3D=3D casc_priv->cascade_map) {
> + num =3D casc_priv->timer_num;
> + priv->timer[num].cascade_handle =3D =
casc_priv;
> +
> + /* set timer busy */
> + priv->idle &=3D ~casc_priv->cascade_map;
> + spin_unlock_irqrestore(&priv->lock, =
flags);
> + return &priv->timer[num];
> + }
> + spin_unlock_irqrestore(&priv->lock, flags);
> + casc_priv++;
> + }
> + }
> +
> + return NULL;
> +}
> +
> +static int set_cascade_timer(struct group_priv *priv, u64 ticks,
> + unsigned int num)
> +{
> + struct cascade_priv *casc_priv;
> + u32 tmp;
> + u32 tmp_ticks;
> + u32 rem_ticks;
> +
> + /* set group tcr reg for cascade */
> + casc_priv =3D priv->timer[num].cascade_handle;
> + if (!casc_priv)
> + return -EINVAL;
> +
> + tmp =3D casc_priv->tcr_value |
> + (casc_priv->tcr_value << MPIC_TIMER_TCR_ROVR_OFFSET);
> + setbits32(priv->group_tcr, tmp);
> +
> + tmp_ticks =3D div_u64_rem(ticks, MAX_TIME_CASCADE, &rem_ticks);
> +
> + out_be32(&priv->regs[num].gtccr, 0);
> + out_be32(&priv->regs[num].gtbcr, tmp_ticks | MPIC_TIMER_STOP);
> +
> + out_be32(&priv->regs[num - 1].gtccr, 0);
> + out_be32(&priv->regs[num - 1].gtbcr, rem_ticks);
> +
> + return 0;
> +}
> +
> +struct mpic_timer *get_cascade_timer(u64 ticks)
> +{
should this be static?
> + struct group_priv *priv =3D NULL;
> + struct mpic_timer *allocated_timer =3D NULL;
> +
> + /* Two cascade timers: Support the maximum time */
> + const u64 max_ticks =3D (u64)MAX_TIME * (u64)MAX_TIME_CASCADE;
> + int ret;
> +
> + if (ticks > max_ticks)
> + return NULL;
> +
> + /* detect idle timer */
> + allocated_timer =3D detect_idle_cascade_timer();
> + if (!allocated_timer)
> + return NULL;
> +
> + priv =3D container_of(allocated_timer, struct group_priv,
> + timer[allocated_timer->num]);
> +
> + /* set ticks to timer */
> + ret =3D set_cascade_timer(priv, ticks, allocated_timer->num);
> + if (ret < 0)
> + return NULL;
> +
> + return allocated_timer;
> +}
> +
> +struct mpic_timer *get_timer(u64 ticks)
> +{
should this be static?
> + struct group_priv *priv;
> + unsigned int num;
> + unsigned int i;
> +
> + list_for_each_entry(priv, &group_list, node) {
> + for (i =3D 0; i < MPIC_ALL_TIMER; i++) {
> + unsigned long flags;
> +
> + /* one timer: Reverse allocation */
> + num =3D MPIC_ALL_TIMER - 1 - i;
> +
> + spin_lock_irqsave(&priv->lock, flags);
> + if (priv->idle & (1 << i)) {
> + /* set timer busy */
> + priv->idle &=3D ~(1 << i);
> + /* set ticks & stop timer */
> + out_be32(&priv->regs[num].gtbcr,
> + ticks | MPIC_TIMER_STOP);
> + out_be32(&priv->regs[num].gtccr, 0);
> +
> + spin_unlock_irqrestore(&priv->lock, =
flags);
> + priv->timer[num].cascade_handle =3D =
NULL;
> +
> + return &priv->timer[num];
> + }
> + spin_unlock_irqrestore(&priv->lock, flags);
> + }
> + }
> +
> + return NULL;
> +}
> +
> +/**
> + * mpic_request_timer - get a hardware timer
> + * @fn: interrupt handler function
> + * @dev: callback function of the data
> + * @time: time for timer
> + *
> + * This executes the "request_irq", returning NULL
> + * else "handle" on success.
> + */
> +struct mpic_timer *mpic_request_timer(irq_handler_t fn, void *dev,
> + const struct timeval *time)
> +{
> + struct mpic_timer *allocated_timer =3D NULL;
> + u64 ticks =3D 0;
> + int ret =3D 0;
> +
> + if (list_empty(&group_list))
> + return NULL;
> +
> + ret =3D transform_time(time, MPIC_TIMER_TCR_CLKDIV_64, &ticks);
> + if (ret < 0)
> + return NULL;
> +
> + if (ticks > MAX_TIME)
> + allocated_timer =3D get_cascade_timer(ticks);
> + else
> + allocated_timer =3D get_timer(ticks);
> +
> + if (!allocated_timer)
> + return NULL;
> +
> + ret =3D request_irq(allocated_timer->irq, fn, IRQF_TRIGGER_LOW,
> + "mpic-global-timer", dev);
> + if (ret)
> + return NULL;
> +
> + allocated_timer->dev =3D dev;
> +
> + return allocated_timer;
> +}
> +EXPORT_SYMBOL(mpic_request_timer);
> +
> +/**
> + * mpic_start_timer - start hardware timer
> + * @handle: the timer to be started.
> + *
> + * It will do ->fn(->dev) callback from the hardware interrupt at
> + * the ->timeval point in the future.
> + */
> +void mpic_start_timer(struct mpic_timer *handle)
> +{
> + struct group_priv *priv =3D container_of(handle, struct =
group_priv,
> + timer[handle->num]);
> +
> + clrbits32(&priv->regs[handle->num].gtbcr, MPIC_TIMER_STOP);
> +}
> +EXPORT_SYMBOL(mpic_start_timer);
> +
> +/**
> + * mpic_stop_timer - stop hardware timer
> + * @handle: the timer to be stoped
> + *
> + * The timer periodically generates an interrupt. Unless user stops =
the timer.
> + */
> +void mpic_stop_timer(struct mpic_timer *handle)
> +{
> + struct group_priv *priv =3D container_of(handle, struct =
group_priv,
> + timer[handle->num]);
> +
> + setbits32(&priv->regs[handle->num].gtbcr, MPIC_TIMER_STOP);
> +}
> +EXPORT_SYMBOL(mpic_stop_timer);
> +
> +/**
> + * mpic_free_timer - free hardware timer
> + * @handle: the timer to be removed.
> + *
> + * Free the timer.
> + *
> + * Note: can not be used in interrupt context.
> + */
> +void mpic_free_timer(struct mpic_timer *handle)
> +{
> + struct group_priv *priv =3D container_of(handle, struct =
group_priv,
> + timer[handle->num]);
> +
> + struct cascade_priv *casc_priv =3D NULL;
> + unsigned long flags;
> +
> + mpic_stop_timer(handle);
> +
> + casc_priv =3D priv->timer[handle->num].cascade_handle;
> +
> + free_irq(priv->timer[handle->num].irq, =
priv->timer[handle->num].dev);
> +
> + spin_lock_irqsave(&priv->lock, flags);
> + if (casc_priv) {
> + u32 tmp;
> + tmp =3D casc_priv->tcr_value | (casc_priv->tcr_value <<
> + MPIC_TIMER_TCR_ROVR_OFFSET);
> + clrbits32(priv->group_tcr, tmp);
> + priv->idle |=3D casc_priv->cascade_map;
> + priv->timer[handle->num].cascade_handle =3D NULL;
> + } else {
> + priv->idle |=3D 1 << (MPIC_ALL_TIMER - 1 - handle->num);
> + }
> + spin_unlock_irqrestore(&priv->lock, flags);
> +}
> +EXPORT_SYMBOL(mpic_free_timer);
> +
> +static void group_init(struct device_node *np)
> +{
> + struct group_priv *priv =3D NULL;
> + const u32 all_timer[] =3D { 0, MPIC_ALL_TIMER };
> + const u32 *p;
> + u32 offset;
> + u32 count;
> +
> + unsigned int i =3D 0;
> + unsigned int j =3D 0;
> + unsigned int irq_index =3D 0;
> + int irq =3D 0;
> + int len =3D 0;
> +
> + priv =3D kzalloc(sizeof(struct group_priv), GFP_KERNEL);
> + if (!priv) {
> + pr_err("%s: cannot allocate memory for group.\n",
> + np->full_name);
> + return;
> + }
> +
> + priv->regs =3D of_iomap(np, 0);
> + if (!priv->regs) {
> + pr_err("%s: cannot ioremap register address.\n",
> + np->full_name);
> + goto out;
> + }
> +
> + priv->group_tcr =3D of_iomap(np, 1);
> + if (!priv->group_tcr) {
> + pr_err("%s: cannot ioremap tcr address.\n", =
np->full_name);
> + goto out;
> + }
> +
> + /* Get irq numbers form dts */
> + p =3D of_get_property(np, "fsl,available-ranges", &len);
> + if (p && len % (2 * sizeof(u32)) !=3D 0) {
> + pr_err("%s: malformed fsl,available-ranges property.\n",
> + np->full_name);
> + goto out;
> + }
> +
> + if (!p) {
> + p =3D all_timer;
> + len =3D sizeof(all_timer);
> + }
> +
> + len /=3D 2 * sizeof(u32);
> +
> + for (i =3D 0; i < len; i++) {
> + offset =3D p[i * 2];
> + count =3D p[i * 2 + 1];
> + for (j =3D 0; j < count; j++) {
> + irq =3D irq_of_parse_and_map(np, irq_index);
> + if (!irq)
> + break;
> +
> + /* Set timer idle */
> + priv->idle |=3D TIMER_OFFSET((offset + j));
> + priv->timer[offset + j].irq =3D irq;
> + priv->timer[offset + j].num =3D offset + j;
> + irq_index++;
> + }
> + }
> +
> + /* Init lock */
> + spin_lock_init(&priv->lock);
> +
> + /* Init timer hardware */
> + setbits32(priv->group_tcr, MPIC_TIMER_TCR_CLKDIV_64);
> +
> + list_add_tail(&priv->node, &group_list);
> +
> + return;
> +out:
> + if (priv->group_tcr)
> + iounmap(priv->group_tcr);
> +
> + if (priv->regs)
> + iounmap(priv->regs);
> +
> + kfree(priv);
> +}
> +
> +static int __init mpic_timer_init(void)
> +{
> + struct device_node *np =3D NULL;
> +
> + ccbfreq =3D fsl_get_sys_freq();
> + if (ccbfreq =3D=3D 0) {
> + pr_err("mpic_timer: No bus frequency "
> + "in device tree.\n");
> + return -ENODEV;
> + }
> +
> + max_value =3D div_u64(ULLONG_MAX, ccbfreq);
> +
> + for_each_compatible_node(np, NULL, "fsl,mpic-global-timer")
> + group_init(np);
> +
> + if (list_empty(&group_list))
> + return -ENODEV;
> +
> + return 0;
> +}
> +arch_initcall(mpic_timer_init);
> --=20
> 1.7.5.1
>=20
>=20
> _______________________________________________
> Linuxppc-dev mailing list
> Linuxppc-dev@lists.ozlabs.org
> https://lists.ozlabs.org/listinfo/linuxppc-dev
^ permalink raw reply
* Re: [PATCH 5/6] powerpc/fsl-pci: Add pci inbound/outbound PM support
From: Kumar Gala @ 2012-07-27 13:24 UTC (permalink / raw)
To: Jia Hongtao; +Cc: B07421, linuxppc-dev
In-Reply-To: <1343125210-16720-5-git-send-email-B38951@freescale.com>
On Jul 24, 2012, at 5:20 AM, Jia Hongtao wrote:
> Power supply for PCI inbound/outbound window registers is off when =
system
> go to deep-sleep state. We save the values of registers before suspend
> and restore to registers after resume.
>=20
> Signed-off-by: Jiang Yutang <b14898@freescale.com>
> Signed-off-by: Jia Hongtao <B38951@freescale.com>
> Signed-off-by: Li Yang <leoli@freescale.com>
> ---
> arch/powerpc/include/asm/pci-bridge.h | 2 +-
> arch/powerpc/sysdev/fsl_pci.c | 121 =
+++++++++++++++++++++++++++++++++
> 2 files changed, 122 insertions(+), 1 deletions(-)
Remind me why we need to save/restore PCI ATMUs, why not just re-parse =
the device tree to restore?
- k=
^ permalink raw reply
* [2/3][PATCH][v2] TDM Framework
From: sandeep @ 2012-07-27 14:05 UTC (permalink / raw)
To: linuxppc-dev, linux-arm-kernel; +Cc: devel, Sandeep Singh, linux-kernel
From: Sandeep Singh <Sandeep@freescale.com>
TDM Framework is an attempt to provide a platform independent layer which can
offer a standard interface for TDM access to different client modules.
Beneath, the framework layer can house different types of TDM drivers to handle
various TDM devices, the hardware intricacies of the devices being completely
taken care by TDM drivers.
This framework layer will allow any type of TDM device to hook with it.
For example Freescale controller as on MPC8315, UCC based TDM controller, etc
The main functions of this Framework are:
- provides interface to TDM clients to access TDM functionalities.
- provides standard interface for TDM drivers to hook with the framework.
- handles various data handling stuff and buffer management.
In future this Framework will be extended to provide Interface for Line control devices also. For example SLIC, E1/T1 Framers etc.
Presently the framework supports only Single Port channelised mode.
Also the configurability options are limited which will be extended later on.
Only kernel mode TDM clients are supported currently. Support for User mode clients will be added later.
Signed-off-by: Sandeep Singh <Sandeep@freescale.com>
Signed-off-by: Poonam Aggrwal <poonam.aggrwal@freescale.com>
Signed-off-by: Hemant Agrawal <hemant@freescale.com>
Signed-off-by: Rajesh Gumasta <rajesh.gumasta@freescale.com>
---
Based on: git://git.am.freescale.net/gitolite/mirrors/galak-powerpc.git
Branch: master
Checkpatch: passed
Changes since v1:
Incorporated Timur's comments:
- Removed unnecessary includes
- Comment format and error prints are more homogenous now.
- Removed unnecessary declarations.
- Corrected return code value
- Called missing module_put()
Changes since RFC:
- Since all read/write operations are in TDM are channel based, polling
on TDM channel for data instead of TDM port before going for read/write.
- Also corrected a faulty error leg
- Removed unused function tdm_port_get_wait_queue.
Incorporated Scott's comments:
- Removed TDM_CORE_DEBUG.
- Added sysfs knob to change use_latest_tdm_data at runtime.
- Works more silently now (lesser prints).
- Cosmetic errors rectified.
- Removed unused function tdm_init.
- Removed unused variables.
- Removed unnecessary typecast and NULL check.
- Removed #include "device/tdm_fsl.h".
Incorporated Timur's comments:
- Handled errors.
- Used dev_err instead of pr_err
- Removed extern from function declaration.
drivers/Kconfig | 1 +
drivers/Makefile | 1 +
drivers/tdm/Kconfig | 18 +
drivers/tdm/Makefile | 5 +
drivers/tdm/tdm-core.c | 1087 +++++++++++++++++++++++++++++++++++++++
include/linux/mod_devicetable.h | 11 +
include/linux/tdm.h | 389 ++++++++++++++
7 files changed, 1512 insertions(+), 0 deletions(-)
create mode 100644 drivers/tdm/Kconfig
create mode 100644 drivers/tdm/Makefile
create mode 100644 drivers/tdm/tdm-core.c
create mode 100644 include/linux/tdm.h
diff --git a/drivers/Kconfig b/drivers/Kconfig
index 5afe5d1..abd6c83 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -136,4 +136,5 @@ source "drivers/virt/Kconfig"
source "drivers/devfreq/Kconfig"
+source "drivers/tdm/Kconfig"
endmenu
diff --git a/drivers/Makefile b/drivers/Makefile
index 1b31421..7cb88e3 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -104,6 +104,7 @@ obj-$(CONFIG_INFINIBAND) += infiniband/
obj-$(CONFIG_SGI_SN) += sn/
obj-y += firmware/
obj-$(CONFIG_CRYPTO) += crypto/
+obj-$(CONFIG_TDM) += tdm/
obj-$(CONFIG_SUPERH) += sh/
obj-$(CONFIG_ARCH_SHMOBILE) += sh/
ifndef CONFIG_ARCH_USES_GETTIMEOFFSET
diff --git a/drivers/tdm/Kconfig b/drivers/tdm/Kconfig
new file mode 100644
index 0000000..0b0fda8
--- /dev/null
+++ b/drivers/tdm/Kconfig
@@ -0,0 +1,18 @@
+#
+# TDM subsystem configuration
+#
+
+menuconfig TDM
+ tristate "TDM support"
+ ---help---
+ More information is contained in the directory <file:Documentation/tdm/>,
+ especially in the file called "summary" there.
+ If you want TDM support, you should say Y here and also to the
+ specific driver for your bus adapter(s) below.
+
+ This TDM support can also be built as a module. If so, the module
+ will be called tdm-core.
+
+if TDM
+
+endif # TDM
diff --git a/drivers/tdm/Makefile b/drivers/tdm/Makefile
new file mode 100644
index 0000000..84e2cb9
--- /dev/null
+++ b/drivers/tdm/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for the TDM core.
+#
+
+obj-$(CONFIG_TDM) += tdm-core.o
diff --git a/drivers/tdm/tdm-core.c b/drivers/tdm/tdm-core.c
new file mode 100644
index 0000000..c633190
--- /dev/null
+++ b/drivers/tdm/tdm-core.c
@@ -0,0 +1,1087 @@
+/*
+ * Copyright 2012 Freescale Semiconductor, Inc.
+ *
+ * TDM core is the interface between TDM clients and TDM devices.
+ * It is also intended to serve as an interface for line controlled
+ * devices later on.
+ *
+ * Author:Hemant Agrawal <hemant@freescale.com>
+ * Rajesh Gumasta <rajesh.gumasta@freescale.com>
+ *
+ * Note that some parts of this code may have been derived from i2c subsystem.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+#include <linux/slab.h>
+#include <linux/tdm.h>
+#include <linux/idr.h>
+
+static DEFINE_MUTEX(tdm_core_lock);
+static DEFINE_IDR(tdm_adapter_idr);
+/* List of TDM adapters registered with TDM framework */
+LIST_HEAD(adapter_list);
+
+/* List of TDM clients registered with TDM framework */
+LIST_HEAD(driver_list);
+
+/*
+ * In case the previous data is not fetched by the client driver, the
+ * de-interleaving function will discard the old data and rewrite the
+ * new data
+ */
+
+static int use_latest_tdm_data = 1;
+
+/* Data structures required for sysfs */
+static struct tdm_sysfs attr = {
+ .attr.name = "use_latest_data",
+ .attr.mode = 0664,
+ .cmd_type = TDM_LATEST_DATA,
+};
+
+static struct attribute *tdm_attr[] = {
+ &attr.attr,
+ NULL
+};
+
+const struct sysfs_ops tdm_ops = {
+ .show = tdm_show_sysfs,
+ .store = tdm_store_sysfs,
+};
+
+static struct kobj_type tdm_type = {
+ .sysfs_ops = &tdm_ops,
+ .default_attrs = tdm_attr,
+};
+
+/* tries to match client driver with the adapter */
+static int tdm_device_match(struct tdm_driver *driver, struct tdm_adapter *adap)
+{
+ /* match on an id table if there is one */
+ if (driver->id_table && driver->id_table->name[0]) {
+ if (!(strcmp(driver->id_table->name, adap->name)))
+ return (int)driver->id_table;
+ }
+ return 0;
+}
+
+static int tdm_attach_driver_adap(struct tdm_driver *driver,
+ struct tdm_adapter *adap)
+{
+ int ret = 0;
+ /* if driver is already attached to any other adapter, return*/
+ if (driver->adapter && (driver->adapter != adap))
+ return 0;
+
+ driver->adapter = adap;
+
+ if (driver->attach_adapter) {
+ ret = driver->attach_adapter(adap);
+ if (ret < 0) {
+ pr_err("tdm: attach_adapter failed for driver [%s]"
+ "err:%d\n", driver->name, ret);
+ return ret;
+ }
+ }
+ adap->drv_count++;
+
+ if (!adap->tasklet_conf) {
+ tdm_sysfs_init();
+ tasklet_init(&adap->tdm_data_tasklet, tdm_data_tasklet_fn,
+ (unsigned long)adap);
+ adap->tasklet_conf = 1;
+ }
+
+ return ret;
+}
+
+/* Detach client driver and adapter */
+static int tdm_detach_driver_adap(struct tdm_driver *driver,
+ struct tdm_adapter *adap)
+{
+ int res = 0;
+
+ if (!driver->adapter || (driver->adapter != adap))
+ return 0;
+
+ adap->drv_count--;
+
+ /* If no more driver is registed with the adapter*/
+ if (!adap->drv_count && adap->tasklet_conf) {
+ tasklet_disable(&adap->tdm_data_tasklet);
+ tasklet_kill(&adap->tdm_data_tasklet);
+ adap->tasklet_conf = 0;
+ }
+
+ if (driver->detach_adapter) {
+ if (driver->detach_adapter(adap))
+ pr_err("tdm: detach_adapter failed for driver [%s]\n",
+ driver->name);
+ }
+
+ driver->adapter = NULL;
+ return res;
+}
+
+/* TDM adapter Registration/De-registration with TDM framework */
+
+static int tdm_register_adapter(struct tdm_adapter *adap)
+{
+ int res = 0;
+ struct tdm_driver *driver, *next;
+
+ mutex_init(&adap->adap_lock);
+ INIT_LIST_HEAD(&adap->myports);
+ spin_lock_init(&adap->portlist_lock);
+
+ adap->drv_count = 0;
+ adap->tasklet_conf = 0;
+
+ list_add_tail(&adap->list, &adapter_list);
+
+ /* initialization of driver by framework in default configuration */
+ init_config_adapter(adap);
+
+ /* Notify drivers */
+ pr_info("adapter [%s] registered\n", adap->name);
+ mutex_lock(&tdm_core_lock);
+ list_for_each_entry_safe(driver, next, &driver_list, list) {
+ if (tdm_device_match(driver, adap)) {
+ res = tdm_attach_driver_adap(driver, adap);
+ if (res == 0) {
+ pr_info("tdm: Driver(ID=%d) is "
+ "attached with Adapter %s(ID"
+ " = %d)\n", driver->id,
+ adap->name, adap->id);
+ } else {
+ pr_err("tdm: Driver(ID=%d) is unable "
+ "to attach with Adapter %s(ID = %d)\n",
+ driver->id, adap->name,
+ adap->id);
+ }
+ }
+ }
+ mutex_unlock(&tdm_core_lock);
+
+ return res;
+}
+
+/*
+ * tdm_add_adapter - declare tdm adapter, use dynamic device number
+ * @adapter: the adapter to add
+ * Context: can sleep
+ *
+ * This routine is used to declare a TDM adapter
+ * When this returns zero, a new device number will be allocated and stored
+ * in adap->id, and the specified adapter became available for the clients.
+ * Otherwise, a negative error number value is returned.
+ */
+int tdm_add_adapter(struct tdm_adapter *adapter)
+{
+ int id, res = 0;
+
+retry:
+ if (idr_pre_get(&tdm_adapter_idr, GFP_KERNEL) == 0)
+ return -ENOMEM;
+
+ mutex_lock(&tdm_core_lock);
+ res = idr_get_new(&tdm_adapter_idr, adapter, &id);
+ mutex_unlock(&tdm_core_lock);
+
+ if (res < 0) {
+ if (res == -EAGAIN)
+ goto retry;
+ return res;
+ }
+
+ adapter->id = id;
+ return tdm_register_adapter(adapter);
+}
+EXPORT_SYMBOL(tdm_add_adapter);
+
+
+/*
+ * tdm_del_adapter - unregister TDM adapter
+ * @adap: the adapter being unregistered
+ *
+ * This unregisters an TDM adapter which was previously registered
+ * by @tdm_add_adapter.
+ */
+int tdm_del_adapter(struct tdm_adapter *adap)
+{
+ int res = 0;
+ struct tdm_adapter *found;
+ struct tdm_driver *driver, *next;
+
+ /* First make sure that this adapter was ever added */
+ mutex_lock(&tdm_core_lock);
+ found = idr_find(&tdm_adapter_idr, adap->id);
+ mutex_unlock(&tdm_core_lock);
+ if (found != adap) {
+ pr_err("tdm: attempting to delete unregistered "
+ "adapter [%s]\n", adap->name);
+ return -EINVAL;
+ }
+
+ /* disable and kill the data processing tasklet */
+ if (adap->tasklet_conf) {
+ tasklet_disable(&adap->tdm_data_tasklet);
+ tasklet_kill(&adap->tdm_data_tasklet);
+ adap->tasklet_conf = 0;
+ }
+
+ /*
+ * Detach any active ports. This can't fail, thus we do not
+ * checking the returned value.
+ */
+ mutex_lock(&tdm_core_lock);
+ list_for_each_entry_safe(driver, next, &driver_list, list) {
+ if (tdm_device_match(driver, adap)) {
+ tdm_detach_driver_adap(driver, adap);
+ pr_info(
+ "Driver(ID=%d) is detached from Adapter %s(ID = %d)\n",
+ driver->id, adap->name, adap->id);
+ }
+ }
+ idr_remove(&tdm_adapter_idr, adap->id);
+ mutex_unlock(&tdm_core_lock);
+
+ pr_debug("adapter [%s] unregistered\n", adap->name);
+
+ list_del(&adap->list);
+ /*
+ * Clear the device structure in case this adapter is ever going to be
+ * added again
+ */
+ adap->parent = NULL;
+
+ return res;
+}
+EXPORT_SYMBOL(tdm_del_adapter);
+
+/* TDM Client Drivers Registration/De-registration Functions */
+int tdm_register_driver(struct tdm_driver *driver)
+{
+ int res = 0;
+ struct tdm_adapter *adap, *next;
+
+ list_add_tail(&driver->list, &driver_list);
+
+ mutex_lock(&tdm_core_lock);
+ /* Walk the adapters that are already present */
+ list_for_each_entry_safe(adap, next, &adapter_list, list) {
+ if (tdm_device_match(driver, adap)) {
+ res = tdm_attach_driver_adap(driver, adap);
+ if (res == 0) {
+ pr_info("TDM Driver(ID=%d)is attached with "
+ "Adapter%s(ID = %d) drv_count=%d",
+ driver->id, adap->name,
+ adap->id, adap->drv_count);
+ } else {
+ pr_err("TDM Driver(ID=%d) unable to attach "
+ "to Adapter%s(ID = %d) drv_count=%d",
+ driver->id, adap->name,
+ adap->id, adap->drv_count);
+ }
+ break;
+ }
+ }
+ mutex_unlock(&tdm_core_lock);
+
+ return res;
+}
+EXPORT_SYMBOL(tdm_register_driver);
+
+/*
+ * tdm_unregister_driver - unregister TDM client driver from TDM framework
+ * @driver: the driver being unregistered
+ */
+void tdm_unregister_driver(struct tdm_driver *driver)
+{
+ /*
+ * A driver can register to only one adapter,
+ * so no need to browse the list
+ */
+ mutex_lock(&tdm_core_lock);
+ tdm_detach_driver_adap(driver, driver->adapter);
+ mutex_unlock(&tdm_core_lock);
+
+ list_del(&driver->list);
+
+ pr_debug("tdm-core: driver [%s] unregistered\n", driver->name);
+}
+EXPORT_SYMBOL(tdm_unregister_driver);
+
+/* Interface to the tdm device/adapter */
+
+/*
+ * tdm_adap_send - issue a TDM write
+ * @adap: Handle to TDM device
+ * @buf: Data that will be written to the TDM device
+ * @count: How many bytes to write
+ *
+ * Returns negative errno, or else the number of bytes written.
+ */
+int tdm_adap_send(struct tdm_adapter *adap, void **buf, int count)
+{
+ int res;
+
+ if (adap->algo->tdm_write)
+ res = adap->algo->tdm_write(adap, buf, count);
+ else {
+ pr_err("TDM level write not supported\n");
+ return -EOPNOTSUPP;
+ }
+
+ /*
+ * If everything went ok (i.e. frame transmitted), return #bytes
+ * transmitted, else error code.
+ */
+ return (res == 1) ? count : res;
+}
+EXPORT_SYMBOL(tdm_adap_send);
+
+/*
+ * tdm_adap_recv - issue a TDM read
+ * @adap: Handle to TDM device
+ * @buf: Where to store data read from TDM device
+ *
+ * Returns negative errno, or else the number of bytes read.
+ */
+int tdm_adap_recv(struct tdm_adapter *adap, void **buf)
+{
+ int res;
+
+ if (adap->algo->tdm_read)
+ res = adap->algo->tdm_read(adap, (u16 **)buf);
+ else {
+ pr_err("TDM level read not supported\n");
+ return -EOPNOTSUPP;
+ }
+ /*
+ * If everything went ok (i.e. frame received), return #bytes
+ * transmitted, else error code.
+ */
+ return res;
+}
+
+/*
+ * tdm_adap_get_write_buf - get next write TDM device buffer
+ * @adap: Handle to TDM device
+ * @buf: pointer to TDM device buffer
+ *
+ * Returns negative errno, or else size of the write buffer.
+ */
+int tdm_adap_get_write_buf(struct tdm_adapter *adap, void **buf)
+{
+ int res;
+
+ if (adap->algo->tdm_get_write_buf) {
+ res = adap->algo->tdm_get_write_buf(adap, (u16 **)buf);
+ } else {
+ pr_err("TDM level write buf get not supported\n");
+ return -EOPNOTSUPP;
+ }
+ /*
+ * If everything went ok (i.e. 1 msg received), return #bytes
+ * transmitted, else error code.
+ */
+ return res;
+}
+EXPORT_SYMBOL(tdm_adap_get_write_buf);
+
+int tdm_adap_enable(struct tdm_driver *drv)
+{
+ int res;
+ struct tdm_adapter *adap;
+ adap = drv->adapter;
+
+ if (adap->algo->tdm_enable) {
+ res = adap->algo->tdm_enable(adap);
+ } else {
+ pr_err("TDM level enable not supported\n");
+ return -EOPNOTSUPP;
+ }
+ return res;
+}
+EXPORT_SYMBOL(tdm_adap_enable);
+
+int tdm_adap_disable(struct tdm_driver *drv)
+{
+ int res;
+ struct tdm_adapter *adap;
+ adap = drv->adapter;
+
+ if (adap->algo->tdm_disable) {
+ res = adap->algo->tdm_disable(adap);
+ } else {
+ pr_err("TDM level enable not supported\n");
+ return -EOPNOTSUPP;
+ }
+ return res;
+}
+EXPORT_SYMBOL(tdm_adap_disable);
+
+struct tdm_adapter *tdm_get_adapter(int id)
+{
+ struct tdm_adapter *adapter;
+
+ mutex_lock(&tdm_core_lock);
+ adapter = idr_find(&tdm_adapter_idr, id);
+ if (adapter && !try_module_get(adapter->owner))
+ adapter = NULL;
+
+ mutex_unlock(&tdm_core_lock);
+
+ return adapter;
+}
+EXPORT_SYMBOL(tdm_get_adapter);
+
+void tdm_put_adapter(struct tdm_adapter *adap)
+{
+ module_put(adap->owner);
+}
+EXPORT_SYMBOL(tdm_put_adapter);
+
+
+/* Port Level APIs of TDM Framework */
+int tdm_port_open(struct tdm_driver *driver, struct tdm_port **h_port)
+{
+ struct tdm_port *port;
+ struct tdm_adapter *adap;
+ unsigned long flags;
+ int res = 0;
+
+ /*
+ * This creates an anonymous tdm_port, which may later be
+ * pointed to some slot.
+ */
+ port = kzalloc(sizeof(*port), GFP_KERNEL);
+ if (!port) {
+ res = -ENOMEM;
+ return res;
+ }
+
+ adap = tdm_get_adapter(driver->adapter->id);
+ if (!adap)
+ return -ENODEV;
+
+ port->rx_max_frames = NUM_SAMPLES_PER_FRAME;
+ port->port_cfg.port_mode = TDM_PORT_CHANNELIZED;
+
+ snprintf(driver->name, TDM_NAME_SIZE, "tdm-dev");
+ port->driver = driver;
+ port->adapter = adap;
+
+ spin_lock_irqsave(&adap->portlist_lock, flags);
+ list_add_tail(&port->list, &adap->myports);
+ spin_unlock_irqrestore(&adap->portlist_lock, flags);
+
+ INIT_LIST_HEAD(&port->mychannels);
+
+ *h_port = port;
+ return res;
+
+}
+EXPORT_SYMBOL(tdm_port_open);
+
+int tdm_port_close(struct tdm_port *h_port)
+{
+ struct tdm_adapter *adap;
+ struct tdm_driver *driver;
+ struct tdm_port *port;
+ struct tdm_channel *temp, *channel;
+ unsigned long flags;
+ int res = 0;
+ port = h_port;
+
+ driver = port->driver;
+
+ list_for_each_entry_safe(channel, temp, &port->mychannels, list) {
+ if (channel)
+ if (channel->in_use) {
+ pr_err("tdm: Cannot close port. Channel in"
+ "use\n");
+ res = -ENXIO;
+ goto out;
+ }
+ }
+ adap = driver->adapter;
+ tdm_put_adapter(adap);
+
+ spin_lock_irqsave(&adap->portlist_lock, flags);
+ list_del(&port->list);
+ spin_unlock_irqrestore(&adap->portlist_lock, flags);
+
+ if (port->p_port_data != NULL) {
+ int i;
+ struct tdm_bd *ch_bd;
+
+ /*
+ * If the tdm is in channelised mode,
+ * de-allocate the channelised buffer
+ */
+ ch_bd = &(port->p_port_data->rx_data_fifo[0]);
+ for (i = 0; ch_bd && i < TDM_CH_RX_BD_RING_SIZE; i++) {
+ ch_bd->flag = 0;
+ ch_bd++;
+ }
+ ch_bd = &(port->p_port_data->tx_data_fifo[0]);
+ for (i = 0; ch_bd && i < TDM_CH_TX_BD_RING_SIZE; i++) {
+ ch_bd->flag = 0;
+ ch_bd++;
+ }
+ kfree(port->p_port_data);
+ }
+ kfree(port);
+ return res;
+out:
+ if (port)
+ kfree(port->p_port_data);
+ kfree(port);
+ return res;
+}
+EXPORT_SYMBOL(tdm_port_close);
+
+int tdm_channel_read(struct tdm_port *h_port, struct tdm_channel *h_channel,
+ void *p_data, u16 *size)
+{
+ struct tdm_channel *channel;
+ struct tdm_bd *rx_bd;
+ unsigned long flags;
+ int res = 0;
+ unsigned short *buf, *buf1;
+ channel = h_channel;
+
+ if (!channel->p_ch_data || !channel->in_use)
+ return -EIO;
+
+ spin_lock_irqsave(&channel->p_ch_data->rx_channel_lock, flags);
+ rx_bd = channel->p_ch_data->rx_out_data;
+
+ if (rx_bd->flag) {
+ *size = rx_bd->length;
+ buf = (u16 *) p_data;
+ buf1 = (u16 *)rx_bd->p_data;
+ memcpy(buf1, buf, NUM_SAMPLES_PER_FRAME);
+ rx_bd->flag = 0;
+ rx_bd->offset = 0;
+ channel->p_ch_data->rx_out_data = (rx_bd->wrap) ?
+ channel->p_ch_data->rx_data_fifo : rx_bd + 1;
+
+ } else {
+ spin_unlock_irqrestore(&channel->p_ch_data->rx_channel_lock,
+ flags);
+ pr_debug("No Data Available");
+ return -EAGAIN;
+ }
+ spin_unlock_irqrestore(&channel->p_ch_data->rx_channel_lock, flags);
+
+ return res;
+}
+EXPORT_SYMBOL(tdm_channel_read);
+
+
+int tdm_channel_write(struct tdm_port *h_port, struct tdm_channel *h_channel,
+ void *p_data, u16 size)
+{
+ struct tdm_port *port;
+ struct tdm_channel *channel;
+ struct tdm_bd *tx_bd;
+ unsigned long flags;
+ int err = 0;
+ port = h_port;
+ channel = h_channel;
+#ifdef DEBUG
+ bool data_flag = 0;
+#endif
+
+ if (p_data == NULL) { /* invalid data*/
+ pr_err("tdm: Invalid Data");
+ return -EINVAL;
+ }
+
+ if (!channel->p_ch_data || !channel->in_use)
+ return -EIO;
+
+ spin_lock_irqsave(&channel->p_ch_data->tx_channel_lock, flags);
+ tx_bd = channel->p_ch_data->tx_in_data;
+
+ if (!tx_bd->flag) {
+ tx_bd->length = size;
+ memcpy(tx_bd->p_data, p_data,
+ size * port->adapter->adapt_cfg.slot_width);
+ tx_bd->flag = 1;
+ tx_bd->offset = 0;
+ channel->p_ch_data->tx_in_data = (tx_bd->wrap) ?
+ channel->p_ch_data->tx_data_fifo : tx_bd+1;
+ port->port_stat.tx_pkt_count++;
+#ifdef DEBUG
+ data_flag = 1;
+#endif
+ } else {
+ spin_unlock_irqrestore(&channel->p_ch_data->tx_channel_lock,
+ flags);
+ port->port_stat.tx_pkt_drop_count++;
+ pr_err("tdm: Transmit failed.");
+ return -ENOMEM;
+ }
+ spin_unlock_irqrestore(&channel->p_ch_data->tx_channel_lock, flags);
+
+#ifdef DEBUG
+ if (data_flag) {
+ int k;
+ pr_info("\nTX port:%d - Write - Port TX-%d\n",
+ port->port_id, size);
+ for (k = 0; k < size; k++)
+ pr_info("%x", p_data[k]);
+ pr_info("\n");
+ }
+#endif
+ return err;
+}
+EXPORT_SYMBOL(tdm_channel_write);
+
+/*
+ * Driver Function for select and poll. Based on Channel, it sleeps on
+ * waitqueue
+ */
+int tdm_ch_poll(struct tdm_channel *h_channel, unsigned int wait_time)
+{
+ struct tdm_channel *channel;
+ channel = h_channel;
+
+ if (!channel->p_ch_data || !channel->in_use)
+ return -EIO;
+
+ if (channel->p_ch_data->rx_out_data->flag) {
+ pr_debug("Data Available");
+ return 0;
+ }
+ if (wait_time) {
+ unsigned long timeout = msecs_to_jiffies(wait_time);
+
+ wait_event_interruptible_timeout(channel->ch_wait_queue,
+ channel->p_ch_data->rx_out_data->flag,
+ timeout);
+
+ if (channel->p_ch_data->rx_out_data->flag) {
+ pr_debug("Data Available");
+ return 0;
+ }
+ }
+ return -EAGAIN;
+}
+EXPORT_SYMBOL(tdm_ch_poll);
+
+unsigned int tdm_port_get_stats(struct tdm_port *h_port,
+ struct tdm_port_stats *portstat)
+{
+ struct tdm_port *port;
+ int port_num;
+ port = h_port;
+
+ if (port == NULL || portstat == NULL) { /* invalid handle */
+ pr_err("tdm: Invalid Handle");
+ return -ENXIO;
+ }
+ port_num = port->port_id;
+
+ memcpy(portstat, &port->port_stat, sizeof(struct tdm_port_stats));
+
+ pr_info("TDM Port %d Get Stats", port_num);
+
+ return 0;
+}
+EXPORT_SYMBOL(tdm_port_get_stats);
+
+/* Data handling functions */
+
+static int tdm_data_rx_deinterleave(struct tdm_adapter *adap)
+{
+ struct tdm_port *port, *next;
+ struct tdm_channel *channel, *temp;
+ struct tdm_bd *ch_bd;
+
+ int i, buf_size, ch_data_len;
+ u16 *input_tdm_buffer;
+ u16 *pcm_buffer;
+ int slot_width;
+ int frame_ch_data_size;
+ bool ch_data;
+ int bytes_in_fifo_per_frame;
+ int bytes_slot_offset;
+
+ ch_data_len = NUM_SAMPLES_PER_FRAME;
+ frame_ch_data_size = NUM_SAMPLES_PER_FRAME;
+ ch_data = 0;
+
+ slot_width = adap->adapt_cfg.slot_width;
+ buf_size = tdm_adap_recv(adap, (void **)&input_tdm_buffer);
+ if (buf_size <= 0 || !input_tdm_buffer)
+ return -EINVAL;
+
+ bytes_in_fifo_per_frame = buf_size/frame_ch_data_size;
+ bytes_slot_offset = bytes_in_fifo_per_frame/slot_width;
+
+ /* de-interleaving for all ports*/
+ list_for_each_entry_safe(port, next, &adap->myports, list) {
+
+ list_for_each_entry_safe(channel, temp, &port->mychannels,
+ list) {
+ /* if the channel is not open */
+ if (!channel->in_use || !channel->p_ch_data)
+ continue;
+ ch_bd = channel->p_ch_data->rx_in_data;
+ spin_lock(&channel->p_ch_data->rx_channel_lock);
+ /*if old data is to be discarded */
+ if (use_latest_tdm_data && ch_bd->flag) {
+ ch_bd->flag = 0;
+ ch_bd->offset = 0;
+ if (ch_bd == channel->p_ch_data->rx_out_data)
+ channel->p_ch_data->rx_out_data =
+ ch_bd->wrap ?
+ channel->p_ch_data->rx_data_fifo
+ : ch_bd+1;
+ port->port_stat.rx_pkt_drop_count++;
+ }
+ /* if the bd is empty */
+ if (!ch_bd->flag) {
+ if (ch_bd->offset == 0)
+ ch_bd->length = port->rx_max_frames;
+
+ pcm_buffer = ch_bd->p_data + ch_bd->offset;
+ /* De-interleaving the data */
+ for (i = 0; i < ch_data_len; i++) {
+ pcm_buffer[i]
+ = input_tdm_buffer[i*
+ bytes_slot_offset +
+ channel->ch_id];
+ }
+ ch_bd->offset += ch_data_len * slot_width;
+
+ if (ch_bd->offset >=
+ (ch_bd->length -
+ frame_ch_data_size)*
+ (adap->adapt_cfg.slot_width)) {
+ ch_bd->flag = 1;
+ ch_bd->offset = 0;
+ channel->p_ch_data->rx_in_data =
+ ch_bd->wrap ?
+ channel->p_ch_data->rx_data_fifo
+ : ch_bd+1;
+ ch_data = 1;
+ wake_up_interruptible
+ (&channel->ch_wait_queue);
+ }
+ } else {
+ port->port_stat.rx_pkt_drop_count++;
+ }
+ spin_unlock(&channel->p_ch_data->rx_channel_lock);
+ }
+
+ if (ch_data) {
+ /* Wake up the Port Data Poll event */
+#ifdef DEBUG
+ pr_info("Port RX-%d-%d\n", channel->ch_id, ch_data_len);
+ for (i = 0; i < ch_data_len; i++)
+ pr_info("%x", pcm_buffer[i]);
+ pr_info("\n");
+#endif
+ port->port_stat.rx_pkt_count++;
+ ch_data = 0;
+ }
+ }
+ return 0;
+}
+
+static int tdm_data_tx_interleave(struct tdm_adapter *adap)
+{
+ struct tdm_port *port, *next;
+ struct tdm_channel *channel, *temp;
+ struct tdm_bd *ch_bd;
+ int i, buf_size, ch_data_len = NUM_SAMPLES_PER_FRAME;
+ bool last_data = 0;
+ u16 *output_tdm_buffer;
+ u16 *pcm_buffer;
+ int frame_ch_data_size = NUM_SAMPLES_PER_FRAME;
+ int bytes_in_fifo_per_frame;
+ int bytes_slot_offset;
+
+#ifdef DEBUG
+ u8 data_flag = 0;
+#endif
+
+ buf_size = tdm_adap_get_write_buf(adap, (void **)&output_tdm_buffer);
+ if (buf_size <= 0 || !output_tdm_buffer)
+ return -EINVAL;
+
+ bytes_in_fifo_per_frame = buf_size/frame_ch_data_size;
+ bytes_slot_offset = bytes_in_fifo_per_frame/adap->adapt_cfg.slot_width;
+
+
+ memset(output_tdm_buffer, 0, sizeof(buf_size));
+
+ list_for_each_entry_safe(port, next, &adap->myports, list) {
+
+ list_for_each_entry_safe(channel, temp, &port->mychannels,
+ list) {
+ pr_debug("TX-Tdm %d (slots-)", channel->ch_id);
+
+
+ /* if the channel is open */
+ if (!channel->in_use || !channel->p_ch_data)
+ continue;
+
+ spin_lock(&channel->p_ch_data->tx_channel_lock);
+ if (!channel->in_use || !channel->p_ch_data)
+ continue;
+ ch_bd = channel->p_ch_data->tx_out_data;
+ if (ch_bd->flag) {
+ pcm_buffer = (u16 *)((uint8_t *)ch_bd->p_data +
+ ch_bd->offset);
+ /* if the buffer has less frames than required*/
+ if (frame_ch_data_size >=
+ (ch_bd->length - ch_bd->offset/
+ adap->adapt_cfg.slot_width)) {
+ ch_data_len =
+ ch_bd->length - ch_bd->offset/
+ adap->adapt_cfg.slot_width;
+ last_data = 1;
+ } else {
+ ch_data_len = frame_ch_data_size;
+ }
+ /* Interleaving the data */
+ for (i = 0; i < ch_data_len; i++) {
+ /*
+ * TODO- need to be genric for any size
+ * assignment
+ */
+ output_tdm_buffer[channel->ch_id +
+ bytes_slot_offset * i] =
+ pcm_buffer[i];
+ }
+ /*
+ * If all the data of this buffer is
+ * transmitted
+ */
+ if (last_data) {
+ ch_bd->flag = 0;
+ ch_bd->offset = 0;
+ channel->p_ch_data->tx_out_data =
+ ch_bd->wrap ?
+ channel->p_ch_data->tx_data_fifo
+ : ch_bd+1;
+ port->port_stat.tx_pkt_conf_count++;
+ } else {
+ ch_bd->offset += ch_data_len *
+ (adap->adapt_cfg.slot_width);
+ }
+#ifdef DEBUG
+ data_flag = 1;
+#endif
+ }
+ spin_unlock(&channel->p_ch_data->tx_channel_lock);
+ }
+ }
+
+#ifdef DEBUG
+ if (data_flag) {
+ pr_info("TX-TDM Interleaved Data-\n");
+ for (i = 0; i < 64; i++)
+ pr_info("%x", output_tdm_buffer[i]);
+ pr_info("\n");
+ }
+#endif
+ return 0;
+}
+
+/* Channel Level APIs of TDM Framework */
+int tdm_channel_open(u16 chanid, u16 ch_width, struct tdm_port *port,
+ struct tdm_channel **h_channel)
+{
+ struct tdm_channel *channel, *temp;
+ unsigned long flags;
+ struct tdm_ch_data *p_ch_data;
+ int res = 0;
+
+ if (ch_width != 1) {
+ pr_err("tdm: Mode not supported\n");
+ return -EINVAL;
+ }
+
+ list_for_each_entry_safe(channel, temp, &port->mychannels, list) {
+ if (channel->ch_id == chanid) {
+ pr_err("tdm: Channel %d already open\n", chanid);
+ return -EINVAL;
+ }
+ }
+
+ channel = kzalloc(sizeof(*channel), GFP_KERNEL);
+ if (!channel) {
+ res = -ENOMEM;
+ goto out;
+ }
+
+ init_waitqueue_head(&channel->ch_wait_queue);
+ p_ch_data = kzalloc(sizeof(struct tdm_ch_data), GFP_KERNEL);
+ if (!p_ch_data) {
+ res = -ENOMEM;
+ goto outdata;
+ }
+
+ p_ch_data->rx_data_fifo[TDM_CH_RX_BD_RING_SIZE-1].wrap = 1;
+ p_ch_data->tx_data_fifo[TDM_CH_TX_BD_RING_SIZE-1].wrap = 1;
+
+ p_ch_data->rx_in_data = p_ch_data->rx_data_fifo;
+ p_ch_data->rx_out_data = p_ch_data->rx_data_fifo;
+ p_ch_data->tx_in_data = p_ch_data->tx_data_fifo;
+ p_ch_data->tx_out_data = p_ch_data->tx_data_fifo;
+ spin_lock_init(&p_ch_data->rx_channel_lock);
+ spin_lock_init(&p_ch_data->tx_channel_lock);
+
+ channel->p_ch_data = p_ch_data;
+
+ channel->ch_id = chanid;
+ channel->ch_cfg.first_slot = chanid;
+ channel->ch_cfg.num_slots = 1; /*
+ * This is 1 for channelized mode and
+ * configurable for other modes
+ */
+ channel->port = port;
+ channel->in_use = 1;
+
+ spin_lock_irqsave(&port->ch_list_lock, flags);
+ list_add_tail(&channel->list, &port->mychannels);
+ spin_unlock_irqrestore(&port->ch_list_lock, flags);
+
+ *h_channel = channel;
+
+ return res;
+
+outdata:
+ kfree(channel);
+out:
+ return res;
+}
+EXPORT_SYMBOL(tdm_channel_open);
+
+int tdm_sysfs_init(void)
+{
+ struct kobject *tdm_kobj;
+ int err = 1;
+ tdm_kobj = kzalloc(sizeof(*tdm_kobj), GFP_KERNEL);
+ if (tdm_kobj) {
+ kobject_init(tdm_kobj, &tdm_type);
+ if (kobject_add(tdm_kobj, NULL, "%s", "tdm")) {
+ pr_err("tdm: Sysfs creation failed\n");
+ kobject_put(tdm_kobj);
+ err = -EINVAL;
+ goto out;
+ }
+ } else {
+ pr_err("tdm: Unable to allocate tdm_kobj\n");
+ err = -ENOMEM;
+ goto out;
+ }
+
+out:
+ return err;
+}
+
+int tdm_channel_close(u16 chanid, u16 ch_width, struct tdm_port *port,
+ struct tdm_channel *h_channel)
+{
+ struct tdm_channel *channel;
+ unsigned long flags;
+ int res = 0;
+ channel = h_channel;
+
+ spin_lock_irqsave(&port->ch_list_lock, flags);
+ list_del(&channel->list);
+ spin_unlock_irqrestore(&port->ch_list_lock, flags);
+
+ if (channel)
+ kfree(channel->p_ch_data);
+ kfree(channel);
+ return res;
+}
+EXPORT_SYMBOL(tdm_channel_close);
+
+ssize_t tdm_show_sysfs(struct kobject *kobj,
+ struct attribute *attr, char *buf)
+{
+ int retval = 0;
+ struct tdm_sysfs *a = container_of(attr,
+ struct tdm_sysfs, attr);
+ switch (a->cmd_type) {
+ case TDM_LATEST_DATA:
+ pr_info("use_latest_tdm_data: %d\n", use_latest_tdm_data);
+ break;
+ default:
+ pr_info("Invalid cmd_type value\n");
+ return -EINVAL;
+ }
+ return retval;
+}
+
+ssize_t tdm_store_sysfs(struct kobject *kobj,
+ struct attribute *attr, const char *buf, size_t len)
+{
+ struct tdm_sysfs *a = container_of(attr,
+ struct tdm_sysfs, attr);
+
+ sscanf(buf, "%d", &a->data);
+ use_latest_tdm_data = a->data;
+ return strlen(buf);
+}
+
+void init_config_adapter(struct tdm_adapter *adap)
+{
+ struct fsl_tdm_adapt_cfg default_adapt_cfg = {
+ .loopback = TDM_PROCESS_NORMAL,
+ .num_ch = NUM_CHANNELS,
+ .ch_size_type = CHANNEL_16BIT_LIN,
+ .frame_len = NUM_SAMPLES_PER_FRAME,
+ .num_frames = NUM_SAMPLES_PER_FRAME,
+ .adap_mode = TDM_ADAPTER_MODE_NONE
+ };
+
+ default_adapt_cfg.slot_width = default_adapt_cfg.ch_size_type/3 + 1;
+
+ memcpy(&adap->adapt_cfg, &default_adapt_cfg,
+ sizeof(struct fsl_tdm_adapt_cfg));
+
+ return;
+}
+EXPORT_SYMBOL(init_config_adapter);
+
+void tdm_data_tasklet_fn(unsigned long data)
+{
+ struct tdm_adapter *adapter;
+ adapter = (struct tdm_adapter *)data;
+ if (adapter != NULL) {
+ tdm_data_tx_interleave(adapter);
+ tdm_data_rx_deinterleave(adapter);
+ }
+}
+
+
+MODULE_AUTHOR("Hemant Agrawal <hemant@freescale.com> and "
+ "Rajesh Gumasta <rajesh.gumasta@freescale.com>");
+MODULE_DESCRIPTION("TDM Driver Framework Core");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h
index 83ac071..573ac40 100644
--- a/include/linux/mod_devicetable.h
+++ b/include/linux/mod_devicetable.h
@@ -425,6 +425,17 @@ struct i2c_device_id {
__attribute__((aligned(sizeof(kernel_ulong_t))));
};
+/* tdm */
+
+#define TDM_NAME_SIZE 20
+#define TDM_MODULE_PREFIX "tdm:"
+
+struct tdm_device_id {
+ char name[TDM_NAME_SIZE];
+ kernel_ulong_t driver_data /* Data private to the driver */
+ __attribute__((aligned(sizeof(kernel_ulong_t))));
+};
+
/* spi */
#define SPI_NAME_SIZE 32
diff --git a/include/linux/tdm.h b/include/linux/tdm.h
new file mode 100644
index 0000000..44cd8cf
--- /dev/null
+++ b/include/linux/tdm.h
@@ -0,0 +1,389 @@
+/* include/linux/tdm.h
+ *
+ * Copyright 2012 Freescale Semiconductor, Inc.
+ *
+ * tdm.h - definitions for the tdm-device framework interface
+ *
+ * Author:Hemant Agrawal <hemant@freescale.com>
+ * Rajesh Gumasta <rajesh.gumasta@freescale.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+#ifndef _LINUX_TDM_H
+#define _LINUX_TDM_H
+
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/device.h> /* for struct device */
+#include <linux/sched.h> /* for completion */
+#include <linux/mutex.h>
+#include <linux/interrupt.h>
+#include <linux/sysfs.h>
+
+#define TDM_LATEST_DATA 1
+#define CHANNEL_8BIT_LIN 0 /* 8 bit linear */
+#define CHANNEL_8BIT_ULAW 1 /* 8 bit Mu-law */
+#define CHANNEL_8BIT_ALAW 2 /* 8 bit A-law */
+#define CHANNEL_16BIT_LIN 3 /* 16 bit Linear */
+
+/*
+ * Default adapter configuration. All the TDM adapters registered with
+ * framework will be configured with following default configuration.
+ */
+#define NUM_CHANNELS 16
+
+/*
+ * Default configuration for typical voice data sample. These parameters
+ * will generally not be required to be changed for voice type applications.
+ */
+
+/* 8 samples per milli sec per channel. Req for voice data */
+#define NUM_SAMPLES_PER_MS 8
+#define NUM_MS 10
+
+/* Number of samples for 1 client buffer */
+#define NUM_SAMPLES_PER_FRAME (NUM_MS * NUM_SAMPLES_PER_MS)
+#define NUM_OF_TDM_BUF 3
+
+/* General options */
+
+struct tdm_adapt_algorithm;
+struct tdm_adapter;
+struct tdm_port;
+
+
+/*
+ * struct tdm_driver - represent an TDM device driver
+ * @class: What kind of tdm device we instantiate (for detect)
+ * @id:Driver id
+ * @name: Name of the driver
+ * @attach_adapter: Callback for device addition (for legacy drivers)
+ * @detach_adapter: Callback for device removal (for legacy drivers)
+ * @probe: Callback for device binding
+ * @remove: Callback for device unbinding
+ * @shutdown: Callback for device shutdown
+ * @suspend: Callback for device suspend
+ * @resume: Callback for device resume
+ * @command: Callback for sending commands to device
+ * @id_table: List of TDM devices supported by this driver
+ * @list: List of drivers created (for tdm-core use only)
+ */
+struct tdm_driver {
+ unsigned int class;
+ unsigned int id;
+ char name[TDM_NAME_SIZE];
+
+ int (*attach_adapter)(struct tdm_adapter *);
+ int (*detach_adapter)(struct tdm_adapter *);
+
+ /* Standard driver model interfaces */
+ int (*probe)(const struct tdm_device_id *);
+ int (*remove)(void);
+
+ /* driver model interfaces that don't relate to enumeration */
+ void (*shutdown)(void);
+ int (*suspend)(pm_message_t mesg);
+ int (*resume)(void);
+
+ const struct tdm_device_id *id_table;
+
+ /* The associated adapter for this driver */
+ struct tdm_adapter *adapter;
+ struct list_head list;
+};
+
+/*
+ * tdm per port statistics structure, used for providing and storing tdm port
+ * statistics.
+ */
+struct tdm_port_stats {
+ unsigned int rx_pkt_count; /* Rx frame count per channel */
+ unsigned int rx_pkt_drop_count; /*
+ * Rx drop count per channel to
+ * clean space for new buffer
+ */
+ unsigned int tx_pkt_count; /* Tx frame count per channel */
+ unsigned int tx_pkt_conf_count; /*
+ * Tx frame confirmation count per
+ * channel
+ */
+ unsigned int tx_pkt_drop_count; /*
+ * Tx drop count per channel due to
+ * queue full
+ */
+};
+
+
+/*
+ * tdm Buffer Descriptor, used for Creating Interleaved and De-interleaved
+ * FIFOs
+ */
+struct tdm_bd {
+ unsigned char flag; /* BD is full or empty */
+ unsigned char wrap; /* BD is last in the queue */
+ unsigned short length; /* Length of data in BD */
+ /*TODO: use dyanmic memory */
+ unsigned short p_data[NUM_SAMPLES_PER_FRAME]; /* Data Pointer */
+ unsigned long offset; /* Offset of the Data Pointer to be used */
+};
+
+#define TDM_CH_RX_BD_RING_SIZE 3
+#define TDM_CH_TX_BD_RING_SIZE 3
+
+/* tdm RX-TX Channelised Data */
+struct tdm_port_data {
+ struct tdm_bd rx_data_fifo[TDM_CH_RX_BD_RING_SIZE]; /*
+ * Rx Channel Data
+ * BD Ring
+ */
+ struct tdm_bd *rx_in_data; /*
+ * Current Channel Rx BD to be filled by
+ * de-interleave function
+ */
+ struct tdm_bd *rx_out_data; /*
+ * Current Channel Rx BD to be
+ * read by App
+ */
+ struct tdm_bd tx_data_fifo[TDM_CH_TX_BD_RING_SIZE]; /*
+ * Tx Channel Data
+ * BD Ring
+ */
+ struct tdm_bd *tx_in_data; /*
+ * Current Channel Tx BD to be
+ * filled by App
+ */
+ struct tdm_bd *tx_out_data; /*
+ * Current Channel Tx BD to be read by
+ * interleave function
+ */
+ spinlock_t rx_channel_lock; /* Spin Lock for Rx Channel */
+ spinlock_t tx_channel_lock; /* Spin Lock for Tx Channel */
+};
+
+/* structure tdm_port_cfg - contains configuration params for a port */
+struct tdm_port_cfg {
+ unsigned short port_mode;
+};
+
+/* struct tdm_port - represent an TDM ports for a device */
+struct tdm_port {
+ unsigned short port_id;
+ unsigned short in_use; /* Port is enabled? */
+ uint16_t rx_max_frames; /*
+ * Received port frames before allowing
+ * read operation in Port Mode
+ */
+
+ struct tdm_port_stats port_stat; /*
+ * A structure parameter defining
+ * TDM port statistics.
+ */
+ struct tdm_port_data *p_port_data; /*
+ * a structure parameter
+ * defining tdm channelised data
+ */
+
+ struct tdm_driver *driver; /* driver for this port */
+ struct tdm_adapter *adapter; /* adapter for this port */
+ struct list_head list; /* list of ports */
+ struct list_head mychannels; /* list of channels, on this port*/
+ spinlock_t ch_list_lock; /* Spin Lock for channel_list */
+ struct tdm_port_cfg port_cfg; /*
+ * A structure parameter defining
+ * TDM port configuration.
+ */
+};
+
+/* tdm RX-TX Channelised Data */
+struct tdm_ch_data {
+ struct tdm_bd rx_data_fifo[TDM_CH_RX_BD_RING_SIZE]; /*
+ * Rx Port Data BD
+ * Ring
+ */
+ struct tdm_bd *rx_in_data; /*
+ * Current Port Rx BD to be filled by
+ * de-interleave function
+ */
+ struct tdm_bd *rx_out_data; /* Current Port Rx BD to be read by App */
+ struct tdm_bd tx_data_fifo[TDM_CH_TX_BD_RING_SIZE]; /*
+ * Tx Port Data BD
+ * Ring
+ */
+ struct tdm_bd *tx_in_data; /*
+ * Current Port Tx BD to be filled by
+ * App
+ */
+ struct tdm_bd *tx_out_data; /*
+ * Current Port Tx BD to be read by
+ * interleave function
+ */
+ spinlock_t rx_channel_lock; /* Spin Lock for Rx Port */
+ spinlock_t tx_channel_lock; /* Spin Lock for Tx Port */
+};
+
+/* Channel config params */
+struct tdm_ch_cfg {
+ unsigned short num_slots;
+ unsigned short first_slot;
+};
+
+/* struct tdm_channel- represent a TDM channel for a port */
+struct tdm_channel {
+ u16 ch_id; /* logical channel number */
+ struct list_head list; /* list of channels in a port*/
+ struct tdm_port *port; /* port for this channel */
+ u8 in_use; /* channel is enabled? */
+ struct tdm_ch_cfg ch_cfg; /* channel configuration */
+ struct tdm_ch_data *p_ch_data; /* data storage space for channel */
+ wait_queue_head_t ch_wait_queue;/* waitQueue for RX Channel Data */
+};
+
+/* tdm_adapt_algorithm is for accessing the routines of device */
+struct tdm_adapt_algorithm {
+ int (*tdm_read)(struct tdm_adapter *, u16 **);
+ int (*tdm_get_write_buf)(struct tdm_adapter *, u16 **);
+ int (*tdm_write)(struct tdm_adapter *, void *, unsigned int len);
+ int (*tdm_enable)(struct tdm_adapter *);
+ int (*tdm_disable)(struct tdm_adapter *);
+};
+
+/* tdm_adapter_mode is to define in mode of the device */
+enum tdm_adapter_mode {
+ TDM_ADAPTER_MODE_NONE = 0x00,
+ TDM_ADAPTER_MODE_T1 = 0x01,
+ TDM_ADAPTER_MODE_E1 = 0x02,
+ TDM_ADAPTER_MODE_T1_RAW = 0x10,
+ TDM_ADAPTER_MODE_E1_RAW = 0x20,
+};
+
+/* tdm_port_mode defines the mode in which the port is configured to operate
+ * It can be channelized/full/fractional.
+ */
+enum tdm_port_mode {
+ TDM_PORT_CHANNELIZED = 0, /* Channelized mode */
+ TDM_PORT_FULL = 1, /* Full mode */
+ TDM_PORT_FRACTIONAL = 2 /* Fractional mode */
+};
+
+/* tdm_process_mode used for testing the tdm device in normal mode or internal
+ * loopback or external loopback
+ */
+enum tdm_process_mode {
+ TDM_PROCESS_NORMAL = 0, /* Normal mode */
+ TDM_PROCESS_INT_LPB = 1, /* Internal loop mode */
+ TDM_PROCESS_EXT_LPB = 2 /* External Loopback mode */
+};
+
+/* TDM configuration parameters */
+struct fsl_tdm_adapt_cfg {
+ u8 num_ch; /* Number of channels in this adpater */
+ u8 ch_size_type; /*
+ * reciever/transmit channel
+ * size for all channels
+ */
+ u8 slot_width; /* 1 or 2 Is defined by channel type */
+ u8 frame_len; /* Length of frame in samples */
+ u32 num_frames;
+ u8 loopback; /* loopback or normal */
+ u8 adap_mode; /*
+ * 0=None, 1= T1, 2= T1-FULL, 3=E1,
+ * 4 = E1-FULL
+ */
+ int max_timeslots; /*
+ * Max Number of timeslots that are
+ * supported on this adapter
+ */
+};
+
+/*
+ * tdm_adapter is the structure used to identify a physical tdm device along
+ * with the access algorithms necessary to access it.
+ */
+struct tdm_adapter {
+ struct module *owner; /* owner of the adapter module */
+ unsigned int id; /* Adapter Id */
+ unsigned int drv_count; /*
+ * Number of drivers associated with the
+ * adapter
+ */
+ const struct tdm_adapt_algorithm *algo; /*
+ * algorithm to access the
+ * adapter
+ */
+
+ char name[TDM_NAME_SIZE]; /* Name of Adapter */
+ struct mutex adap_lock;
+ struct device *parent;
+
+ struct tasklet_struct tdm_data_tasklet; /*
+ * tasklet handle to perform
+ * data processing
+ */
+ int tasklet_conf; /* flag for tasklet configuration */
+ int tdm_rx_flag;
+
+ struct list_head myports; /*
+ * list of ports, created on this
+ * adapter
+ */
+ struct list_head list;
+ spinlock_t portlist_lock;
+ void *data;
+ struct fsl_tdm_adapt_cfg adapt_cfg;
+};
+
+struct tdm_sysfs {
+ struct attribute attr;
+ int data;
+ u32 cmd_type;
+};
+
+/* functions exported by tdm.o */
+
+int tdm_add_adapter(struct tdm_adapter *adpater);
+int tdm_del_adapter(struct tdm_adapter *adapter);
+int tdm_register_driver(struct tdm_driver *driver);
+void tdm_unregister_driver(struct tdm_driver *driver);
+void init_config_adapter(struct tdm_adapter *adapter);
+
+int tdm_port_open(struct tdm_driver *driver, struct tdm_port **h_port);
+int tdm_port_close(struct tdm_port *h_port);
+int tdm_channel_read(struct tdm_port *h_port, struct tdm_channel *h_channel,
+ void *p_data, u16 *size);
+int tdm_channel_write(struct tdm_port *h_port, struct tdm_channel *h_channel,
+ void *p_data, u16 size);
+int tdm_ch_poll(struct tdm_channel *h_channel, unsigned int wait_time);
+
+int tdm_channel_open(u16 chanid, u16 ch_width, struct tdm_port *port,
+ struct tdm_channel **h_channel);
+int tdm_channel_close(u16 chanid, u16 ch_width, struct tdm_port *port,
+ struct tdm_channel *h_channel);
+/* this tasklet is created for each adapter instance */
+void tdm_data_tasklet_fn(unsigned long);
+int tdm_sysfs_init(void);
+ssize_t tdm_show_sysfs(struct kobject *kobj,
+ struct attribute *attr, char *buf);
+ssize_t tdm_store_sysfs(struct kobject *kobj,
+ struct attribute *attr, const char *buf, size_t len);
+
+struct tdm_adapter *tdm_get_adapter(int id);
+void tdm_put_adapter(struct tdm_adapter *adap);
+
+#endif /* __KERNEL__ */
+
+#define TDM_E_OK 0
--
1.5.6.5
^ permalink raw reply related
* [1/3][PATCH][v2] Adding documentation for TDM
From: sandeep @ 2012-07-27 14:05 UTC (permalink / raw)
To: linuxppc-dev, linux-arm-kernel; +Cc: devel, Sandeep Singh, linux-kernel
In-Reply-To: <1343397940-12975-1-git-send-email-sandeep@freescale.com>
From: Sandeep Singh <Sandeep@freescale.com>
tdm-summary.txt contains general description about TDM.
tdm-framework.txt contains specific description of TDM framework.
Signed-off-by: Sandeep Singh <Sandeep@freescale.com>
Signed-off-by: Poonam Aggrwal <poonam.aggrwal@freescale.com>
---
Changes since v1:
Incorporated Laight's comments.
-Removed reference to unused config.
Documentation/tdm/tdm-framework.txt | 258 +++++++++++++++++++++++++++++++++++
Documentation/tdm/tdm-summary.txt | 103 ++++++++++++++
2 files changed, 361 insertions(+), 0 deletions(-)
create mode 100644 Documentation/tdm/tdm-framework.txt
create mode 100644 Documentation/tdm/tdm-summary.txt
diff --git a/Documentation/tdm/tdm-framework.txt b/Documentation/tdm/tdm-framework.txt
new file mode 100644
index 0000000..f96189a
--- /dev/null
+++ b/Documentation/tdm/tdm-framework.txt
@@ -0,0 +1,258 @@
+This document gives an overview of TDM framework and its interface with low
+level drivers and upper level users/clients.
+
+Terminology:
+============
+1. TDM: Time Division Multiplexing.
+2. TDM channel: The channel is the smallest entity on which all the TDM read/
+ write operations will occur. Technically each channel maps to a set of
+ consecutive time slots on the physical TDM frame. The channels will be
+ dynamically created and destroyed using tdm_open_channel and
+ tdm_close_channel.
+3. TDM adapter or Adapter: Refers to an instance of TDM controller/device on
+ the system.
+4. TDM frame: Is a set of TDM channels which is transmitted sequentially over
+ time. The frame start is identified by a frame sync signal that is briefly
+ asserted at the beginning of each frame.
+
+X----------TDM Frame 0-------------X------TDM Frame 1-----------------X
+|----|----|----|----|----|----|----|----|----|----|----|----|----|----|
+| 0 | 1 | 2 | 3 | 4 | ...| n | 0 | 1 | 2 | 3 | 4 | ...| n |...
+|----|----|----|----|----|----|----|----|----|----|----|----|----|----|
+<----> <---->
+ch 0 ch 0
+
+4. TDM client: Application/driver which registers with TDM framework to use TDM
+ device.
+5. TDM port: It can be seen as a virtual device exposed to a client. At a time
+ TDM port can work in one of the follwing configurations:
+ full/fractional/E1/T1/raw.
+
+TDM modes
+========
+A TDM device can operate in one of the following modes:
+1. Single port full mode - Single user/no interleaving
+2. Single port channelised mode (raw, E1, T1)- many users using different
+ channels
+3. Single port fractional mode -
+4. Multi port mode - multiple users using different ports in different
+ configurations.
+
+All the above configurations differ in number of TDM client they support,
+number of TDM channels and number of TDM ports.
+
+Currently we are supporting only single port channelised mode. Hence all the
+explanations below refer to channelised mode of TDM. This framework can be
+easily extended to support other modes.
+
+Single port Channelised Mode
+==============================
+In single port channelised mode there can be only one port and each channel
+can have only one time slot.The number of active channels can be less than
+the maximum supported channels/slots.
+
+X----------TDM Frame 0-------------X------TDM Frame 1-----------------X
+|----|----|----|----|----|----|----|----|----|----|----|----|----|----|
+| 0 | 1 | 2 | 3 | 4 | ...| n | 0 | 1 | 2 | 3 | 4 | ...| n |...
+|----|----|----|----|----|----|----|----|----|----|----|----|----|----|
+<----><---> <---->
+ch 0 ch1 ch 0
+client0 client1
+
+TDM Subsystem Overview
+========================
+
+ |-----------------------|
+ |user mode TDM clients |
+ |-----------------------|
+ ||
+-------------------------------------------------------------------
+ tdm-dev.c ||
+ ||
+ || |------------------------|
+ client register | kernel mode TDM clients|
+ || |------------------------|
+ || ||
+ || ||
+ || client register
+ || ||
+ \/ \/
+ ______________________________________________________________
+ | |
+ | client interface |
+ |------------------------------------------------------------|
+ | TDM Subsystem Framework |
+ | (tdm-core.c) |
+ | |
+ | ->buffer handling |
+ | ->interleaving/de-interleaving |
+ | |
+ |------------------------------------------------------------|
+ | TDM interface Line control interface |
+ |____________________________________________________________|
+ /\ /\
+ || ||
+ device register device register
+ || ||
+ || ||
+
+ fsl_tdm.c ucc_tdm.c slic_zarlink.c framer.c
+--------------------------------------------------------------------------
+_______________________ _____________________ ________ ________
+| | | | | | | |
+|[h/w] TDM controller | |UCC TDM controller | | SLIC | |Framer|
+|_____________________| |___________________| |______| |______|
+
+
+
+TDM Adapter Registration:
+=========================
+All the TDM adapter drivers will get registered as platform drivers to Linux.
+For every instance of the TDM adapter the relevant driver will be probed.
+As part of probe the driver will
+1. Do the basic initialization in terms of memory allocation for various
+ driver specific data structures, interrupts registration, etc.
+2. Initialize the TDM adapter and configure it in default configuration.
+ like operating mode, number of channels, channel type, etc.
+3. Add the TDM adapter to the TDM Framework via tdm_add_adapter() API.
+ As a result TDM framework will add this adapter to it's queue of
+ available adapters. As part of this adapter registration TDM framework
+ is also supplied a list of access algorithms for the particular TDM
+ adapter.
+4. Notifies the clients
+
+TDM Client Registration:
+========================
+Every TDM client gets itself registered with the TDM framework layer as
+a TDM driver using the API tdm_add_driver(). As part of this the TDM client
+supplies to the TDM framework the adapter with which it wants to hook and
+the function pointers of attach and detach functions which must be called
+as soon as the requested adapter is available.
+
+As a result the TDM framework keeps association of TDM adapters and TDM
+client drivers.
+As soon as this association gets established a tasklet is created for the
+adapter which is handled by tdm_data_tasklet_fn. The primary function of
+this tasklet acts as an interface to transfer the TDM data between the
+TDM adapter and the TDM client drivers.
+
+
+Currently TDM adaper can only be used in the default configuration.
+ie the configuration cannot be modified by TDM clients. This will
+be enhanced in future.
+
+Data handling:
+==============
+Some basic assumptions about data handling:
+
+1. As per standard voice rate of 8Khz or 8192Hz. Which means 8192 samples must
+be sent every second. So if there are multiple clients sending voice data
+over TDM interface the rate should be such that the individual samples
+sent by them must be transmitted at 8Kz.
+
+This is defined in the driver as
+
+ #define CH_1_MS_FRAMES 8
+
+2. Number of milliseconds at which TDM Rx interrupts occur
+This is basically the time for which the TDM data is sent in one Tx or Rx
+cycle of TDM controller hardware. In one DMA we send the data for 10ms.
+This gives enough time so that no buffer overflow or under-run occurs for
+transmit and receive respectively.
+
+ #define NUM_MS 10
+
+3. TDM has programmable slot length (8 bits or 16 bits). It can be configured
+depending on the type of sample. For example the sample could be 16 bit linear
+or 8bit u-law encoded etc. Presently only word length of 16 is supported
+which is the default configuration.
+
+4. Number of channels means the total number of channels per TDM port.
+For example for E1 mode it will be 24, for T1 it will be 32, etc.
+There can also be raw mode, where the use case is not E1 or T1.
+Here the number of channels can be any number as per the use case.
+
+The whole framework follows a triple buffer approach to make sure that TDM data
+is played continuously at the desired rate.
+
+Buffers Involved:
+=================
+
+1.TDM driver or device buffers:
+These buffers are the device level buffers. They contain the TDM data which is
+transmitted/received on the TDM physical signals. As such these buffers must
+be allocated from driver layer so that all the hardware requirements are met.
+As an optimized design to remove extra memcopies, the client can pass the data
+in the same buffers. But this is only true for full mode of TDM. Where the
+user data can be straightaway passed to the hardware for transmission.
+Although in other cases memcopy cannot be avoided, because the framework layer
+will have to interleave the individual channels data to create the TDM frame
+data buffer.For channelised mode size of this buffer will be governed by:
+
+- number of channels
+- number of slots per channel
+- number of bytes per slot
+- number of frames per ms
+- number of ms
+
+For a channelised mode with single port the size of the device level buffer
+will be:
+
+channels * slots per channel * bytes per slot * frames per ms *
+number of ms channels * NUM_BYTES_PER_SLOT * NUM_MS * CH_1_MS_FRAMES
+
+There will be 3 such buffers.
+
+2.Channel level buffers:
+In case the TDM device is configured for multiport/multichannel the Framework
+layer needs to maintain the data for each channel. Hence for each channel
+opened a Buffer Descriptor ring of 3 BDs(see note below) is allocated both for
+transmit and receive. The client reads from/writes to the buffers pointed by
+these BD rings.
+
+The framework layer maintains a Data Process Tasklet per TDM device which is
+scheduled from every Rx interrupt. The interrupt handling periodicity is
+governed by the TDM data buffer size configured in the above section. The data
+tasklet when scheduled, will do Rx and Tx processing to copy the data from/to
+the channel specific interleaved buffers. The TDM controller will DMA the
+data which is copied in the interleaved buffers or device level buffers.
+
+TDM framework provides the port level APIs and channel level APIs to the TDM
+client drivers to send and receive the respective data on different TDM slots.
+
+
+num of buffers = 3
+
+TDM client1 TDM Client2
+
+buf0------->buf1 buf0------->buf1
+^ | ^ |
+| V | V
+----buf2------ ------buf2----
+ | |
+ | |
+ | |
+ V V
+-----------------------------------------
+| |
+| DATA Tasklet |
+| |
+-----------------------------------------
+ |
+ |
+ V
+-----------------------------------------
+| TDM buffer interleaved * 3 |
+-----------------------------------------
+
+
+Not Implemented/Future work:
+============================
+1. TDM client will use the default configuration which is done at init time
+ and is not configurable. In future this should be made configurable as per
+ the needs of client.
+2. The TDM framework still needs to be enhanced to configure the ports and
+ their attributes. Currently only single port channelised mode is supported.
+3. Line control interface is not available in the framework presently.
+ Presently it offer very minimal kind of interface.
+4. SLIC interface will be enhanced as per Zarlink Open source APIs in future.
diff --git a/Documentation/tdm/tdm-summary.txt b/Documentation/tdm/tdm-summary.txt
new file mode 100644
index 0000000..f010f76
--- /dev/null
+++ b/Documentation/tdm/tdm-summary.txt
@@ -0,0 +1,103 @@
+Time Division Multiplexing (TDM)
+=================================
+
+TDM is a type of digital or analog multiplexing in which two or more bit
+streams or signals are transferred apparently simultaneously as sub-channels
+in one communication channel, but are physically taking turns on the channel.
+
+The time domain is divided into several recurrent timeslots of fixed duration.
+These timeslot are grouped together to form a channel. A sample byte or data
+block of channel 1 is transmitted during timeslots allocated to channel 1,
+channel 2 during timeslot for channel 2, etc.
+
+One TDM frame consists of multiple channels. After the last channel the cycle
+starts all over again with a new frame, starting with the second sample, byte
+or data block from channel 1, and so on.
+
+X----------TDM Frame 0-------------X------TDM Frame 1-----------------X
+|----|----|----|----|----|----|----|----|----|----|----|----|----|----|
+| 0 | 1 | 2 | 3 | 4 | ...| n | 0 | 1 | 2 | 3 | 4 | ...| n |...
+|----|----|----|----|----|----|----|----|----|----|----|----|----|----|
+<----> <---->
+channel 0 channel 0
+-------------------------------------------------------------------->
+ Increasing Time
+
+Physical TDM interface
+=======================
+
+Physically TDM interface is a serial full duplex interface designed to
+communicate with variety of serial devices like industry standard framers,
+codecs, other DSPs, and microprocessors. It is typically used to transfer
+samples in a periodic manner. The TDM consists of independent transmitter and
+receiver sections with independent clock generation and frame synchronization.
+
+External TDM signals are:
+1. TDM_TCK: TDM Transmit clock
+2. TDM_RCK: TDM Receive clock
+3. TDM_TFS: TDM Tx frame sync to identify frame boundary
+4. TDM_RFS: TDM Rx Frame sync to identify frame boundary
+5. TDM_TD: TDM Tx data
+6. TDM_RD: TDM Rx data
+
+TDM is generally used to simultaneously transmit periodic data for multiple
+users. Common use cases would be to carry voice for various telephone
+subscribers in the telephone networks. It is widely used to carry telephonic
+data of various industry standards like E1/T1 data, etc.
+
+T1 Details
+==========
+T1 frame consists of 24 channels of 8 bits each plus one frame alignment bit.
+So a T1 frame has a total of 24x8 + 1 = 193 bits. Since each T1 frame contains
+one byte of voice data for each of 24 channels and the system needs to maintain
+a data rate of 8000 samples/sec. This would require 8000 frames/sec to be sent,
+yielding a data rate of 8000x193 bit/sec = 1.544 Mbps.
+
+E1 Details
+===========
+E1 frame consists of 32 channels each of 8 bits. Thus having a total frame
+length of 32x8 = 256 bits. Similar to the case of T1 it has to maintain a data
+rate of 8000 frames/sec. Thus having a data rate of 8000 x 256 bits/sec =
+2.048 Mbps.
+
+TDM use cases
+=============
+
+With SLIC kind of devices
+=========================
+SLIC stands for Subscriber Line Interface Card.
+Typically TDM systems consist of TDM controller and a line control device.
+
+The TDM controller interfaces to the line control device through TDM interface
+which is digital TDM multiplexed data.
+
+The Line controller has the functionality to interface with the TDM controller
+at one end and interface with the analog units at the other. For example if the
+line control device is a SLIC kind of device.
+The typical setup would be:
+
+|------------------|
+| |
+| | /-------\ |---------|
+| TDM controller |/ TDM \ | SLIC |<--------> s-ch0 analog phone 1
+| |\ data / | |<--------> s-ch1 analog phone 2
+| | \-------/ |---------|<--------> s-ch2 analog phone 3
+| |<----digital----> <analog>
+|------------------|
+
+
+
+Another use case (VoIP):
+========================
+
+ Voice packets on network
+ |--------| |------| _________ |------| |------|
+>----| |/---\| TDM | ( ) | TDM |/---\| |----->
+<----| SLIC |\---/| | ( n/w ) | |\---/| SLIC |-----<
+>----| | |------| --------- |------| | |----->
+ |--------| mux demux |------|
+
+In the above figure analog phones are connected to the hosts via SLICs.
+The voice spoken on the phones is multiplexed converted into VoIP packets
+and sent over network. At the rendering end the multiplexed data
+is de-multiplexed and sent to respective listeners via SLIC.
--
1.5.6.5
^ permalink raw reply related
* [3/3][PATCH][v2] Added TDM device support and Freescale Starlite driver
From: sandeep @ 2012-07-27 14:05 UTC (permalink / raw)
To: linuxppc-dev, linux-arm-kernel; +Cc: devel, Sandeep Singh, linux-kernel
In-Reply-To: <1343397940-12975-2-git-send-email-sandeep@freescale.com>
From: Sandeep Singh <Sandeep@freescale.com>
Freescale TDM controller consists of a TDM module supporting 128 channels
running at up to 50 Mbps with 8-bit and 16-bit word size. The TDM bus connects
gluelessly to most T1/E1 frames as well as to common buses such as the H.110,
SCAS, and MVIP. TDM also supports an I2S mode. The TDM module operates in
independent or shared mode when receiving or transmitting data.
This controller is available on MPC8315, P1010, P1020, P1022 and P1024 Freescale SOCs.
The driver registers itself with the TDM Framework & provides TDM functionality to the client modules.
In its present form this driver supports only channelised mode.
Signed-off-by: Sandeep Singh <Sandeep@freescale.com>
Signed-off-by: Poonam Aggrwal <poonam.aggrwal@freescale.com>
---
Based on: git://git.am.freescale.net/gitolite/mirrors/galak-powerpc.git
Branch: master
Checkpatch: passed
First patch version was RFC
Changes from RFC:
- Enabling Tx FIFO for TDM
- Removed unused variables.
- PMUXCR has been removed as it is taken care by u-boot
Incorporated Timur's comments:
- Improved Copyright statement.
- Removed unused function.
- Introduced read after each write to register
- Used spin_event_timeout for polling
- Removed unused spinlock
- Moved all macros and structures from header file to tdm_fsl.c
- Rectified cosmetic problems.
drivers/tdm/Kconfig | 3 -
drivers/tdm/Makefile | 2 +-
drivers/tdm/device/Kconfig | 15 +
drivers/tdm/device/Makefile | 9 +
drivers/tdm/device/tdm_fsl.c | 1186 ++++++++++++++++++++++++++++++++++++++++++
5 files changed, 1211 insertions(+), 4 deletions(-)
create mode 100644 drivers/tdm/device/Kconfig
create mode 100644 drivers/tdm/device/Makefile
create mode 100644 drivers/tdm/device/tdm_fsl.c
diff --git a/drivers/tdm/Kconfig b/drivers/tdm/Kconfig
index 0b0fda8..69b8987 100644
--- a/drivers/tdm/Kconfig
+++ b/drivers/tdm/Kconfig
@@ -13,6 +13,3 @@ menuconfig TDM
This TDM support can also be built as a module. If so, the module
will be called tdm-core.
-if TDM
-
-endif # TDM
diff --git a/drivers/tdm/Makefile b/drivers/tdm/Makefile
index 84e2cb9..a605b3d 100644
--- a/drivers/tdm/Makefile
+++ b/drivers/tdm/Makefile
@@ -2,4 +2,4 @@
# Makefile for the TDM core.
#
-obj-$(CONFIG_TDM) += tdm-core.o
+obj-$(CONFIG_TDM) += tdm-core.o device/
diff --git a/drivers/tdm/device/Kconfig b/drivers/tdm/device/Kconfig
new file mode 100644
index 0000000..9fd1b06
--- /dev/null
+++ b/drivers/tdm/device/Kconfig
@@ -0,0 +1,15 @@
+#
+# TDM device configuration
+#
+
+menu "TDM Device support"
+
+config TDM_FSL
+ tristate "Driver for Freescale TDM controller"
+ depends on FSL_SOC
+ ---help---
+ This is a driver for Freescale TDM controller. The controller
+ is found in various Freescale SOCs viz MPC8315, P1020. The TDM driver
+ basically multiplexes and demultiplexes data from different channels.
+ The TDM can interface SLIC kind of devices.
+endmenu
diff --git a/drivers/tdm/device/Makefile b/drivers/tdm/device/Makefile
new file mode 100644
index 0000000..4156d7f
--- /dev/null
+++ b/drivers/tdm/device/Makefile
@@ -0,0 +1,9 @@
+#
+# Makefile for the TDM device drivers.
+#
+
+obj-y += tdm_fsl.o
+
+#ifeq ($(CONFIG_TDM_DEBUG_BUS),y)
+#EXTRA_CFLAGS += -DDEBUG
+#endif
diff --git a/drivers/tdm/device/tdm_fsl.c b/drivers/tdm/device/tdm_fsl.c
new file mode 100644
index 0000000..040b0ea
--- /dev/null
+++ b/drivers/tdm/device/tdm_fsl.c
@@ -0,0 +1,1186 @@
+/*
+ * Copyright 2007-2012 Freescale Semiconductor, Inc, All rights reserved.
+ *
+ * TDM driver for Freescale TDM controller.
+ * This driver can interface with SLIC device to run VOIP kind of
+ * applications.
+ *
+ * Author: P. V. Suresh <pala@freescale.com>
+ * Hemant Agrawal <hemant@freescale.com>
+ * Rajesh Gumasta <rajesh.gumasta@freescale.com>
+ *
+ * Modifier: Sandeep Kr. Singh <sandeep@freescale.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Note that this is a complete rewrite of P.V. Suresh's driver code.
+ * But we have used so much of his original code and ideas that it seems
+ * only fair to recognize him as co-author -- Rajesh & Hemant
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/of_platform.h>
+#include <linux/io.h>
+#include <linux/tdm.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/dma-mapping.h>
+#include <linux/spinlock.h>
+#include <sysdev/fsl_soc.h>
+
+#define DRV_DESC "Freescale TDM Driver Adapter"
+#define DRV_NAME "fsl_tdm"
+
+/* TDM data register offset */
+#define TDM_TDR_OFFSET 0x108
+#define TDM_RDR_OFFSET 0x100
+#define TDM_DATAREG_OFFSET 0x100
+#define TDM_CLKREG_OFFSET 0x180
+
+/* TCD params */
+#define SOFF_VAL 0x08
+#define DOFF_VAL 0x08
+#define NBYTES 0x08 /*Minor Bytes transfer count*/
+#define SLAST 0x00 /* last source addr adjustment*/
+#define SLAST_SGA 0x00
+#define DLAST_SGA 0x00
+
+/* RIR Params*/
+#define RIR_RFSD_VAL 0x01
+#define RIR_RFWM_VAL 0x00
+
+/* TIR Params*/
+#define TIR_RFSD_VAL 0x01
+#define TIR_RFWM_VAL 0x00
+
+/* TDMTCEN */
+#define NUM_TDMTCEN_REG 0x04
+#define TDMTCEN_REG_LEN 32
+
+
+#define DMAC_TX_INT 1
+#define DMAC_RX_INT 2
+
+/* DMA GPOR */
+#define DMAGPOR_SNOOP 0x00000040 /* Enable Snooping */
+
+/* DMA Control Register (DMACR) */
+#define DMACR_EMLM 0x00000080 /* Enable Minor loop Mapping */
+#define DMACR_CLM 0x00000040 /* Continuous link mode */
+#define DMACR_HALT 0x00000020 /* Halt DMA */
+#define DMACR_HOE 0x00000010 /* Halt on Error */
+#define DMACR_ERGA 0x00000008 /* Round robin among the groups */
+#define DMACR_ERCA 0x00000004 /* Round robin Port Arbitration */
+#define DMACR_EDBG 0x00000002 /* Debug */
+#define DMACR_EBW 0x00000001 /* Enable Buffer */
+
+/* DMA Error Status DMAES */
+#define DMAES_VLD 0x80000000 /* Logical OR of all DMA errors. */
+#define DMAES_ECX 0x00010000 /* Transfer cancelled */
+#define DMAES_GPE 0x00008000 /* Group priority error */
+#define DMAES_CPE 0x00004000 /* Channel priority error */
+/* errored/cancelled channel */
+#define DMAES_ERRCHN(errch) (((errch) & 0x1F00) >> 8)
+#define DMAES_SAE 0x00000080 /* Source address error */
+#define DMAES_SOE 0x00000040 /* Source offset error */
+#define DMAES_DAE 0x00000020 /* Destination address error */
+#define DMAES_DOE 0x00000010 /* Destination offset error */
+#define DMAES_NCE 0x00000008 /* Nbytes citer error */
+#define DMAES_SGE 0x00000004 /* Scatter gather error */
+#define DMAES_SBE 0x00000002 /* Source bus error */
+#define DMAES_DBE 0x00000001 /* Destination bus error */
+
+/* DMA Enable Request (DMAERQH, DMAERQL) Enable/disable device
+ request for the channel */
+#define DMA_SET_ENABLE_REQUEST(regs, ch) out_8(((regs)->dmasreq), ch)
+#define DMA_CLEAR_ENABLE_REQUEST(regs, ch) out_8(((regs)->dmacerq), ch)
+
+/* DMA Enable Error Interrupt (DMAEEIH, DMAEEIL) Enable/disable
+ error interrupt for the channel */
+#define DMA_SET_ENABLE_ERROR_INT(regs, ch) out_8(((regs)->dmaseei), ch)
+#define DMA_CLEAR_ENABLE_ERROR_INT(regs, ch) out_8(((regs)->dmaceei), ch)
+
+/* Clear interrupt/error for the channel */
+#define DMA_CLEAR_INTT_REQUEST(regs, ch) out_8(((regs)->dmacint), ch)
+#define DMA_CLEAR_ERROR(regs, ch) out_8(((regs)->dmacerr), ch)
+
+/* Clear done bit for the channel */
+#define DMA_CLEAR_DONE_BIT(regs, ch) out_8(((regs)->dmacdne), ch)
+/* Set start bit for the channel */
+#define DMA_SET_START_BIT(regs, ch) out_8(((regs)->dmassrt), ch)
+
+#define TDMTX_DMA_CH 0 /* TDM Tx uses DMA channel 0 HardWired */
+#define TDMRX_DMA_CH 1 /* TDM Rx uses DMA channel 1 Hardwired */
+#define TCD_SIZE 64 /* 64 byte buffer for TCD */
+
+/* Source address modulo */
+#define DMA_TCD1_SMOD(smod) (((smod) & 0x1F) << 27)
+/* Source data transfer size */
+#define DMA_TCD1_SSIZE(ssize) (((ssize) & 0x7) << 24)
+
+/* Destination address modulo */
+#define DMA_TCD1_DMOD(dmod) (((dmod) & 0x1F) << 19)
+/* data transfer size */
+#define DMA_TCD1_DSIZE(dsize) (((dsize) & 0x7) << 16)
+
+/* Source address signed offset */
+#define DMA_TCD1_SOFF(soff) ((soff) & 0xFFFF)
+
+/* Enable link to another channel on minor iteration completion. */
+#define DMA_TCD5_E_MINOR_LINK 0x80000000
+/* Link to this channel. */
+#define DMA_TCD5_LINK_CH(ch) (((ch) & 0x3F) << 25)
+/* Current iteration count when linking disnabled */
+#define DMA_TCD5_CITER_DISABLE_LINK(citer) (((citer) & 0x7FFF) << 16)
+/* Current iteration count when linking enabled */
+#define DMA_TCD5_CITER_ENABLE_LINK(citer) (((citer) & 0x00FF) << 16)
+/* Destination address signed offset */
+#define DMA_TCD5_DOFF(doff) ((doff) & 0xFFFF)
+
+/* Beginning iteration count when linking disabled */
+#define DMA_TCD7_BITER_DISABLE_LINK(citer) (((citer) & 0x7FFF) << 16)
+/* Beginning iteration count when linking enabled */
+#define DMA_TCD7_BITER_ENABLE_LINK(citer) (((citer) & 0x00FF) << 16)
+#define DMA_TCD7_BWC(bw) (((bw)&0x3)<<14) /* BandWidth Control. */
+/* Link channel number */
+#define DMA_TCD7_LINKCH(ch) (((ch) & 0x1F) << 8)
+#define DMA_TCD7_DONE 0x00000080 /* Channel done */
+#define DMA_TCD7_ACTIVE 0x00000040 /* Channel active */
+#define DMA_TCD7_E_MAJOR_LINK 0x00000020 /* channel to channel linking */
+#define DMA_TCD7_E_SG 0x00000010 /* Enable scatter gather */
+#define DMA_TCD7_D_REQ 0x00000008 /* Disable request */
+/* interrupt on half major counter */
+#define DMA_TCD7_INT_HALF 0x00000004
+#define DMA_TCD7_INT_MAJ 0x00000002 /* interrupt on major counter */
+#define DMA_TCD7_START 0x00000001 /* Channel start */
+
+/* Source data transfer size */
+#define SSIZE_08BITS 0x00
+#define SSIZE_16BITS 0x01
+#define SSIZE_32BITS 0x02
+#define SSIZE_64BITS 0x03
+
+/* max number of TDM-DMA channels */
+#define DMA_MAX_CHANNELS 4
+
+/* each DMA-ch contains 8 Transfer Control Discriptors */
+#define MAX_TCD_WORD 8
+
+/* TDMGIR General Interface Register */
+#define GIR_LPBK 0x00000004 /* loopback mode */
+#define GIR_CTS 0x00000002 /* Common TDM signals */
+#define GIR_RTS 0x00000001 /* Rx & Tx sharing */
+
+/* TDMRIR Recieve Interface Rgister */
+#define RIR_RFWM_MASK 0x00000003 /* Recieve FIFO Watermark */
+#define RIR_RFWM_SHIFT 16
+#define RIR_RFWM(x) ((x & RIR_RFWM_MASK) << RIR_RFWM_SHIFT)
+#define RIR_RFEN 0x00008000 /* Recieve FIFO Enable */
+#define RIR_RWEN 0x00004000 /* Recieve Wide FIFO Enable */
+#define RIR_RDMA 0x00000040 /* Recieve DMA Enable */
+#define RIR_RFSD_SHIFT 0x00000004 /* Recieve Frame Sync Delay */
+#define RIR_RFSD_MASK 0x00000003
+#define RIR_RFSD(x) ((x & RIR_RFSD_MASK) << RIR_RFSD_SHIFT)
+#define RIR_RSO 0x00002000 /* Recieve sync Out */
+#define RIR_RSL 0x00000800 /* Recieve sync Out Length */
+#define RIR_RSOE 0x00000400 /* Recieve sync Out Edge */
+#define RIR_RCOE 0x00000200 /* Recieve Clock Output Enable */
+#define RIR_RSA 0x00000008 /* Recieve Sync Active */
+#define RIR_RDE 0x00000004 /* Recieve Data Edge */
+#define RIR_RFSE 0x00000002 /* Recieve Frame Sync Edge */
+#define RIR_RRDO 0x00000001 /* Revieve Reversed Data Order */
+
+/* TDMTIR Transmit Interface Rgister */
+#define TIR_TFWM_MASK 0x00000003 /* Transmit FIFO Watermark */
+#define TIR_TFWM_SHIFT 16
+#define TIR_TFWM(x) ((x & TIR_TFWM_MASK) << TIR_TFWM_SHIFT)
+#define TIR_TFEN 0x00008000 /* Transmit FIFO Enable */
+#define TIR_TWEN 0x00004000 /* Transmit Wide FIFO Enable */
+#define TIR_TDMA 0x00000040 /* Transmit DMA Enable */
+#define TIR_TFSD_SHIFT 0x00000004 /* Transmit Frame Sync Delay */
+#define TIR_TFSD_MASK 0x00000003
+#define TIR_TFSD(x) ((x & TIR_TFSD_MASK) << TIR_TFSD_SHIFT)
+#define TIR_TSO 0x00002000 /* Transmit Sync Output */
+#define TIR_TSL 0x00000800 /* Transmit sync Out Length */
+#define TIR_TSOE 0x00000400 /* Transmit sync Out Edge */
+#define TIR_TCOE 0x00000200 /* Transmit Clock Output Enable */
+#define TIR_TSA 0x00000008 /* Transmit Sync Active */
+#define TIR_TDE 0x00000004 /* Transmit Data Edge */
+#define TIR_TFSE 0x00000002 /* Transmit Frame Sync Edge */
+#define TIR_TRDO 0x00000001 /* Transmit Reversed Data Order */
+
+/*TDMRFP Revieve Frame Parameters */
+#define RFP_RNCF_SHIFT 0x00000010 /* Number of Channels in TDM Frame */
+#define RFP_RNCF_MASK 0x000000FF
+#define RFP_RNCF(x) (((x - 1) & RFP_RNCF_MASK) << RFP_RNCF_SHIFT)
+#define RFP_RCS_SHIFT 0x00000004 /* Recieve Channel Size */
+#define RFP_RCS_MASK 0x00000003
+#define RFP_RCS(x) ((x & RFP_RCS_MASK) << RFP_RCS_SHIFT)
+#define RFP_RT1 0x00000002 /* Recieve T1 Frame */
+
+/*TDMTFP Transmit Frame Parameters */
+#define TFP_TNCF_SHIFT 0x00000010 /* Number of Channels in TDM Frame */
+#define TFP_TNCF_MASK 0x000000FF
+#define TFP_TNCF(x) (((x - 1) & TFP_TNCF_MASK) << TFP_TNCF_SHIFT)
+#define TFP_TCS_SHIFT 0x00000004 /* Transmit Channel Size */
+#define TFP_TCS_MASK 0x00000003
+#define TFP_TCS(x) ((x & RFP_RCS_MASK) << RFP_RCS_SHIFT)
+#define TFP_TT1 0x00000002 /* Transmit T1 Frame */
+
+
+/* TDMRCR Recieve Control Register */
+#define RCR_REN 0x00000001 /* Recieve Enable */
+/* TDMTCR Transmit Control Register */
+#define TCR_TEN 0x00000001 /* Transmit Enable */
+
+/* TDMRIER receive interrupt enable register */
+#define RIER_RCEUE 0x00000100 /* Channel Enable Update Enable */
+#define RIER_RLCEE 0x00000080 /* Recieve Last Channel Event Enable */
+#define RIER_RFSEE 0x00000040 /* Recieve Frame Sync Event Enable */
+#define RIER_RFFEE 0x00000020 /* Recieve FIFO Full Event Enable */
+#define RIER_RDREE 0x00000010 /* Recieve Data Ready Event Enable */
+#define RIER_RSEEE 0x00000008 /* Recieve Sync Error Event Enable */
+#define RIER_ROEE 0x00000004 /* Recieve Overrun Event Enable */
+
+/* TDMTIER transmit interrupt enable register */
+#define TIER_TCEUE 0x00000100 /* Channel Enable Update Enable */
+#define TIER_TLCEE 0x00000080 /* Transmit Last Channel Event */
+#define TIER_TFSEE 0x00000040 /* Transmit Frame Sync Event Enable */
+#define TIER_TFFEE 0x00000020 /* Transmit FIFO Full Event Enable */
+#define TIER_TDREE 0x00000010 /* Transmit Data Ready Event Enable */
+#define TIER_TSEEE 0x00000008 /* Transmit Sync Error Event Enable */
+#define TIER_TUEE 0x00000004 /* Transmit Overrun Event Enable */
+
+/* TDMRER Recieve Event Register */
+#define RER_RCEU 0x00000100 /* Recieve Channel Enable Update */
+#define RER_RLCE 0x00000080 /* Recieve Last Channel Event */
+#define RER_RFSE 0x00000040 /* Recieve Frame Sync Event */
+#define RER_RFFE 0x00000020 /* Recieve FIFO Full Event */
+#define RER_RDRE 0x00000010 /* Recieve Data Ready Event */
+#define RER_RSEE 0x00000008 /* Recieve Sync Error Event */
+#define RER_ROE 0x00000004 /* Recieve Overrun Event */
+
+/* TDMTER Transmit Event Register */
+#define TER_TCEU 0x00000100 /* Transmit Channel Enable Update */
+#define TER_TLCE 0x00000080 /* Transmit Last Channel Event */
+#define TER_TFSE 0x00000040 /* Transmit Frame Sync Event */
+#define TER_TFEE 0x00000020 /* Transmit FIFO Full Event */
+#define TER_TDRE 0x00000010 /* Transmit Data Ready Event */
+#define TER_TSEE 0x00000008 /* Transmit Sync Error Event */
+#define TER_TUE 0x00000004 /* Transmit Overrun Event */
+
+/* TDMRSR Recieve Status Register */
+#define RSR_RFCNT 0x00000038 /* Recieve FIFO counter */
+#define RSSS_MASK 0x00000003 /* Recieve SYNC Status */
+#define RSR_RSSS_SHIFT 1
+#define RSR_RSSS(sss) (((sss) >> (RSR_RSSS_SHIFT)) & (RSR_RSSS_MASK))
+#define RSR_RENS 0x00000001 /* Recieve Enable Status */
+
+/* TDMTSR Transmit Status Register */
+#define TSR_TFCNT 0x00000038 /* Transmit FIFO counter */
+#define TSR_TSSS_MASK 0x00000003 /* Transmit SYNC Status */
+#define TSR_TSSS_SHIFT 1
+#define TSR_TSSS(sss) (((sss) >> (TSR_TSSS_SHIFT)) & TSR_TSSS_MASK)
+#define TSR_TENS 0x00000001 /* Transmit Enable Status */
+
+
+/* channel parameters */
+#define TDM_ENABLE_TIMEOUT 1000 /* time out for TDM rx, tx enable */
+#define NUM_OF_TDM_BUF 3 /* Number of tdm buffers for startlite
+ DMA */
+#define ALIGNED_2_BYTES 0x02 /* 2-bytes alignment */
+#define ALIGNED_4_BYTES 0x04 /* 4-bytes alignment */
+#define ALIGNED_8_BYTES 0x08 /* 8-bytes alignment */
+#define ALIGNED_16_BYTES 0x10 /* 16-bytes alignment */
+#define ALIGNED_32_BYTES 0x20 /* 32-bytes alignment */
+#define ALIGNED_64_BYTES 0x40 /* 64-bytes alignment */
+
+static int tdmen = 1;
+
+module_param(tdmen, int, S_IRUSR);
+MODULE_PARM_DESC(tdmen, "Enable TDM: Enable=1, Disable=0(default)");
+
+/* DMAC TCD structure */
+struct tcd {
+ u32 tcd[MAX_TCD_WORD];
+};
+
+/* DMA Controllor */
+struct dmac_regs {
+ u32 dmacr; /* DMA Control Register */
+ u32 dmaes; /* DMA Error Status Register */
+ u32 dmaerqh; /* DMA Enable Request */
+ u32 dmaerql; /* DMA Enable Request */
+ u32 dmaeeih; /* DMA Enable Error Interrupt */
+ u32 dmaeeil; /* DMA Enable Error Interrupt */
+
+ u8 dmaserq; /* DMA Set Enable Request */
+ u8 dmacerq; /* DMA Clear Enable Request */
+ u8 dmaseei; /* DMA Set Enable Error Interrupt */
+ u8 dmaceei; /* DMA Clear Enable Error Interrupt */
+
+ u8 dmacint; /* DMA Clear Interrupt Request */
+ u8 dmacerr; /* DMA Clear Error */
+ u8 dmassrt; /* DMA Set Start Bit */
+ u8 dmacdne; /* DMA Clear Done Bit */
+
+ u32 dmainth; /* DMA Interrupt Request High */
+ u32 dmaintl; /* DMA Interrupt Request */
+ u32 dmaerrh; /* DMA Error */
+ u32 dmaerrl; /* DMA Error */
+ u32 dmahrsh; /* DMA Hardware Request status */
+ u32 dmahrsl; /* DMA HardWired Request status */
+ u32 dmagpor; /* DMA General Purpose Register */
+ u8 reserved0[0xC4];
+ u8 dchpri[DMA_MAX_CHANNELS]; /* DMA Port Priority */
+ u8 reserved1[0xEFC];
+ struct tcd tcd[DMA_MAX_CHANNELS]; /*Transfer Control Descriptor */
+};
+
+/* TDM Control Registers. */
+struct tdm_regs {
+ u32 gir; /* General Interface Register */
+ u32 rir; /* Receive Interface Register */
+ u32 tir; /* Transmit Interface Register */
+ u32 rfp; /* Receive Frame Parameters */
+ u32 tfp; /* Transmit Frame Parameters */
+ u8 reserved0[0xC];
+ u32 rcen[4]; /* Recieve Channel Enabled */
+ u8 reserved1[0x10];
+ u32 tcen[4]; /* Transmit Channel Enabled */
+ u8 reservedd2[0x10];
+ u32 tcma[4]; /* Transmit Channel Mask */
+ u8 reservederved3[0x10];
+ u32 rcr; /* Receiver Control Register */
+ u32 tcr; /* Transmitter Control Register */
+ u32 rier; /* Receive Interrupt Enable Register */
+ u32 tier; /* Transmit Interrupt Enable Register */
+ u8 reserved4[0x10];
+ u32 rer; /* Receive Event Register */
+ u32 ter; /* Transmit Event Register */
+ u32 rsr; /* Receive Status Register */
+ u32 tsr; /* Transmit Status Register */
+};
+
+struct tdm_data {
+ u64 rdr; /* Receive Data Register */
+ u64 tdr; /* Transmit Dataa Register */
+};
+
+struct tdm_clock {
+ u32 rx; /* Transmit Dataa Register */
+ u32 tx; /* Receive Data Register */
+};
+
+
+struct tdm_priv {
+ struct tdm_regs __iomem *tdm_regs;
+ struct tdm_data __iomem *data_regs;
+ struct dmac_regs __iomem *dmac_regs;
+ struct tdm_clock __iomem *clk_regs;
+ u32 ptdm_base;
+ u8 *tdm_input_data;
+ u8 *tdm_output_data;
+ dma_addr_t dma_input_paddr; /* dma mapped buffer for TDM Rx */
+ dma_addr_t dma_output_paddr; /* dma mapped buffer for TDM Tx */
+ void *dma_input_vaddr;
+ void *dma_output_vaddr;
+ u32 phase_rx;
+ u32 phase_tx;
+ struct tcd *dma_rx_tcd[NUM_OF_TDM_BUF];
+ struct tcd *dma_tx_tcd[NUM_OF_TDM_BUF];
+ dma_addr_t dma_rx_tcd_paddr;
+ dma_addr_t dma_tx_tcd_paddr;
+ void *dma_rx_tcd_vaddr;
+ void *dma_tx_tcd_vaddr;
+ u32 tdm_buffer_size;
+ u32 tdm_err_intr;
+ u32 dmac_err_intr;
+ u32 dmac_done_intr;
+ int tdm_active;
+ struct device *device;
+ spinlock_t tdmlock;
+ struct tdm_adapter *adap;
+};
+
+/* Extend a given size to make it alignable */
+static inline int ALIGNABLE_SIZE(u32 size, u32 alignment)
+{
+ return size + alignment - 1;
+}
+
+/* Align a given address */
+static inline void *ALIGN_ADDRESS(void *address, u32 alignment)
+{
+ return (void *)(((unsigned long) address + alignment - 1) &
+ (~(alignment - 1)));
+}
+
+/* Size of the buffer */
+static inline int TDM_1BUF_SIZE(u32 num_ch, u32 channel_size, u32 frame_size)
+{
+ return frame_size *
+ ALIGN(channel_size * num_ch, ALIGNED_8_BYTES);
+}
+
+/* Alignable size of the 3 buffers */
+static inline int TDM_3BUF_SIZE(u32 num_ch, u32 channel_size, u32 frame_size)
+{
+ return
+ ALIGNABLE_SIZE((TDM_1BUF_SIZE(num_ch, channel_size, frame_size) *
+ NUM_OF_TDM_BUF), ALIGNED_8_BYTES);
+}
+
+
+
+/* Initialize the Tx Transmit Control Descriptor parameters*/
+static void tx_tcd_init(struct tdm_priv *priv)
+{
+ int i;
+ u32 iter;
+ u32 offset;
+ dma_addr_t physaddr;
+ struct tdm_adapter *adap;
+ int bytes_in_fifo_per_frame;
+ adap = priv->adap;
+
+ if (!adap) {
+ dev_err(priv->device, "%s:Invalid handle\n", __func__);
+ return;
+ }
+ bytes_in_fifo_per_frame =
+ ALIGN(adap->adapt_cfg.num_ch * adap->adapt_cfg.slot_width, 8);
+
+ iter = (bytes_in_fifo_per_frame / NBYTES) * adap->adapt_cfg.num_frames;
+
+ for (i = 0; i < NUM_OF_TDM_BUF; i++) {
+ offset = i * adap->adapt_cfg.num_frames *
+ bytes_in_fifo_per_frame;
+ /* TCD word 0: source addr */
+ priv->dma_tx_tcd[i]->tcd[0] = (u32)priv->dma_output_paddr
+ + offset;
+
+ /* TCD word 1: ssize=dsize=64bit, soff=8, smod=dmod=0 */
+ priv->dma_tx_tcd[i]->tcd[1] =
+ DMA_TCD1_SOFF(SOFF_VAL) | DMA_TCD1_SSIZE(SSIZE_64BITS) |
+ DMA_TCD1_DSIZE(SSIZE_64BITS);
+
+ /*
+ * TCD word 2: number of bytes for minor loop, wide fifo
+ * 8 bytes for dma
+ */
+ priv->dma_tx_tcd[i]->tcd[2] = NBYTES;
+
+ /* TCD word 3: last source addr adjustment = 0 */
+ priv->dma_tx_tcd[i]->tcd[3] = SLAST;
+
+ /* TCD word 4: destination addr */
+ priv->dma_tx_tcd[i]->tcd[4] = TDM_TDR_OFFSET + priv->ptdm_base;
+
+ /*
+ * channel to channel linking is disabled ,
+ * destination offset is inc destination adr by 8,
+ * current iteration(citer) = number of transfers for frame
+ */
+ /* TCD word 5: citer count, dest addr offset */
+ priv->dma_tx_tcd[i]->tcd[5] = DMA_TCD5_CITER_DISABLE_LINK(iter);
+
+ /* TCD word 6: enable scater gather, interrupt on 1 Frame, */
+ priv->dma_tx_tcd[i]->tcd[6] = SLAST_SGA;
+
+ /*
+ * TCD word 7: begining major iteration count(biter),
+ * channel control/status
+ */
+ priv->dma_tx_tcd[i]->tcd[7] =
+ DMA_TCD7_BITER_DISABLE_LINK(iter) | DMA_TCD7_E_SG;
+ }
+
+ /* Linking the TCDs togather for SG operation */
+ physaddr = priv->dma_tx_tcd_paddr;
+ priv->dma_tx_tcd[2]->tcd[6] = ALIGN(physaddr, ALIGNED_32_BYTES);
+ physaddr += TCD_SIZE;
+ priv->dma_tx_tcd[0]->tcd[6] = ALIGN(physaddr, ALIGNED_32_BYTES);
+ physaddr += TCD_SIZE;
+ priv->dma_tx_tcd[1]->tcd[6] = ALIGN(physaddr, ALIGNED_32_BYTES);
+}
+
+/* Initialize the Rx Transmit Control Discriptor parameters */
+static void rx_tcd_init(struct tdm_priv *priv)
+{
+ int i;
+ u32 iter;
+ u32 offset;
+ dma_addr_t physaddr;
+ struct tdm_adapter *adap;
+ int bytes_in_fifo_per_frame;
+ adap = priv->adap;
+ bytes_in_fifo_per_frame =
+ ALIGN(adap->adapt_cfg.num_ch * adap->adapt_cfg.slot_width, 8);
+
+ iter = (bytes_in_fifo_per_frame / NBYTES) * adap->adapt_cfg.num_frames;
+
+ for (i = 0; i < NUM_OF_TDM_BUF; i++) {
+ /* TCD word 0: source addr */
+ priv->dma_rx_tcd[i]->tcd[0] = TDM_RDR_OFFSET + priv->ptdm_base;
+
+ /* TCD word 1: ssize=dsize=64bit, soff=smod=dmod=0 */
+ priv->dma_rx_tcd[i]->tcd[1] =
+ DMA_TCD1_SSIZE(SSIZE_64BITS) |
+ DMA_TCD1_DSIZE(SSIZE_64BITS);
+
+ /*
+ * TCD word 2: number of bytes for minor loop,
+ * wide fifo 8 bytes for dma
+ */
+ priv->dma_rx_tcd[i]->tcd[2] = NBYTES;
+
+ /* TCD word 3: last source addr adjustment = 0 */
+ priv->dma_rx_tcd[i]->tcd[3] = SLAST;
+
+ offset = i * adap->adapt_cfg.num_frames *
+ bytes_in_fifo_per_frame;
+
+ /* TCD word 4: destination addr */
+ priv->dma_rx_tcd[i]->tcd[4] = (u32)priv->dma_input_paddr
+ + offset;
+
+ /*
+ * channel to channel linking is disabled ,
+ * destination offset is inc destination adr by 8,
+ * current iteration(citer) = number of transfers for frame
+ */
+ /* TCD word 5: citer count, dest addr offset */
+ priv->dma_rx_tcd[i]->tcd[5] =
+ DMA_TCD5_DOFF(DOFF_VAL) |
+ DMA_TCD5_CITER_DISABLE_LINK(iter);
+
+ /* TCD word 6: enable scater gather, interrupt on 1 Frame, */
+ priv->dma_rx_tcd[i]->tcd[6] = DLAST_SGA;
+
+ /*
+ * TCD word 7: begining major iteration count(biter),
+ * channel control/status
+ */
+ priv->dma_rx_tcd[i]->tcd[7] =
+ DMA_TCD7_BITER_DISABLE_LINK(iter) | DMA_TCD7_E_SG |
+ DMA_TCD7_INT_MAJ;
+ }
+
+ /* Next TCD for SG operation */
+ physaddr = priv->dma_rx_tcd_paddr;
+ priv->dma_rx_tcd[2]->tcd[6] = ALIGN(physaddr, ALIGNED_32_BYTES);
+ physaddr += TCD_SIZE;
+ priv->dma_rx_tcd[0]->tcd[6] = ALIGN(physaddr, ALIGNED_32_BYTES);
+ physaddr += TCD_SIZE;
+ priv->dma_rx_tcd[1]->tcd[6] = ALIGN(physaddr, ALIGNED_32_BYTES);
+}
+
+static irqreturn_t dmac_done_isr(int irq, void *p)
+{
+ u32 ch;
+ int ret = IRQ_NONE;
+ struct tdm_priv *priv;
+
+ priv = p;
+
+ ch = in_be32(&priv->dmac_regs->dmaintl);
+
+ /* clear interrupt */
+ if (ch & DMAC_RX_INT) {
+ out_8(&priv->dmac_regs->dmacint, TDMRX_DMA_CH);
+ ret = IRQ_HANDLED;
+ /* track phases for Rx/Tx */
+ priv->phase_rx += 1;
+ if (priv->phase_rx == NUM_OF_TDM_BUF)
+ priv->phase_rx = 0;
+ }
+ if (ch & DMAC_TX_INT) {
+ out_8(&priv->dmac_regs->dmacint, TDMTX_DMA_CH);
+ ret = IRQ_HANDLED;
+ }
+
+ if (ret == IRQ_HANDLED) {
+ /* set the flag and wake up the thread */
+ priv->adap->tdm_rx_flag = 1;
+
+ /* schedule the tasklet */
+ if (priv->adap->tasklet_conf)
+ tasklet_schedule(&priv->adap->tdm_data_tasklet);
+ }
+ return ret;
+}
+
+static int init_tdm(struct tdm_priv *priv)
+{
+ u8 *buf;
+ int i;
+ int buf_size;
+ dma_addr_t physaddr = 0;
+ int ret = 0;
+ struct tdm_adapter *adap;
+
+
+ adap = priv->adap;
+
+ /*
+ * Allocate memory for Rx/Tx buffer according to active time slots
+ * BufferSize = NUM_OF_TDM_BUF * NUM_SAMPLES_PER_FRAME * slot_width *
+ * num_ch
+ */
+ /*Allocating Rx Buffer*/
+ buf_size = TDM_3BUF_SIZE(adap->adapt_cfg.num_ch,
+ adap->adapt_cfg.slot_width,
+ adap->adapt_cfg.num_frames);
+ buf = dma_alloc_coherent(priv->device, buf_size, &physaddr, GFP_KERNEL);
+ if (!buf) {
+ ret = -ENOMEM;
+ goto err_alloc_ip;
+ }
+ priv->dma_input_paddr = physaddr;
+ priv->dma_input_vaddr = buf;
+ priv->tdm_input_data = ALIGN_ADDRESS(buf, ALIGNED_8_BYTES);
+
+ buf = dma_alloc_coherent(priv->device, buf_size, &physaddr, GFP_KERNEL);
+ if (!buf) {
+ ret = -ENOMEM;
+ goto err_alloc_op;
+ }
+ priv->dma_output_paddr = physaddr;
+ priv->dma_output_vaddr = buf;
+ priv->tdm_output_data = ALIGN_ADDRESS(buf, ALIGNED_8_BYTES);
+
+ /* allocate memory for TCD buffer descriptors */
+ buf = dma_alloc_coherent(priv->device, NUM_OF_TDM_BUF * TCD_SIZE,
+ &physaddr, GFP_KERNEL);
+ if (!buf) {
+ ret = -ENOMEM;
+ goto err_alloc_rx;
+ }
+
+ memset(buf, 0, NUM_OF_TDM_BUF * TCD_SIZE);
+ priv->dma_rx_tcd_paddr = physaddr;
+ priv->dma_rx_tcd_vaddr = buf;
+ for (i = 0; i < NUM_OF_TDM_BUF; i++) {
+ priv->dma_rx_tcd[i] = ALIGN_ADDRESS(buf, ALIGNED_32_BYTES);
+ buf += TCD_SIZE;
+ }
+
+ buf = dma_alloc_coherent(priv->device, 3 * TCD_SIZE, &physaddr,
+ GFP_KERNEL);
+ if (!buf) {
+ ret = -ENOMEM;
+ goto err_alloc_tx;
+ }
+ memset(buf, 0, NUM_OF_TDM_BUF * TCD_SIZE);
+ priv->dma_tx_tcd_paddr = physaddr;
+ priv->dma_tx_tcd_vaddr = buf;
+ for (i = 0; i < NUM_OF_TDM_BUF; i++) {
+ priv->dma_tx_tcd[i] = ALIGN_ADDRESS(buf, ALIGNED_32_BYTES);
+ buf += TCD_SIZE;
+ }
+
+ priv->phase_rx = 0;
+ priv->phase_tx = 0;
+ return 0;
+
+err_alloc_tx:
+ dma_free_coherent(priv->device, NUM_OF_TDM_BUF * TCD_SIZE,
+ priv->dma_rx_tcd_vaddr, priv->dma_rx_tcd_paddr);
+err_alloc_rx:
+ dma_free_coherent(priv->device, buf_size, priv->dma_output_vaddr,
+ priv->dma_output_paddr);
+err_alloc_op:
+ dma_free_coherent(priv->device, buf_size, priv->dma_input_vaddr,
+ priv->dma_input_paddr);
+err_alloc_ip:
+ return ret;
+}
+
+/* TDM register programming */
+static int tdm_fsl_reg_init(struct tdm_priv *priv)
+{
+ int i;
+ int ch_size_type;
+ struct tdm_adapter *adap;
+
+ if (!priv) {
+ pr_err("%s: Invalid handle\n", __func__);
+ return -EINVAL;
+ }
+ adap = priv->adap;
+
+ /* channel/group round robin */
+ out_be32(&priv->dmac_regs->dmacr, DMACR_ERGA | DMACR_ERCA);
+ /* Enable error Interrupts for TDM Rx &Tx */
+ out_8(&priv->dmac_regs->dmaseei, TDMTX_DMA_CH);
+ in_8(&priv->dmac_regs->dmaseei);
+ out_8(&priv->dmac_regs->dmaseei, TDMRX_DMA_CH);
+ in_8(&priv->dmac_regs->dmaseei);
+ out_be32(&priv->dmac_regs->dmagpor, DMAGPOR_SNOOP);
+
+ tx_tcd_init(priv);
+ rx_tcd_init(priv);
+
+ /* TDM RD->TD loopback, Share T/R Fsync,Clock */
+ if (adap->adapt_cfg.loopback)
+ out_be32(&priv->tdm_regs->gir, GIR_LPBK | GIR_RTS);
+ else
+ out_be32(&priv->tdm_regs->gir, GIR_RTS);
+
+ /*
+ * Rx Water mark 0, FIFO enable, Wide fifo, DMA enable for RX,
+ * Receive Sync out, syncwidth = ch width, Rx clk out,zero sync,
+ * falling edge , data order
+ */
+
+ out_be32(&priv->tdm_regs->rir,
+ RIR_RFWM(RIR_RFWM_VAL) | RIR_RFEN | RIR_RWEN |
+ RIR_RDMA | RIR_RSL | RIR_RSO | RIR_RCOE | RIR_RRDO |
+ RIR_RFSD(RIR_RFSD_VAL));
+ out_be32(&priv->tdm_regs->tir,
+ TIR_TFWM(TIR_RFWM_VAL) | TIR_TFEN | TIR_TWEN |
+ TIR_TDMA | TIR_TSL | TIR_TSO | TIR_TRDO |
+ TIR_TFSD(TIR_RFSD_VAL));
+
+ /* no of channels ,Channel size-coading */
+ switch (adap->adapt_cfg.ch_size_type) {
+ case CHANNEL_8BIT_LIN:
+ ch_size_type = CHANNEL_8BIT_LIN;
+ break;
+ case CHANNEL_8BIT_ULAW:
+ ch_size_type = CHANNEL_8BIT_ULAW;
+ break;
+ case CHANNEL_8BIT_ALAW:
+ ch_size_type = CHANNEL_8BIT_ALAW;
+ break;
+ case CHANNEL_16BIT_LIN:
+ ch_size_type = CHANNEL_16BIT_LIN;
+ break;
+ default:
+ dev_err(priv->device, "%s:Invalid channel size_type\n"
+ "Setting channel to default size: 16 bits",
+ __func__);
+ ch_size_type = CHANNEL_16BIT_LIN;
+ }
+ out_be32(&priv->tdm_regs->rfp,
+ RFP_RNCF(adap->adapt_cfg.num_ch) |
+ RFP_RCS(ch_size_type));
+ out_be32(&priv->tdm_regs->tfp,
+ TFP_TNCF(adap->adapt_cfg.num_ch) |
+ TFP_TCS(ch_size_type));
+
+ out_be32(&priv->tdm_regs->rier, 0);
+ out_be32(&priv->tdm_regs->tier, 0);
+
+ /* clear all receive and transmit chs */
+ for (i = 0; i < 4; i++) {
+ out_be32(&priv->tdm_regs->tcma[i], 0);
+ out_be32(&priv->tdm_regs->tcen[i], 0);
+ out_be32(&priv->tdm_regs->rcen[i], 0);
+ }
+
+ return 0;
+
+}
+
+static void tdm_fsl_stop(struct tdm_priv *priv)
+{
+ /* stop the Tx & Rx */
+ out_be32(&priv->tdm_regs->tcr, 0);
+ out_be32(&priv->tdm_regs->rcr, 0);
+
+ /* Clear DMA error Enable Request DMAEEIH/L */
+ out_8(&priv->dmac_regs->dmaceei, TDMTX_DMA_CH);
+ in_8(&priv->dmac_regs->dmaceei);
+ out_8(&priv->dmac_regs->dmaceei, TDMRX_DMA_CH);
+ in_8(&priv->dmac_regs->dmaceei);
+ out_8(&priv->dmac_regs->dmacint, TDMRX_DMA_CH);
+ in_8(&priv->dmac_regs->dmacint);
+ out_8(&priv->dmac_regs->dmacint, TDMTX_DMA_CH);
+ in_8(&priv->dmac_regs->dmacint);
+
+ /* disable the dma request */
+ out_8(&priv->dmac_regs->dmacerq, TDMRX_DMA_CH);
+ in_8(&priv->dmac_regs->dmacerq);
+ out_8(&priv->dmac_regs->dmacerq, TDMTX_DMA_CH);
+}
+
+static int tdm_fsl_disable(struct tdm_adapter *adap)
+{
+ struct tdm_priv *priv;
+
+ priv = adap->data;
+ if (priv->tdm_active == 0) {
+ dev_warn(priv->device, "already Disabled");
+ return 0;
+ }
+
+ spin_lock(&priv->tdmlock);
+ priv->tdm_active = 0;
+ spin_unlock(&priv->tdmlock);
+
+ return 0;
+}
+
+static int tdm_fsl_enable(struct tdm_adapter *adap)
+{
+ int i;
+ u32 ch_enab[NUM_TDMTCEN_REG] = {0};
+ unsigned long timeout;
+ struct tdm_priv *priv;
+ u32 ph;
+
+ priv = adap->data;
+ ph = priv->phase_tx;
+
+ if (priv->tdm_active == 1) {
+ dev_warn(priv->device, "already Enabled");
+ return 0;
+ }
+
+ /* enable the Channels required 0 to number of ch -1 */
+ for (i = 0; i < adap->adapt_cfg.num_ch; i++)
+ ch_enab[i / TDMTCEN_REG_LEN] |= (1 << (i & 0x1F));
+
+ for (i = 0; i < NUM_TDMTCEN_REG; i++) {
+ out_be32(&priv->tdm_regs->rcen[i], ch_enab[i]);
+ out_be32(&priv->tdm_regs->tcen[i], ch_enab[i]);
+ }
+
+ /* Clear the DONE bit */
+ out_8(&priv->dmac_regs->dmacdne, TDMRX_DMA_CH);
+ in_8(&priv->dmac_regs->dmacdne);
+ out_8(&priv->dmac_regs->dmacdne, TDMTX_DMA_CH);
+
+ /* Load the Tx transfer control descriptors */
+ for (i = 0; i < MAX_TCD_WORD; i++)
+ out_be32(&priv->dmac_regs->tcd[TDMTX_DMA_CH].tcd[i],
+ priv->dma_tx_tcd[ph]->tcd[i]);
+
+ /* Load the Rx transfer control descriptors */
+ for (i = 0; i < MAX_TCD_WORD; i++)
+ out_be32(&priv->dmac_regs->tcd[TDMRX_DMA_CH].tcd[i],
+ priv->dma_rx_tcd[ph]->tcd[i]);
+
+ /* enable the dma request */
+ out_8(&priv->dmac_regs->dmaserq, TDMRX_DMA_CH);
+ in_8(&priv->dmac_regs->dmaserq);
+ out_8(&priv->dmac_regs->dmaserq, TDMTX_DMA_CH);
+
+ /* Enable Receiver, transmitter */
+ timeout = jiffies + TDM_ENABLE_TIMEOUT;
+ out_be32(&priv->tdm_regs->tcr, TCR_TEN);
+ spin_event_timeout(!(in_be32(&priv->tdm_regs->tsr) & TSR_TENS),
+ timeout, 0);
+
+ timeout = jiffies + TDM_ENABLE_TIMEOUT;
+ out_be32(&priv->tdm_regs->rcr, RCR_REN);
+ spin_event_timeout(!(in_be32(&priv->tdm_regs->rsr) & RSR_RENS),
+ timeout, 0);
+
+ spin_lock(&priv->tdmlock);
+ priv->tdm_active = 1;
+ spin_unlock(&priv->tdmlock);
+
+ return 1;
+}
+
+static int tdm_fsl_read(struct tdm_adapter *adap,
+ u16 **input_tdm_buffer)
+{
+ struct tdm_priv *priv;
+ u32 phase_rx;
+ u32 buf_addr;
+ int bytes_in_fifo_per_frame, buf_size;
+
+ /* point to where to start for the current phase data processing */
+ bytes_in_fifo_per_frame =
+ ALIGN(adap->adapt_cfg.num_ch * adap->adapt_cfg.slot_width, 8);
+
+ priv = adap->data;
+ if (!priv) {
+ pr_err("%s: Invalid handle\n", __func__);
+ return -EINVAL;
+ }
+
+ if (priv->tdm_active == 0) {
+ dev_warn(priv->device, "TDM is not ready");
+ return 0;
+ }
+
+ if (priv->phase_rx == 0)
+ phase_rx = NUM_OF_TDM_BUF - 1;
+ else
+ phase_rx = priv->phase_rx - 1;
+
+ buf_size = bytes_in_fifo_per_frame * adap->adapt_cfg.num_frames;
+ buf_addr = buf_size * phase_rx;
+ *input_tdm_buffer = (u16 *)(priv->tdm_input_data + buf_addr);
+
+ return buf_size;
+}
+
+static int tdm_fsl_get_write_buf(struct tdm_adapter *adap,
+ u16 **output_tdm_buffer)
+{
+ struct tdm_priv *priv;
+ u32 tmp;
+ u32 phase_tx;
+ u32 buf_addr;
+ int bytes_in_fifo_per_frame, buf_size;
+
+ /* point to where to start for the current phase data processing */
+ bytes_in_fifo_per_frame =
+ ALIGN(adap->adapt_cfg.num_ch * adap->adapt_cfg.slot_width, 8);
+
+ priv = adap->data;
+ if (!priv) {
+ pr_err("%s: Invalid handle\n", __func__);
+ return -EINVAL;
+ }
+
+ if (priv->tdm_active == 0) {
+ dev_warn(priv->device, "TDM is not ready");
+ return 0;
+ }
+
+ tmp = in_be32(&priv->dmac_regs->tcd[TDMTX_DMA_CH].tcd[0]);
+
+ tmp -= priv->dma_tx_tcd[0]->tcd[0];
+
+ priv->phase_tx = tmp/(bytes_in_fifo_per_frame *
+ adap->adapt_cfg.num_frames);
+
+ if (priv->phase_tx == 0)
+ phase_tx = NUM_OF_TDM_BUF - 1;
+ else
+ phase_tx = priv->phase_tx - 1;
+
+ buf_size = bytes_in_fifo_per_frame * adap->adapt_cfg.num_frames;
+ buf_addr = buf_size * phase_tx;
+ *output_tdm_buffer = (u16 *)(priv->tdm_output_data + buf_addr);
+
+ return buf_size;
+}
+
+static const struct tdm_adapt_algorithm tdm_algo = {
+ .tdm_read = tdm_fsl_read,
+ .tdm_get_write_buf = tdm_fsl_get_write_buf,
+ .tdm_enable = tdm_fsl_enable,
+ .tdm_disable = tdm_fsl_disable,
+};
+
+static struct tdm_adapter tdm_fsl_ops = {
+ .owner = THIS_MODULE,
+ .name = "fsl_tdm",
+ .algo = &tdm_algo,
+};
+
+static int __devinit tdm_fsl_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct tdm_priv *priv;
+ struct resource res;
+
+ priv = kzalloc(sizeof(struct tdm_priv), GFP_KERNEL);
+ if (!priv) {
+ ret = -ENOMEM;
+ goto err_alloc;
+ }
+
+ dev_set_drvdata(&pdev->dev, priv);
+ priv->device = &pdev->dev;
+ ret = of_address_to_resource(pdev->dev.of_node, 0, &res);
+ if (ret) {
+ ret = -EINVAL;
+ goto err_resource;
+ }
+
+ priv->ptdm_base = (u32)res.start;
+ priv->tdm_regs = of_iomap(pdev->dev.of_node, 0);
+ if (!priv->tdm_regs) {
+ ret = -ENOMEM;
+ goto err_tdmregs;
+ }
+
+ priv->dmac_regs = of_iomap(pdev->dev.of_node, 1);
+ if (!priv->dmac_regs) {
+ ret = -ENOMEM;
+ goto err_dmacreg;
+ }
+
+ priv->dmac_done_intr = irq_of_parse_and_map(pdev->dev.of_node, 0);
+ if (priv->dmac_done_intr == NO_IRQ) {
+ ret = -EINVAL;
+ goto err_dmacdone_irqmap;
+ }
+ ret = request_irq(priv->dmac_done_intr, dmac_done_isr, 0,
+ "dmac_done_isr", priv);
+ if (ret)
+ goto err_dmacdoneisr;
+
+ priv->adap = &tdm_fsl_ops;
+
+ /* Wait q initilization */
+ priv->adap->tdm_rx_flag = 0;
+ /* TODO - these should be configured by dts or init time */
+ priv->adap->data = priv;
+ priv->adap->parent = &pdev->dev;
+
+ ret = tdm_add_adapter(priv->adap);
+ if (ret < 0) {
+ dev_err(priv->device, "failed to add adapter\n");
+ goto fail_adapter;
+ }
+
+ /* Device does not supports 36 bit mode */
+ dma_set_mask(&pdev->dev, DMA_BIT_MASK(32));
+
+ ret = init_tdm(priv);
+ if (ret)
+ goto err_tdminit;
+
+ tdm_fsl_reg_init(priv);
+
+ spin_lock_init(&priv->tdmlock);
+ priv->tdm_active = 0;
+
+ if (tdmen) {
+ ret = tdm_fsl_enable(priv->adap);
+ if (!ret)
+ goto err_tdminit;
+ }
+
+ return 0;
+
+err_tdminit:
+fail_adapter:
+ free_irq(priv->dmac_done_intr, priv);
+err_dmacdoneisr:
+ free_irq(priv->tdm_err_intr, priv);
+err_dmacdone_irqmap:
+ irq_dispose_mapping(priv->dmac_done_intr);
+err_dmacreg:
+ iounmap(priv->dmac_regs);
+err_tdmregs:
+err_resource:
+ dev_set_drvdata(&pdev->dev, NULL);
+ kfree(priv);
+err_alloc:
+ return ret;
+}
+
+static int __devexit tdm_fsl_remove(struct platform_device *pdev)
+{
+ struct tdm_priv *priv;
+ int buf_size;
+ struct tdm_adapter *adap;
+
+ if (!pdev) {
+ pr_err("%s: Invalid handle\n", __func__);
+ return -EINVAL;
+ }
+
+ priv = dev_get_drvdata(&pdev->dev);
+ adap = priv->adap;
+
+ tdm_fsl_disable(priv->adap);
+
+ tdm_fsl_stop(priv);
+
+ tdm_del_adapter(priv->adap);
+ dev_set_drvdata(&pdev->dev, NULL);
+
+ /* free the irqs and dispose their mapping */
+ free_irq(priv->tdm_err_intr, priv);
+ free_irq(priv->dmac_done_intr, priv);
+ irq_dispose_mapping(priv->tdm_err_intr);
+ irq_dispose_mapping(priv->dmac_done_intr);
+ iounmap(priv->tdm_regs);
+ iounmap(priv->dmac_regs);
+
+ /* free the buffers */
+ buf_size =
+ TDM_3BUF_SIZE(adap->adapt_cfg.num_ch,
+ adap->adapt_cfg.slot_width,
+ adap->adapt_cfg.num_frames);
+ dma_free_coherent(priv->device, buf_size, priv->dma_input_vaddr,
+ priv->dma_input_paddr);
+ dma_free_coherent(priv->device, buf_size, priv->dma_output_vaddr,
+ priv->dma_output_paddr);
+
+ /* free the TCDs */
+ dma_free_coherent(priv->device, NUM_OF_TDM_BUF * TCD_SIZE,
+ priv->dma_rx_tcd_vaddr, priv->dma_rx_tcd_paddr);
+ dma_free_coherent(priv->device, NUM_OF_TDM_BUF * TCD_SIZE,
+ priv->dma_tx_tcd_vaddr, priv->dma_tx_tcd_paddr);
+ dev_set_drvdata(&pdev->dev, NULL);
+ kfree(priv);
+ return 0;
+}
+
+static const struct of_device_id fsl_tdm_match[] = {
+ {
+ .compatible = "fsl,tdm1.0",
+ },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, fsl_tdm_match);
+
+static struct platform_driver tdm_fsl_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = DRV_NAME,
+ .of_match_table = fsl_tdm_match,
+
+ },
+ .probe = tdm_fsl_probe,
+ .remove = __devexit_p(tdm_fsl_remove),
+};
+
+static int __init tdm_fsl_init(void)
+{
+ int ret;
+ pr_info(DRV_NAME ": " DRV_DESC ":Init\n");
+ ret = platform_driver_register(&tdm_fsl_driver);
+ if (ret)
+ pr_err(DRV_NAME "of_register_platform_driver failed (%i)\n",
+ ret);
+ return ret;
+}
+
+static void __exit tdm_fsl_exit(void)
+{
+ pr_info(DRV_NAME ": " DRV_DESC ":Exit\n");
+ platform_driver_unregister(&tdm_fsl_driver);
+}
+
+module_init(tdm_fsl_init);
+module_exit(tdm_fsl_exit);
+/*
+ module_platform_driver(tdm_fsl_driver);
+ */
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("P.V.Suresh, Freescale Semiconductor");
+MODULE_DESCRIPTION("Driver For Freescale TDM controller");
--
1.5.6.5
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox