18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (C) 2017 HiSilicon Limited, All Rights Reserved. 48c2ecf20Sopenharmony_ci * Author: Gabriele Paoloni <gabriele.paoloni@huawei.com> 58c2ecf20Sopenharmony_ci * Author: Zhichang Yuan <yuanzhichang@hisilicon.com> 68c2ecf20Sopenharmony_ci * Author: John Garry <john.garry@huawei.com> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#define pr_fmt(fmt) "LOGIC PIO: " fmt 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include <linux/of.h> 128c2ecf20Sopenharmony_ci#include <linux/io.h> 138c2ecf20Sopenharmony_ci#include <linux/logic_pio.h> 148c2ecf20Sopenharmony_ci#include <linux/mm.h> 158c2ecf20Sopenharmony_ci#include <linux/rculist.h> 168c2ecf20Sopenharmony_ci#include <linux/sizes.h> 178c2ecf20Sopenharmony_ci#include <linux/slab.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci/* The unique hardware address list */ 208c2ecf20Sopenharmony_cistatic LIST_HEAD(io_range_list); 218c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(io_range_mutex); 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci/* Consider a kernel general helper for this */ 248c2ecf20Sopenharmony_ci#define in_range(b, first, len) ((b) >= (first) && (b) < (first) + (len)) 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_ci/** 278c2ecf20Sopenharmony_ci * logic_pio_register_range - register logical PIO range for a host 288c2ecf20Sopenharmony_ci * @new_range: pointer to the IO range to be registered. 298c2ecf20Sopenharmony_ci * 308c2ecf20Sopenharmony_ci * Returns 0 on success, the error code in case of failure. 318c2ecf20Sopenharmony_ci * If the range already exists, -EEXIST will be returned, which should be 328c2ecf20Sopenharmony_ci * considered a success. 338c2ecf20Sopenharmony_ci * 348c2ecf20Sopenharmony_ci * Register a new IO range node in the IO range list. 358c2ecf20Sopenharmony_ci */ 368c2ecf20Sopenharmony_ciint logic_pio_register_range(struct logic_pio_hwaddr *new_range) 378c2ecf20Sopenharmony_ci{ 388c2ecf20Sopenharmony_ci struct logic_pio_hwaddr *range; 398c2ecf20Sopenharmony_ci resource_size_t start; 408c2ecf20Sopenharmony_ci resource_size_t end; 418c2ecf20Sopenharmony_ci resource_size_t mmio_end = 0; 428c2ecf20Sopenharmony_ci resource_size_t iio_sz = MMIO_UPPER_LIMIT; 438c2ecf20Sopenharmony_ci int ret = 0; 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci if (!new_range || !new_range->fwnode || !new_range->size || 468c2ecf20Sopenharmony_ci (new_range->flags == LOGIC_PIO_INDIRECT && !new_range->ops)) 478c2ecf20Sopenharmony_ci return -EINVAL; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci start = new_range->hw_start; 508c2ecf20Sopenharmony_ci end = new_range->hw_start + new_range->size; 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci mutex_lock(&io_range_mutex); 538c2ecf20Sopenharmony_ci list_for_each_entry(range, &io_range_list, list) { 548c2ecf20Sopenharmony_ci if (range->fwnode == new_range->fwnode) { 558c2ecf20Sopenharmony_ci /* range already there */ 568c2ecf20Sopenharmony_ci ret = -EEXIST; 578c2ecf20Sopenharmony_ci goto end_register; 588c2ecf20Sopenharmony_ci } 598c2ecf20Sopenharmony_ci if (range->flags == LOGIC_PIO_CPU_MMIO && 608c2ecf20Sopenharmony_ci new_range->flags == LOGIC_PIO_CPU_MMIO) { 618c2ecf20Sopenharmony_ci /* for MMIO ranges we need to check for overlap */ 628c2ecf20Sopenharmony_ci if (start >= range->hw_start + range->size || 638c2ecf20Sopenharmony_ci end < range->hw_start) { 648c2ecf20Sopenharmony_ci mmio_end = range->io_start + range->size; 658c2ecf20Sopenharmony_ci } else { 668c2ecf20Sopenharmony_ci ret = -EFAULT; 678c2ecf20Sopenharmony_ci goto end_register; 688c2ecf20Sopenharmony_ci } 698c2ecf20Sopenharmony_ci } else if (range->flags == LOGIC_PIO_INDIRECT && 708c2ecf20Sopenharmony_ci new_range->flags == LOGIC_PIO_INDIRECT) { 718c2ecf20Sopenharmony_ci iio_sz += range->size; 728c2ecf20Sopenharmony_ci } 738c2ecf20Sopenharmony_ci } 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci /* range not registered yet, check for available space */ 768c2ecf20Sopenharmony_ci if (new_range->flags == LOGIC_PIO_CPU_MMIO) { 778c2ecf20Sopenharmony_ci if (mmio_end + new_range->size - 1 > MMIO_UPPER_LIMIT) { 788c2ecf20Sopenharmony_ci /* if it's too big check if 64K space can be reserved */ 798c2ecf20Sopenharmony_ci if (mmio_end + SZ_64K - 1 > MMIO_UPPER_LIMIT) { 808c2ecf20Sopenharmony_ci ret = -E2BIG; 818c2ecf20Sopenharmony_ci goto end_register; 828c2ecf20Sopenharmony_ci } 838c2ecf20Sopenharmony_ci new_range->size = SZ_64K; 848c2ecf20Sopenharmony_ci pr_warn("Requested IO range too big, new size set to 64K\n"); 858c2ecf20Sopenharmony_ci } 868c2ecf20Sopenharmony_ci new_range->io_start = mmio_end; 878c2ecf20Sopenharmony_ci } else if (new_range->flags == LOGIC_PIO_INDIRECT) { 888c2ecf20Sopenharmony_ci if (iio_sz + new_range->size - 1 > IO_SPACE_LIMIT) { 898c2ecf20Sopenharmony_ci ret = -E2BIG; 908c2ecf20Sopenharmony_ci goto end_register; 918c2ecf20Sopenharmony_ci } 928c2ecf20Sopenharmony_ci new_range->io_start = iio_sz; 938c2ecf20Sopenharmony_ci } else { 948c2ecf20Sopenharmony_ci /* invalid flag */ 958c2ecf20Sopenharmony_ci ret = -EINVAL; 968c2ecf20Sopenharmony_ci goto end_register; 978c2ecf20Sopenharmony_ci } 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci list_add_tail_rcu(&new_range->list, &io_range_list); 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ciend_register: 1028c2ecf20Sopenharmony_ci mutex_unlock(&io_range_mutex); 1038c2ecf20Sopenharmony_ci return ret; 1048c2ecf20Sopenharmony_ci} 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci/** 1078c2ecf20Sopenharmony_ci * logic_pio_unregister_range - unregister a logical PIO range for a host 1088c2ecf20Sopenharmony_ci * @range: pointer to the IO range which has been already registered. 1098c2ecf20Sopenharmony_ci * 1108c2ecf20Sopenharmony_ci * Unregister a previously-registered IO range node. 1118c2ecf20Sopenharmony_ci */ 1128c2ecf20Sopenharmony_civoid logic_pio_unregister_range(struct logic_pio_hwaddr *range) 1138c2ecf20Sopenharmony_ci{ 1148c2ecf20Sopenharmony_ci mutex_lock(&io_range_mutex); 1158c2ecf20Sopenharmony_ci list_del_rcu(&range->list); 1168c2ecf20Sopenharmony_ci mutex_unlock(&io_range_mutex); 1178c2ecf20Sopenharmony_ci synchronize_rcu(); 1188c2ecf20Sopenharmony_ci} 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci/** 1218c2ecf20Sopenharmony_ci * find_io_range_by_fwnode - find logical PIO range for given FW node 1228c2ecf20Sopenharmony_ci * @fwnode: FW node handle associated with logical PIO range 1238c2ecf20Sopenharmony_ci * 1248c2ecf20Sopenharmony_ci * Returns pointer to node on success, NULL otherwise. 1258c2ecf20Sopenharmony_ci * 1268c2ecf20Sopenharmony_ci * Traverse the io_range_list to find the registered node for @fwnode. 1278c2ecf20Sopenharmony_ci */ 1288c2ecf20Sopenharmony_cistruct logic_pio_hwaddr *find_io_range_by_fwnode(struct fwnode_handle *fwnode) 1298c2ecf20Sopenharmony_ci{ 1308c2ecf20Sopenharmony_ci struct logic_pio_hwaddr *range, *found_range = NULL; 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci rcu_read_lock(); 1338c2ecf20Sopenharmony_ci list_for_each_entry_rcu(range, &io_range_list, list) { 1348c2ecf20Sopenharmony_ci if (range->fwnode == fwnode) { 1358c2ecf20Sopenharmony_ci found_range = range; 1368c2ecf20Sopenharmony_ci break; 1378c2ecf20Sopenharmony_ci } 1388c2ecf20Sopenharmony_ci } 1398c2ecf20Sopenharmony_ci rcu_read_unlock(); 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci return found_range; 1428c2ecf20Sopenharmony_ci} 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci/* Return a registered range given an input PIO token */ 1458c2ecf20Sopenharmony_cistatic struct logic_pio_hwaddr *find_io_range(unsigned long pio) 1468c2ecf20Sopenharmony_ci{ 1478c2ecf20Sopenharmony_ci struct logic_pio_hwaddr *range, *found_range = NULL; 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci rcu_read_lock(); 1508c2ecf20Sopenharmony_ci list_for_each_entry_rcu(range, &io_range_list, list) { 1518c2ecf20Sopenharmony_ci if (in_range(pio, range->io_start, range->size)) { 1528c2ecf20Sopenharmony_ci found_range = range; 1538c2ecf20Sopenharmony_ci break; 1548c2ecf20Sopenharmony_ci } 1558c2ecf20Sopenharmony_ci } 1568c2ecf20Sopenharmony_ci rcu_read_unlock(); 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci if (!found_range) 1598c2ecf20Sopenharmony_ci pr_err("PIO entry token 0x%lx invalid\n", pio); 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci return found_range; 1628c2ecf20Sopenharmony_ci} 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci/** 1658c2ecf20Sopenharmony_ci * logic_pio_to_hwaddr - translate logical PIO to HW address 1668c2ecf20Sopenharmony_ci * @pio: logical PIO value 1678c2ecf20Sopenharmony_ci * 1688c2ecf20Sopenharmony_ci * Returns HW address if valid, ~0 otherwise. 1698c2ecf20Sopenharmony_ci * 1708c2ecf20Sopenharmony_ci * Translate the input logical PIO to the corresponding hardware address. 1718c2ecf20Sopenharmony_ci * The input PIO should be unique in the whole logical PIO space. 1728c2ecf20Sopenharmony_ci */ 1738c2ecf20Sopenharmony_ciresource_size_t logic_pio_to_hwaddr(unsigned long pio) 1748c2ecf20Sopenharmony_ci{ 1758c2ecf20Sopenharmony_ci struct logic_pio_hwaddr *range; 1768c2ecf20Sopenharmony_ci 1778c2ecf20Sopenharmony_ci range = find_io_range(pio); 1788c2ecf20Sopenharmony_ci if (range) 1798c2ecf20Sopenharmony_ci return range->hw_start + pio - range->io_start; 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci return (resource_size_t)~0; 1828c2ecf20Sopenharmony_ci} 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci/** 1858c2ecf20Sopenharmony_ci * logic_pio_trans_hwaddr - translate HW address to logical PIO 1868c2ecf20Sopenharmony_ci * @fwnode: FW node reference for the host 1878c2ecf20Sopenharmony_ci * @addr: Host-relative HW address 1888c2ecf20Sopenharmony_ci * @size: size to translate 1898c2ecf20Sopenharmony_ci * 1908c2ecf20Sopenharmony_ci * Returns Logical PIO value if successful, ~0UL otherwise 1918c2ecf20Sopenharmony_ci */ 1928c2ecf20Sopenharmony_ciunsigned long logic_pio_trans_hwaddr(struct fwnode_handle *fwnode, 1938c2ecf20Sopenharmony_ci resource_size_t addr, resource_size_t size) 1948c2ecf20Sopenharmony_ci{ 1958c2ecf20Sopenharmony_ci struct logic_pio_hwaddr *range; 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci range = find_io_range_by_fwnode(fwnode); 1988c2ecf20Sopenharmony_ci if (!range || range->flags == LOGIC_PIO_CPU_MMIO) { 1998c2ecf20Sopenharmony_ci pr_err("IO range not found or invalid\n"); 2008c2ecf20Sopenharmony_ci return ~0UL; 2018c2ecf20Sopenharmony_ci } 2028c2ecf20Sopenharmony_ci if (range->size < size) { 2038c2ecf20Sopenharmony_ci pr_err("resource size %pa cannot fit in IO range size %pa\n", 2048c2ecf20Sopenharmony_ci &size, &range->size); 2058c2ecf20Sopenharmony_ci return ~0UL; 2068c2ecf20Sopenharmony_ci } 2078c2ecf20Sopenharmony_ci return addr - range->hw_start + range->io_start; 2088c2ecf20Sopenharmony_ci} 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ciunsigned long logic_pio_trans_cpuaddr(resource_size_t addr) 2118c2ecf20Sopenharmony_ci{ 2128c2ecf20Sopenharmony_ci struct logic_pio_hwaddr *range; 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ci rcu_read_lock(); 2158c2ecf20Sopenharmony_ci list_for_each_entry_rcu(range, &io_range_list, list) { 2168c2ecf20Sopenharmony_ci if (range->flags != LOGIC_PIO_CPU_MMIO) 2178c2ecf20Sopenharmony_ci continue; 2188c2ecf20Sopenharmony_ci if (in_range(addr, range->hw_start, range->size)) { 2198c2ecf20Sopenharmony_ci unsigned long cpuaddr; 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci cpuaddr = addr - range->hw_start + range->io_start; 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci rcu_read_unlock(); 2248c2ecf20Sopenharmony_ci return cpuaddr; 2258c2ecf20Sopenharmony_ci } 2268c2ecf20Sopenharmony_ci } 2278c2ecf20Sopenharmony_ci rcu_read_unlock(); 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci pr_err("addr %pa not registered in io_range_list\n", &addr); 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci return ~0UL; 2328c2ecf20Sopenharmony_ci} 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci#if defined(CONFIG_INDIRECT_PIO) && defined(PCI_IOBASE) 2358c2ecf20Sopenharmony_ci#define BUILD_LOGIC_IO(bwl, type) \ 2368c2ecf20Sopenharmony_citype logic_in##bwl(unsigned long addr) \ 2378c2ecf20Sopenharmony_ci{ \ 2388c2ecf20Sopenharmony_ci type ret = (type)~0; \ 2398c2ecf20Sopenharmony_ci \ 2408c2ecf20Sopenharmony_ci if (addr < MMIO_UPPER_LIMIT) { \ 2418c2ecf20Sopenharmony_ci ret = _in##bwl(addr); \ 2428c2ecf20Sopenharmony_ci } else if (addr >= MMIO_UPPER_LIMIT && addr < IO_SPACE_LIMIT) { \ 2438c2ecf20Sopenharmony_ci struct logic_pio_hwaddr *entry = find_io_range(addr); \ 2448c2ecf20Sopenharmony_ci \ 2458c2ecf20Sopenharmony_ci if (entry) \ 2468c2ecf20Sopenharmony_ci ret = entry->ops->in(entry->hostdata, \ 2478c2ecf20Sopenharmony_ci addr, sizeof(type)); \ 2488c2ecf20Sopenharmony_ci else \ 2498c2ecf20Sopenharmony_ci WARN_ON_ONCE(1); \ 2508c2ecf20Sopenharmony_ci } \ 2518c2ecf20Sopenharmony_ci return ret; \ 2528c2ecf20Sopenharmony_ci} \ 2538c2ecf20Sopenharmony_ci \ 2548c2ecf20Sopenharmony_civoid logic_out##bwl(type value, unsigned long addr) \ 2558c2ecf20Sopenharmony_ci{ \ 2568c2ecf20Sopenharmony_ci if (addr < MMIO_UPPER_LIMIT) { \ 2578c2ecf20Sopenharmony_ci _out##bwl(value, addr); \ 2588c2ecf20Sopenharmony_ci } else if (addr >= MMIO_UPPER_LIMIT && addr < IO_SPACE_LIMIT) { \ 2598c2ecf20Sopenharmony_ci struct logic_pio_hwaddr *entry = find_io_range(addr); \ 2608c2ecf20Sopenharmony_ci \ 2618c2ecf20Sopenharmony_ci if (entry) \ 2628c2ecf20Sopenharmony_ci entry->ops->out(entry->hostdata, \ 2638c2ecf20Sopenharmony_ci addr, value, sizeof(type)); \ 2648c2ecf20Sopenharmony_ci else \ 2658c2ecf20Sopenharmony_ci WARN_ON_ONCE(1); \ 2668c2ecf20Sopenharmony_ci } \ 2678c2ecf20Sopenharmony_ci} \ 2688c2ecf20Sopenharmony_ci \ 2698c2ecf20Sopenharmony_civoid logic_ins##bwl(unsigned long addr, void *buffer, \ 2708c2ecf20Sopenharmony_ci unsigned int count) \ 2718c2ecf20Sopenharmony_ci{ \ 2728c2ecf20Sopenharmony_ci if (addr < MMIO_UPPER_LIMIT) { \ 2738c2ecf20Sopenharmony_ci reads##bwl(PCI_IOBASE + addr, buffer, count); \ 2748c2ecf20Sopenharmony_ci } else if (addr >= MMIO_UPPER_LIMIT && addr < IO_SPACE_LIMIT) { \ 2758c2ecf20Sopenharmony_ci struct logic_pio_hwaddr *entry = find_io_range(addr); \ 2768c2ecf20Sopenharmony_ci \ 2778c2ecf20Sopenharmony_ci if (entry) \ 2788c2ecf20Sopenharmony_ci entry->ops->ins(entry->hostdata, \ 2798c2ecf20Sopenharmony_ci addr, buffer, sizeof(type), count); \ 2808c2ecf20Sopenharmony_ci else \ 2818c2ecf20Sopenharmony_ci WARN_ON_ONCE(1); \ 2828c2ecf20Sopenharmony_ci } \ 2838c2ecf20Sopenharmony_ci \ 2848c2ecf20Sopenharmony_ci} \ 2858c2ecf20Sopenharmony_ci \ 2868c2ecf20Sopenharmony_civoid logic_outs##bwl(unsigned long addr, const void *buffer, \ 2878c2ecf20Sopenharmony_ci unsigned int count) \ 2888c2ecf20Sopenharmony_ci{ \ 2898c2ecf20Sopenharmony_ci if (addr < MMIO_UPPER_LIMIT) { \ 2908c2ecf20Sopenharmony_ci writes##bwl(PCI_IOBASE + addr, buffer, count); \ 2918c2ecf20Sopenharmony_ci } else if (addr >= MMIO_UPPER_LIMIT && addr < IO_SPACE_LIMIT) { \ 2928c2ecf20Sopenharmony_ci struct logic_pio_hwaddr *entry = find_io_range(addr); \ 2938c2ecf20Sopenharmony_ci \ 2948c2ecf20Sopenharmony_ci if (entry) \ 2958c2ecf20Sopenharmony_ci entry->ops->outs(entry->hostdata, \ 2968c2ecf20Sopenharmony_ci addr, buffer, sizeof(type), count); \ 2978c2ecf20Sopenharmony_ci else \ 2988c2ecf20Sopenharmony_ci WARN_ON_ONCE(1); \ 2998c2ecf20Sopenharmony_ci } \ 3008c2ecf20Sopenharmony_ci} 3018c2ecf20Sopenharmony_ci 3028c2ecf20Sopenharmony_ciBUILD_LOGIC_IO(b, u8) 3038c2ecf20Sopenharmony_ciEXPORT_SYMBOL(logic_inb); 3048c2ecf20Sopenharmony_ciEXPORT_SYMBOL(logic_insb); 3058c2ecf20Sopenharmony_ciEXPORT_SYMBOL(logic_outb); 3068c2ecf20Sopenharmony_ciEXPORT_SYMBOL(logic_outsb); 3078c2ecf20Sopenharmony_ci 3088c2ecf20Sopenharmony_ciBUILD_LOGIC_IO(w, u16) 3098c2ecf20Sopenharmony_ciEXPORT_SYMBOL(logic_inw); 3108c2ecf20Sopenharmony_ciEXPORT_SYMBOL(logic_insw); 3118c2ecf20Sopenharmony_ciEXPORT_SYMBOL(logic_outw); 3128c2ecf20Sopenharmony_ciEXPORT_SYMBOL(logic_outsw); 3138c2ecf20Sopenharmony_ci 3148c2ecf20Sopenharmony_ciBUILD_LOGIC_IO(l, u32) 3158c2ecf20Sopenharmony_ciEXPORT_SYMBOL(logic_inl); 3168c2ecf20Sopenharmony_ciEXPORT_SYMBOL(logic_insl); 3178c2ecf20Sopenharmony_ciEXPORT_SYMBOL(logic_outl); 3188c2ecf20Sopenharmony_ciEXPORT_SYMBOL(logic_outsl); 3198c2ecf20Sopenharmony_ci 3208c2ecf20Sopenharmony_ci#endif /* CONFIG_INDIRECT_PIO && PCI_IOBASE */ 321