/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * Copyright (C) 2012 ARM Limited */ #define pr_fmt(fmt) "vexpress-config: " fmt #include #include #include #include #include #include #include #include #include #include #define VEXPRESS_CONFIG_MAX_BRIDGES 2 struct vexpress_config_bridge { struct device_node *node; struct vexpress_config_bridge_info *info; struct list_head transactions; spinlock_t transactions_lock; } vexpress_config_bridges[VEXPRESS_CONFIG_MAX_BRIDGES]; static DECLARE_BITMAP(vexpress_config_bridges_map, ARRAY_SIZE(vexpress_config_bridges)); static DEFINE_MUTEX(vexpress_config_bridges_mutex); struct vexpress_config_bridge *vexpress_config_bridge_register( struct device_node *node, struct vexpress_config_bridge_info *info) { struct vexpress_config_bridge *bridge; int i; pr_debug("Registering bridge '%s'\n", info->name); mutex_lock(&vexpress_config_bridges_mutex); i = find_first_zero_bit(vexpress_config_bridges_map, ARRAY_SIZE(vexpress_config_bridges)); if (i >= ARRAY_SIZE(vexpress_config_bridges)) { pr_err("Can't register more bridges!\n"); mutex_unlock(&vexpress_config_bridges_mutex); return NULL; } __set_bit(i, vexpress_config_bridges_map); bridge = &vexpress_config_bridges[i]; bridge->node = node; bridge->info = info; INIT_LIST_HEAD(&bridge->transactions); spin_lock_init(&bridge->transactions_lock); mutex_unlock(&vexpress_config_bridges_mutex); return bridge; } EXPORT_SYMBOL(vexpress_config_bridge_register); void vexpress_config_bridge_unregister(struct vexpress_config_bridge *bridge) { struct vexpress_config_bridge __bridge = *bridge; int i; mutex_lock(&vexpress_config_bridges_mutex); for (i = 0; i < ARRAY_SIZE(vexpress_config_bridges); i++) if (&vexpress_config_bridges[i] == bridge) __clear_bit(i, vexpress_config_bridges_map); mutex_unlock(&vexpress_config_bridges_mutex); WARN_ON(!list_empty(&__bridge.transactions)); while (!list_empty(&__bridge.transactions)) cpu_relax(); } EXPORT_SYMBOL(vexpress_config_bridge_unregister); struct vexpress_config_func { struct vexpress_config_bridge *bridge; void *func; }; struct vexpress_config_func *__vexpress_config_func_get(struct device *dev, struct device_node *node) { struct device_node *bridge_node; struct vexpress_config_func *func; int i; if (WARN_ON(dev && node && dev->of_node != node)) return NULL; if (dev && !node) node = dev->of_node; func = kzalloc(sizeof(*func), GFP_KERNEL); if (!func) return NULL; bridge_node = of_node_get(node); while (bridge_node) { const __be32 *prop = of_get_property(bridge_node, "arm,vexpress,config-bridge", NULL); if (prop) { bridge_node = of_find_node_by_phandle( be32_to_cpup(prop)); break; } bridge_node = of_get_next_parent(bridge_node); } mutex_lock(&vexpress_config_bridges_mutex); for (i = 0; i < ARRAY_SIZE(vexpress_config_bridges); i++) { struct vexpress_config_bridge *bridge = &vexpress_config_bridges[i]; if (test_bit(i, vexpress_config_bridges_map) && bridge->node == bridge_node) { func->bridge = bridge; func->func = bridge->info->func_get(dev, node); break; } } mutex_unlock(&vexpress_config_bridges_mutex); if (!func->func) { of_node_put(node); kfree(func); return NULL; } return func; } EXPORT_SYMBOL(__vexpress_config_func_get); void vexpress_config_func_put(struct vexpress_config_func *func) { func->bridge->info->func_put(func->func); of_node_put(func->bridge->node); kfree(func); } EXPORT_SYMBOL(vexpress_config_func_put); struct vexpress_config_trans { struct vexpress_config_func *func; int offset; bool write; u32 *data; int status; struct completion completion; struct list_head list; }; static void vexpress_config_dump_trans(const char *what, struct vexpress_config_trans *trans) { pr_debug("%s %s trans %p func 0x%p offset %d data 0x%x status %d\n", what, trans->write ? "write" : "read", trans, trans->func->func, trans->offset, trans->data ? *trans->data : 0, trans->status); } static int vexpress_config_schedule(struct vexpress_config_trans *trans) { int status; struct vexpress_config_bridge *bridge = trans->func->bridge; unsigned long flags; init_completion(&trans->completion); trans->status = -EFAULT; spin_lock_irqsave(&bridge->transactions_lock, flags); if (list_empty(&bridge->transactions)) { vexpress_config_dump_trans("Executing", trans); status = bridge->info->func_exec(trans->func->func, trans->offset, trans->write, trans->data); } else { vexpress_config_dump_trans("Queuing", trans); status = VEXPRESS_CONFIG_STATUS_WAIT; } switch (status) { case VEXPRESS_CONFIG_STATUS_DONE: vexpress_config_dump_trans("Finished", trans); trans->status = status; break; case VEXPRESS_CONFIG_STATUS_WAIT: list_add_tail(&trans->list, &bridge->transactions); break; } spin_unlock_irqrestore(&bridge->transactions_lock, flags); return status; } void vexpress_config_complete(struct vexpress_config_bridge *bridge, int status) { struct vexpress_config_trans *trans; unsigned long flags; const char *message = "Completed"; spin_lock_irqsave(&bridge->transactions_lock, flags); trans = list_first_entry(&bridge->transactions, struct vexpress_config_trans, list); trans->status = status; do { vexpress_config_dump_trans(message, trans); list_del(&trans->list); complete(&trans->completion); if (list_empty(&bridge->transactions)) break; trans = list_first_entry(&bridge->transactions, struct vexpress_config_trans, list); vexpress_config_dump_trans("Executing pending", trans); trans->status = bridge->info->func_exec(trans->func->func, trans->offset, trans->write, trans->data); message = "Finished pending"; } while (trans->status == VEXPRESS_CONFIG_STATUS_DONE); spin_unlock_irqrestore(&bridge->transactions_lock, flags); } EXPORT_SYMBOL(vexpress_config_complete); int vexpress_config_wait(struct vexpress_config_trans *trans) { wait_for_completion(&trans->completion); return trans->status; } EXPORT_SYMBOL(vexpress_config_wait); int vexpress_config_read(struct vexpress_config_func *func, int offset, u32 *data) { struct vexpress_config_trans trans = { .func = func, .offset = offset, .write = false, .data = data, .status = 0, }; int status = vexpress_config_schedule(&trans); if (status == VEXPRESS_CONFIG_STATUS_WAIT) status = vexpress_config_wait(&trans); return status; } EXPORT_SYMBOL(vexpress_config_read); int vexpress_config_write(struct vexpress_config_func *func, int offset, u32 data) { struct vexpress_config_trans trans = { .func = func, .offset = offset, .write = true, .data = &data, .status = 0, }; int status = vexpress_config_schedule(&trans); if (status == VEXPRESS_CONFIG_STATUS_WAIT) status = vexpress_config_wait(&trans); return status; } EXPORT_SYMBOL(vexpress_config_write);