162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Driver for FPGA Device Feature List (DFL) Support 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2017-2018 Intel Corporation, Inc. 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Authors: 862306a36Sopenharmony_ci * Kang Luwei <luwei.kang@intel.com> 962306a36Sopenharmony_ci * Zhang Yi <yi.z.zhang@intel.com> 1062306a36Sopenharmony_ci * Wu Hao <hao.wu@intel.com> 1162306a36Sopenharmony_ci * Xiao Guangrong <guangrong.xiao@linux.intel.com> 1262306a36Sopenharmony_ci */ 1362306a36Sopenharmony_ci#include <linux/dfl.h> 1462306a36Sopenharmony_ci#include <linux/fpga-dfl.h> 1562306a36Sopenharmony_ci#include <linux/module.h> 1662306a36Sopenharmony_ci#include <linux/overflow.h> 1762306a36Sopenharmony_ci#include <linux/uaccess.h> 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci#include "dfl.h" 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_cistatic DEFINE_MUTEX(dfl_id_mutex); 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci/* 2462306a36Sopenharmony_ci * when adding a new feature dev support in DFL framework, it's required to 2562306a36Sopenharmony_ci * add a new item in enum dfl_id_type and provide related information in below 2662306a36Sopenharmony_ci * dfl_devs table which is indexed by dfl_id_type, e.g. name string used for 2762306a36Sopenharmony_ci * platform device creation (define name strings in dfl.h, as they could be 2862306a36Sopenharmony_ci * reused by platform device drivers). 2962306a36Sopenharmony_ci * 3062306a36Sopenharmony_ci * if the new feature dev needs chardev support, then it's required to add 3162306a36Sopenharmony_ci * a new item in dfl_chardevs table and configure dfl_devs[i].devt_type as 3262306a36Sopenharmony_ci * index to dfl_chardevs table. If no chardev support just set devt_type 3362306a36Sopenharmony_ci * as one invalid index (DFL_FPGA_DEVT_MAX). 3462306a36Sopenharmony_ci */ 3562306a36Sopenharmony_cienum dfl_fpga_devt_type { 3662306a36Sopenharmony_ci DFL_FPGA_DEVT_FME, 3762306a36Sopenharmony_ci DFL_FPGA_DEVT_PORT, 3862306a36Sopenharmony_ci DFL_FPGA_DEVT_MAX, 3962306a36Sopenharmony_ci}; 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_cistatic struct lock_class_key dfl_pdata_keys[DFL_ID_MAX]; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_cistatic const char *dfl_pdata_key_strings[DFL_ID_MAX] = { 4462306a36Sopenharmony_ci "dfl-fme-pdata", 4562306a36Sopenharmony_ci "dfl-port-pdata", 4662306a36Sopenharmony_ci}; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci/** 4962306a36Sopenharmony_ci * struct dfl_dev_info - dfl feature device information. 5062306a36Sopenharmony_ci * @name: name string of the feature platform device. 5162306a36Sopenharmony_ci * @dfh_id: id value in Device Feature Header (DFH) register by DFL spec. 5262306a36Sopenharmony_ci * @id: idr id of the feature dev. 5362306a36Sopenharmony_ci * @devt_type: index to dfl_chrdevs[]. 5462306a36Sopenharmony_ci */ 5562306a36Sopenharmony_cistruct dfl_dev_info { 5662306a36Sopenharmony_ci const char *name; 5762306a36Sopenharmony_ci u16 dfh_id; 5862306a36Sopenharmony_ci struct idr id; 5962306a36Sopenharmony_ci enum dfl_fpga_devt_type devt_type; 6062306a36Sopenharmony_ci}; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci/* it is indexed by dfl_id_type */ 6362306a36Sopenharmony_cistatic struct dfl_dev_info dfl_devs[] = { 6462306a36Sopenharmony_ci {.name = DFL_FPGA_FEATURE_DEV_FME, .dfh_id = DFH_ID_FIU_FME, 6562306a36Sopenharmony_ci .devt_type = DFL_FPGA_DEVT_FME}, 6662306a36Sopenharmony_ci {.name = DFL_FPGA_FEATURE_DEV_PORT, .dfh_id = DFH_ID_FIU_PORT, 6762306a36Sopenharmony_ci .devt_type = DFL_FPGA_DEVT_PORT}, 6862306a36Sopenharmony_ci}; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci/** 7162306a36Sopenharmony_ci * struct dfl_chardev_info - chardev information of dfl feature device 7262306a36Sopenharmony_ci * @name: nmae string of the char device. 7362306a36Sopenharmony_ci * @devt: devt of the char device. 7462306a36Sopenharmony_ci */ 7562306a36Sopenharmony_cistruct dfl_chardev_info { 7662306a36Sopenharmony_ci const char *name; 7762306a36Sopenharmony_ci dev_t devt; 7862306a36Sopenharmony_ci}; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci/* indexed by enum dfl_fpga_devt_type */ 8162306a36Sopenharmony_cistatic struct dfl_chardev_info dfl_chrdevs[] = { 8262306a36Sopenharmony_ci {.name = DFL_FPGA_FEATURE_DEV_FME}, 8362306a36Sopenharmony_ci {.name = DFL_FPGA_FEATURE_DEV_PORT}, 8462306a36Sopenharmony_ci}; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_cistatic void dfl_ids_init(void) 8762306a36Sopenharmony_ci{ 8862306a36Sopenharmony_ci int i; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(dfl_devs); i++) 9162306a36Sopenharmony_ci idr_init(&dfl_devs[i].id); 9262306a36Sopenharmony_ci} 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_cistatic void dfl_ids_destroy(void) 9562306a36Sopenharmony_ci{ 9662306a36Sopenharmony_ci int i; 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(dfl_devs); i++) 9962306a36Sopenharmony_ci idr_destroy(&dfl_devs[i].id); 10062306a36Sopenharmony_ci} 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_cistatic int dfl_id_alloc(enum dfl_id_type type, struct device *dev) 10362306a36Sopenharmony_ci{ 10462306a36Sopenharmony_ci int id; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci WARN_ON(type >= DFL_ID_MAX); 10762306a36Sopenharmony_ci mutex_lock(&dfl_id_mutex); 10862306a36Sopenharmony_ci id = idr_alloc(&dfl_devs[type].id, dev, 0, 0, GFP_KERNEL); 10962306a36Sopenharmony_ci mutex_unlock(&dfl_id_mutex); 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci return id; 11262306a36Sopenharmony_ci} 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_cistatic void dfl_id_free(enum dfl_id_type type, int id) 11562306a36Sopenharmony_ci{ 11662306a36Sopenharmony_ci WARN_ON(type >= DFL_ID_MAX); 11762306a36Sopenharmony_ci mutex_lock(&dfl_id_mutex); 11862306a36Sopenharmony_ci idr_remove(&dfl_devs[type].id, id); 11962306a36Sopenharmony_ci mutex_unlock(&dfl_id_mutex); 12062306a36Sopenharmony_ci} 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_cistatic enum dfl_id_type feature_dev_id_type(struct platform_device *pdev) 12362306a36Sopenharmony_ci{ 12462306a36Sopenharmony_ci int i; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(dfl_devs); i++) 12762306a36Sopenharmony_ci if (!strcmp(dfl_devs[i].name, pdev->name)) 12862306a36Sopenharmony_ci return i; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci return DFL_ID_MAX; 13162306a36Sopenharmony_ci} 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_cistatic enum dfl_id_type dfh_id_to_type(u16 id) 13462306a36Sopenharmony_ci{ 13562306a36Sopenharmony_ci int i; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(dfl_devs); i++) 13862306a36Sopenharmony_ci if (dfl_devs[i].dfh_id == id) 13962306a36Sopenharmony_ci return i; 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci return DFL_ID_MAX; 14262306a36Sopenharmony_ci} 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci/* 14562306a36Sopenharmony_ci * introduce a global port_ops list, it allows port drivers to register ops 14662306a36Sopenharmony_ci * in such list, then other feature devices (e.g. FME), could use the port 14762306a36Sopenharmony_ci * functions even related port platform device is hidden. Below is one example, 14862306a36Sopenharmony_ci * in virtualization case of PCIe-based FPGA DFL device, when SRIOV is 14962306a36Sopenharmony_ci * enabled, port (and it's AFU) is turned into VF and port platform device 15062306a36Sopenharmony_ci * is hidden from system but it's still required to access port to finish FPGA 15162306a36Sopenharmony_ci * reconfiguration function in FME. 15262306a36Sopenharmony_ci */ 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_cistatic DEFINE_MUTEX(dfl_port_ops_mutex); 15562306a36Sopenharmony_cistatic LIST_HEAD(dfl_port_ops_list); 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci/** 15862306a36Sopenharmony_ci * dfl_fpga_port_ops_get - get matched port ops from the global list 15962306a36Sopenharmony_ci * @pdev: platform device to match with associated port ops. 16062306a36Sopenharmony_ci * Return: matched port ops on success, NULL otherwise. 16162306a36Sopenharmony_ci * 16262306a36Sopenharmony_ci * Please note that must dfl_fpga_port_ops_put after use the port_ops. 16362306a36Sopenharmony_ci */ 16462306a36Sopenharmony_cistruct dfl_fpga_port_ops *dfl_fpga_port_ops_get(struct platform_device *pdev) 16562306a36Sopenharmony_ci{ 16662306a36Sopenharmony_ci struct dfl_fpga_port_ops *ops = NULL; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci mutex_lock(&dfl_port_ops_mutex); 16962306a36Sopenharmony_ci if (list_empty(&dfl_port_ops_list)) 17062306a36Sopenharmony_ci goto done; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci list_for_each_entry(ops, &dfl_port_ops_list, node) { 17362306a36Sopenharmony_ci /* match port_ops using the name of platform device */ 17462306a36Sopenharmony_ci if (!strcmp(pdev->name, ops->name)) { 17562306a36Sopenharmony_ci if (!try_module_get(ops->owner)) 17662306a36Sopenharmony_ci ops = NULL; 17762306a36Sopenharmony_ci goto done; 17862306a36Sopenharmony_ci } 17962306a36Sopenharmony_ci } 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci ops = NULL; 18262306a36Sopenharmony_cidone: 18362306a36Sopenharmony_ci mutex_unlock(&dfl_port_ops_mutex); 18462306a36Sopenharmony_ci return ops; 18562306a36Sopenharmony_ci} 18662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dfl_fpga_port_ops_get); 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci/** 18962306a36Sopenharmony_ci * dfl_fpga_port_ops_put - put port ops 19062306a36Sopenharmony_ci * @ops: port ops. 19162306a36Sopenharmony_ci */ 19262306a36Sopenharmony_civoid dfl_fpga_port_ops_put(struct dfl_fpga_port_ops *ops) 19362306a36Sopenharmony_ci{ 19462306a36Sopenharmony_ci if (ops && ops->owner) 19562306a36Sopenharmony_ci module_put(ops->owner); 19662306a36Sopenharmony_ci} 19762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dfl_fpga_port_ops_put); 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci/** 20062306a36Sopenharmony_ci * dfl_fpga_port_ops_add - add port_ops to global list 20162306a36Sopenharmony_ci * @ops: port ops to add. 20262306a36Sopenharmony_ci */ 20362306a36Sopenharmony_civoid dfl_fpga_port_ops_add(struct dfl_fpga_port_ops *ops) 20462306a36Sopenharmony_ci{ 20562306a36Sopenharmony_ci mutex_lock(&dfl_port_ops_mutex); 20662306a36Sopenharmony_ci list_add_tail(&ops->node, &dfl_port_ops_list); 20762306a36Sopenharmony_ci mutex_unlock(&dfl_port_ops_mutex); 20862306a36Sopenharmony_ci} 20962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dfl_fpga_port_ops_add); 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci/** 21262306a36Sopenharmony_ci * dfl_fpga_port_ops_del - remove port_ops from global list 21362306a36Sopenharmony_ci * @ops: port ops to del. 21462306a36Sopenharmony_ci */ 21562306a36Sopenharmony_civoid dfl_fpga_port_ops_del(struct dfl_fpga_port_ops *ops) 21662306a36Sopenharmony_ci{ 21762306a36Sopenharmony_ci mutex_lock(&dfl_port_ops_mutex); 21862306a36Sopenharmony_ci list_del(&ops->node); 21962306a36Sopenharmony_ci mutex_unlock(&dfl_port_ops_mutex); 22062306a36Sopenharmony_ci} 22162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dfl_fpga_port_ops_del); 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci/** 22462306a36Sopenharmony_ci * dfl_fpga_check_port_id - check the port id 22562306a36Sopenharmony_ci * @pdev: port platform device. 22662306a36Sopenharmony_ci * @pport_id: port id to compare. 22762306a36Sopenharmony_ci * 22862306a36Sopenharmony_ci * Return: 1 if port device matches with given port id, otherwise 0. 22962306a36Sopenharmony_ci */ 23062306a36Sopenharmony_ciint dfl_fpga_check_port_id(struct platform_device *pdev, void *pport_id) 23162306a36Sopenharmony_ci{ 23262306a36Sopenharmony_ci struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); 23362306a36Sopenharmony_ci struct dfl_fpga_port_ops *port_ops; 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci if (pdata->id != FEATURE_DEV_ID_UNUSED) 23662306a36Sopenharmony_ci return pdata->id == *(int *)pport_id; 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci port_ops = dfl_fpga_port_ops_get(pdev); 23962306a36Sopenharmony_ci if (!port_ops || !port_ops->get_id) 24062306a36Sopenharmony_ci return 0; 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci pdata->id = port_ops->get_id(pdev); 24362306a36Sopenharmony_ci dfl_fpga_port_ops_put(port_ops); 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci return pdata->id == *(int *)pport_id; 24662306a36Sopenharmony_ci} 24762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dfl_fpga_check_port_id); 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_cistatic DEFINE_IDA(dfl_device_ida); 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_cistatic const struct dfl_device_id * 25262306a36Sopenharmony_cidfl_match_one_device(const struct dfl_device_id *id, struct dfl_device *ddev) 25362306a36Sopenharmony_ci{ 25462306a36Sopenharmony_ci if (id->type == ddev->type && id->feature_id == ddev->feature_id) 25562306a36Sopenharmony_ci return id; 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci return NULL; 25862306a36Sopenharmony_ci} 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_cistatic int dfl_bus_match(struct device *dev, struct device_driver *drv) 26162306a36Sopenharmony_ci{ 26262306a36Sopenharmony_ci struct dfl_device *ddev = to_dfl_dev(dev); 26362306a36Sopenharmony_ci struct dfl_driver *ddrv = to_dfl_drv(drv); 26462306a36Sopenharmony_ci const struct dfl_device_id *id_entry; 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci id_entry = ddrv->id_table; 26762306a36Sopenharmony_ci if (id_entry) { 26862306a36Sopenharmony_ci while (id_entry->feature_id) { 26962306a36Sopenharmony_ci if (dfl_match_one_device(id_entry, ddev)) { 27062306a36Sopenharmony_ci ddev->id_entry = id_entry; 27162306a36Sopenharmony_ci return 1; 27262306a36Sopenharmony_ci } 27362306a36Sopenharmony_ci id_entry++; 27462306a36Sopenharmony_ci } 27562306a36Sopenharmony_ci } 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci return 0; 27862306a36Sopenharmony_ci} 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_cistatic int dfl_bus_probe(struct device *dev) 28162306a36Sopenharmony_ci{ 28262306a36Sopenharmony_ci struct dfl_driver *ddrv = to_dfl_drv(dev->driver); 28362306a36Sopenharmony_ci struct dfl_device *ddev = to_dfl_dev(dev); 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ci return ddrv->probe(ddev); 28662306a36Sopenharmony_ci} 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_cistatic void dfl_bus_remove(struct device *dev) 28962306a36Sopenharmony_ci{ 29062306a36Sopenharmony_ci struct dfl_driver *ddrv = to_dfl_drv(dev->driver); 29162306a36Sopenharmony_ci struct dfl_device *ddev = to_dfl_dev(dev); 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci if (ddrv->remove) 29462306a36Sopenharmony_ci ddrv->remove(ddev); 29562306a36Sopenharmony_ci} 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_cistatic int dfl_bus_uevent(const struct device *dev, struct kobj_uevent_env *env) 29862306a36Sopenharmony_ci{ 29962306a36Sopenharmony_ci const struct dfl_device *ddev = to_dfl_dev(dev); 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci return add_uevent_var(env, "MODALIAS=dfl:t%04Xf%04X", 30262306a36Sopenharmony_ci ddev->type, ddev->feature_id); 30362306a36Sopenharmony_ci} 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_cistatic ssize_t 30662306a36Sopenharmony_citype_show(struct device *dev, struct device_attribute *attr, char *buf) 30762306a36Sopenharmony_ci{ 30862306a36Sopenharmony_ci struct dfl_device *ddev = to_dfl_dev(dev); 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_ci return sprintf(buf, "0x%x\n", ddev->type); 31162306a36Sopenharmony_ci} 31262306a36Sopenharmony_cistatic DEVICE_ATTR_RO(type); 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_cistatic ssize_t 31562306a36Sopenharmony_cifeature_id_show(struct device *dev, struct device_attribute *attr, char *buf) 31662306a36Sopenharmony_ci{ 31762306a36Sopenharmony_ci struct dfl_device *ddev = to_dfl_dev(dev); 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci return sprintf(buf, "0x%x\n", ddev->feature_id); 32062306a36Sopenharmony_ci} 32162306a36Sopenharmony_cistatic DEVICE_ATTR_RO(feature_id); 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_cistatic struct attribute *dfl_dev_attrs[] = { 32462306a36Sopenharmony_ci &dev_attr_type.attr, 32562306a36Sopenharmony_ci &dev_attr_feature_id.attr, 32662306a36Sopenharmony_ci NULL, 32762306a36Sopenharmony_ci}; 32862306a36Sopenharmony_ciATTRIBUTE_GROUPS(dfl_dev); 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_cistatic struct bus_type dfl_bus_type = { 33162306a36Sopenharmony_ci .name = "dfl", 33262306a36Sopenharmony_ci .match = dfl_bus_match, 33362306a36Sopenharmony_ci .probe = dfl_bus_probe, 33462306a36Sopenharmony_ci .remove = dfl_bus_remove, 33562306a36Sopenharmony_ci .uevent = dfl_bus_uevent, 33662306a36Sopenharmony_ci .dev_groups = dfl_dev_groups, 33762306a36Sopenharmony_ci}; 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_cistatic void release_dfl_dev(struct device *dev) 34062306a36Sopenharmony_ci{ 34162306a36Sopenharmony_ci struct dfl_device *ddev = to_dfl_dev(dev); 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci if (ddev->mmio_res.parent) 34462306a36Sopenharmony_ci release_resource(&ddev->mmio_res); 34562306a36Sopenharmony_ci 34662306a36Sopenharmony_ci kfree(ddev->params); 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci ida_free(&dfl_device_ida, ddev->id); 34962306a36Sopenharmony_ci kfree(ddev->irqs); 35062306a36Sopenharmony_ci kfree(ddev); 35162306a36Sopenharmony_ci} 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_cistatic struct dfl_device * 35462306a36Sopenharmony_cidfl_dev_add(struct dfl_feature_platform_data *pdata, 35562306a36Sopenharmony_ci struct dfl_feature *feature) 35662306a36Sopenharmony_ci{ 35762306a36Sopenharmony_ci struct platform_device *pdev = pdata->dev; 35862306a36Sopenharmony_ci struct resource *parent_res; 35962306a36Sopenharmony_ci struct dfl_device *ddev; 36062306a36Sopenharmony_ci int id, i, ret; 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ci ddev = kzalloc(sizeof(*ddev), GFP_KERNEL); 36362306a36Sopenharmony_ci if (!ddev) 36462306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 36562306a36Sopenharmony_ci 36662306a36Sopenharmony_ci id = ida_alloc(&dfl_device_ida, GFP_KERNEL); 36762306a36Sopenharmony_ci if (id < 0) { 36862306a36Sopenharmony_ci dev_err(&pdev->dev, "unable to get id\n"); 36962306a36Sopenharmony_ci kfree(ddev); 37062306a36Sopenharmony_ci return ERR_PTR(id); 37162306a36Sopenharmony_ci } 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ci /* freeing resources by put_device() after device_initialize() */ 37462306a36Sopenharmony_ci device_initialize(&ddev->dev); 37562306a36Sopenharmony_ci ddev->dev.parent = &pdev->dev; 37662306a36Sopenharmony_ci ddev->dev.bus = &dfl_bus_type; 37762306a36Sopenharmony_ci ddev->dev.release = release_dfl_dev; 37862306a36Sopenharmony_ci ddev->id = id; 37962306a36Sopenharmony_ci ret = dev_set_name(&ddev->dev, "dfl_dev.%d", id); 38062306a36Sopenharmony_ci if (ret) 38162306a36Sopenharmony_ci goto put_dev; 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_ci ddev->type = feature_dev_id_type(pdev); 38462306a36Sopenharmony_ci ddev->feature_id = feature->id; 38562306a36Sopenharmony_ci ddev->revision = feature->revision; 38662306a36Sopenharmony_ci ddev->dfh_version = feature->dfh_version; 38762306a36Sopenharmony_ci ddev->cdev = pdata->dfl_cdev; 38862306a36Sopenharmony_ci if (feature->param_size) { 38962306a36Sopenharmony_ci ddev->params = kmemdup(feature->params, feature->param_size, GFP_KERNEL); 39062306a36Sopenharmony_ci if (!ddev->params) { 39162306a36Sopenharmony_ci ret = -ENOMEM; 39262306a36Sopenharmony_ci goto put_dev; 39362306a36Sopenharmony_ci } 39462306a36Sopenharmony_ci ddev->param_size = feature->param_size; 39562306a36Sopenharmony_ci } 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_ci /* add mmio resource */ 39862306a36Sopenharmony_ci parent_res = &pdev->resource[feature->resource_index]; 39962306a36Sopenharmony_ci ddev->mmio_res.flags = IORESOURCE_MEM; 40062306a36Sopenharmony_ci ddev->mmio_res.start = parent_res->start; 40162306a36Sopenharmony_ci ddev->mmio_res.end = parent_res->end; 40262306a36Sopenharmony_ci ddev->mmio_res.name = dev_name(&ddev->dev); 40362306a36Sopenharmony_ci ret = insert_resource(parent_res, &ddev->mmio_res); 40462306a36Sopenharmony_ci if (ret) { 40562306a36Sopenharmony_ci dev_err(&pdev->dev, "%s failed to claim resource: %pR\n", 40662306a36Sopenharmony_ci dev_name(&ddev->dev), &ddev->mmio_res); 40762306a36Sopenharmony_ci goto put_dev; 40862306a36Sopenharmony_ci } 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_ci /* then add irq resource */ 41162306a36Sopenharmony_ci if (feature->nr_irqs) { 41262306a36Sopenharmony_ci ddev->irqs = kcalloc(feature->nr_irqs, 41362306a36Sopenharmony_ci sizeof(*ddev->irqs), GFP_KERNEL); 41462306a36Sopenharmony_ci if (!ddev->irqs) { 41562306a36Sopenharmony_ci ret = -ENOMEM; 41662306a36Sopenharmony_ci goto put_dev; 41762306a36Sopenharmony_ci } 41862306a36Sopenharmony_ci 41962306a36Sopenharmony_ci for (i = 0; i < feature->nr_irqs; i++) 42062306a36Sopenharmony_ci ddev->irqs[i] = feature->irq_ctx[i].irq; 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ci ddev->num_irqs = feature->nr_irqs; 42362306a36Sopenharmony_ci } 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_ci ret = device_add(&ddev->dev); 42662306a36Sopenharmony_ci if (ret) 42762306a36Sopenharmony_ci goto put_dev; 42862306a36Sopenharmony_ci 42962306a36Sopenharmony_ci dev_dbg(&pdev->dev, "add dfl_dev: %s\n", dev_name(&ddev->dev)); 43062306a36Sopenharmony_ci return ddev; 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_ciput_dev: 43362306a36Sopenharmony_ci /* calls release_dfl_dev() which does the clean up */ 43462306a36Sopenharmony_ci put_device(&ddev->dev); 43562306a36Sopenharmony_ci return ERR_PTR(ret); 43662306a36Sopenharmony_ci} 43762306a36Sopenharmony_ci 43862306a36Sopenharmony_cistatic void dfl_devs_remove(struct dfl_feature_platform_data *pdata) 43962306a36Sopenharmony_ci{ 44062306a36Sopenharmony_ci struct dfl_feature *feature; 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_ci dfl_fpga_dev_for_each_feature(pdata, feature) { 44362306a36Sopenharmony_ci if (feature->ddev) { 44462306a36Sopenharmony_ci device_unregister(&feature->ddev->dev); 44562306a36Sopenharmony_ci feature->ddev = NULL; 44662306a36Sopenharmony_ci } 44762306a36Sopenharmony_ci } 44862306a36Sopenharmony_ci} 44962306a36Sopenharmony_ci 45062306a36Sopenharmony_cistatic int dfl_devs_add(struct dfl_feature_platform_data *pdata) 45162306a36Sopenharmony_ci{ 45262306a36Sopenharmony_ci struct dfl_feature *feature; 45362306a36Sopenharmony_ci struct dfl_device *ddev; 45462306a36Sopenharmony_ci int ret; 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci dfl_fpga_dev_for_each_feature(pdata, feature) { 45762306a36Sopenharmony_ci if (feature->ioaddr) 45862306a36Sopenharmony_ci continue; 45962306a36Sopenharmony_ci 46062306a36Sopenharmony_ci if (feature->ddev) { 46162306a36Sopenharmony_ci ret = -EEXIST; 46262306a36Sopenharmony_ci goto err; 46362306a36Sopenharmony_ci } 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ci ddev = dfl_dev_add(pdata, feature); 46662306a36Sopenharmony_ci if (IS_ERR(ddev)) { 46762306a36Sopenharmony_ci ret = PTR_ERR(ddev); 46862306a36Sopenharmony_ci goto err; 46962306a36Sopenharmony_ci } 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_ci feature->ddev = ddev; 47262306a36Sopenharmony_ci } 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_ci return 0; 47562306a36Sopenharmony_ci 47662306a36Sopenharmony_cierr: 47762306a36Sopenharmony_ci dfl_devs_remove(pdata); 47862306a36Sopenharmony_ci return ret; 47962306a36Sopenharmony_ci} 48062306a36Sopenharmony_ci 48162306a36Sopenharmony_ciint __dfl_driver_register(struct dfl_driver *dfl_drv, struct module *owner) 48262306a36Sopenharmony_ci{ 48362306a36Sopenharmony_ci if (!dfl_drv || !dfl_drv->probe || !dfl_drv->id_table) 48462306a36Sopenharmony_ci return -EINVAL; 48562306a36Sopenharmony_ci 48662306a36Sopenharmony_ci dfl_drv->drv.owner = owner; 48762306a36Sopenharmony_ci dfl_drv->drv.bus = &dfl_bus_type; 48862306a36Sopenharmony_ci 48962306a36Sopenharmony_ci return driver_register(&dfl_drv->drv); 49062306a36Sopenharmony_ci} 49162306a36Sopenharmony_ciEXPORT_SYMBOL(__dfl_driver_register); 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_civoid dfl_driver_unregister(struct dfl_driver *dfl_drv) 49462306a36Sopenharmony_ci{ 49562306a36Sopenharmony_ci driver_unregister(&dfl_drv->drv); 49662306a36Sopenharmony_ci} 49762306a36Sopenharmony_ciEXPORT_SYMBOL(dfl_driver_unregister); 49862306a36Sopenharmony_ci 49962306a36Sopenharmony_ci#define is_header_feature(feature) ((feature)->id == FEATURE_ID_FIU_HEADER) 50062306a36Sopenharmony_ci 50162306a36Sopenharmony_ci/** 50262306a36Sopenharmony_ci * dfl_fpga_dev_feature_uinit - uinit for sub features of dfl feature device 50362306a36Sopenharmony_ci * @pdev: feature device. 50462306a36Sopenharmony_ci */ 50562306a36Sopenharmony_civoid dfl_fpga_dev_feature_uinit(struct platform_device *pdev) 50662306a36Sopenharmony_ci{ 50762306a36Sopenharmony_ci struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); 50862306a36Sopenharmony_ci struct dfl_feature *feature; 50962306a36Sopenharmony_ci 51062306a36Sopenharmony_ci dfl_devs_remove(pdata); 51162306a36Sopenharmony_ci 51262306a36Sopenharmony_ci dfl_fpga_dev_for_each_feature(pdata, feature) { 51362306a36Sopenharmony_ci if (feature->ops) { 51462306a36Sopenharmony_ci if (feature->ops->uinit) 51562306a36Sopenharmony_ci feature->ops->uinit(pdev, feature); 51662306a36Sopenharmony_ci feature->ops = NULL; 51762306a36Sopenharmony_ci } 51862306a36Sopenharmony_ci } 51962306a36Sopenharmony_ci} 52062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dfl_fpga_dev_feature_uinit); 52162306a36Sopenharmony_ci 52262306a36Sopenharmony_cistatic int dfl_feature_instance_init(struct platform_device *pdev, 52362306a36Sopenharmony_ci struct dfl_feature_platform_data *pdata, 52462306a36Sopenharmony_ci struct dfl_feature *feature, 52562306a36Sopenharmony_ci struct dfl_feature_driver *drv) 52662306a36Sopenharmony_ci{ 52762306a36Sopenharmony_ci void __iomem *base; 52862306a36Sopenharmony_ci int ret = 0; 52962306a36Sopenharmony_ci 53062306a36Sopenharmony_ci if (!is_header_feature(feature)) { 53162306a36Sopenharmony_ci base = devm_platform_ioremap_resource(pdev, 53262306a36Sopenharmony_ci feature->resource_index); 53362306a36Sopenharmony_ci if (IS_ERR(base)) { 53462306a36Sopenharmony_ci dev_err(&pdev->dev, 53562306a36Sopenharmony_ci "ioremap failed for feature 0x%x!\n", 53662306a36Sopenharmony_ci feature->id); 53762306a36Sopenharmony_ci return PTR_ERR(base); 53862306a36Sopenharmony_ci } 53962306a36Sopenharmony_ci 54062306a36Sopenharmony_ci feature->ioaddr = base; 54162306a36Sopenharmony_ci } 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_ci if (drv->ops->init) { 54462306a36Sopenharmony_ci ret = drv->ops->init(pdev, feature); 54562306a36Sopenharmony_ci if (ret) 54662306a36Sopenharmony_ci return ret; 54762306a36Sopenharmony_ci } 54862306a36Sopenharmony_ci 54962306a36Sopenharmony_ci feature->ops = drv->ops; 55062306a36Sopenharmony_ci 55162306a36Sopenharmony_ci return ret; 55262306a36Sopenharmony_ci} 55362306a36Sopenharmony_ci 55462306a36Sopenharmony_cistatic bool dfl_feature_drv_match(struct dfl_feature *feature, 55562306a36Sopenharmony_ci struct dfl_feature_driver *driver) 55662306a36Sopenharmony_ci{ 55762306a36Sopenharmony_ci const struct dfl_feature_id *ids = driver->id_table; 55862306a36Sopenharmony_ci 55962306a36Sopenharmony_ci if (ids) { 56062306a36Sopenharmony_ci while (ids->id) { 56162306a36Sopenharmony_ci if (ids->id == feature->id) 56262306a36Sopenharmony_ci return true; 56362306a36Sopenharmony_ci ids++; 56462306a36Sopenharmony_ci } 56562306a36Sopenharmony_ci } 56662306a36Sopenharmony_ci return false; 56762306a36Sopenharmony_ci} 56862306a36Sopenharmony_ci 56962306a36Sopenharmony_ci/** 57062306a36Sopenharmony_ci * dfl_fpga_dev_feature_init - init for sub features of dfl feature device 57162306a36Sopenharmony_ci * @pdev: feature device. 57262306a36Sopenharmony_ci * @feature_drvs: drvs for sub features. 57362306a36Sopenharmony_ci * 57462306a36Sopenharmony_ci * This function will match sub features with given feature drvs list and 57562306a36Sopenharmony_ci * use matched drv to init related sub feature. 57662306a36Sopenharmony_ci * 57762306a36Sopenharmony_ci * Return: 0 on success, negative error code otherwise. 57862306a36Sopenharmony_ci */ 57962306a36Sopenharmony_ciint dfl_fpga_dev_feature_init(struct platform_device *pdev, 58062306a36Sopenharmony_ci struct dfl_feature_driver *feature_drvs) 58162306a36Sopenharmony_ci{ 58262306a36Sopenharmony_ci struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); 58362306a36Sopenharmony_ci struct dfl_feature_driver *drv = feature_drvs; 58462306a36Sopenharmony_ci struct dfl_feature *feature; 58562306a36Sopenharmony_ci int ret; 58662306a36Sopenharmony_ci 58762306a36Sopenharmony_ci while (drv->ops) { 58862306a36Sopenharmony_ci dfl_fpga_dev_for_each_feature(pdata, feature) { 58962306a36Sopenharmony_ci if (dfl_feature_drv_match(feature, drv)) { 59062306a36Sopenharmony_ci ret = dfl_feature_instance_init(pdev, pdata, 59162306a36Sopenharmony_ci feature, drv); 59262306a36Sopenharmony_ci if (ret) 59362306a36Sopenharmony_ci goto exit; 59462306a36Sopenharmony_ci } 59562306a36Sopenharmony_ci } 59662306a36Sopenharmony_ci drv++; 59762306a36Sopenharmony_ci } 59862306a36Sopenharmony_ci 59962306a36Sopenharmony_ci ret = dfl_devs_add(pdata); 60062306a36Sopenharmony_ci if (ret) 60162306a36Sopenharmony_ci goto exit; 60262306a36Sopenharmony_ci 60362306a36Sopenharmony_ci return 0; 60462306a36Sopenharmony_ciexit: 60562306a36Sopenharmony_ci dfl_fpga_dev_feature_uinit(pdev); 60662306a36Sopenharmony_ci return ret; 60762306a36Sopenharmony_ci} 60862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dfl_fpga_dev_feature_init); 60962306a36Sopenharmony_ci 61062306a36Sopenharmony_cistatic void dfl_chardev_uinit(void) 61162306a36Sopenharmony_ci{ 61262306a36Sopenharmony_ci int i; 61362306a36Sopenharmony_ci 61462306a36Sopenharmony_ci for (i = 0; i < DFL_FPGA_DEVT_MAX; i++) 61562306a36Sopenharmony_ci if (MAJOR(dfl_chrdevs[i].devt)) { 61662306a36Sopenharmony_ci unregister_chrdev_region(dfl_chrdevs[i].devt, 61762306a36Sopenharmony_ci MINORMASK + 1); 61862306a36Sopenharmony_ci dfl_chrdevs[i].devt = MKDEV(0, 0); 61962306a36Sopenharmony_ci } 62062306a36Sopenharmony_ci} 62162306a36Sopenharmony_ci 62262306a36Sopenharmony_cistatic int dfl_chardev_init(void) 62362306a36Sopenharmony_ci{ 62462306a36Sopenharmony_ci int i, ret; 62562306a36Sopenharmony_ci 62662306a36Sopenharmony_ci for (i = 0; i < DFL_FPGA_DEVT_MAX; i++) { 62762306a36Sopenharmony_ci ret = alloc_chrdev_region(&dfl_chrdevs[i].devt, 0, 62862306a36Sopenharmony_ci MINORMASK + 1, dfl_chrdevs[i].name); 62962306a36Sopenharmony_ci if (ret) 63062306a36Sopenharmony_ci goto exit; 63162306a36Sopenharmony_ci } 63262306a36Sopenharmony_ci 63362306a36Sopenharmony_ci return 0; 63462306a36Sopenharmony_ci 63562306a36Sopenharmony_ciexit: 63662306a36Sopenharmony_ci dfl_chardev_uinit(); 63762306a36Sopenharmony_ci return ret; 63862306a36Sopenharmony_ci} 63962306a36Sopenharmony_ci 64062306a36Sopenharmony_cistatic dev_t dfl_get_devt(enum dfl_fpga_devt_type type, int id) 64162306a36Sopenharmony_ci{ 64262306a36Sopenharmony_ci if (type >= DFL_FPGA_DEVT_MAX) 64362306a36Sopenharmony_ci return 0; 64462306a36Sopenharmony_ci 64562306a36Sopenharmony_ci return MKDEV(MAJOR(dfl_chrdevs[type].devt), id); 64662306a36Sopenharmony_ci} 64762306a36Sopenharmony_ci 64862306a36Sopenharmony_ci/** 64962306a36Sopenharmony_ci * dfl_fpga_dev_ops_register - register cdev ops for feature dev 65062306a36Sopenharmony_ci * 65162306a36Sopenharmony_ci * @pdev: feature dev. 65262306a36Sopenharmony_ci * @fops: file operations for feature dev's cdev. 65362306a36Sopenharmony_ci * @owner: owning module/driver. 65462306a36Sopenharmony_ci * 65562306a36Sopenharmony_ci * Return: 0 on success, negative error code otherwise. 65662306a36Sopenharmony_ci */ 65762306a36Sopenharmony_ciint dfl_fpga_dev_ops_register(struct platform_device *pdev, 65862306a36Sopenharmony_ci const struct file_operations *fops, 65962306a36Sopenharmony_ci struct module *owner) 66062306a36Sopenharmony_ci{ 66162306a36Sopenharmony_ci struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); 66262306a36Sopenharmony_ci 66362306a36Sopenharmony_ci cdev_init(&pdata->cdev, fops); 66462306a36Sopenharmony_ci pdata->cdev.owner = owner; 66562306a36Sopenharmony_ci 66662306a36Sopenharmony_ci /* 66762306a36Sopenharmony_ci * set parent to the feature device so that its refcount is 66862306a36Sopenharmony_ci * decreased after the last refcount of cdev is gone, that 66962306a36Sopenharmony_ci * makes sure the feature device is valid during device 67062306a36Sopenharmony_ci * file's life-cycle. 67162306a36Sopenharmony_ci */ 67262306a36Sopenharmony_ci pdata->cdev.kobj.parent = &pdev->dev.kobj; 67362306a36Sopenharmony_ci 67462306a36Sopenharmony_ci return cdev_add(&pdata->cdev, pdev->dev.devt, 1); 67562306a36Sopenharmony_ci} 67662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dfl_fpga_dev_ops_register); 67762306a36Sopenharmony_ci 67862306a36Sopenharmony_ci/** 67962306a36Sopenharmony_ci * dfl_fpga_dev_ops_unregister - unregister cdev ops for feature dev 68062306a36Sopenharmony_ci * @pdev: feature dev. 68162306a36Sopenharmony_ci */ 68262306a36Sopenharmony_civoid dfl_fpga_dev_ops_unregister(struct platform_device *pdev) 68362306a36Sopenharmony_ci{ 68462306a36Sopenharmony_ci struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); 68562306a36Sopenharmony_ci 68662306a36Sopenharmony_ci cdev_del(&pdata->cdev); 68762306a36Sopenharmony_ci} 68862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dfl_fpga_dev_ops_unregister); 68962306a36Sopenharmony_ci 69062306a36Sopenharmony_ci/** 69162306a36Sopenharmony_ci * struct build_feature_devs_info - info collected during feature dev build. 69262306a36Sopenharmony_ci * 69362306a36Sopenharmony_ci * @dev: device to enumerate. 69462306a36Sopenharmony_ci * @cdev: the container device for all feature devices. 69562306a36Sopenharmony_ci * @nr_irqs: number of irqs for all feature devices. 69662306a36Sopenharmony_ci * @irq_table: Linux IRQ numbers for all irqs, indexed by local irq index of 69762306a36Sopenharmony_ci * this device. 69862306a36Sopenharmony_ci * @feature_dev: current feature device. 69962306a36Sopenharmony_ci * @ioaddr: header register region address of current FIU in enumeration. 70062306a36Sopenharmony_ci * @start: register resource start of current FIU. 70162306a36Sopenharmony_ci * @len: max register resource length of current FIU. 70262306a36Sopenharmony_ci * @sub_features: a sub features linked list for feature device in enumeration. 70362306a36Sopenharmony_ci * @feature_num: number of sub features for feature device in enumeration. 70462306a36Sopenharmony_ci */ 70562306a36Sopenharmony_cistruct build_feature_devs_info { 70662306a36Sopenharmony_ci struct device *dev; 70762306a36Sopenharmony_ci struct dfl_fpga_cdev *cdev; 70862306a36Sopenharmony_ci unsigned int nr_irqs; 70962306a36Sopenharmony_ci int *irq_table; 71062306a36Sopenharmony_ci 71162306a36Sopenharmony_ci struct platform_device *feature_dev; 71262306a36Sopenharmony_ci void __iomem *ioaddr; 71362306a36Sopenharmony_ci resource_size_t start; 71462306a36Sopenharmony_ci resource_size_t len; 71562306a36Sopenharmony_ci struct list_head sub_features; 71662306a36Sopenharmony_ci int feature_num; 71762306a36Sopenharmony_ci}; 71862306a36Sopenharmony_ci 71962306a36Sopenharmony_ci/** 72062306a36Sopenharmony_ci * struct dfl_feature_info - sub feature info collected during feature dev build 72162306a36Sopenharmony_ci * 72262306a36Sopenharmony_ci * @fid: id of this sub feature. 72362306a36Sopenharmony_ci * @revision: revision of this sub feature 72462306a36Sopenharmony_ci * @dfh_version: version of Device Feature Header (DFH) 72562306a36Sopenharmony_ci * @mmio_res: mmio resource of this sub feature. 72662306a36Sopenharmony_ci * @ioaddr: mapped base address of mmio resource. 72762306a36Sopenharmony_ci * @node: node in sub_features linked list. 72862306a36Sopenharmony_ci * @irq_base: start of irq index in this sub feature. 72962306a36Sopenharmony_ci * @nr_irqs: number of irqs of this sub feature. 73062306a36Sopenharmony_ci * @param_size: size DFH parameters. 73162306a36Sopenharmony_ci * @params: DFH parameter data. 73262306a36Sopenharmony_ci */ 73362306a36Sopenharmony_cistruct dfl_feature_info { 73462306a36Sopenharmony_ci u16 fid; 73562306a36Sopenharmony_ci u8 revision; 73662306a36Sopenharmony_ci u8 dfh_version; 73762306a36Sopenharmony_ci struct resource mmio_res; 73862306a36Sopenharmony_ci void __iomem *ioaddr; 73962306a36Sopenharmony_ci struct list_head node; 74062306a36Sopenharmony_ci unsigned int irq_base; 74162306a36Sopenharmony_ci unsigned int nr_irqs; 74262306a36Sopenharmony_ci unsigned int param_size; 74362306a36Sopenharmony_ci u64 params[]; 74462306a36Sopenharmony_ci}; 74562306a36Sopenharmony_ci 74662306a36Sopenharmony_cistatic void dfl_fpga_cdev_add_port_dev(struct dfl_fpga_cdev *cdev, 74762306a36Sopenharmony_ci struct platform_device *port) 74862306a36Sopenharmony_ci{ 74962306a36Sopenharmony_ci struct dfl_feature_platform_data *pdata = dev_get_platdata(&port->dev); 75062306a36Sopenharmony_ci 75162306a36Sopenharmony_ci mutex_lock(&cdev->lock); 75262306a36Sopenharmony_ci list_add(&pdata->node, &cdev->port_dev_list); 75362306a36Sopenharmony_ci get_device(&pdata->dev->dev); 75462306a36Sopenharmony_ci mutex_unlock(&cdev->lock); 75562306a36Sopenharmony_ci} 75662306a36Sopenharmony_ci 75762306a36Sopenharmony_ci/* 75862306a36Sopenharmony_ci * register current feature device, it is called when we need to switch to 75962306a36Sopenharmony_ci * another feature parsing or we have parsed all features on given device 76062306a36Sopenharmony_ci * feature list. 76162306a36Sopenharmony_ci */ 76262306a36Sopenharmony_cistatic int build_info_commit_dev(struct build_feature_devs_info *binfo) 76362306a36Sopenharmony_ci{ 76462306a36Sopenharmony_ci struct platform_device *fdev = binfo->feature_dev; 76562306a36Sopenharmony_ci struct dfl_feature_platform_data *pdata; 76662306a36Sopenharmony_ci struct dfl_feature_info *finfo, *p; 76762306a36Sopenharmony_ci enum dfl_id_type type; 76862306a36Sopenharmony_ci int ret, index = 0, res_idx = 0; 76962306a36Sopenharmony_ci 77062306a36Sopenharmony_ci type = feature_dev_id_type(fdev); 77162306a36Sopenharmony_ci if (WARN_ON_ONCE(type >= DFL_ID_MAX)) 77262306a36Sopenharmony_ci return -EINVAL; 77362306a36Sopenharmony_ci 77462306a36Sopenharmony_ci /* 77562306a36Sopenharmony_ci * we do not need to care for the memory which is associated with 77662306a36Sopenharmony_ci * the platform device. After calling platform_device_unregister(), 77762306a36Sopenharmony_ci * it will be automatically freed by device's release() callback, 77862306a36Sopenharmony_ci * platform_device_release(). 77962306a36Sopenharmony_ci */ 78062306a36Sopenharmony_ci pdata = kzalloc(struct_size(pdata, features, binfo->feature_num), GFP_KERNEL); 78162306a36Sopenharmony_ci if (!pdata) 78262306a36Sopenharmony_ci return -ENOMEM; 78362306a36Sopenharmony_ci 78462306a36Sopenharmony_ci pdata->dev = fdev; 78562306a36Sopenharmony_ci pdata->num = binfo->feature_num; 78662306a36Sopenharmony_ci pdata->dfl_cdev = binfo->cdev; 78762306a36Sopenharmony_ci pdata->id = FEATURE_DEV_ID_UNUSED; 78862306a36Sopenharmony_ci mutex_init(&pdata->lock); 78962306a36Sopenharmony_ci lockdep_set_class_and_name(&pdata->lock, &dfl_pdata_keys[type], 79062306a36Sopenharmony_ci dfl_pdata_key_strings[type]); 79162306a36Sopenharmony_ci 79262306a36Sopenharmony_ci /* 79362306a36Sopenharmony_ci * the count should be initialized to 0 to make sure 79462306a36Sopenharmony_ci *__fpga_port_enable() following __fpga_port_disable() 79562306a36Sopenharmony_ci * works properly for port device. 79662306a36Sopenharmony_ci * and it should always be 0 for fme device. 79762306a36Sopenharmony_ci */ 79862306a36Sopenharmony_ci WARN_ON(pdata->disable_count); 79962306a36Sopenharmony_ci 80062306a36Sopenharmony_ci fdev->dev.platform_data = pdata; 80162306a36Sopenharmony_ci 80262306a36Sopenharmony_ci /* each sub feature has one MMIO resource */ 80362306a36Sopenharmony_ci fdev->num_resources = binfo->feature_num; 80462306a36Sopenharmony_ci fdev->resource = kcalloc(binfo->feature_num, sizeof(*fdev->resource), 80562306a36Sopenharmony_ci GFP_KERNEL); 80662306a36Sopenharmony_ci if (!fdev->resource) 80762306a36Sopenharmony_ci return -ENOMEM; 80862306a36Sopenharmony_ci 80962306a36Sopenharmony_ci /* fill features and resource information for feature dev */ 81062306a36Sopenharmony_ci list_for_each_entry_safe(finfo, p, &binfo->sub_features, node) { 81162306a36Sopenharmony_ci struct dfl_feature *feature = &pdata->features[index++]; 81262306a36Sopenharmony_ci struct dfl_feature_irq_ctx *ctx; 81362306a36Sopenharmony_ci unsigned int i; 81462306a36Sopenharmony_ci 81562306a36Sopenharmony_ci /* save resource information for each feature */ 81662306a36Sopenharmony_ci feature->dev = fdev; 81762306a36Sopenharmony_ci feature->id = finfo->fid; 81862306a36Sopenharmony_ci feature->revision = finfo->revision; 81962306a36Sopenharmony_ci feature->dfh_version = finfo->dfh_version; 82062306a36Sopenharmony_ci 82162306a36Sopenharmony_ci if (finfo->param_size) { 82262306a36Sopenharmony_ci feature->params = devm_kmemdup(binfo->dev, 82362306a36Sopenharmony_ci finfo->params, finfo->param_size, 82462306a36Sopenharmony_ci GFP_KERNEL); 82562306a36Sopenharmony_ci if (!feature->params) 82662306a36Sopenharmony_ci return -ENOMEM; 82762306a36Sopenharmony_ci 82862306a36Sopenharmony_ci feature->param_size = finfo->param_size; 82962306a36Sopenharmony_ci } 83062306a36Sopenharmony_ci /* 83162306a36Sopenharmony_ci * the FIU header feature has some fundamental functions (sriov 83262306a36Sopenharmony_ci * set, port enable/disable) needed for the dfl bus device and 83362306a36Sopenharmony_ci * other sub features. So its mmio resource should be mapped by 83462306a36Sopenharmony_ci * DFL bus device. And we should not assign it to feature 83562306a36Sopenharmony_ci * devices (dfl-fme/afu) again. 83662306a36Sopenharmony_ci */ 83762306a36Sopenharmony_ci if (is_header_feature(feature)) { 83862306a36Sopenharmony_ci feature->resource_index = -1; 83962306a36Sopenharmony_ci feature->ioaddr = 84062306a36Sopenharmony_ci devm_ioremap_resource(binfo->dev, 84162306a36Sopenharmony_ci &finfo->mmio_res); 84262306a36Sopenharmony_ci if (IS_ERR(feature->ioaddr)) 84362306a36Sopenharmony_ci return PTR_ERR(feature->ioaddr); 84462306a36Sopenharmony_ci } else { 84562306a36Sopenharmony_ci feature->resource_index = res_idx; 84662306a36Sopenharmony_ci fdev->resource[res_idx++] = finfo->mmio_res; 84762306a36Sopenharmony_ci } 84862306a36Sopenharmony_ci 84962306a36Sopenharmony_ci if (finfo->nr_irqs) { 85062306a36Sopenharmony_ci ctx = devm_kcalloc(binfo->dev, finfo->nr_irqs, 85162306a36Sopenharmony_ci sizeof(*ctx), GFP_KERNEL); 85262306a36Sopenharmony_ci if (!ctx) 85362306a36Sopenharmony_ci return -ENOMEM; 85462306a36Sopenharmony_ci 85562306a36Sopenharmony_ci for (i = 0; i < finfo->nr_irqs; i++) 85662306a36Sopenharmony_ci ctx[i].irq = 85762306a36Sopenharmony_ci binfo->irq_table[finfo->irq_base + i]; 85862306a36Sopenharmony_ci 85962306a36Sopenharmony_ci feature->irq_ctx = ctx; 86062306a36Sopenharmony_ci feature->nr_irqs = finfo->nr_irqs; 86162306a36Sopenharmony_ci } 86262306a36Sopenharmony_ci 86362306a36Sopenharmony_ci list_del(&finfo->node); 86462306a36Sopenharmony_ci kfree(finfo); 86562306a36Sopenharmony_ci } 86662306a36Sopenharmony_ci 86762306a36Sopenharmony_ci ret = platform_device_add(binfo->feature_dev); 86862306a36Sopenharmony_ci if (!ret) { 86962306a36Sopenharmony_ci if (type == PORT_ID) 87062306a36Sopenharmony_ci dfl_fpga_cdev_add_port_dev(binfo->cdev, 87162306a36Sopenharmony_ci binfo->feature_dev); 87262306a36Sopenharmony_ci else 87362306a36Sopenharmony_ci binfo->cdev->fme_dev = 87462306a36Sopenharmony_ci get_device(&binfo->feature_dev->dev); 87562306a36Sopenharmony_ci /* 87662306a36Sopenharmony_ci * reset it to avoid build_info_free() freeing their resource. 87762306a36Sopenharmony_ci * 87862306a36Sopenharmony_ci * The resource of successfully registered feature devices 87962306a36Sopenharmony_ci * will be freed by platform_device_unregister(). See the 88062306a36Sopenharmony_ci * comments in build_info_create_dev(). 88162306a36Sopenharmony_ci */ 88262306a36Sopenharmony_ci binfo->feature_dev = NULL; 88362306a36Sopenharmony_ci } 88462306a36Sopenharmony_ci 88562306a36Sopenharmony_ci return ret; 88662306a36Sopenharmony_ci} 88762306a36Sopenharmony_ci 88862306a36Sopenharmony_cistatic int 88962306a36Sopenharmony_cibuild_info_create_dev(struct build_feature_devs_info *binfo, 89062306a36Sopenharmony_ci enum dfl_id_type type) 89162306a36Sopenharmony_ci{ 89262306a36Sopenharmony_ci struct platform_device *fdev; 89362306a36Sopenharmony_ci 89462306a36Sopenharmony_ci if (type >= DFL_ID_MAX) 89562306a36Sopenharmony_ci return -EINVAL; 89662306a36Sopenharmony_ci 89762306a36Sopenharmony_ci /* 89862306a36Sopenharmony_ci * we use -ENODEV as the initialization indicator which indicates 89962306a36Sopenharmony_ci * whether the id need to be reclaimed 90062306a36Sopenharmony_ci */ 90162306a36Sopenharmony_ci fdev = platform_device_alloc(dfl_devs[type].name, -ENODEV); 90262306a36Sopenharmony_ci if (!fdev) 90362306a36Sopenharmony_ci return -ENOMEM; 90462306a36Sopenharmony_ci 90562306a36Sopenharmony_ci binfo->feature_dev = fdev; 90662306a36Sopenharmony_ci binfo->feature_num = 0; 90762306a36Sopenharmony_ci 90862306a36Sopenharmony_ci INIT_LIST_HEAD(&binfo->sub_features); 90962306a36Sopenharmony_ci 91062306a36Sopenharmony_ci fdev->id = dfl_id_alloc(type, &fdev->dev); 91162306a36Sopenharmony_ci if (fdev->id < 0) 91262306a36Sopenharmony_ci return fdev->id; 91362306a36Sopenharmony_ci 91462306a36Sopenharmony_ci fdev->dev.parent = &binfo->cdev->region->dev; 91562306a36Sopenharmony_ci fdev->dev.devt = dfl_get_devt(dfl_devs[type].devt_type, fdev->id); 91662306a36Sopenharmony_ci 91762306a36Sopenharmony_ci return 0; 91862306a36Sopenharmony_ci} 91962306a36Sopenharmony_ci 92062306a36Sopenharmony_cistatic void build_info_free(struct build_feature_devs_info *binfo) 92162306a36Sopenharmony_ci{ 92262306a36Sopenharmony_ci struct dfl_feature_info *finfo, *p; 92362306a36Sopenharmony_ci 92462306a36Sopenharmony_ci /* 92562306a36Sopenharmony_ci * it is a valid id, free it. See comments in 92662306a36Sopenharmony_ci * build_info_create_dev() 92762306a36Sopenharmony_ci */ 92862306a36Sopenharmony_ci if (binfo->feature_dev && binfo->feature_dev->id >= 0) { 92962306a36Sopenharmony_ci dfl_id_free(feature_dev_id_type(binfo->feature_dev), 93062306a36Sopenharmony_ci binfo->feature_dev->id); 93162306a36Sopenharmony_ci 93262306a36Sopenharmony_ci list_for_each_entry_safe(finfo, p, &binfo->sub_features, node) { 93362306a36Sopenharmony_ci list_del(&finfo->node); 93462306a36Sopenharmony_ci kfree(finfo); 93562306a36Sopenharmony_ci } 93662306a36Sopenharmony_ci } 93762306a36Sopenharmony_ci 93862306a36Sopenharmony_ci platform_device_put(binfo->feature_dev); 93962306a36Sopenharmony_ci 94062306a36Sopenharmony_ci devm_kfree(binfo->dev, binfo); 94162306a36Sopenharmony_ci} 94262306a36Sopenharmony_ci 94362306a36Sopenharmony_cistatic inline u32 feature_size(u64 value) 94462306a36Sopenharmony_ci{ 94562306a36Sopenharmony_ci u32 ofst = FIELD_GET(DFH_NEXT_HDR_OFST, value); 94662306a36Sopenharmony_ci /* workaround for private features with invalid size, use 4K instead */ 94762306a36Sopenharmony_ci return ofst ? ofst : 4096; 94862306a36Sopenharmony_ci} 94962306a36Sopenharmony_ci 95062306a36Sopenharmony_cistatic u16 feature_id(u64 value) 95162306a36Sopenharmony_ci{ 95262306a36Sopenharmony_ci u16 id = FIELD_GET(DFH_ID, value); 95362306a36Sopenharmony_ci u8 type = FIELD_GET(DFH_TYPE, value); 95462306a36Sopenharmony_ci 95562306a36Sopenharmony_ci if (type == DFH_TYPE_FIU) 95662306a36Sopenharmony_ci return FEATURE_ID_FIU_HEADER; 95762306a36Sopenharmony_ci else if (type == DFH_TYPE_PRIVATE) 95862306a36Sopenharmony_ci return id; 95962306a36Sopenharmony_ci else if (type == DFH_TYPE_AFU) 96062306a36Sopenharmony_ci return FEATURE_ID_AFU; 96162306a36Sopenharmony_ci 96262306a36Sopenharmony_ci WARN_ON(1); 96362306a36Sopenharmony_ci return 0; 96462306a36Sopenharmony_ci} 96562306a36Sopenharmony_ci 96662306a36Sopenharmony_cistatic u64 *find_param(u64 *params, resource_size_t max, int param_id) 96762306a36Sopenharmony_ci{ 96862306a36Sopenharmony_ci u64 *end = params + max / sizeof(u64); 96962306a36Sopenharmony_ci u64 v, next; 97062306a36Sopenharmony_ci 97162306a36Sopenharmony_ci while (params < end) { 97262306a36Sopenharmony_ci v = *params; 97362306a36Sopenharmony_ci if (param_id == FIELD_GET(DFHv1_PARAM_HDR_ID, v)) 97462306a36Sopenharmony_ci return params; 97562306a36Sopenharmony_ci 97662306a36Sopenharmony_ci if (FIELD_GET(DFHv1_PARAM_HDR_NEXT_EOP, v)) 97762306a36Sopenharmony_ci break; 97862306a36Sopenharmony_ci 97962306a36Sopenharmony_ci next = FIELD_GET(DFHv1_PARAM_HDR_NEXT_OFFSET, v); 98062306a36Sopenharmony_ci params += next; 98162306a36Sopenharmony_ci } 98262306a36Sopenharmony_ci 98362306a36Sopenharmony_ci return NULL; 98462306a36Sopenharmony_ci} 98562306a36Sopenharmony_ci 98662306a36Sopenharmony_ci/** 98762306a36Sopenharmony_ci * dfh_find_param() - find parameter block for the given parameter id 98862306a36Sopenharmony_ci * @dfl_dev: dfl device 98962306a36Sopenharmony_ci * @param_id: id of dfl parameter 99062306a36Sopenharmony_ci * @psize: destination to store size of parameter data in bytes 99162306a36Sopenharmony_ci * 99262306a36Sopenharmony_ci * Return: pointer to start of parameter data, PTR_ERR otherwise. 99362306a36Sopenharmony_ci */ 99462306a36Sopenharmony_civoid *dfh_find_param(struct dfl_device *dfl_dev, int param_id, size_t *psize) 99562306a36Sopenharmony_ci{ 99662306a36Sopenharmony_ci u64 *phdr = find_param(dfl_dev->params, dfl_dev->param_size, param_id); 99762306a36Sopenharmony_ci 99862306a36Sopenharmony_ci if (!phdr) 99962306a36Sopenharmony_ci return ERR_PTR(-ENOENT); 100062306a36Sopenharmony_ci 100162306a36Sopenharmony_ci if (psize) 100262306a36Sopenharmony_ci *psize = (FIELD_GET(DFHv1_PARAM_HDR_NEXT_OFFSET, *phdr) - 1) * sizeof(u64); 100362306a36Sopenharmony_ci 100462306a36Sopenharmony_ci return phdr + 1; 100562306a36Sopenharmony_ci} 100662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dfh_find_param); 100762306a36Sopenharmony_ci 100862306a36Sopenharmony_cistatic int parse_feature_irqs(struct build_feature_devs_info *binfo, 100962306a36Sopenharmony_ci resource_size_t ofst, struct dfl_feature_info *finfo) 101062306a36Sopenharmony_ci{ 101162306a36Sopenharmony_ci void __iomem *base = binfo->ioaddr + ofst; 101262306a36Sopenharmony_ci unsigned int i, ibase, inr = 0; 101362306a36Sopenharmony_ci void *params = finfo->params; 101462306a36Sopenharmony_ci enum dfl_id_type type; 101562306a36Sopenharmony_ci u16 fid = finfo->fid; 101662306a36Sopenharmony_ci int virq; 101762306a36Sopenharmony_ci u64 *p; 101862306a36Sopenharmony_ci u64 v; 101962306a36Sopenharmony_ci 102062306a36Sopenharmony_ci switch (finfo->dfh_version) { 102162306a36Sopenharmony_ci case 0: 102262306a36Sopenharmony_ci /* 102362306a36Sopenharmony_ci * DFHv0 only provides MMIO resource information for each feature 102462306a36Sopenharmony_ci * in the DFL header. There is no generic interrupt information. 102562306a36Sopenharmony_ci * Instead, features with interrupt functionality provide 102662306a36Sopenharmony_ci * the information in feature specific registers. 102762306a36Sopenharmony_ci */ 102862306a36Sopenharmony_ci type = feature_dev_id_type(binfo->feature_dev); 102962306a36Sopenharmony_ci if (type == PORT_ID) { 103062306a36Sopenharmony_ci switch (fid) { 103162306a36Sopenharmony_ci case PORT_FEATURE_ID_UINT: 103262306a36Sopenharmony_ci v = readq(base + PORT_UINT_CAP); 103362306a36Sopenharmony_ci ibase = FIELD_GET(PORT_UINT_CAP_FST_VECT, v); 103462306a36Sopenharmony_ci inr = FIELD_GET(PORT_UINT_CAP_INT_NUM, v); 103562306a36Sopenharmony_ci break; 103662306a36Sopenharmony_ci case PORT_FEATURE_ID_ERROR: 103762306a36Sopenharmony_ci v = readq(base + PORT_ERROR_CAP); 103862306a36Sopenharmony_ci ibase = FIELD_GET(PORT_ERROR_CAP_INT_VECT, v); 103962306a36Sopenharmony_ci inr = FIELD_GET(PORT_ERROR_CAP_SUPP_INT, v); 104062306a36Sopenharmony_ci break; 104162306a36Sopenharmony_ci } 104262306a36Sopenharmony_ci } else if (type == FME_ID) { 104362306a36Sopenharmony_ci switch (fid) { 104462306a36Sopenharmony_ci case FME_FEATURE_ID_GLOBAL_ERR: 104562306a36Sopenharmony_ci v = readq(base + FME_ERROR_CAP); 104662306a36Sopenharmony_ci ibase = FIELD_GET(FME_ERROR_CAP_INT_VECT, v); 104762306a36Sopenharmony_ci inr = FIELD_GET(FME_ERROR_CAP_SUPP_INT, v); 104862306a36Sopenharmony_ci break; 104962306a36Sopenharmony_ci } 105062306a36Sopenharmony_ci } 105162306a36Sopenharmony_ci break; 105262306a36Sopenharmony_ci 105362306a36Sopenharmony_ci case 1: 105462306a36Sopenharmony_ci /* 105562306a36Sopenharmony_ci * DFHv1 provides interrupt resource information in DFHv1 105662306a36Sopenharmony_ci * parameter blocks. 105762306a36Sopenharmony_ci */ 105862306a36Sopenharmony_ci p = find_param(params, finfo->param_size, DFHv1_PARAM_ID_MSI_X); 105962306a36Sopenharmony_ci if (!p) 106062306a36Sopenharmony_ci break; 106162306a36Sopenharmony_ci 106262306a36Sopenharmony_ci p++; 106362306a36Sopenharmony_ci ibase = FIELD_GET(DFHv1_PARAM_MSI_X_STARTV, *p); 106462306a36Sopenharmony_ci inr = FIELD_GET(DFHv1_PARAM_MSI_X_NUMV, *p); 106562306a36Sopenharmony_ci break; 106662306a36Sopenharmony_ci 106762306a36Sopenharmony_ci default: 106862306a36Sopenharmony_ci dev_warn(binfo->dev, "unexpected DFH version %d\n", finfo->dfh_version); 106962306a36Sopenharmony_ci break; 107062306a36Sopenharmony_ci } 107162306a36Sopenharmony_ci 107262306a36Sopenharmony_ci if (!inr) { 107362306a36Sopenharmony_ci finfo->irq_base = 0; 107462306a36Sopenharmony_ci finfo->nr_irqs = 0; 107562306a36Sopenharmony_ci return 0; 107662306a36Sopenharmony_ci } 107762306a36Sopenharmony_ci 107862306a36Sopenharmony_ci dev_dbg(binfo->dev, "feature: 0x%x, irq_base: %u, nr_irqs: %u\n", 107962306a36Sopenharmony_ci fid, ibase, inr); 108062306a36Sopenharmony_ci 108162306a36Sopenharmony_ci if (ibase + inr > binfo->nr_irqs) { 108262306a36Sopenharmony_ci dev_err(binfo->dev, 108362306a36Sopenharmony_ci "Invalid interrupt number in feature 0x%x\n", fid); 108462306a36Sopenharmony_ci return -EINVAL; 108562306a36Sopenharmony_ci } 108662306a36Sopenharmony_ci 108762306a36Sopenharmony_ci for (i = 0; i < inr; i++) { 108862306a36Sopenharmony_ci virq = binfo->irq_table[ibase + i]; 108962306a36Sopenharmony_ci if (virq < 0 || virq > NR_IRQS) { 109062306a36Sopenharmony_ci dev_err(binfo->dev, 109162306a36Sopenharmony_ci "Invalid irq table entry for feature 0x%x\n", 109262306a36Sopenharmony_ci fid); 109362306a36Sopenharmony_ci return -EINVAL; 109462306a36Sopenharmony_ci } 109562306a36Sopenharmony_ci } 109662306a36Sopenharmony_ci 109762306a36Sopenharmony_ci finfo->irq_base = ibase; 109862306a36Sopenharmony_ci finfo->nr_irqs = inr; 109962306a36Sopenharmony_ci 110062306a36Sopenharmony_ci return 0; 110162306a36Sopenharmony_ci} 110262306a36Sopenharmony_ci 110362306a36Sopenharmony_cistatic int dfh_get_param_size(void __iomem *dfh_base, resource_size_t max) 110462306a36Sopenharmony_ci{ 110562306a36Sopenharmony_ci int size = 0; 110662306a36Sopenharmony_ci u64 v, next; 110762306a36Sopenharmony_ci 110862306a36Sopenharmony_ci if (!FIELD_GET(DFHv1_CSR_SIZE_GRP_HAS_PARAMS, 110962306a36Sopenharmony_ci readq(dfh_base + DFHv1_CSR_SIZE_GRP))) 111062306a36Sopenharmony_ci return 0; 111162306a36Sopenharmony_ci 111262306a36Sopenharmony_ci while (size + DFHv1_PARAM_HDR < max) { 111362306a36Sopenharmony_ci v = readq(dfh_base + DFHv1_PARAM_HDR + size); 111462306a36Sopenharmony_ci 111562306a36Sopenharmony_ci next = FIELD_GET(DFHv1_PARAM_HDR_NEXT_OFFSET, v); 111662306a36Sopenharmony_ci if (!next) 111762306a36Sopenharmony_ci return -EINVAL; 111862306a36Sopenharmony_ci 111962306a36Sopenharmony_ci size += next * sizeof(u64); 112062306a36Sopenharmony_ci 112162306a36Sopenharmony_ci if (FIELD_GET(DFHv1_PARAM_HDR_NEXT_EOP, v)) 112262306a36Sopenharmony_ci return size; 112362306a36Sopenharmony_ci } 112462306a36Sopenharmony_ci 112562306a36Sopenharmony_ci return -ENOENT; 112662306a36Sopenharmony_ci} 112762306a36Sopenharmony_ci 112862306a36Sopenharmony_ci/* 112962306a36Sopenharmony_ci * when create sub feature instances, for private features, it doesn't need 113062306a36Sopenharmony_ci * to provide resource size and feature id as they could be read from DFH 113162306a36Sopenharmony_ci * register. For afu sub feature, its register region only contains user 113262306a36Sopenharmony_ci * defined registers, so never trust any information from it, just use the 113362306a36Sopenharmony_ci * resource size information provided by its parent FIU. 113462306a36Sopenharmony_ci */ 113562306a36Sopenharmony_cistatic int 113662306a36Sopenharmony_cicreate_feature_instance(struct build_feature_devs_info *binfo, 113762306a36Sopenharmony_ci resource_size_t ofst, resource_size_t size, u16 fid) 113862306a36Sopenharmony_ci{ 113962306a36Sopenharmony_ci struct dfl_feature_info *finfo; 114062306a36Sopenharmony_ci resource_size_t start, end; 114162306a36Sopenharmony_ci int dfh_psize = 0; 114262306a36Sopenharmony_ci u8 revision = 0; 114362306a36Sopenharmony_ci u64 v, addr_off; 114462306a36Sopenharmony_ci u8 dfh_ver = 0; 114562306a36Sopenharmony_ci int ret; 114662306a36Sopenharmony_ci 114762306a36Sopenharmony_ci if (fid != FEATURE_ID_AFU) { 114862306a36Sopenharmony_ci v = readq(binfo->ioaddr + ofst); 114962306a36Sopenharmony_ci revision = FIELD_GET(DFH_REVISION, v); 115062306a36Sopenharmony_ci dfh_ver = FIELD_GET(DFH_VERSION, v); 115162306a36Sopenharmony_ci /* read feature size and id if inputs are invalid */ 115262306a36Sopenharmony_ci size = size ? size : feature_size(v); 115362306a36Sopenharmony_ci fid = fid ? fid : feature_id(v); 115462306a36Sopenharmony_ci if (dfh_ver == 1) { 115562306a36Sopenharmony_ci dfh_psize = dfh_get_param_size(binfo->ioaddr + ofst, size); 115662306a36Sopenharmony_ci if (dfh_psize < 0) { 115762306a36Sopenharmony_ci dev_err(binfo->dev, 115862306a36Sopenharmony_ci "failed to read size of DFHv1 parameters %d\n", 115962306a36Sopenharmony_ci dfh_psize); 116062306a36Sopenharmony_ci return dfh_psize; 116162306a36Sopenharmony_ci } 116262306a36Sopenharmony_ci dev_dbg(binfo->dev, "dfhv1_psize %d\n", dfh_psize); 116362306a36Sopenharmony_ci } 116462306a36Sopenharmony_ci } 116562306a36Sopenharmony_ci 116662306a36Sopenharmony_ci if (binfo->len - ofst < size) 116762306a36Sopenharmony_ci return -EINVAL; 116862306a36Sopenharmony_ci 116962306a36Sopenharmony_ci finfo = kzalloc(struct_size(finfo, params, dfh_psize / sizeof(u64)), GFP_KERNEL); 117062306a36Sopenharmony_ci if (!finfo) 117162306a36Sopenharmony_ci return -ENOMEM; 117262306a36Sopenharmony_ci 117362306a36Sopenharmony_ci memcpy_fromio(finfo->params, binfo->ioaddr + ofst + DFHv1_PARAM_HDR, dfh_psize); 117462306a36Sopenharmony_ci finfo->param_size = dfh_psize; 117562306a36Sopenharmony_ci 117662306a36Sopenharmony_ci finfo->fid = fid; 117762306a36Sopenharmony_ci finfo->revision = revision; 117862306a36Sopenharmony_ci finfo->dfh_version = dfh_ver; 117962306a36Sopenharmony_ci if (dfh_ver == 1) { 118062306a36Sopenharmony_ci v = readq(binfo->ioaddr + ofst + DFHv1_CSR_ADDR); 118162306a36Sopenharmony_ci addr_off = FIELD_GET(DFHv1_CSR_ADDR_MASK, v); 118262306a36Sopenharmony_ci if (FIELD_GET(DFHv1_CSR_ADDR_REL, v)) 118362306a36Sopenharmony_ci start = addr_off << 1; 118462306a36Sopenharmony_ci else 118562306a36Sopenharmony_ci start = binfo->start + ofst + addr_off; 118662306a36Sopenharmony_ci 118762306a36Sopenharmony_ci v = readq(binfo->ioaddr + ofst + DFHv1_CSR_SIZE_GRP); 118862306a36Sopenharmony_ci end = start + FIELD_GET(DFHv1_CSR_SIZE_GRP_SIZE, v) - 1; 118962306a36Sopenharmony_ci } else { 119062306a36Sopenharmony_ci start = binfo->start + ofst; 119162306a36Sopenharmony_ci end = start + size - 1; 119262306a36Sopenharmony_ci } 119362306a36Sopenharmony_ci finfo->mmio_res.flags = IORESOURCE_MEM; 119462306a36Sopenharmony_ci finfo->mmio_res.start = start; 119562306a36Sopenharmony_ci finfo->mmio_res.end = end; 119662306a36Sopenharmony_ci 119762306a36Sopenharmony_ci ret = parse_feature_irqs(binfo, ofst, finfo); 119862306a36Sopenharmony_ci if (ret) { 119962306a36Sopenharmony_ci kfree(finfo); 120062306a36Sopenharmony_ci return ret; 120162306a36Sopenharmony_ci } 120262306a36Sopenharmony_ci 120362306a36Sopenharmony_ci list_add_tail(&finfo->node, &binfo->sub_features); 120462306a36Sopenharmony_ci binfo->feature_num++; 120562306a36Sopenharmony_ci 120662306a36Sopenharmony_ci return 0; 120762306a36Sopenharmony_ci} 120862306a36Sopenharmony_ci 120962306a36Sopenharmony_cistatic int parse_feature_port_afu(struct build_feature_devs_info *binfo, 121062306a36Sopenharmony_ci resource_size_t ofst) 121162306a36Sopenharmony_ci{ 121262306a36Sopenharmony_ci u64 v = readq(binfo->ioaddr + PORT_HDR_CAP); 121362306a36Sopenharmony_ci u32 size = FIELD_GET(PORT_CAP_MMIO_SIZE, v) << 10; 121462306a36Sopenharmony_ci 121562306a36Sopenharmony_ci WARN_ON(!size); 121662306a36Sopenharmony_ci 121762306a36Sopenharmony_ci return create_feature_instance(binfo, ofst, size, FEATURE_ID_AFU); 121862306a36Sopenharmony_ci} 121962306a36Sopenharmony_ci 122062306a36Sopenharmony_ci#define is_feature_dev_detected(binfo) (!!(binfo)->feature_dev) 122162306a36Sopenharmony_ci 122262306a36Sopenharmony_cistatic int parse_feature_afu(struct build_feature_devs_info *binfo, 122362306a36Sopenharmony_ci resource_size_t ofst) 122462306a36Sopenharmony_ci{ 122562306a36Sopenharmony_ci if (!is_feature_dev_detected(binfo)) { 122662306a36Sopenharmony_ci dev_err(binfo->dev, "this AFU does not belong to any FIU.\n"); 122762306a36Sopenharmony_ci return -EINVAL; 122862306a36Sopenharmony_ci } 122962306a36Sopenharmony_ci 123062306a36Sopenharmony_ci switch (feature_dev_id_type(binfo->feature_dev)) { 123162306a36Sopenharmony_ci case PORT_ID: 123262306a36Sopenharmony_ci return parse_feature_port_afu(binfo, ofst); 123362306a36Sopenharmony_ci default: 123462306a36Sopenharmony_ci dev_info(binfo->dev, "AFU belonging to FIU %s is not supported yet.\n", 123562306a36Sopenharmony_ci binfo->feature_dev->name); 123662306a36Sopenharmony_ci } 123762306a36Sopenharmony_ci 123862306a36Sopenharmony_ci return 0; 123962306a36Sopenharmony_ci} 124062306a36Sopenharmony_ci 124162306a36Sopenharmony_cistatic int build_info_prepare(struct build_feature_devs_info *binfo, 124262306a36Sopenharmony_ci resource_size_t start, resource_size_t len) 124362306a36Sopenharmony_ci{ 124462306a36Sopenharmony_ci struct device *dev = binfo->dev; 124562306a36Sopenharmony_ci void __iomem *ioaddr; 124662306a36Sopenharmony_ci 124762306a36Sopenharmony_ci if (!devm_request_mem_region(dev, start, len, dev_name(dev))) { 124862306a36Sopenharmony_ci dev_err(dev, "request region fail, start:%pa, len:%pa\n", 124962306a36Sopenharmony_ci &start, &len); 125062306a36Sopenharmony_ci return -EBUSY; 125162306a36Sopenharmony_ci } 125262306a36Sopenharmony_ci 125362306a36Sopenharmony_ci ioaddr = devm_ioremap(dev, start, len); 125462306a36Sopenharmony_ci if (!ioaddr) { 125562306a36Sopenharmony_ci dev_err(dev, "ioremap region fail, start:%pa, len:%pa\n", 125662306a36Sopenharmony_ci &start, &len); 125762306a36Sopenharmony_ci return -ENOMEM; 125862306a36Sopenharmony_ci } 125962306a36Sopenharmony_ci 126062306a36Sopenharmony_ci binfo->start = start; 126162306a36Sopenharmony_ci binfo->len = len; 126262306a36Sopenharmony_ci binfo->ioaddr = ioaddr; 126362306a36Sopenharmony_ci 126462306a36Sopenharmony_ci return 0; 126562306a36Sopenharmony_ci} 126662306a36Sopenharmony_ci 126762306a36Sopenharmony_cistatic void build_info_complete(struct build_feature_devs_info *binfo) 126862306a36Sopenharmony_ci{ 126962306a36Sopenharmony_ci devm_iounmap(binfo->dev, binfo->ioaddr); 127062306a36Sopenharmony_ci devm_release_mem_region(binfo->dev, binfo->start, binfo->len); 127162306a36Sopenharmony_ci} 127262306a36Sopenharmony_ci 127362306a36Sopenharmony_cistatic int parse_feature_fiu(struct build_feature_devs_info *binfo, 127462306a36Sopenharmony_ci resource_size_t ofst) 127562306a36Sopenharmony_ci{ 127662306a36Sopenharmony_ci int ret = 0; 127762306a36Sopenharmony_ci u32 offset; 127862306a36Sopenharmony_ci u16 id; 127962306a36Sopenharmony_ci u64 v; 128062306a36Sopenharmony_ci 128162306a36Sopenharmony_ci if (is_feature_dev_detected(binfo)) { 128262306a36Sopenharmony_ci build_info_complete(binfo); 128362306a36Sopenharmony_ci 128462306a36Sopenharmony_ci ret = build_info_commit_dev(binfo); 128562306a36Sopenharmony_ci if (ret) 128662306a36Sopenharmony_ci return ret; 128762306a36Sopenharmony_ci 128862306a36Sopenharmony_ci ret = build_info_prepare(binfo, binfo->start + ofst, 128962306a36Sopenharmony_ci binfo->len - ofst); 129062306a36Sopenharmony_ci if (ret) 129162306a36Sopenharmony_ci return ret; 129262306a36Sopenharmony_ci } 129362306a36Sopenharmony_ci 129462306a36Sopenharmony_ci v = readq(binfo->ioaddr + DFH); 129562306a36Sopenharmony_ci id = FIELD_GET(DFH_ID, v); 129662306a36Sopenharmony_ci 129762306a36Sopenharmony_ci /* create platform device for dfl feature dev */ 129862306a36Sopenharmony_ci ret = build_info_create_dev(binfo, dfh_id_to_type(id)); 129962306a36Sopenharmony_ci if (ret) 130062306a36Sopenharmony_ci return ret; 130162306a36Sopenharmony_ci 130262306a36Sopenharmony_ci ret = create_feature_instance(binfo, 0, 0, 0); 130362306a36Sopenharmony_ci if (ret) 130462306a36Sopenharmony_ci return ret; 130562306a36Sopenharmony_ci /* 130662306a36Sopenharmony_ci * find and parse FIU's child AFU via its NEXT_AFU register. 130762306a36Sopenharmony_ci * please note that only Port has valid NEXT_AFU pointer per spec. 130862306a36Sopenharmony_ci */ 130962306a36Sopenharmony_ci v = readq(binfo->ioaddr + NEXT_AFU); 131062306a36Sopenharmony_ci 131162306a36Sopenharmony_ci offset = FIELD_GET(NEXT_AFU_NEXT_DFH_OFST, v); 131262306a36Sopenharmony_ci if (offset) 131362306a36Sopenharmony_ci return parse_feature_afu(binfo, offset); 131462306a36Sopenharmony_ci 131562306a36Sopenharmony_ci dev_dbg(binfo->dev, "No AFUs detected on FIU %d\n", id); 131662306a36Sopenharmony_ci 131762306a36Sopenharmony_ci return ret; 131862306a36Sopenharmony_ci} 131962306a36Sopenharmony_ci 132062306a36Sopenharmony_cistatic int parse_feature_private(struct build_feature_devs_info *binfo, 132162306a36Sopenharmony_ci resource_size_t ofst) 132262306a36Sopenharmony_ci{ 132362306a36Sopenharmony_ci if (!is_feature_dev_detected(binfo)) { 132462306a36Sopenharmony_ci dev_err(binfo->dev, "the private feature 0x%x does not belong to any AFU.\n", 132562306a36Sopenharmony_ci feature_id(readq(binfo->ioaddr + ofst))); 132662306a36Sopenharmony_ci return -EINVAL; 132762306a36Sopenharmony_ci } 132862306a36Sopenharmony_ci 132962306a36Sopenharmony_ci return create_feature_instance(binfo, ofst, 0, 0); 133062306a36Sopenharmony_ci} 133162306a36Sopenharmony_ci 133262306a36Sopenharmony_ci/** 133362306a36Sopenharmony_ci * parse_feature - parse a feature on given device feature list 133462306a36Sopenharmony_ci * 133562306a36Sopenharmony_ci * @binfo: build feature devices information. 133662306a36Sopenharmony_ci * @ofst: offset to current FIU header 133762306a36Sopenharmony_ci */ 133862306a36Sopenharmony_cistatic int parse_feature(struct build_feature_devs_info *binfo, 133962306a36Sopenharmony_ci resource_size_t ofst) 134062306a36Sopenharmony_ci{ 134162306a36Sopenharmony_ci u64 v; 134262306a36Sopenharmony_ci u32 type; 134362306a36Sopenharmony_ci 134462306a36Sopenharmony_ci v = readq(binfo->ioaddr + ofst + DFH); 134562306a36Sopenharmony_ci type = FIELD_GET(DFH_TYPE, v); 134662306a36Sopenharmony_ci 134762306a36Sopenharmony_ci switch (type) { 134862306a36Sopenharmony_ci case DFH_TYPE_AFU: 134962306a36Sopenharmony_ci return parse_feature_afu(binfo, ofst); 135062306a36Sopenharmony_ci case DFH_TYPE_PRIVATE: 135162306a36Sopenharmony_ci return parse_feature_private(binfo, ofst); 135262306a36Sopenharmony_ci case DFH_TYPE_FIU: 135362306a36Sopenharmony_ci return parse_feature_fiu(binfo, ofst); 135462306a36Sopenharmony_ci default: 135562306a36Sopenharmony_ci dev_info(binfo->dev, 135662306a36Sopenharmony_ci "Feature Type %x is not supported.\n", type); 135762306a36Sopenharmony_ci } 135862306a36Sopenharmony_ci 135962306a36Sopenharmony_ci return 0; 136062306a36Sopenharmony_ci} 136162306a36Sopenharmony_ci 136262306a36Sopenharmony_cistatic int parse_feature_list(struct build_feature_devs_info *binfo, 136362306a36Sopenharmony_ci resource_size_t start, resource_size_t len) 136462306a36Sopenharmony_ci{ 136562306a36Sopenharmony_ci resource_size_t end = start + len; 136662306a36Sopenharmony_ci int ret = 0; 136762306a36Sopenharmony_ci u32 ofst = 0; 136862306a36Sopenharmony_ci u64 v; 136962306a36Sopenharmony_ci 137062306a36Sopenharmony_ci ret = build_info_prepare(binfo, start, len); 137162306a36Sopenharmony_ci if (ret) 137262306a36Sopenharmony_ci return ret; 137362306a36Sopenharmony_ci 137462306a36Sopenharmony_ci /* walk through the device feature list via DFH's next DFH pointer. */ 137562306a36Sopenharmony_ci for (; start < end; start += ofst) { 137662306a36Sopenharmony_ci if (end - start < DFH_SIZE) { 137762306a36Sopenharmony_ci dev_err(binfo->dev, "The region is too small to contain a feature.\n"); 137862306a36Sopenharmony_ci return -EINVAL; 137962306a36Sopenharmony_ci } 138062306a36Sopenharmony_ci 138162306a36Sopenharmony_ci ret = parse_feature(binfo, start - binfo->start); 138262306a36Sopenharmony_ci if (ret) 138362306a36Sopenharmony_ci return ret; 138462306a36Sopenharmony_ci 138562306a36Sopenharmony_ci v = readq(binfo->ioaddr + start - binfo->start + DFH); 138662306a36Sopenharmony_ci ofst = FIELD_GET(DFH_NEXT_HDR_OFST, v); 138762306a36Sopenharmony_ci 138862306a36Sopenharmony_ci /* stop parsing if EOL(End of List) is set or offset is 0 */ 138962306a36Sopenharmony_ci if ((v & DFH_EOL) || !ofst) 139062306a36Sopenharmony_ci break; 139162306a36Sopenharmony_ci } 139262306a36Sopenharmony_ci 139362306a36Sopenharmony_ci /* commit current feature device when reach the end of list */ 139462306a36Sopenharmony_ci build_info_complete(binfo); 139562306a36Sopenharmony_ci 139662306a36Sopenharmony_ci if (is_feature_dev_detected(binfo)) 139762306a36Sopenharmony_ci ret = build_info_commit_dev(binfo); 139862306a36Sopenharmony_ci 139962306a36Sopenharmony_ci return ret; 140062306a36Sopenharmony_ci} 140162306a36Sopenharmony_ci 140262306a36Sopenharmony_cistruct dfl_fpga_enum_info *dfl_fpga_enum_info_alloc(struct device *dev) 140362306a36Sopenharmony_ci{ 140462306a36Sopenharmony_ci struct dfl_fpga_enum_info *info; 140562306a36Sopenharmony_ci 140662306a36Sopenharmony_ci get_device(dev); 140762306a36Sopenharmony_ci 140862306a36Sopenharmony_ci info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); 140962306a36Sopenharmony_ci if (!info) { 141062306a36Sopenharmony_ci put_device(dev); 141162306a36Sopenharmony_ci return NULL; 141262306a36Sopenharmony_ci } 141362306a36Sopenharmony_ci 141462306a36Sopenharmony_ci info->dev = dev; 141562306a36Sopenharmony_ci INIT_LIST_HEAD(&info->dfls); 141662306a36Sopenharmony_ci 141762306a36Sopenharmony_ci return info; 141862306a36Sopenharmony_ci} 141962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dfl_fpga_enum_info_alloc); 142062306a36Sopenharmony_ci 142162306a36Sopenharmony_civoid dfl_fpga_enum_info_free(struct dfl_fpga_enum_info *info) 142262306a36Sopenharmony_ci{ 142362306a36Sopenharmony_ci struct dfl_fpga_enum_dfl *tmp, *dfl; 142462306a36Sopenharmony_ci struct device *dev; 142562306a36Sopenharmony_ci 142662306a36Sopenharmony_ci if (!info) 142762306a36Sopenharmony_ci return; 142862306a36Sopenharmony_ci 142962306a36Sopenharmony_ci dev = info->dev; 143062306a36Sopenharmony_ci 143162306a36Sopenharmony_ci /* remove all device feature lists in the list. */ 143262306a36Sopenharmony_ci list_for_each_entry_safe(dfl, tmp, &info->dfls, node) { 143362306a36Sopenharmony_ci list_del(&dfl->node); 143462306a36Sopenharmony_ci devm_kfree(dev, dfl); 143562306a36Sopenharmony_ci } 143662306a36Sopenharmony_ci 143762306a36Sopenharmony_ci /* remove irq table */ 143862306a36Sopenharmony_ci if (info->irq_table) 143962306a36Sopenharmony_ci devm_kfree(dev, info->irq_table); 144062306a36Sopenharmony_ci 144162306a36Sopenharmony_ci devm_kfree(dev, info); 144262306a36Sopenharmony_ci put_device(dev); 144362306a36Sopenharmony_ci} 144462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dfl_fpga_enum_info_free); 144562306a36Sopenharmony_ci 144662306a36Sopenharmony_ci/** 144762306a36Sopenharmony_ci * dfl_fpga_enum_info_add_dfl - add info of a device feature list to enum info 144862306a36Sopenharmony_ci * 144962306a36Sopenharmony_ci * @info: ptr to dfl_fpga_enum_info 145062306a36Sopenharmony_ci * @start: mmio resource address of the device feature list. 145162306a36Sopenharmony_ci * @len: mmio resource length of the device feature list. 145262306a36Sopenharmony_ci * 145362306a36Sopenharmony_ci * One FPGA device may have one or more Device Feature Lists (DFLs), use this 145462306a36Sopenharmony_ci * function to add information of each DFL to common data structure for next 145562306a36Sopenharmony_ci * step enumeration. 145662306a36Sopenharmony_ci * 145762306a36Sopenharmony_ci * Return: 0 on success, negative error code otherwise. 145862306a36Sopenharmony_ci */ 145962306a36Sopenharmony_ciint dfl_fpga_enum_info_add_dfl(struct dfl_fpga_enum_info *info, 146062306a36Sopenharmony_ci resource_size_t start, resource_size_t len) 146162306a36Sopenharmony_ci{ 146262306a36Sopenharmony_ci struct dfl_fpga_enum_dfl *dfl; 146362306a36Sopenharmony_ci 146462306a36Sopenharmony_ci dfl = devm_kzalloc(info->dev, sizeof(*dfl), GFP_KERNEL); 146562306a36Sopenharmony_ci if (!dfl) 146662306a36Sopenharmony_ci return -ENOMEM; 146762306a36Sopenharmony_ci 146862306a36Sopenharmony_ci dfl->start = start; 146962306a36Sopenharmony_ci dfl->len = len; 147062306a36Sopenharmony_ci 147162306a36Sopenharmony_ci list_add_tail(&dfl->node, &info->dfls); 147262306a36Sopenharmony_ci 147362306a36Sopenharmony_ci return 0; 147462306a36Sopenharmony_ci} 147562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dfl_fpga_enum_info_add_dfl); 147662306a36Sopenharmony_ci 147762306a36Sopenharmony_ci/** 147862306a36Sopenharmony_ci * dfl_fpga_enum_info_add_irq - add irq table to enum info 147962306a36Sopenharmony_ci * 148062306a36Sopenharmony_ci * @info: ptr to dfl_fpga_enum_info 148162306a36Sopenharmony_ci * @nr_irqs: number of irqs of the DFL fpga device to be enumerated. 148262306a36Sopenharmony_ci * @irq_table: Linux IRQ numbers for all irqs, indexed by local irq index of 148362306a36Sopenharmony_ci * this device. 148462306a36Sopenharmony_ci * 148562306a36Sopenharmony_ci * One FPGA device may have several interrupts. This function adds irq 148662306a36Sopenharmony_ci * information of the DFL fpga device to enum info for next step enumeration. 148762306a36Sopenharmony_ci * This function should be called before dfl_fpga_feature_devs_enumerate(). 148862306a36Sopenharmony_ci * As we only support one irq domain for all DFLs in the same enum info, adding 148962306a36Sopenharmony_ci * irq table a second time for the same enum info will return error. 149062306a36Sopenharmony_ci * 149162306a36Sopenharmony_ci * If we need to enumerate DFLs which belong to different irq domains, we 149262306a36Sopenharmony_ci * should fill more enum info and enumerate them one by one. 149362306a36Sopenharmony_ci * 149462306a36Sopenharmony_ci * Return: 0 on success, negative error code otherwise. 149562306a36Sopenharmony_ci */ 149662306a36Sopenharmony_ciint dfl_fpga_enum_info_add_irq(struct dfl_fpga_enum_info *info, 149762306a36Sopenharmony_ci unsigned int nr_irqs, int *irq_table) 149862306a36Sopenharmony_ci{ 149962306a36Sopenharmony_ci if (!nr_irqs || !irq_table) 150062306a36Sopenharmony_ci return -EINVAL; 150162306a36Sopenharmony_ci 150262306a36Sopenharmony_ci if (info->irq_table) 150362306a36Sopenharmony_ci return -EEXIST; 150462306a36Sopenharmony_ci 150562306a36Sopenharmony_ci info->irq_table = devm_kmemdup(info->dev, irq_table, 150662306a36Sopenharmony_ci sizeof(int) * nr_irqs, GFP_KERNEL); 150762306a36Sopenharmony_ci if (!info->irq_table) 150862306a36Sopenharmony_ci return -ENOMEM; 150962306a36Sopenharmony_ci 151062306a36Sopenharmony_ci info->nr_irqs = nr_irqs; 151162306a36Sopenharmony_ci 151262306a36Sopenharmony_ci return 0; 151362306a36Sopenharmony_ci} 151462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dfl_fpga_enum_info_add_irq); 151562306a36Sopenharmony_ci 151662306a36Sopenharmony_cistatic int remove_feature_dev(struct device *dev, void *data) 151762306a36Sopenharmony_ci{ 151862306a36Sopenharmony_ci struct platform_device *pdev = to_platform_device(dev); 151962306a36Sopenharmony_ci enum dfl_id_type type = feature_dev_id_type(pdev); 152062306a36Sopenharmony_ci int id = pdev->id; 152162306a36Sopenharmony_ci 152262306a36Sopenharmony_ci platform_device_unregister(pdev); 152362306a36Sopenharmony_ci 152462306a36Sopenharmony_ci dfl_id_free(type, id); 152562306a36Sopenharmony_ci 152662306a36Sopenharmony_ci return 0; 152762306a36Sopenharmony_ci} 152862306a36Sopenharmony_ci 152962306a36Sopenharmony_cistatic void remove_feature_devs(struct dfl_fpga_cdev *cdev) 153062306a36Sopenharmony_ci{ 153162306a36Sopenharmony_ci device_for_each_child(&cdev->region->dev, NULL, remove_feature_dev); 153262306a36Sopenharmony_ci} 153362306a36Sopenharmony_ci 153462306a36Sopenharmony_ci/** 153562306a36Sopenharmony_ci * dfl_fpga_feature_devs_enumerate - enumerate feature devices 153662306a36Sopenharmony_ci * @info: information for enumeration. 153762306a36Sopenharmony_ci * 153862306a36Sopenharmony_ci * This function creates a container device (base FPGA region), enumerates 153962306a36Sopenharmony_ci * feature devices based on the enumeration info and creates platform devices 154062306a36Sopenharmony_ci * under the container device. 154162306a36Sopenharmony_ci * 154262306a36Sopenharmony_ci * Return: dfl_fpga_cdev struct on success, -errno on failure 154362306a36Sopenharmony_ci */ 154462306a36Sopenharmony_cistruct dfl_fpga_cdev * 154562306a36Sopenharmony_cidfl_fpga_feature_devs_enumerate(struct dfl_fpga_enum_info *info) 154662306a36Sopenharmony_ci{ 154762306a36Sopenharmony_ci struct build_feature_devs_info *binfo; 154862306a36Sopenharmony_ci struct dfl_fpga_enum_dfl *dfl; 154962306a36Sopenharmony_ci struct dfl_fpga_cdev *cdev; 155062306a36Sopenharmony_ci int ret = 0; 155162306a36Sopenharmony_ci 155262306a36Sopenharmony_ci if (!info->dev) 155362306a36Sopenharmony_ci return ERR_PTR(-ENODEV); 155462306a36Sopenharmony_ci 155562306a36Sopenharmony_ci cdev = devm_kzalloc(info->dev, sizeof(*cdev), GFP_KERNEL); 155662306a36Sopenharmony_ci if (!cdev) 155762306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 155862306a36Sopenharmony_ci 155962306a36Sopenharmony_ci cdev->parent = info->dev; 156062306a36Sopenharmony_ci mutex_init(&cdev->lock); 156162306a36Sopenharmony_ci INIT_LIST_HEAD(&cdev->port_dev_list); 156262306a36Sopenharmony_ci 156362306a36Sopenharmony_ci cdev->region = fpga_region_register(info->dev, NULL, NULL); 156462306a36Sopenharmony_ci if (IS_ERR(cdev->region)) { 156562306a36Sopenharmony_ci ret = PTR_ERR(cdev->region); 156662306a36Sopenharmony_ci goto free_cdev_exit; 156762306a36Sopenharmony_ci } 156862306a36Sopenharmony_ci 156962306a36Sopenharmony_ci /* create and init build info for enumeration */ 157062306a36Sopenharmony_ci binfo = devm_kzalloc(info->dev, sizeof(*binfo), GFP_KERNEL); 157162306a36Sopenharmony_ci if (!binfo) { 157262306a36Sopenharmony_ci ret = -ENOMEM; 157362306a36Sopenharmony_ci goto unregister_region_exit; 157462306a36Sopenharmony_ci } 157562306a36Sopenharmony_ci 157662306a36Sopenharmony_ci binfo->dev = info->dev; 157762306a36Sopenharmony_ci binfo->cdev = cdev; 157862306a36Sopenharmony_ci 157962306a36Sopenharmony_ci binfo->nr_irqs = info->nr_irqs; 158062306a36Sopenharmony_ci if (info->nr_irqs) 158162306a36Sopenharmony_ci binfo->irq_table = info->irq_table; 158262306a36Sopenharmony_ci 158362306a36Sopenharmony_ci /* 158462306a36Sopenharmony_ci * start enumeration for all feature devices based on Device Feature 158562306a36Sopenharmony_ci * Lists. 158662306a36Sopenharmony_ci */ 158762306a36Sopenharmony_ci list_for_each_entry(dfl, &info->dfls, node) { 158862306a36Sopenharmony_ci ret = parse_feature_list(binfo, dfl->start, dfl->len); 158962306a36Sopenharmony_ci if (ret) { 159062306a36Sopenharmony_ci remove_feature_devs(cdev); 159162306a36Sopenharmony_ci build_info_free(binfo); 159262306a36Sopenharmony_ci goto unregister_region_exit; 159362306a36Sopenharmony_ci } 159462306a36Sopenharmony_ci } 159562306a36Sopenharmony_ci 159662306a36Sopenharmony_ci build_info_free(binfo); 159762306a36Sopenharmony_ci 159862306a36Sopenharmony_ci return cdev; 159962306a36Sopenharmony_ci 160062306a36Sopenharmony_ciunregister_region_exit: 160162306a36Sopenharmony_ci fpga_region_unregister(cdev->region); 160262306a36Sopenharmony_cifree_cdev_exit: 160362306a36Sopenharmony_ci devm_kfree(info->dev, cdev); 160462306a36Sopenharmony_ci return ERR_PTR(ret); 160562306a36Sopenharmony_ci} 160662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dfl_fpga_feature_devs_enumerate); 160762306a36Sopenharmony_ci 160862306a36Sopenharmony_ci/** 160962306a36Sopenharmony_ci * dfl_fpga_feature_devs_remove - remove all feature devices 161062306a36Sopenharmony_ci * @cdev: fpga container device. 161162306a36Sopenharmony_ci * 161262306a36Sopenharmony_ci * Remove the container device and all feature devices under given container 161362306a36Sopenharmony_ci * devices. 161462306a36Sopenharmony_ci */ 161562306a36Sopenharmony_civoid dfl_fpga_feature_devs_remove(struct dfl_fpga_cdev *cdev) 161662306a36Sopenharmony_ci{ 161762306a36Sopenharmony_ci struct dfl_feature_platform_data *pdata, *ptmp; 161862306a36Sopenharmony_ci 161962306a36Sopenharmony_ci mutex_lock(&cdev->lock); 162062306a36Sopenharmony_ci if (cdev->fme_dev) 162162306a36Sopenharmony_ci put_device(cdev->fme_dev); 162262306a36Sopenharmony_ci 162362306a36Sopenharmony_ci list_for_each_entry_safe(pdata, ptmp, &cdev->port_dev_list, node) { 162462306a36Sopenharmony_ci struct platform_device *port_dev = pdata->dev; 162562306a36Sopenharmony_ci 162662306a36Sopenharmony_ci /* remove released ports */ 162762306a36Sopenharmony_ci if (!device_is_registered(&port_dev->dev)) { 162862306a36Sopenharmony_ci dfl_id_free(feature_dev_id_type(port_dev), 162962306a36Sopenharmony_ci port_dev->id); 163062306a36Sopenharmony_ci platform_device_put(port_dev); 163162306a36Sopenharmony_ci } 163262306a36Sopenharmony_ci 163362306a36Sopenharmony_ci list_del(&pdata->node); 163462306a36Sopenharmony_ci put_device(&port_dev->dev); 163562306a36Sopenharmony_ci } 163662306a36Sopenharmony_ci mutex_unlock(&cdev->lock); 163762306a36Sopenharmony_ci 163862306a36Sopenharmony_ci remove_feature_devs(cdev); 163962306a36Sopenharmony_ci 164062306a36Sopenharmony_ci fpga_region_unregister(cdev->region); 164162306a36Sopenharmony_ci devm_kfree(cdev->parent, cdev); 164262306a36Sopenharmony_ci} 164362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dfl_fpga_feature_devs_remove); 164462306a36Sopenharmony_ci 164562306a36Sopenharmony_ci/** 164662306a36Sopenharmony_ci * __dfl_fpga_cdev_find_port - find a port under given container device 164762306a36Sopenharmony_ci * 164862306a36Sopenharmony_ci * @cdev: container device 164962306a36Sopenharmony_ci * @data: data passed to match function 165062306a36Sopenharmony_ci * @match: match function used to find specific port from the port device list 165162306a36Sopenharmony_ci * 165262306a36Sopenharmony_ci * Find a port device under container device. This function needs to be 165362306a36Sopenharmony_ci * invoked with lock held. 165462306a36Sopenharmony_ci * 165562306a36Sopenharmony_ci * Return: pointer to port's platform device if successful, NULL otherwise. 165662306a36Sopenharmony_ci * 165762306a36Sopenharmony_ci * NOTE: you will need to drop the device reference with put_device() after use. 165862306a36Sopenharmony_ci */ 165962306a36Sopenharmony_cistruct platform_device * 166062306a36Sopenharmony_ci__dfl_fpga_cdev_find_port(struct dfl_fpga_cdev *cdev, void *data, 166162306a36Sopenharmony_ci int (*match)(struct platform_device *, void *)) 166262306a36Sopenharmony_ci{ 166362306a36Sopenharmony_ci struct dfl_feature_platform_data *pdata; 166462306a36Sopenharmony_ci struct platform_device *port_dev; 166562306a36Sopenharmony_ci 166662306a36Sopenharmony_ci list_for_each_entry(pdata, &cdev->port_dev_list, node) { 166762306a36Sopenharmony_ci port_dev = pdata->dev; 166862306a36Sopenharmony_ci 166962306a36Sopenharmony_ci if (match(port_dev, data) && get_device(&port_dev->dev)) 167062306a36Sopenharmony_ci return port_dev; 167162306a36Sopenharmony_ci } 167262306a36Sopenharmony_ci 167362306a36Sopenharmony_ci return NULL; 167462306a36Sopenharmony_ci} 167562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(__dfl_fpga_cdev_find_port); 167662306a36Sopenharmony_ci 167762306a36Sopenharmony_cistatic int __init dfl_fpga_init(void) 167862306a36Sopenharmony_ci{ 167962306a36Sopenharmony_ci int ret; 168062306a36Sopenharmony_ci 168162306a36Sopenharmony_ci ret = bus_register(&dfl_bus_type); 168262306a36Sopenharmony_ci if (ret) 168362306a36Sopenharmony_ci return ret; 168462306a36Sopenharmony_ci 168562306a36Sopenharmony_ci dfl_ids_init(); 168662306a36Sopenharmony_ci 168762306a36Sopenharmony_ci ret = dfl_chardev_init(); 168862306a36Sopenharmony_ci if (ret) { 168962306a36Sopenharmony_ci dfl_ids_destroy(); 169062306a36Sopenharmony_ci bus_unregister(&dfl_bus_type); 169162306a36Sopenharmony_ci } 169262306a36Sopenharmony_ci 169362306a36Sopenharmony_ci return ret; 169462306a36Sopenharmony_ci} 169562306a36Sopenharmony_ci 169662306a36Sopenharmony_ci/** 169762306a36Sopenharmony_ci * dfl_fpga_cdev_release_port - release a port platform device 169862306a36Sopenharmony_ci * 169962306a36Sopenharmony_ci * @cdev: parent container device. 170062306a36Sopenharmony_ci * @port_id: id of the port platform device. 170162306a36Sopenharmony_ci * 170262306a36Sopenharmony_ci * This function allows user to release a port platform device. This is a 170362306a36Sopenharmony_ci * mandatory step before turn a port from PF into VF for SRIOV support. 170462306a36Sopenharmony_ci * 170562306a36Sopenharmony_ci * Return: 0 on success, negative error code otherwise. 170662306a36Sopenharmony_ci */ 170762306a36Sopenharmony_ciint dfl_fpga_cdev_release_port(struct dfl_fpga_cdev *cdev, int port_id) 170862306a36Sopenharmony_ci{ 170962306a36Sopenharmony_ci struct dfl_feature_platform_data *pdata; 171062306a36Sopenharmony_ci struct platform_device *port_pdev; 171162306a36Sopenharmony_ci int ret = -ENODEV; 171262306a36Sopenharmony_ci 171362306a36Sopenharmony_ci mutex_lock(&cdev->lock); 171462306a36Sopenharmony_ci port_pdev = __dfl_fpga_cdev_find_port(cdev, &port_id, 171562306a36Sopenharmony_ci dfl_fpga_check_port_id); 171662306a36Sopenharmony_ci if (!port_pdev) 171762306a36Sopenharmony_ci goto unlock_exit; 171862306a36Sopenharmony_ci 171962306a36Sopenharmony_ci if (!device_is_registered(&port_pdev->dev)) { 172062306a36Sopenharmony_ci ret = -EBUSY; 172162306a36Sopenharmony_ci goto put_dev_exit; 172262306a36Sopenharmony_ci } 172362306a36Sopenharmony_ci 172462306a36Sopenharmony_ci pdata = dev_get_platdata(&port_pdev->dev); 172562306a36Sopenharmony_ci 172662306a36Sopenharmony_ci mutex_lock(&pdata->lock); 172762306a36Sopenharmony_ci ret = dfl_feature_dev_use_begin(pdata, true); 172862306a36Sopenharmony_ci mutex_unlock(&pdata->lock); 172962306a36Sopenharmony_ci if (ret) 173062306a36Sopenharmony_ci goto put_dev_exit; 173162306a36Sopenharmony_ci 173262306a36Sopenharmony_ci platform_device_del(port_pdev); 173362306a36Sopenharmony_ci cdev->released_port_num++; 173462306a36Sopenharmony_ciput_dev_exit: 173562306a36Sopenharmony_ci put_device(&port_pdev->dev); 173662306a36Sopenharmony_ciunlock_exit: 173762306a36Sopenharmony_ci mutex_unlock(&cdev->lock); 173862306a36Sopenharmony_ci return ret; 173962306a36Sopenharmony_ci} 174062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dfl_fpga_cdev_release_port); 174162306a36Sopenharmony_ci 174262306a36Sopenharmony_ci/** 174362306a36Sopenharmony_ci * dfl_fpga_cdev_assign_port - assign a port platform device back 174462306a36Sopenharmony_ci * 174562306a36Sopenharmony_ci * @cdev: parent container device. 174662306a36Sopenharmony_ci * @port_id: id of the port platform device. 174762306a36Sopenharmony_ci * 174862306a36Sopenharmony_ci * This function allows user to assign a port platform device back. This is 174962306a36Sopenharmony_ci * a mandatory step after disable SRIOV support. 175062306a36Sopenharmony_ci * 175162306a36Sopenharmony_ci * Return: 0 on success, negative error code otherwise. 175262306a36Sopenharmony_ci */ 175362306a36Sopenharmony_ciint dfl_fpga_cdev_assign_port(struct dfl_fpga_cdev *cdev, int port_id) 175462306a36Sopenharmony_ci{ 175562306a36Sopenharmony_ci struct dfl_feature_platform_data *pdata; 175662306a36Sopenharmony_ci struct platform_device *port_pdev; 175762306a36Sopenharmony_ci int ret = -ENODEV; 175862306a36Sopenharmony_ci 175962306a36Sopenharmony_ci mutex_lock(&cdev->lock); 176062306a36Sopenharmony_ci port_pdev = __dfl_fpga_cdev_find_port(cdev, &port_id, 176162306a36Sopenharmony_ci dfl_fpga_check_port_id); 176262306a36Sopenharmony_ci if (!port_pdev) 176362306a36Sopenharmony_ci goto unlock_exit; 176462306a36Sopenharmony_ci 176562306a36Sopenharmony_ci if (device_is_registered(&port_pdev->dev)) { 176662306a36Sopenharmony_ci ret = -EBUSY; 176762306a36Sopenharmony_ci goto put_dev_exit; 176862306a36Sopenharmony_ci } 176962306a36Sopenharmony_ci 177062306a36Sopenharmony_ci ret = platform_device_add(port_pdev); 177162306a36Sopenharmony_ci if (ret) 177262306a36Sopenharmony_ci goto put_dev_exit; 177362306a36Sopenharmony_ci 177462306a36Sopenharmony_ci pdata = dev_get_platdata(&port_pdev->dev); 177562306a36Sopenharmony_ci 177662306a36Sopenharmony_ci mutex_lock(&pdata->lock); 177762306a36Sopenharmony_ci dfl_feature_dev_use_end(pdata); 177862306a36Sopenharmony_ci mutex_unlock(&pdata->lock); 177962306a36Sopenharmony_ci 178062306a36Sopenharmony_ci cdev->released_port_num--; 178162306a36Sopenharmony_ciput_dev_exit: 178262306a36Sopenharmony_ci put_device(&port_pdev->dev); 178362306a36Sopenharmony_ciunlock_exit: 178462306a36Sopenharmony_ci mutex_unlock(&cdev->lock); 178562306a36Sopenharmony_ci return ret; 178662306a36Sopenharmony_ci} 178762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dfl_fpga_cdev_assign_port); 178862306a36Sopenharmony_ci 178962306a36Sopenharmony_cistatic void config_port_access_mode(struct device *fme_dev, int port_id, 179062306a36Sopenharmony_ci bool is_vf) 179162306a36Sopenharmony_ci{ 179262306a36Sopenharmony_ci void __iomem *base; 179362306a36Sopenharmony_ci u64 v; 179462306a36Sopenharmony_ci 179562306a36Sopenharmony_ci base = dfl_get_feature_ioaddr_by_id(fme_dev, FME_FEATURE_ID_HEADER); 179662306a36Sopenharmony_ci 179762306a36Sopenharmony_ci v = readq(base + FME_HDR_PORT_OFST(port_id)); 179862306a36Sopenharmony_ci 179962306a36Sopenharmony_ci v &= ~FME_PORT_OFST_ACC_CTRL; 180062306a36Sopenharmony_ci v |= FIELD_PREP(FME_PORT_OFST_ACC_CTRL, 180162306a36Sopenharmony_ci is_vf ? FME_PORT_OFST_ACC_VF : FME_PORT_OFST_ACC_PF); 180262306a36Sopenharmony_ci 180362306a36Sopenharmony_ci writeq(v, base + FME_HDR_PORT_OFST(port_id)); 180462306a36Sopenharmony_ci} 180562306a36Sopenharmony_ci 180662306a36Sopenharmony_ci#define config_port_vf_mode(dev, id) config_port_access_mode(dev, id, true) 180762306a36Sopenharmony_ci#define config_port_pf_mode(dev, id) config_port_access_mode(dev, id, false) 180862306a36Sopenharmony_ci 180962306a36Sopenharmony_ci/** 181062306a36Sopenharmony_ci * dfl_fpga_cdev_config_ports_pf - configure ports to PF access mode 181162306a36Sopenharmony_ci * 181262306a36Sopenharmony_ci * @cdev: parent container device. 181362306a36Sopenharmony_ci * 181462306a36Sopenharmony_ci * This function is needed in sriov configuration routine. It could be used to 181562306a36Sopenharmony_ci * configure the all released ports from VF access mode to PF. 181662306a36Sopenharmony_ci */ 181762306a36Sopenharmony_civoid dfl_fpga_cdev_config_ports_pf(struct dfl_fpga_cdev *cdev) 181862306a36Sopenharmony_ci{ 181962306a36Sopenharmony_ci struct dfl_feature_platform_data *pdata; 182062306a36Sopenharmony_ci 182162306a36Sopenharmony_ci mutex_lock(&cdev->lock); 182262306a36Sopenharmony_ci list_for_each_entry(pdata, &cdev->port_dev_list, node) { 182362306a36Sopenharmony_ci if (device_is_registered(&pdata->dev->dev)) 182462306a36Sopenharmony_ci continue; 182562306a36Sopenharmony_ci 182662306a36Sopenharmony_ci config_port_pf_mode(cdev->fme_dev, pdata->id); 182762306a36Sopenharmony_ci } 182862306a36Sopenharmony_ci mutex_unlock(&cdev->lock); 182962306a36Sopenharmony_ci} 183062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dfl_fpga_cdev_config_ports_pf); 183162306a36Sopenharmony_ci 183262306a36Sopenharmony_ci/** 183362306a36Sopenharmony_ci * dfl_fpga_cdev_config_ports_vf - configure ports to VF access mode 183462306a36Sopenharmony_ci * 183562306a36Sopenharmony_ci * @cdev: parent container device. 183662306a36Sopenharmony_ci * @num_vfs: VF device number. 183762306a36Sopenharmony_ci * 183862306a36Sopenharmony_ci * This function is needed in sriov configuration routine. It could be used to 183962306a36Sopenharmony_ci * configure the released ports from PF access mode to VF. 184062306a36Sopenharmony_ci * 184162306a36Sopenharmony_ci * Return: 0 on success, negative error code otherwise. 184262306a36Sopenharmony_ci */ 184362306a36Sopenharmony_ciint dfl_fpga_cdev_config_ports_vf(struct dfl_fpga_cdev *cdev, int num_vfs) 184462306a36Sopenharmony_ci{ 184562306a36Sopenharmony_ci struct dfl_feature_platform_data *pdata; 184662306a36Sopenharmony_ci int ret = 0; 184762306a36Sopenharmony_ci 184862306a36Sopenharmony_ci mutex_lock(&cdev->lock); 184962306a36Sopenharmony_ci /* 185062306a36Sopenharmony_ci * can't turn multiple ports into 1 VF device, only 1 port for 1 VF 185162306a36Sopenharmony_ci * device, so if released port number doesn't match VF device number, 185262306a36Sopenharmony_ci * then reject the request with -EINVAL error code. 185362306a36Sopenharmony_ci */ 185462306a36Sopenharmony_ci if (cdev->released_port_num != num_vfs) { 185562306a36Sopenharmony_ci ret = -EINVAL; 185662306a36Sopenharmony_ci goto done; 185762306a36Sopenharmony_ci } 185862306a36Sopenharmony_ci 185962306a36Sopenharmony_ci list_for_each_entry(pdata, &cdev->port_dev_list, node) { 186062306a36Sopenharmony_ci if (device_is_registered(&pdata->dev->dev)) 186162306a36Sopenharmony_ci continue; 186262306a36Sopenharmony_ci 186362306a36Sopenharmony_ci config_port_vf_mode(cdev->fme_dev, pdata->id); 186462306a36Sopenharmony_ci } 186562306a36Sopenharmony_cidone: 186662306a36Sopenharmony_ci mutex_unlock(&cdev->lock); 186762306a36Sopenharmony_ci return ret; 186862306a36Sopenharmony_ci} 186962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dfl_fpga_cdev_config_ports_vf); 187062306a36Sopenharmony_ci 187162306a36Sopenharmony_cistatic irqreturn_t dfl_irq_handler(int irq, void *arg) 187262306a36Sopenharmony_ci{ 187362306a36Sopenharmony_ci struct eventfd_ctx *trigger = arg; 187462306a36Sopenharmony_ci 187562306a36Sopenharmony_ci eventfd_signal(trigger, 1); 187662306a36Sopenharmony_ci return IRQ_HANDLED; 187762306a36Sopenharmony_ci} 187862306a36Sopenharmony_ci 187962306a36Sopenharmony_cistatic int do_set_irq_trigger(struct dfl_feature *feature, unsigned int idx, 188062306a36Sopenharmony_ci int fd) 188162306a36Sopenharmony_ci{ 188262306a36Sopenharmony_ci struct platform_device *pdev = feature->dev; 188362306a36Sopenharmony_ci struct eventfd_ctx *trigger; 188462306a36Sopenharmony_ci int irq, ret; 188562306a36Sopenharmony_ci 188662306a36Sopenharmony_ci irq = feature->irq_ctx[idx].irq; 188762306a36Sopenharmony_ci 188862306a36Sopenharmony_ci if (feature->irq_ctx[idx].trigger) { 188962306a36Sopenharmony_ci free_irq(irq, feature->irq_ctx[idx].trigger); 189062306a36Sopenharmony_ci kfree(feature->irq_ctx[idx].name); 189162306a36Sopenharmony_ci eventfd_ctx_put(feature->irq_ctx[idx].trigger); 189262306a36Sopenharmony_ci feature->irq_ctx[idx].trigger = NULL; 189362306a36Sopenharmony_ci } 189462306a36Sopenharmony_ci 189562306a36Sopenharmony_ci if (fd < 0) 189662306a36Sopenharmony_ci return 0; 189762306a36Sopenharmony_ci 189862306a36Sopenharmony_ci feature->irq_ctx[idx].name = 189962306a36Sopenharmony_ci kasprintf(GFP_KERNEL, "fpga-irq[%u](%s-%x)", idx, 190062306a36Sopenharmony_ci dev_name(&pdev->dev), feature->id); 190162306a36Sopenharmony_ci if (!feature->irq_ctx[idx].name) 190262306a36Sopenharmony_ci return -ENOMEM; 190362306a36Sopenharmony_ci 190462306a36Sopenharmony_ci trigger = eventfd_ctx_fdget(fd); 190562306a36Sopenharmony_ci if (IS_ERR(trigger)) { 190662306a36Sopenharmony_ci ret = PTR_ERR(trigger); 190762306a36Sopenharmony_ci goto free_name; 190862306a36Sopenharmony_ci } 190962306a36Sopenharmony_ci 191062306a36Sopenharmony_ci ret = request_irq(irq, dfl_irq_handler, 0, 191162306a36Sopenharmony_ci feature->irq_ctx[idx].name, trigger); 191262306a36Sopenharmony_ci if (!ret) { 191362306a36Sopenharmony_ci feature->irq_ctx[idx].trigger = trigger; 191462306a36Sopenharmony_ci return ret; 191562306a36Sopenharmony_ci } 191662306a36Sopenharmony_ci 191762306a36Sopenharmony_ci eventfd_ctx_put(trigger); 191862306a36Sopenharmony_cifree_name: 191962306a36Sopenharmony_ci kfree(feature->irq_ctx[idx].name); 192062306a36Sopenharmony_ci 192162306a36Sopenharmony_ci return ret; 192262306a36Sopenharmony_ci} 192362306a36Sopenharmony_ci 192462306a36Sopenharmony_ci/** 192562306a36Sopenharmony_ci * dfl_fpga_set_irq_triggers - set eventfd triggers for dfl feature interrupts 192662306a36Sopenharmony_ci * 192762306a36Sopenharmony_ci * @feature: dfl sub feature. 192862306a36Sopenharmony_ci * @start: start of irq index in this dfl sub feature. 192962306a36Sopenharmony_ci * @count: number of irqs. 193062306a36Sopenharmony_ci * @fds: eventfds to bind with irqs. unbind related irq if fds[n] is negative. 193162306a36Sopenharmony_ci * unbind "count" specified number of irqs if fds ptr is NULL. 193262306a36Sopenharmony_ci * 193362306a36Sopenharmony_ci * Bind given eventfds with irqs in this dfl sub feature. Unbind related irq if 193462306a36Sopenharmony_ci * fds[n] is negative. Unbind "count" specified number of irqs if fds ptr is 193562306a36Sopenharmony_ci * NULL. 193662306a36Sopenharmony_ci * 193762306a36Sopenharmony_ci * Return: 0 on success, negative error code otherwise. 193862306a36Sopenharmony_ci */ 193962306a36Sopenharmony_ciint dfl_fpga_set_irq_triggers(struct dfl_feature *feature, unsigned int start, 194062306a36Sopenharmony_ci unsigned int count, int32_t *fds) 194162306a36Sopenharmony_ci{ 194262306a36Sopenharmony_ci unsigned int i; 194362306a36Sopenharmony_ci int ret = 0; 194462306a36Sopenharmony_ci 194562306a36Sopenharmony_ci /* overflow */ 194662306a36Sopenharmony_ci if (unlikely(start + count < start)) 194762306a36Sopenharmony_ci return -EINVAL; 194862306a36Sopenharmony_ci 194962306a36Sopenharmony_ci /* exceeds nr_irqs */ 195062306a36Sopenharmony_ci if (start + count > feature->nr_irqs) 195162306a36Sopenharmony_ci return -EINVAL; 195262306a36Sopenharmony_ci 195362306a36Sopenharmony_ci for (i = 0; i < count; i++) { 195462306a36Sopenharmony_ci int fd = fds ? fds[i] : -1; 195562306a36Sopenharmony_ci 195662306a36Sopenharmony_ci ret = do_set_irq_trigger(feature, start + i, fd); 195762306a36Sopenharmony_ci if (ret) { 195862306a36Sopenharmony_ci while (i--) 195962306a36Sopenharmony_ci do_set_irq_trigger(feature, start + i, -1); 196062306a36Sopenharmony_ci break; 196162306a36Sopenharmony_ci } 196262306a36Sopenharmony_ci } 196362306a36Sopenharmony_ci 196462306a36Sopenharmony_ci return ret; 196562306a36Sopenharmony_ci} 196662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dfl_fpga_set_irq_triggers); 196762306a36Sopenharmony_ci 196862306a36Sopenharmony_ci/** 196962306a36Sopenharmony_ci * dfl_feature_ioctl_get_num_irqs - dfl feature _GET_IRQ_NUM ioctl interface. 197062306a36Sopenharmony_ci * @pdev: the feature device which has the sub feature 197162306a36Sopenharmony_ci * @feature: the dfl sub feature 197262306a36Sopenharmony_ci * @arg: ioctl argument 197362306a36Sopenharmony_ci * 197462306a36Sopenharmony_ci * Return: 0 on success, negative error code otherwise. 197562306a36Sopenharmony_ci */ 197662306a36Sopenharmony_cilong dfl_feature_ioctl_get_num_irqs(struct platform_device *pdev, 197762306a36Sopenharmony_ci struct dfl_feature *feature, 197862306a36Sopenharmony_ci unsigned long arg) 197962306a36Sopenharmony_ci{ 198062306a36Sopenharmony_ci return put_user(feature->nr_irqs, (__u32 __user *)arg); 198162306a36Sopenharmony_ci} 198262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dfl_feature_ioctl_get_num_irqs); 198362306a36Sopenharmony_ci 198462306a36Sopenharmony_ci/** 198562306a36Sopenharmony_ci * dfl_feature_ioctl_set_irq - dfl feature _SET_IRQ ioctl interface. 198662306a36Sopenharmony_ci * @pdev: the feature device which has the sub feature 198762306a36Sopenharmony_ci * @feature: the dfl sub feature 198862306a36Sopenharmony_ci * @arg: ioctl argument 198962306a36Sopenharmony_ci * 199062306a36Sopenharmony_ci * Return: 0 on success, negative error code otherwise. 199162306a36Sopenharmony_ci */ 199262306a36Sopenharmony_cilong dfl_feature_ioctl_set_irq(struct platform_device *pdev, 199362306a36Sopenharmony_ci struct dfl_feature *feature, 199462306a36Sopenharmony_ci unsigned long arg) 199562306a36Sopenharmony_ci{ 199662306a36Sopenharmony_ci struct dfl_feature_platform_data *pdata = dev_get_platdata(&pdev->dev); 199762306a36Sopenharmony_ci struct dfl_fpga_irq_set hdr; 199862306a36Sopenharmony_ci s32 *fds; 199962306a36Sopenharmony_ci long ret; 200062306a36Sopenharmony_ci 200162306a36Sopenharmony_ci if (!feature->nr_irqs) 200262306a36Sopenharmony_ci return -ENOENT; 200362306a36Sopenharmony_ci 200462306a36Sopenharmony_ci if (copy_from_user(&hdr, (void __user *)arg, sizeof(hdr))) 200562306a36Sopenharmony_ci return -EFAULT; 200662306a36Sopenharmony_ci 200762306a36Sopenharmony_ci if (!hdr.count || (hdr.start + hdr.count > feature->nr_irqs) || 200862306a36Sopenharmony_ci (hdr.start + hdr.count < hdr.start)) 200962306a36Sopenharmony_ci return -EINVAL; 201062306a36Sopenharmony_ci 201162306a36Sopenharmony_ci fds = memdup_user((void __user *)(arg + sizeof(hdr)), 201262306a36Sopenharmony_ci array_size(hdr.count, sizeof(s32))); 201362306a36Sopenharmony_ci if (IS_ERR(fds)) 201462306a36Sopenharmony_ci return PTR_ERR(fds); 201562306a36Sopenharmony_ci 201662306a36Sopenharmony_ci mutex_lock(&pdata->lock); 201762306a36Sopenharmony_ci ret = dfl_fpga_set_irq_triggers(feature, hdr.start, hdr.count, fds); 201862306a36Sopenharmony_ci mutex_unlock(&pdata->lock); 201962306a36Sopenharmony_ci 202062306a36Sopenharmony_ci kfree(fds); 202162306a36Sopenharmony_ci return ret; 202262306a36Sopenharmony_ci} 202362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(dfl_feature_ioctl_set_irq); 202462306a36Sopenharmony_ci 202562306a36Sopenharmony_cistatic void __exit dfl_fpga_exit(void) 202662306a36Sopenharmony_ci{ 202762306a36Sopenharmony_ci dfl_chardev_uinit(); 202862306a36Sopenharmony_ci dfl_ids_destroy(); 202962306a36Sopenharmony_ci bus_unregister(&dfl_bus_type); 203062306a36Sopenharmony_ci} 203162306a36Sopenharmony_ci 203262306a36Sopenharmony_cimodule_init(dfl_fpga_init); 203362306a36Sopenharmony_cimodule_exit(dfl_fpga_exit); 203462306a36Sopenharmony_ci 203562306a36Sopenharmony_ciMODULE_DESCRIPTION("FPGA Device Feature List (DFL) Support"); 203662306a36Sopenharmony_ciMODULE_AUTHOR("Intel Corporation"); 203762306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 2038