724 lines
16 KiB
C
724 lines
16 KiB
C
|
/*
|
||
|
* Copyright (C) 2007 STMicroelectronics Limited
|
||
|
* Author: Stuart Menefy <stuart.menefy@st.com>
|
||
|
*
|
||
|
* May be copied or modified under the terms of the GNU General Public
|
||
|
* License. See linux/COPYING for more information.
|
||
|
*/
|
||
|
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/bootmem.h>
|
||
|
#include <linux/io.h>
|
||
|
#include <linux/sysdev.h>
|
||
|
#include <linux/list.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/debugfs.h>
|
||
|
#include <linux/seq_file.h>
|
||
|
#include <linux/types.h>
|
||
|
#include <linux/stm/platform.h>
|
||
|
#include <linux/stm/sysconf.h>
|
||
|
|
||
|
#define DRIVER_NAME "stm-sysconf"
|
||
|
|
||
|
struct sysconf_field {
|
||
|
u8 group, num;
|
||
|
u8 lsb, msb;
|
||
|
void __iomem *reg;
|
||
|
const char *owner;
|
||
|
struct list_head list;
|
||
|
#ifdef DEBUG
|
||
|
enum { magic_good = 0x600df00d, magic_bad = 0xdeadbeef } magic;
|
||
|
#define MAGIC_SET(field) field->magic = magic_good
|
||
|
#define MAGIC_CLEAR(field) field->magic = magic_bad
|
||
|
#define MAGIC_CHECK(field) BUG_ON(field->magic != magic_good)
|
||
|
#else
|
||
|
#define MAGIC_SET(field)
|
||
|
#define MAGIC_CLEAR(field)
|
||
|
#define MAGIC_CHECK(field)
|
||
|
#endif
|
||
|
};
|
||
|
|
||
|
struct sysconf_group {
|
||
|
void __iomem *base;
|
||
|
const char *name;
|
||
|
const char *(*reg_name)(int num);
|
||
|
struct sysconf_block *block;
|
||
|
};
|
||
|
|
||
|
struct sysconf_block {
|
||
|
void __iomem *base;
|
||
|
unsigned long size;
|
||
|
struct platform_device *pdev;
|
||
|
#ifdef CONFIG_PM
|
||
|
unsigned long *snapshot;
|
||
|
#endif
|
||
|
};
|
||
|
|
||
|
static int sysconf_blocks_num;
|
||
|
static struct sysconf_block *sysconf_blocks;
|
||
|
|
||
|
static int sysconf_groups_num;
|
||
|
static struct sysconf_group *sysconf_groups;
|
||
|
|
||
|
static LIST_HEAD(sysconf_fields);
|
||
|
static DEFINE_SPINLOCK(sysconf_fields_lock);
|
||
|
|
||
|
static DEFINE_SPINLOCK(sysconf_registers_lock);
|
||
|
|
||
|
|
||
|
|
||
|
/* We need a small stash of allocations before kmalloc becomes available */
|
||
|
#define NUM_EARLY_FIELDS 128
|
||
|
#define EARLY_BITS_MAPS_SIZE DIV_ROUND_UP(NUM_EARLY_FIELDS, BITS_PER_LONG)
|
||
|
|
||
|
static struct sysconf_field early_fields[NUM_EARLY_FIELDS];
|
||
|
static unsigned long early_fields_map[EARLY_BITS_MAPS_SIZE];
|
||
|
|
||
|
static struct sysconf_field *field_alloc(void)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
for (i = 0; i < NUM_EARLY_FIELDS; i++)
|
||
|
if (test_and_set_bit(i, early_fields_map) == 0)
|
||
|
return &early_fields[i];
|
||
|
|
||
|
return kmalloc(sizeof(struct sysconf_field), GFP_KERNEL);
|
||
|
}
|
||
|
|
||
|
static void field_free(struct sysconf_field *field)
|
||
|
{
|
||
|
if (field >= early_fields && field < early_fields + NUM_EARLY_FIELDS)
|
||
|
clear_bit(field - early_fields, early_fields_map);
|
||
|
else
|
||
|
kfree(field);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
struct sysconf_field *sysconf_claim(int group, int num, int lsb, int msb,
|
||
|
const char *devname)
|
||
|
{
|
||
|
struct sysconf_field *field, *entry;
|
||
|
enum {
|
||
|
status_searching,
|
||
|
status_found_register,
|
||
|
status_add_field_here,
|
||
|
status_conflict,
|
||
|
} status = status_searching;
|
||
|
int bit_avail = 0;
|
||
|
|
||
|
pr_debug("%s(group=%d, num=%d, lsb=%d, msb=%d, devname='%s')\n",
|
||
|
__func__, group, num, lsb, msb, devname);
|
||
|
|
||
|
BUG_ON(group < 0 || group >= sysconf_groups_num);
|
||
|
BUG_ON(num < 0 || num > ((1 << 8) - 1));
|
||
|
BUG_ON(lsb < 0 || lsb > 32);
|
||
|
BUG_ON(msb < 0 || msb > 32);
|
||
|
BUG_ON(lsb > msb);
|
||
|
|
||
|
field = field_alloc();
|
||
|
if (!field)
|
||
|
return NULL;
|
||
|
|
||
|
field->group = group;
|
||
|
field->num = num;
|
||
|
field->lsb = lsb;
|
||
|
field->msb = msb;
|
||
|
|
||
|
field->reg = sysconf_groups[group].base + (num * 4);
|
||
|
BUG_ON(field->reg >= sysconf_groups[group].block->base +
|
||
|
sysconf_groups[group].block->size);
|
||
|
field->owner = devname;
|
||
|
MAGIC_SET(field);
|
||
|
|
||
|
spin_lock(&sysconf_fields_lock);
|
||
|
|
||
|
/* The list is always in group->num->lsb/msb order, so it's easy to
|
||
|
* find a place to insert a new field (and to detect conflicts) */
|
||
|
list_for_each_entry(entry, &sysconf_fields, list) {
|
||
|
if (entry->group == group && entry->num == num) {
|
||
|
status = status_found_register;
|
||
|
/* Someone already claimed a field from this
|
||
|
* register - let's try to find some space for
|
||
|
* requested bits... */
|
||
|
if (bit_avail <= lsb && msb < entry->lsb) {
|
||
|
status = status_add_field_here;
|
||
|
break;
|
||
|
}
|
||
|
bit_avail = entry->msb + 1;
|
||
|
} else if ((entry->group == group && entry->num > num) ||
|
||
|
entry->group > group) {
|
||
|
/* Ok, there is no point of looking further -
|
||
|
* the group and/or num values are bigger then
|
||
|
* the ones we are looking for */
|
||
|
if ((status == status_found_register &&
|
||
|
bit_avail <= lsb) ||
|
||
|
status == status_searching)
|
||
|
/* A remainder of the given register is not
|
||
|
* used or the register wasn't used at all */
|
||
|
status = status_add_field_here;
|
||
|
else
|
||
|
/* Apparently some bits of the claimed field
|
||
|
* are already in use... */
|
||
|
status = status_conflict;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
switch (status) {
|
||
|
case status_searching:
|
||
|
case status_found_register:
|
||
|
/* Either nothing was on the list at all or the claimed
|
||
|
* field should be added as the last element... */
|
||
|
list_add_tail(&field->list, &sysconf_fields);
|
||
|
break;
|
||
|
case status_add_field_here:
|
||
|
/* So we should insert the new field between current
|
||
|
* list entry and the previous one */
|
||
|
list_add(&field->list, entry->list.prev);
|
||
|
break;
|
||
|
case status_conflict:
|
||
|
/* Apparently there was no place in the
|
||
|
* register to fulfill the request... */
|
||
|
MAGIC_CLEAR(field);
|
||
|
field_free(field);
|
||
|
field = NULL;
|
||
|
pr_debug("%s(): conflict!\n", __func__);
|
||
|
break;
|
||
|
default:
|
||
|
BUG();
|
||
|
}
|
||
|
|
||
|
spin_unlock(&sysconf_fields_lock);
|
||
|
|
||
|
pr_debug("%s()=0x%p\n", __func__, field);
|
||
|
|
||
|
return field;
|
||
|
}
|
||
|
EXPORT_SYMBOL(sysconf_claim);
|
||
|
|
||
|
void sysconf_release(struct sysconf_field *field)
|
||
|
{
|
||
|
pr_debug("%s(field=0x%p)\n", __func__, field);
|
||
|
|
||
|
BUG_ON(!field);
|
||
|
MAGIC_CHECK(field);
|
||
|
|
||
|
spin_lock(&sysconf_fields_lock);
|
||
|
list_del(&field->list);
|
||
|
spin_unlock(&sysconf_fields_lock);
|
||
|
|
||
|
MAGIC_CLEAR(field);
|
||
|
field_free(field);
|
||
|
}
|
||
|
EXPORT_SYMBOL(sysconf_release);
|
||
|
|
||
|
void sysconf_write(struct sysconf_field *field, unsigned long value)
|
||
|
{
|
||
|
int field_bits;
|
||
|
|
||
|
pr_debug("%s(field=0x%p (%s %d[%d:%d]) = 0x%08lx)\n", __func__, field,
|
||
|
sysconf_groups[field->group].name, field->num,
|
||
|
field->msb, field->lsb, value);
|
||
|
|
||
|
BUG_ON(!field);
|
||
|
MAGIC_CHECK(field);
|
||
|
|
||
|
field_bits = field->msb - field->lsb + 1;
|
||
|
BUG_ON(field_bits < 1 || field_bits > 32);
|
||
|
|
||
|
if (field_bits == 32) {
|
||
|
/* Operating on the whole register, nice and easy */
|
||
|
writel(value, field->reg);
|
||
|
} else {
|
||
|
unsigned long flags;
|
||
|
u32 reg_mask;
|
||
|
u32 tmp;
|
||
|
|
||
|
reg_mask = ~(((1 << field_bits) - 1) << field->lsb);
|
||
|
|
||
|
spin_lock_irqsave(&sysconf_registers_lock, flags);
|
||
|
tmp = readl(field->reg);
|
||
|
tmp &= reg_mask;
|
||
|
tmp |= value << field->lsb;
|
||
|
writel(tmp, field->reg);
|
||
|
spin_unlock_irqrestore(&sysconf_registers_lock, flags);
|
||
|
}
|
||
|
}
|
||
|
EXPORT_SYMBOL(sysconf_write);
|
||
|
|
||
|
unsigned long sysconf_read(struct sysconf_field *field)
|
||
|
{
|
||
|
int field_bits;
|
||
|
u32 result;
|
||
|
|
||
|
pr_debug("%s(field=0x%p (%s %d[%d:%d]))\n", __func__, field,
|
||
|
sysconf_groups[field->group].name, field->num,
|
||
|
field->msb, field->lsb);
|
||
|
|
||
|
BUG_ON(!field);
|
||
|
MAGIC_CHECK(field);
|
||
|
|
||
|
field_bits = field->msb - field->lsb + 1;
|
||
|
BUG_ON(field_bits < 1 || field_bits > 32);
|
||
|
|
||
|
result = readl(field->reg);
|
||
|
|
||
|
if (field_bits != 32) {
|
||
|
result >>= field->lsb;
|
||
|
result &= (1 << field_bits) - 1;
|
||
|
}
|
||
|
|
||
|
pr_debug("%s()=0x%u\n", __func__, result);
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
EXPORT_SYMBOL(sysconf_read);
|
||
|
|
||
|
void *sysconf_address(struct sysconf_field *field)
|
||
|
{
|
||
|
pr_debug("%s(field=0x%p)\n", __func__, field);
|
||
|
|
||
|
BUG_ON(!field);
|
||
|
MAGIC_CHECK(field);
|
||
|
|
||
|
pr_debug("%s()=0x%p\n", __func__, field->reg);
|
||
|
|
||
|
return field->reg;
|
||
|
}
|
||
|
EXPORT_SYMBOL(sysconf_address);
|
||
|
|
||
|
unsigned long sysconf_mask(struct sysconf_field *field)
|
||
|
{
|
||
|
int field_bits;
|
||
|
u32 result;
|
||
|
|
||
|
pr_debug("%s(field=0x%p)\n", __func__, field);
|
||
|
|
||
|
BUG_ON(!field);
|
||
|
MAGIC_CHECK(field);
|
||
|
|
||
|
field_bits = field->msb - field->lsb + 1;
|
||
|
BUG_ON(field_bits < 1 || field_bits > 32);
|
||
|
|
||
|
if (field_bits == 32)
|
||
|
result = ~0UL;
|
||
|
else
|
||
|
result = ((1 << field_bits) - 1) << field->lsb;
|
||
|
|
||
|
pr_debug("%s()=0x%u\n", __func__, result);
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
EXPORT_SYMBOL(sysconf_mask);
|
||
|
|
||
|
const char *sysconf_group_name(int group)
|
||
|
{
|
||
|
BUG_ON(group < 0 || group >= sysconf_groups_num);
|
||
|
|
||
|
return sysconf_groups[group].name;
|
||
|
|
||
|
}
|
||
|
EXPORT_SYMBOL(sysconf_group_name);
|
||
|
|
||
|
const char *sysconf_reg_name(int group, int num)
|
||
|
{
|
||
|
BUG_ON(group < 0 || group >= sysconf_groups_num);
|
||
|
|
||
|
return sysconf_groups[group].reg_name ?
|
||
|
sysconf_groups[group].reg_name(num) : NULL;
|
||
|
}
|
||
|
EXPORT_SYMBOL(sysconf_reg_name);
|
||
|
|
||
|
|
||
|
#ifdef CONFIG_PM
|
||
|
static int sysconf_pm_freeze(void)
|
||
|
{
|
||
|
int result = 0;
|
||
|
int i;
|
||
|
|
||
|
pr_debug("%s()\n", __func__);
|
||
|
|
||
|
for (i = 0; i < sysconf_blocks_num; i++) {
|
||
|
struct sysconf_block *block = &sysconf_blocks[i];
|
||
|
int j;
|
||
|
|
||
|
block->snapshot = kmalloc(block->size, GFP_NOWAIT);
|
||
|
if (!block->snapshot) {
|
||
|
pr_err("Failed to freeze %s!\n",
|
||
|
dev_name(&block->pdev->dev));
|
||
|
result = -ENOMEM;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
for (j = 0; j < block->size; j += sizeof(unsigned long))
|
||
|
block->snapshot[j / sizeof(unsigned long)] =
|
||
|
readl(block->base + j);
|
||
|
}
|
||
|
|
||
|
pr_debug("%s()=%d\n", __func__, result);
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
static int sysconf_pm_restore(void)
|
||
|
{
|
||
|
int result = 0;
|
||
|
int i;
|
||
|
|
||
|
pr_debug("%s()\n", __func__);
|
||
|
|
||
|
for (i = 0; i < sysconf_blocks_num; i++) {
|
||
|
struct sysconf_block *block = &sysconf_blocks[i];
|
||
|
int j;
|
||
|
|
||
|
if (!block->snapshot) {
|
||
|
pr_err("Failed to restore %s!\n",
|
||
|
dev_name(&block->pdev->dev));
|
||
|
result = -EINVAL;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
for (j = 0; j < block->size; j += sizeof(unsigned long))
|
||
|
writel(block->snapshot[j / sizeof(unsigned long)],
|
||
|
block->base + j);
|
||
|
|
||
|
kfree(block->snapshot);
|
||
|
block->snapshot = NULL;
|
||
|
}
|
||
|
|
||
|
pr_debug("%s()=%d\n", __func__, result);
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
static int sysconf_sysdev_suspend(struct sys_device *dev, pm_message_t state)
|
||
|
{
|
||
|
int result = 0;
|
||
|
static unsigned long prev_state = PM_EVENT_ON;
|
||
|
|
||
|
pr_debug("%s()\n", __func__);
|
||
|
|
||
|
switch (state.event) {
|
||
|
case PM_EVENT_ON:
|
||
|
if (prev_state == PM_EVENT_FREEZE)
|
||
|
result = sysconf_pm_restore();
|
||
|
break;
|
||
|
case PM_EVENT_SUSPEND:
|
||
|
break;
|
||
|
case PM_EVENT_FREEZE:
|
||
|
result = sysconf_pm_freeze();
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
prev_state = state.event;
|
||
|
|
||
|
pr_debug("%s()=%d\n", __func__, result);
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
static int sysconf_sysdev_resume(struct sys_device *dev)
|
||
|
{
|
||
|
return sysconf_sysdev_suspend(dev, PMSG_ON);
|
||
|
}
|
||
|
|
||
|
static struct sysdev_class sysconf_sysdev_class = {
|
||
|
.name = "sysconf",
|
||
|
.suspend = sysconf_sysdev_suspend,
|
||
|
.resume = sysconf_sysdev_resume,
|
||
|
};
|
||
|
|
||
|
struct sys_device sysconf_sysdev_dev = {
|
||
|
.id = 0,
|
||
|
.cls = &sysconf_sysdev_class,
|
||
|
};
|
||
|
|
||
|
static int __init sysconf_sysdev_init(void)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
ret = sysdev_class_register(&sysconf_sysdev_class);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
ret = sysdev_register(&sysconf_sysdev_dev);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
module_init(sysconf_sysdev_init);
|
||
|
#endif
|
||
|
|
||
|
|
||
|
|
||
|
#ifdef CONFIG_DEBUG_FS
|
||
|
|
||
|
enum sysconf_seq_state { state_blocks, state_groups, state_fields, state_last };
|
||
|
|
||
|
static void *sysconf_seq_start(struct seq_file *s, loff_t *pos)
|
||
|
{
|
||
|
if (*pos >= state_last)
|
||
|
return NULL;
|
||
|
|
||
|
return pos;
|
||
|
}
|
||
|
|
||
|
static void *sysconf_seq_next(struct seq_file *s, void *v, loff_t *pos)
|
||
|
{
|
||
|
(*pos)++;
|
||
|
|
||
|
if (*pos >= state_last)
|
||
|
return NULL;
|
||
|
|
||
|
return pos;
|
||
|
}
|
||
|
|
||
|
static void sysconf_seq_stop(struct seq_file *s, void *v)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
static int sysconf_seq_show_blocks(struct seq_file *s)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
seq_printf(s, "blocks:\n");
|
||
|
|
||
|
for (i = 0; i < sysconf_blocks_num; i++) {
|
||
|
struct sysconf_block *block = &sysconf_blocks[i];
|
||
|
struct resource *mem = platform_get_resource(block->pdev,
|
||
|
IORESOURCE_MEM, 0);
|
||
|
|
||
|
seq_printf(s, "- %s: 0x%08x (0x%p), 0x%lxb\n",
|
||
|
dev_name(&block->pdev->dev), mem->start,
|
||
|
block->base, block->size);
|
||
|
}
|
||
|
|
||
|
seq_printf(s, "\n");
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int sysconf_seq_show_groups(struct seq_file *s)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
seq_printf(s, "groups:\n");
|
||
|
|
||
|
for (i = 0; i < sysconf_groups_num; i++) {
|
||
|
struct sysconf_group *group = &sysconf_groups[i];
|
||
|
|
||
|
seq_printf(s, "- %s: 0x%p (%s)\n",
|
||
|
group->name, group->base,
|
||
|
dev_name(&group->block->pdev->dev));
|
||
|
}
|
||
|
|
||
|
seq_printf(s, "\n");
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int sysconf_seq_show_fields(struct seq_file *s)
|
||
|
{
|
||
|
struct sysconf_field *field;
|
||
|
|
||
|
seq_printf(s, "claimed fields:\n");
|
||
|
|
||
|
spin_lock(&sysconf_fields_lock);
|
||
|
|
||
|
list_for_each_entry(field, &sysconf_fields, list) {
|
||
|
struct sysconf_group *group = &sysconf_groups[field->group];
|
||
|
|
||
|
if (group->reg_name)
|
||
|
seq_printf(s, "- %s[", group->reg_name(field->num));
|
||
|
else
|
||
|
seq_printf(s, "- %s%d[", group->name, field->num);
|
||
|
|
||
|
if (field->msb == field->lsb)
|
||
|
seq_printf(s, "%d", field->msb);
|
||
|
else
|
||
|
seq_printf(s, "%d:%d", field->msb, field->lsb);
|
||
|
|
||
|
seq_printf(s, "] = 0x%0*lx (0x%p, %s)\n",
|
||
|
(field->msb - field->lsb + 4) / 4,
|
||
|
sysconf_read(field),
|
||
|
field->reg, field->owner);
|
||
|
}
|
||
|
|
||
|
spin_unlock(&sysconf_fields_lock);
|
||
|
|
||
|
seq_printf(s, "\n");
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int sysconf_seq_show(struct seq_file *s, void *v)
|
||
|
{
|
||
|
enum sysconf_seq_state state = *((loff_t *)v);
|
||
|
|
||
|
switch (state) {
|
||
|
case state_blocks:
|
||
|
return sysconf_seq_show_blocks(s);
|
||
|
case state_groups:
|
||
|
return sysconf_seq_show_groups(s);
|
||
|
case state_fields:
|
||
|
return sysconf_seq_show_fields(s);
|
||
|
default:
|
||
|
BUG();
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static struct seq_operations sysconf_seq_ops = {
|
||
|
.start = sysconf_seq_start,
|
||
|
.next = sysconf_seq_next,
|
||
|
.stop = sysconf_seq_stop,
|
||
|
.show = sysconf_seq_show,
|
||
|
};
|
||
|
|
||
|
static int sysconf_debugfs_open(struct inode *inode, struct file *file)
|
||
|
{
|
||
|
return seq_open(file, &sysconf_seq_ops);
|
||
|
}
|
||
|
|
||
|
static const struct file_operations sysconf_debugfs_ops = {
|
||
|
.owner = THIS_MODULE,
|
||
|
.open = sysconf_debugfs_open,
|
||
|
.read = seq_read,
|
||
|
.llseek = seq_lseek,
|
||
|
.release = seq_release,
|
||
|
};
|
||
|
|
||
|
static int __init sysconf_debugfs_init(void)
|
||
|
{
|
||
|
debugfs_create_file("sysconf", S_IFREG | S_IRUGO,
|
||
|
NULL, NULL, &sysconf_debugfs_ops);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
subsys_initcall(sysconf_debugfs_init);
|
||
|
|
||
|
#endif
|
||
|
|
||
|
|
||
|
|
||
|
/* This is called early to allow board start up code to use sysconf
|
||
|
* registers (in particular console devices). */
|
||
|
void __init sysconf_early_init(struct platform_device *pdevs, int pdevs_num)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
pr_debug("%s(pdevs=%p, pdevs_num=%d)\n", __func__, pdevs, pdevs_num);
|
||
|
|
||
|
/* I don't like panicing, but if we failed here, we probably
|
||
|
* don't have any way to report things have gone wrong.
|
||
|
* So a panic here at least gives some hope of being
|
||
|
* able to debug the problem. */
|
||
|
|
||
|
sysconf_blocks_num = pdevs_num;
|
||
|
sysconf_blocks = alloc_bootmem(sizeof(*sysconf_blocks) *
|
||
|
sysconf_blocks_num);
|
||
|
if (!sysconf_blocks)
|
||
|
panic("Failed to allocate memory for sysconf blocks!");
|
||
|
|
||
|
for (i = 0; i < sysconf_blocks_num; i++) {
|
||
|
struct sysconf_block *block = &sysconf_blocks[i];
|
||
|
struct stm_plat_sysconf_data *data = pdevs[i].dev.platform_data;
|
||
|
struct resource *mem;
|
||
|
|
||
|
block->pdev = &pdevs[i];
|
||
|
|
||
|
mem = platform_get_resource(&pdevs[i], IORESOURCE_MEM, 0);
|
||
|
BUG_ON(!mem);
|
||
|
|
||
|
block->size = mem->end - mem->start + 1;
|
||
|
block->base = ioremap(mem->start, block->size);
|
||
|
if (!block->base)
|
||
|
panic("Unable to ioremap %s registers!",
|
||
|
dev_name(&block->pdev->dev));
|
||
|
|
||
|
sysconf_groups_num += data->groups_num;
|
||
|
}
|
||
|
|
||
|
sysconf_groups = alloc_bootmem(sizeof(*sysconf_groups) *
|
||
|
sysconf_groups_num);
|
||
|
if (!sysconf_groups)
|
||
|
panic("Failed to allocate memory for sysconf groups!\n");
|
||
|
|
||
|
for (i = 0; i < sysconf_blocks_num; i++) {
|
||
|
struct stm_plat_sysconf_data *data = pdevs[i].dev.platform_data;
|
||
|
struct sysconf_block *block = &sysconf_blocks[i];
|
||
|
int j;
|
||
|
|
||
|
for (j = 0; j < data->groups_num; j++) {
|
||
|
struct stm_plat_sysconf_group *info = &data->groups[j];
|
||
|
struct sysconf_group *group;
|
||
|
|
||
|
BUG_ON(info->group < 0 ||
|
||
|
info->group >= sysconf_groups_num);
|
||
|
|
||
|
group = &sysconf_groups[info->group];
|
||
|
|
||
|
BUG_ON(group->base != NULL);
|
||
|
|
||
|
group->base = block->base + info->offset;
|
||
|
group->name = info->name;
|
||
|
group->reg_name = info->reg_name;
|
||
|
group->block = block;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
static int __init sysconf_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
int result = -EINVAL;
|
||
|
int i;
|
||
|
|
||
|
pr_debug("%s(pdev=%p)\n", __func__, pdev);
|
||
|
|
||
|
/* Confirm that the device has been initialized earlier */
|
||
|
for (i = 0; i < sysconf_blocks_num; i++) {
|
||
|
if (sysconf_blocks[i].pdev == pdev) {
|
||
|
result = 0;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (result == 0) {
|
||
|
struct resource *mem = platform_get_resource(pdev,
|
||
|
IORESOURCE_MEM, 0);
|
||
|
|
||
|
BUG_ON(!mem);
|
||
|
|
||
|
if (request_mem_region(mem->start, mem->end -
|
||
|
mem->start + 1, pdev->name) == NULL) {
|
||
|
pr_err("Memory region request failed for %s!\n",
|
||
|
dev_name(&pdev->dev));
|
||
|
result = -EBUSY;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pr_debug("%s()=%d\n", __func__, result);
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
static struct platform_driver sysconf_driver = {
|
||
|
.probe = sysconf_probe,
|
||
|
.driver = {
|
||
|
.name = DRIVER_NAME,
|
||
|
.owner = THIS_MODULE,
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static int __init sysconf_init(void)
|
||
|
{
|
||
|
return platform_driver_register(&sysconf_driver);
|
||
|
}
|
||
|
arch_initcall(sysconf_init);
|