1031e3601SZhichang Yuan // SPDX-License-Identifier: GPL-2.0+ 2031e3601SZhichang Yuan /* 3031e3601SZhichang Yuan * Copyright (C) 2017 HiSilicon Limited, All Rights Reserved. 4031e3601SZhichang Yuan * Author: Gabriele Paoloni <gabriele.paoloni@huawei.com> 5031e3601SZhichang Yuan * Author: Zhichang Yuan <yuanzhichang@hisilicon.com> 6031e3601SZhichang Yuan */ 7031e3601SZhichang Yuan 8031e3601SZhichang Yuan #define pr_fmt(fmt) "LOGIC PIO: " fmt 9031e3601SZhichang Yuan 10031e3601SZhichang Yuan #include <linux/of.h> 11031e3601SZhichang Yuan #include <linux/io.h> 12031e3601SZhichang Yuan #include <linux/logic_pio.h> 13031e3601SZhichang Yuan #include <linux/mm.h> 14031e3601SZhichang Yuan #include <linux/rculist.h> 15031e3601SZhichang Yuan #include <linux/sizes.h> 16031e3601SZhichang Yuan #include <linux/slab.h> 17031e3601SZhichang Yuan 18031e3601SZhichang Yuan /* The unique hardware address list */ 19031e3601SZhichang Yuan static LIST_HEAD(io_range_list); 20031e3601SZhichang Yuan static DEFINE_MUTEX(io_range_mutex); 21031e3601SZhichang Yuan 22031e3601SZhichang Yuan /* Consider a kernel general helper for this */ 23031e3601SZhichang Yuan #define in_range(b, first, len) ((b) >= (first) && (b) < (first) + (len)) 24031e3601SZhichang Yuan 25031e3601SZhichang Yuan /** 26031e3601SZhichang Yuan * logic_pio_register_range - register logical PIO range for a host 27031e3601SZhichang Yuan * @new_range: pointer to the IO range to be registered. 28031e3601SZhichang Yuan * 29031e3601SZhichang Yuan * Returns 0 on success, the error code in case of failure. 30031e3601SZhichang Yuan * 31031e3601SZhichang Yuan * Register a new IO range node in the IO range list. 32031e3601SZhichang Yuan */ 33031e3601SZhichang Yuan int logic_pio_register_range(struct logic_pio_hwaddr *new_range) 34031e3601SZhichang Yuan { 35031e3601SZhichang Yuan struct logic_pio_hwaddr *range; 36031e3601SZhichang Yuan resource_size_t start; 37031e3601SZhichang Yuan resource_size_t end; 38031e3601SZhichang Yuan resource_size_t mmio_sz = 0; 39031e3601SZhichang Yuan resource_size_t iio_sz = MMIO_UPPER_LIMIT; 40031e3601SZhichang Yuan int ret = 0; 41031e3601SZhichang Yuan 42031e3601SZhichang Yuan if (!new_range || !new_range->fwnode || !new_range->size) 43031e3601SZhichang Yuan return -EINVAL; 44031e3601SZhichang Yuan 45031e3601SZhichang Yuan start = new_range->hw_start; 46031e3601SZhichang Yuan end = new_range->hw_start + new_range->size; 47031e3601SZhichang Yuan 48031e3601SZhichang Yuan mutex_lock(&io_range_mutex); 49*06709e81SJohn Garry list_for_each_entry(range, &io_range_list, list) { 50031e3601SZhichang Yuan if (range->fwnode == new_range->fwnode) { 51031e3601SZhichang Yuan /* range already there */ 52031e3601SZhichang Yuan goto end_register; 53031e3601SZhichang Yuan } 54031e3601SZhichang Yuan if (range->flags == LOGIC_PIO_CPU_MMIO && 55031e3601SZhichang Yuan new_range->flags == LOGIC_PIO_CPU_MMIO) { 56031e3601SZhichang Yuan /* for MMIO ranges we need to check for overlap */ 57031e3601SZhichang Yuan if (start >= range->hw_start + range->size || 58031e3601SZhichang Yuan end < range->hw_start) { 59031e3601SZhichang Yuan mmio_sz += range->size; 60031e3601SZhichang Yuan } else { 61031e3601SZhichang Yuan ret = -EFAULT; 62031e3601SZhichang Yuan goto end_register; 63031e3601SZhichang Yuan } 64031e3601SZhichang Yuan } else if (range->flags == LOGIC_PIO_INDIRECT && 65031e3601SZhichang Yuan new_range->flags == LOGIC_PIO_INDIRECT) { 66031e3601SZhichang Yuan iio_sz += range->size; 67031e3601SZhichang Yuan } 68031e3601SZhichang Yuan } 69031e3601SZhichang Yuan 70031e3601SZhichang Yuan /* range not registered yet, check for available space */ 71031e3601SZhichang Yuan if (new_range->flags == LOGIC_PIO_CPU_MMIO) { 72031e3601SZhichang Yuan if (mmio_sz + new_range->size - 1 > MMIO_UPPER_LIMIT) { 73031e3601SZhichang Yuan /* if it's too big check if 64K space can be reserved */ 74031e3601SZhichang Yuan if (mmio_sz + SZ_64K - 1 > MMIO_UPPER_LIMIT) { 75031e3601SZhichang Yuan ret = -E2BIG; 76031e3601SZhichang Yuan goto end_register; 77031e3601SZhichang Yuan } 78031e3601SZhichang Yuan new_range->size = SZ_64K; 79031e3601SZhichang Yuan pr_warn("Requested IO range too big, new size set to 64K\n"); 80031e3601SZhichang Yuan } 81031e3601SZhichang Yuan new_range->io_start = mmio_sz; 82031e3601SZhichang Yuan } else if (new_range->flags == LOGIC_PIO_INDIRECT) { 83031e3601SZhichang Yuan if (iio_sz + new_range->size - 1 > IO_SPACE_LIMIT) { 84031e3601SZhichang Yuan ret = -E2BIG; 85031e3601SZhichang Yuan goto end_register; 86031e3601SZhichang Yuan } 87031e3601SZhichang Yuan new_range->io_start = iio_sz; 88031e3601SZhichang Yuan } else { 89031e3601SZhichang Yuan /* invalid flag */ 90031e3601SZhichang Yuan ret = -EINVAL; 91031e3601SZhichang Yuan goto end_register; 92031e3601SZhichang Yuan } 93031e3601SZhichang Yuan 94031e3601SZhichang Yuan list_add_tail_rcu(&new_range->list, &io_range_list); 95031e3601SZhichang Yuan 96031e3601SZhichang Yuan end_register: 97031e3601SZhichang Yuan mutex_unlock(&io_range_mutex); 98031e3601SZhichang Yuan return ret; 99031e3601SZhichang Yuan } 100031e3601SZhichang Yuan 101031e3601SZhichang Yuan /** 102031e3601SZhichang Yuan * find_io_range_by_fwnode - find logical PIO range for given FW node 103031e3601SZhichang Yuan * @fwnode: FW node handle associated with logical PIO range 104031e3601SZhichang Yuan * 105031e3601SZhichang Yuan * Returns pointer to node on success, NULL otherwise. 106031e3601SZhichang Yuan * 107031e3601SZhichang Yuan * Traverse the io_range_list to find the registered node for @fwnode. 108031e3601SZhichang Yuan */ 109031e3601SZhichang Yuan struct logic_pio_hwaddr *find_io_range_by_fwnode(struct fwnode_handle *fwnode) 110031e3601SZhichang Yuan { 111*06709e81SJohn Garry struct logic_pio_hwaddr *range, *found_range = NULL; 112031e3601SZhichang Yuan 113*06709e81SJohn Garry rcu_read_lock(); 114031e3601SZhichang Yuan list_for_each_entry_rcu(range, &io_range_list, list) { 115*06709e81SJohn Garry if (range->fwnode == fwnode) { 116*06709e81SJohn Garry found_range = range; 117*06709e81SJohn Garry break; 118031e3601SZhichang Yuan } 119*06709e81SJohn Garry } 120*06709e81SJohn Garry rcu_read_unlock(); 121*06709e81SJohn Garry 122*06709e81SJohn Garry return found_range; 123031e3601SZhichang Yuan } 124031e3601SZhichang Yuan 125031e3601SZhichang Yuan /* Return a registered range given an input PIO token */ 126031e3601SZhichang Yuan static struct logic_pio_hwaddr *find_io_range(unsigned long pio) 127031e3601SZhichang Yuan { 128*06709e81SJohn Garry struct logic_pio_hwaddr *range, *found_range = NULL; 129031e3601SZhichang Yuan 130*06709e81SJohn Garry rcu_read_lock(); 131031e3601SZhichang Yuan list_for_each_entry_rcu(range, &io_range_list, list) { 132*06709e81SJohn Garry if (in_range(pio, range->io_start, range->size)) { 133*06709e81SJohn Garry found_range = range; 134*06709e81SJohn Garry break; 135031e3601SZhichang Yuan } 136*06709e81SJohn Garry } 137*06709e81SJohn Garry rcu_read_unlock(); 138*06709e81SJohn Garry 139*06709e81SJohn Garry if (!found_range) 140*06709e81SJohn Garry pr_err("PIO entry token 0x%lx invalid\n", pio); 141*06709e81SJohn Garry 142*06709e81SJohn Garry return found_range; 143031e3601SZhichang Yuan } 144031e3601SZhichang Yuan 145031e3601SZhichang Yuan /** 146031e3601SZhichang Yuan * logic_pio_to_hwaddr - translate logical PIO to HW address 147031e3601SZhichang Yuan * @pio: logical PIO value 148031e3601SZhichang Yuan * 149031e3601SZhichang Yuan * Returns HW address if valid, ~0 otherwise. 150031e3601SZhichang Yuan * 151031e3601SZhichang Yuan * Translate the input logical PIO to the corresponding hardware address. 152031e3601SZhichang Yuan * The input PIO should be unique in the whole logical PIO space. 153031e3601SZhichang Yuan */ 154031e3601SZhichang Yuan resource_size_t logic_pio_to_hwaddr(unsigned long pio) 155031e3601SZhichang Yuan { 156031e3601SZhichang Yuan struct logic_pio_hwaddr *range; 157031e3601SZhichang Yuan 158031e3601SZhichang Yuan range = find_io_range(pio); 159031e3601SZhichang Yuan if (range) 160031e3601SZhichang Yuan return range->hw_start + pio - range->io_start; 161031e3601SZhichang Yuan 162031e3601SZhichang Yuan return (resource_size_t)~0; 163031e3601SZhichang Yuan } 164031e3601SZhichang Yuan 165031e3601SZhichang Yuan /** 166031e3601SZhichang Yuan * logic_pio_trans_hwaddr - translate HW address to logical PIO 167031e3601SZhichang Yuan * @fwnode: FW node reference for the host 168031e3601SZhichang Yuan * @addr: Host-relative HW address 169031e3601SZhichang Yuan * @size: size to translate 170031e3601SZhichang Yuan * 171031e3601SZhichang Yuan * Returns Logical PIO value if successful, ~0UL otherwise 172031e3601SZhichang Yuan */ 173031e3601SZhichang Yuan unsigned long logic_pio_trans_hwaddr(struct fwnode_handle *fwnode, 174031e3601SZhichang Yuan resource_size_t addr, resource_size_t size) 175031e3601SZhichang Yuan { 176031e3601SZhichang Yuan struct logic_pio_hwaddr *range; 177031e3601SZhichang Yuan 178031e3601SZhichang Yuan range = find_io_range_by_fwnode(fwnode); 179031e3601SZhichang Yuan if (!range || range->flags == LOGIC_PIO_CPU_MMIO) { 180031e3601SZhichang Yuan pr_err("IO range not found or invalid\n"); 181031e3601SZhichang Yuan return ~0UL; 182031e3601SZhichang Yuan } 183031e3601SZhichang Yuan if (range->size < size) { 184031e3601SZhichang Yuan pr_err("resource size %pa cannot fit in IO range size %pa\n", 185031e3601SZhichang Yuan &size, &range->size); 186031e3601SZhichang Yuan return ~0UL; 187031e3601SZhichang Yuan } 188031e3601SZhichang Yuan return addr - range->hw_start + range->io_start; 189031e3601SZhichang Yuan } 190031e3601SZhichang Yuan 191031e3601SZhichang Yuan unsigned long logic_pio_trans_cpuaddr(resource_size_t addr) 192031e3601SZhichang Yuan { 193031e3601SZhichang Yuan struct logic_pio_hwaddr *range; 194031e3601SZhichang Yuan 195*06709e81SJohn Garry rcu_read_lock(); 196031e3601SZhichang Yuan list_for_each_entry_rcu(range, &io_range_list, list) { 197031e3601SZhichang Yuan if (range->flags != LOGIC_PIO_CPU_MMIO) 198031e3601SZhichang Yuan continue; 199*06709e81SJohn Garry if (in_range(addr, range->hw_start, range->size)) { 200*06709e81SJohn Garry unsigned long cpuaddr; 201*06709e81SJohn Garry 202*06709e81SJohn Garry cpuaddr = addr - range->hw_start + range->io_start; 203*06709e81SJohn Garry 204*06709e81SJohn Garry rcu_read_unlock(); 205*06709e81SJohn Garry return cpuaddr; 206031e3601SZhichang Yuan } 207*06709e81SJohn Garry } 208*06709e81SJohn Garry rcu_read_unlock(); 209*06709e81SJohn Garry 210*06709e81SJohn Garry pr_err("addr %pa not registered in io_range_list\n", &addr); 211*06709e81SJohn Garry 212031e3601SZhichang Yuan return ~0UL; 213031e3601SZhichang Yuan } 214031e3601SZhichang Yuan 215031e3601SZhichang Yuan #if defined(CONFIG_INDIRECT_PIO) && defined(PCI_IOBASE) 216031e3601SZhichang Yuan #define BUILD_LOGIC_IO(bw, type) \ 217031e3601SZhichang Yuan type logic_in##bw(unsigned long addr) \ 218031e3601SZhichang Yuan { \ 219031e3601SZhichang Yuan type ret = (type)~0; \ 220031e3601SZhichang Yuan \ 221031e3601SZhichang Yuan if (addr < MMIO_UPPER_LIMIT) { \ 222031e3601SZhichang Yuan ret = read##bw(PCI_IOBASE + addr); \ 223031e3601SZhichang Yuan } else if (addr >= MMIO_UPPER_LIMIT && addr < IO_SPACE_LIMIT) { \ 224031e3601SZhichang Yuan struct logic_pio_hwaddr *entry = find_io_range(addr); \ 225031e3601SZhichang Yuan \ 226031e3601SZhichang Yuan if (entry && entry->ops) \ 227031e3601SZhichang Yuan ret = entry->ops->in(entry->hostdata, \ 228031e3601SZhichang Yuan addr, sizeof(type)); \ 229031e3601SZhichang Yuan else \ 230031e3601SZhichang Yuan WARN_ON_ONCE(1); \ 231031e3601SZhichang Yuan } \ 232031e3601SZhichang Yuan return ret; \ 233031e3601SZhichang Yuan } \ 234031e3601SZhichang Yuan \ 235031e3601SZhichang Yuan void logic_out##bw(type value, unsigned long addr) \ 236031e3601SZhichang Yuan { \ 237031e3601SZhichang Yuan if (addr < MMIO_UPPER_LIMIT) { \ 238031e3601SZhichang Yuan write##bw(value, PCI_IOBASE + addr); \ 239031e3601SZhichang Yuan } else if (addr >= MMIO_UPPER_LIMIT && addr < IO_SPACE_LIMIT) { \ 240031e3601SZhichang Yuan struct logic_pio_hwaddr *entry = find_io_range(addr); \ 241031e3601SZhichang Yuan \ 242031e3601SZhichang Yuan if (entry && entry->ops) \ 243031e3601SZhichang Yuan entry->ops->out(entry->hostdata, \ 244031e3601SZhichang Yuan addr, value, sizeof(type)); \ 245031e3601SZhichang Yuan else \ 246031e3601SZhichang Yuan WARN_ON_ONCE(1); \ 247031e3601SZhichang Yuan } \ 248031e3601SZhichang Yuan } \ 249031e3601SZhichang Yuan \ 250031e3601SZhichang Yuan void logic_ins##bw(unsigned long addr, void *buffer, \ 251031e3601SZhichang Yuan unsigned int count) \ 252031e3601SZhichang Yuan { \ 253031e3601SZhichang Yuan if (addr < MMIO_UPPER_LIMIT) { \ 254031e3601SZhichang Yuan reads##bw(PCI_IOBASE + addr, buffer, count); \ 255031e3601SZhichang Yuan } else if (addr >= MMIO_UPPER_LIMIT && addr < IO_SPACE_LIMIT) { \ 256031e3601SZhichang Yuan struct logic_pio_hwaddr *entry = find_io_range(addr); \ 257031e3601SZhichang Yuan \ 258031e3601SZhichang Yuan if (entry && entry->ops) \ 259031e3601SZhichang Yuan entry->ops->ins(entry->hostdata, \ 260031e3601SZhichang Yuan addr, buffer, sizeof(type), count); \ 261031e3601SZhichang Yuan else \ 262031e3601SZhichang Yuan WARN_ON_ONCE(1); \ 263031e3601SZhichang Yuan } \ 264031e3601SZhichang Yuan \ 265031e3601SZhichang Yuan } \ 266031e3601SZhichang Yuan \ 267031e3601SZhichang Yuan void logic_outs##bw(unsigned long addr, const void *buffer, \ 268031e3601SZhichang Yuan unsigned int count) \ 269031e3601SZhichang Yuan { \ 270031e3601SZhichang Yuan if (addr < MMIO_UPPER_LIMIT) { \ 271031e3601SZhichang Yuan writes##bw(PCI_IOBASE + addr, buffer, count); \ 272031e3601SZhichang Yuan } else if (addr >= MMIO_UPPER_LIMIT && addr < IO_SPACE_LIMIT) { \ 273031e3601SZhichang Yuan struct logic_pio_hwaddr *entry = find_io_range(addr); \ 274031e3601SZhichang Yuan \ 275031e3601SZhichang Yuan if (entry && entry->ops) \ 276031e3601SZhichang Yuan entry->ops->outs(entry->hostdata, \ 277031e3601SZhichang Yuan addr, buffer, sizeof(type), count); \ 278031e3601SZhichang Yuan else \ 279031e3601SZhichang Yuan WARN_ON_ONCE(1); \ 280031e3601SZhichang Yuan } \ 281031e3601SZhichang Yuan } 282031e3601SZhichang Yuan 283031e3601SZhichang Yuan BUILD_LOGIC_IO(b, u8) 284031e3601SZhichang Yuan EXPORT_SYMBOL(logic_inb); 285031e3601SZhichang Yuan EXPORT_SYMBOL(logic_insb); 286031e3601SZhichang Yuan EXPORT_SYMBOL(logic_outb); 287031e3601SZhichang Yuan EXPORT_SYMBOL(logic_outsb); 288031e3601SZhichang Yuan 289031e3601SZhichang Yuan BUILD_LOGIC_IO(w, u16) 290031e3601SZhichang Yuan EXPORT_SYMBOL(logic_inw); 291031e3601SZhichang Yuan EXPORT_SYMBOL(logic_insw); 292031e3601SZhichang Yuan EXPORT_SYMBOL(logic_outw); 293031e3601SZhichang Yuan EXPORT_SYMBOL(logic_outsw); 294031e3601SZhichang Yuan 295031e3601SZhichang Yuan BUILD_LOGIC_IO(l, u32) 296031e3601SZhichang Yuan EXPORT_SYMBOL(logic_inl); 297031e3601SZhichang Yuan EXPORT_SYMBOL(logic_insl); 298031e3601SZhichang Yuan EXPORT_SYMBOL(logic_outl); 299031e3601SZhichang Yuan EXPORT_SYMBOL(logic_outsl); 300031e3601SZhichang Yuan 301031e3601SZhichang Yuan #endif /* CONFIG_INDIRECT_PIO && PCI_IOBASE */ 302