162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2017 HiSilicon Limited, All Rights Reserved. 462306a36Sopenharmony_ci * Author: Gabriele Paoloni <gabriele.paoloni@huawei.com> 562306a36Sopenharmony_ci * Author: Zhichang Yuan <yuanzhichang@hisilicon.com> 662306a36Sopenharmony_ci * Author: John Garry <john.garry@huawei.com> 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#define pr_fmt(fmt) "LOGIC PIO: " fmt 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <linux/of.h> 1262306a36Sopenharmony_ci#include <linux/io.h> 1362306a36Sopenharmony_ci#include <linux/logic_pio.h> 1462306a36Sopenharmony_ci#include <linux/mm.h> 1562306a36Sopenharmony_ci#include <linux/rculist.h> 1662306a36Sopenharmony_ci#include <linux/sizes.h> 1762306a36Sopenharmony_ci#include <linux/slab.h> 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci/* The unique hardware address list */ 2062306a36Sopenharmony_cistatic LIST_HEAD(io_range_list); 2162306a36Sopenharmony_cistatic DEFINE_MUTEX(io_range_mutex); 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci/** 2462306a36Sopenharmony_ci * logic_pio_register_range - register logical PIO range for a host 2562306a36Sopenharmony_ci * @new_range: pointer to the IO range to be registered. 2662306a36Sopenharmony_ci * 2762306a36Sopenharmony_ci * Returns 0 on success, the error code in case of failure. 2862306a36Sopenharmony_ci * If the range already exists, -EEXIST will be returned, which should be 2962306a36Sopenharmony_ci * considered a success. 3062306a36Sopenharmony_ci * 3162306a36Sopenharmony_ci * Register a new IO range node in the IO range list. 3262306a36Sopenharmony_ci */ 3362306a36Sopenharmony_ciint logic_pio_register_range(struct logic_pio_hwaddr *new_range) 3462306a36Sopenharmony_ci{ 3562306a36Sopenharmony_ci struct logic_pio_hwaddr *range; 3662306a36Sopenharmony_ci resource_size_t start; 3762306a36Sopenharmony_ci resource_size_t end; 3862306a36Sopenharmony_ci resource_size_t mmio_end = 0; 3962306a36Sopenharmony_ci resource_size_t iio_sz = MMIO_UPPER_LIMIT; 4062306a36Sopenharmony_ci int ret = 0; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci if (!new_range || !new_range->fwnode || !new_range->size || 4362306a36Sopenharmony_ci (new_range->flags == LOGIC_PIO_INDIRECT && !new_range->ops)) 4462306a36Sopenharmony_ci return -EINVAL; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci start = new_range->hw_start; 4762306a36Sopenharmony_ci end = new_range->hw_start + new_range->size; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci mutex_lock(&io_range_mutex); 5062306a36Sopenharmony_ci list_for_each_entry(range, &io_range_list, list) { 5162306a36Sopenharmony_ci if (range->fwnode == new_range->fwnode) { 5262306a36Sopenharmony_ci /* range already there */ 5362306a36Sopenharmony_ci ret = -EEXIST; 5462306a36Sopenharmony_ci goto end_register; 5562306a36Sopenharmony_ci } 5662306a36Sopenharmony_ci if (range->flags == LOGIC_PIO_CPU_MMIO && 5762306a36Sopenharmony_ci new_range->flags == LOGIC_PIO_CPU_MMIO) { 5862306a36Sopenharmony_ci /* for MMIO ranges we need to check for overlap */ 5962306a36Sopenharmony_ci if (start >= range->hw_start + range->size || 6062306a36Sopenharmony_ci end < range->hw_start) { 6162306a36Sopenharmony_ci mmio_end = range->io_start + range->size; 6262306a36Sopenharmony_ci } else { 6362306a36Sopenharmony_ci ret = -EFAULT; 6462306a36Sopenharmony_ci goto end_register; 6562306a36Sopenharmony_ci } 6662306a36Sopenharmony_ci } else if (range->flags == LOGIC_PIO_INDIRECT && 6762306a36Sopenharmony_ci new_range->flags == LOGIC_PIO_INDIRECT) { 6862306a36Sopenharmony_ci iio_sz += range->size; 6962306a36Sopenharmony_ci } 7062306a36Sopenharmony_ci } 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci /* range not registered yet, check for available space */ 7362306a36Sopenharmony_ci if (new_range->flags == LOGIC_PIO_CPU_MMIO) { 7462306a36Sopenharmony_ci if (mmio_end + new_range->size - 1 > MMIO_UPPER_LIMIT) { 7562306a36Sopenharmony_ci /* if it's too big check if 64K space can be reserved */ 7662306a36Sopenharmony_ci if (mmio_end + SZ_64K - 1 > MMIO_UPPER_LIMIT) { 7762306a36Sopenharmony_ci ret = -E2BIG; 7862306a36Sopenharmony_ci goto end_register; 7962306a36Sopenharmony_ci } 8062306a36Sopenharmony_ci new_range->size = SZ_64K; 8162306a36Sopenharmony_ci pr_warn("Requested IO range too big, new size set to 64K\n"); 8262306a36Sopenharmony_ci } 8362306a36Sopenharmony_ci new_range->io_start = mmio_end; 8462306a36Sopenharmony_ci } else if (new_range->flags == LOGIC_PIO_INDIRECT) { 8562306a36Sopenharmony_ci if (iio_sz + new_range->size - 1 > IO_SPACE_LIMIT) { 8662306a36Sopenharmony_ci ret = -E2BIG; 8762306a36Sopenharmony_ci goto end_register; 8862306a36Sopenharmony_ci } 8962306a36Sopenharmony_ci new_range->io_start = iio_sz; 9062306a36Sopenharmony_ci } else { 9162306a36Sopenharmony_ci /* invalid flag */ 9262306a36Sopenharmony_ci ret = -EINVAL; 9362306a36Sopenharmony_ci goto end_register; 9462306a36Sopenharmony_ci } 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci list_add_tail_rcu(&new_range->list, &io_range_list); 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ciend_register: 9962306a36Sopenharmony_ci mutex_unlock(&io_range_mutex); 10062306a36Sopenharmony_ci return ret; 10162306a36Sopenharmony_ci} 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci/** 10462306a36Sopenharmony_ci * logic_pio_unregister_range - unregister a logical PIO range for a host 10562306a36Sopenharmony_ci * @range: pointer to the IO range which has been already registered. 10662306a36Sopenharmony_ci * 10762306a36Sopenharmony_ci * Unregister a previously-registered IO range node. 10862306a36Sopenharmony_ci */ 10962306a36Sopenharmony_civoid logic_pio_unregister_range(struct logic_pio_hwaddr *range) 11062306a36Sopenharmony_ci{ 11162306a36Sopenharmony_ci mutex_lock(&io_range_mutex); 11262306a36Sopenharmony_ci list_del_rcu(&range->list); 11362306a36Sopenharmony_ci mutex_unlock(&io_range_mutex); 11462306a36Sopenharmony_ci synchronize_rcu(); 11562306a36Sopenharmony_ci} 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci/** 11862306a36Sopenharmony_ci * find_io_range_by_fwnode - find logical PIO range for given FW node 11962306a36Sopenharmony_ci * @fwnode: FW node handle associated with logical PIO range 12062306a36Sopenharmony_ci * 12162306a36Sopenharmony_ci * Returns pointer to node on success, NULL otherwise. 12262306a36Sopenharmony_ci * 12362306a36Sopenharmony_ci * Traverse the io_range_list to find the registered node for @fwnode. 12462306a36Sopenharmony_ci */ 12562306a36Sopenharmony_cistruct logic_pio_hwaddr *find_io_range_by_fwnode(struct fwnode_handle *fwnode) 12662306a36Sopenharmony_ci{ 12762306a36Sopenharmony_ci struct logic_pio_hwaddr *range, *found_range = NULL; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci rcu_read_lock(); 13062306a36Sopenharmony_ci list_for_each_entry_rcu(range, &io_range_list, list) { 13162306a36Sopenharmony_ci if (range->fwnode == fwnode) { 13262306a36Sopenharmony_ci found_range = range; 13362306a36Sopenharmony_ci break; 13462306a36Sopenharmony_ci } 13562306a36Sopenharmony_ci } 13662306a36Sopenharmony_ci rcu_read_unlock(); 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci return found_range; 13962306a36Sopenharmony_ci} 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci/* Return a registered range given an input PIO token */ 14262306a36Sopenharmony_cistatic struct logic_pio_hwaddr *find_io_range(unsigned long pio) 14362306a36Sopenharmony_ci{ 14462306a36Sopenharmony_ci struct logic_pio_hwaddr *range, *found_range = NULL; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci rcu_read_lock(); 14762306a36Sopenharmony_ci list_for_each_entry_rcu(range, &io_range_list, list) { 14862306a36Sopenharmony_ci if (in_range(pio, range->io_start, range->size)) { 14962306a36Sopenharmony_ci found_range = range; 15062306a36Sopenharmony_ci break; 15162306a36Sopenharmony_ci } 15262306a36Sopenharmony_ci } 15362306a36Sopenharmony_ci rcu_read_unlock(); 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci if (!found_range) 15662306a36Sopenharmony_ci pr_err("PIO entry token 0x%lx invalid\n", pio); 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci return found_range; 15962306a36Sopenharmony_ci} 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci/** 16262306a36Sopenharmony_ci * logic_pio_to_hwaddr - translate logical PIO to HW address 16362306a36Sopenharmony_ci * @pio: logical PIO value 16462306a36Sopenharmony_ci * 16562306a36Sopenharmony_ci * Returns HW address if valid, ~0 otherwise. 16662306a36Sopenharmony_ci * 16762306a36Sopenharmony_ci * Translate the input logical PIO to the corresponding hardware address. 16862306a36Sopenharmony_ci * The input PIO should be unique in the whole logical PIO space. 16962306a36Sopenharmony_ci */ 17062306a36Sopenharmony_ciresource_size_t logic_pio_to_hwaddr(unsigned long pio) 17162306a36Sopenharmony_ci{ 17262306a36Sopenharmony_ci struct logic_pio_hwaddr *range; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci range = find_io_range(pio); 17562306a36Sopenharmony_ci if (range) 17662306a36Sopenharmony_ci return range->hw_start + pio - range->io_start; 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci return (resource_size_t)~0; 17962306a36Sopenharmony_ci} 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci/** 18262306a36Sopenharmony_ci * logic_pio_trans_hwaddr - translate HW address to logical PIO 18362306a36Sopenharmony_ci * @fwnode: FW node reference for the host 18462306a36Sopenharmony_ci * @addr: Host-relative HW address 18562306a36Sopenharmony_ci * @size: size to translate 18662306a36Sopenharmony_ci * 18762306a36Sopenharmony_ci * Returns Logical PIO value if successful, ~0UL otherwise 18862306a36Sopenharmony_ci */ 18962306a36Sopenharmony_ciunsigned long logic_pio_trans_hwaddr(struct fwnode_handle *fwnode, 19062306a36Sopenharmony_ci resource_size_t addr, resource_size_t size) 19162306a36Sopenharmony_ci{ 19262306a36Sopenharmony_ci struct logic_pio_hwaddr *range; 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci range = find_io_range_by_fwnode(fwnode); 19562306a36Sopenharmony_ci if (!range || range->flags == LOGIC_PIO_CPU_MMIO) { 19662306a36Sopenharmony_ci pr_err("IO range not found or invalid\n"); 19762306a36Sopenharmony_ci return ~0UL; 19862306a36Sopenharmony_ci } 19962306a36Sopenharmony_ci if (range->size < size) { 20062306a36Sopenharmony_ci pr_err("resource size %pa cannot fit in IO range size %pa\n", 20162306a36Sopenharmony_ci &size, &range->size); 20262306a36Sopenharmony_ci return ~0UL; 20362306a36Sopenharmony_ci } 20462306a36Sopenharmony_ci return addr - range->hw_start + range->io_start; 20562306a36Sopenharmony_ci} 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ciunsigned long logic_pio_trans_cpuaddr(resource_size_t addr) 20862306a36Sopenharmony_ci{ 20962306a36Sopenharmony_ci struct logic_pio_hwaddr *range; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci rcu_read_lock(); 21262306a36Sopenharmony_ci list_for_each_entry_rcu(range, &io_range_list, list) { 21362306a36Sopenharmony_ci if (range->flags != LOGIC_PIO_CPU_MMIO) 21462306a36Sopenharmony_ci continue; 21562306a36Sopenharmony_ci if (in_range(addr, range->hw_start, range->size)) { 21662306a36Sopenharmony_ci unsigned long cpuaddr; 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci cpuaddr = addr - range->hw_start + range->io_start; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci rcu_read_unlock(); 22162306a36Sopenharmony_ci return cpuaddr; 22262306a36Sopenharmony_ci } 22362306a36Sopenharmony_ci } 22462306a36Sopenharmony_ci rcu_read_unlock(); 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci pr_err("addr %pa not registered in io_range_list\n", &addr); 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci return ~0UL; 22962306a36Sopenharmony_ci} 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci#if defined(CONFIG_INDIRECT_PIO) && defined(PCI_IOBASE) 23262306a36Sopenharmony_ci#define BUILD_LOGIC_IO(bwl, type) \ 23362306a36Sopenharmony_citype logic_in##bwl(unsigned long addr) \ 23462306a36Sopenharmony_ci{ \ 23562306a36Sopenharmony_ci type ret = (type)~0; \ 23662306a36Sopenharmony_ci \ 23762306a36Sopenharmony_ci if (addr < MMIO_UPPER_LIMIT) { \ 23862306a36Sopenharmony_ci ret = _in##bwl(addr); \ 23962306a36Sopenharmony_ci } else if (addr >= MMIO_UPPER_LIMIT && addr < IO_SPACE_LIMIT) { \ 24062306a36Sopenharmony_ci struct logic_pio_hwaddr *entry = find_io_range(addr); \ 24162306a36Sopenharmony_ci \ 24262306a36Sopenharmony_ci if (entry) \ 24362306a36Sopenharmony_ci ret = entry->ops->in(entry->hostdata, \ 24462306a36Sopenharmony_ci addr, sizeof(type)); \ 24562306a36Sopenharmony_ci else \ 24662306a36Sopenharmony_ci WARN_ON_ONCE(1); \ 24762306a36Sopenharmony_ci } \ 24862306a36Sopenharmony_ci return ret; \ 24962306a36Sopenharmony_ci} \ 25062306a36Sopenharmony_ci \ 25162306a36Sopenharmony_civoid logic_out##bwl(type value, unsigned long addr) \ 25262306a36Sopenharmony_ci{ \ 25362306a36Sopenharmony_ci if (addr < MMIO_UPPER_LIMIT) { \ 25462306a36Sopenharmony_ci _out##bwl(value, addr); \ 25562306a36Sopenharmony_ci } else if (addr >= MMIO_UPPER_LIMIT && addr < IO_SPACE_LIMIT) { \ 25662306a36Sopenharmony_ci struct logic_pio_hwaddr *entry = find_io_range(addr); \ 25762306a36Sopenharmony_ci \ 25862306a36Sopenharmony_ci if (entry) \ 25962306a36Sopenharmony_ci entry->ops->out(entry->hostdata, \ 26062306a36Sopenharmony_ci addr, value, sizeof(type)); \ 26162306a36Sopenharmony_ci else \ 26262306a36Sopenharmony_ci WARN_ON_ONCE(1); \ 26362306a36Sopenharmony_ci } \ 26462306a36Sopenharmony_ci} \ 26562306a36Sopenharmony_ci \ 26662306a36Sopenharmony_civoid logic_ins##bwl(unsigned long addr, void *buffer, \ 26762306a36Sopenharmony_ci unsigned int count) \ 26862306a36Sopenharmony_ci{ \ 26962306a36Sopenharmony_ci if (addr < MMIO_UPPER_LIMIT) { \ 27062306a36Sopenharmony_ci reads##bwl(PCI_IOBASE + addr, buffer, count); \ 27162306a36Sopenharmony_ci } else if (addr >= MMIO_UPPER_LIMIT && addr < IO_SPACE_LIMIT) { \ 27262306a36Sopenharmony_ci struct logic_pio_hwaddr *entry = find_io_range(addr); \ 27362306a36Sopenharmony_ci \ 27462306a36Sopenharmony_ci if (entry) \ 27562306a36Sopenharmony_ci entry->ops->ins(entry->hostdata, \ 27662306a36Sopenharmony_ci addr, buffer, sizeof(type), count); \ 27762306a36Sopenharmony_ci else \ 27862306a36Sopenharmony_ci WARN_ON_ONCE(1); \ 27962306a36Sopenharmony_ci } \ 28062306a36Sopenharmony_ci \ 28162306a36Sopenharmony_ci} \ 28262306a36Sopenharmony_ci \ 28362306a36Sopenharmony_civoid logic_outs##bwl(unsigned long addr, const void *buffer, \ 28462306a36Sopenharmony_ci unsigned int count) \ 28562306a36Sopenharmony_ci{ \ 28662306a36Sopenharmony_ci if (addr < MMIO_UPPER_LIMIT) { \ 28762306a36Sopenharmony_ci writes##bwl(PCI_IOBASE + addr, buffer, count); \ 28862306a36Sopenharmony_ci } else if (addr >= MMIO_UPPER_LIMIT && addr < IO_SPACE_LIMIT) { \ 28962306a36Sopenharmony_ci struct logic_pio_hwaddr *entry = find_io_range(addr); \ 29062306a36Sopenharmony_ci \ 29162306a36Sopenharmony_ci if (entry) \ 29262306a36Sopenharmony_ci entry->ops->outs(entry->hostdata, \ 29362306a36Sopenharmony_ci addr, buffer, sizeof(type), count); \ 29462306a36Sopenharmony_ci else \ 29562306a36Sopenharmony_ci WARN_ON_ONCE(1); \ 29662306a36Sopenharmony_ci } \ 29762306a36Sopenharmony_ci} 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ciBUILD_LOGIC_IO(b, u8) 30062306a36Sopenharmony_ciEXPORT_SYMBOL(logic_inb); 30162306a36Sopenharmony_ciEXPORT_SYMBOL(logic_insb); 30262306a36Sopenharmony_ciEXPORT_SYMBOL(logic_outb); 30362306a36Sopenharmony_ciEXPORT_SYMBOL(logic_outsb); 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ciBUILD_LOGIC_IO(w, u16) 30662306a36Sopenharmony_ciEXPORT_SYMBOL(logic_inw); 30762306a36Sopenharmony_ciEXPORT_SYMBOL(logic_insw); 30862306a36Sopenharmony_ciEXPORT_SYMBOL(logic_outw); 30962306a36Sopenharmony_ciEXPORT_SYMBOL(logic_outsw); 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ciBUILD_LOGIC_IO(l, u32) 31262306a36Sopenharmony_ciEXPORT_SYMBOL(logic_inl); 31362306a36Sopenharmony_ciEXPORT_SYMBOL(logic_insl); 31462306a36Sopenharmony_ciEXPORT_SYMBOL(logic_outl); 31562306a36Sopenharmony_ciEXPORT_SYMBOL(logic_outsl); 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci#endif /* CONFIG_INDIRECT_PIO && PCI_IOBASE */ 318