| /* |
| * 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 <linux/bitops.h> |
| #include <linux/completion.h> |
| #include <linux/export.h> |
| #include <linux/init.h> |
| #include <linux/list.h> |
| #include <linux/of.h> |
| #include <linux/of_device.h> |
| #include <linux/slab.h> |
| #include <linux/string.h> |
| #include <linux/vexpress.h> |
| |
| |
| #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); |